build: show inherited members in dgeni (#3599)

* build: show inherited members in dgeni

* Introduced a new Dgeni processor that will take care of the inherited docs for TypeScript classes.
* Cleaned up the categorizer processor and made it compatible with the `inherited-docs` processor.
* Fixes that the `docs-private-filter` processor doesn't filter the referenced members in the class docs.

This basically allows us to extend other classes in our components like in #3036

* Handle private-docs param for all docs.

* Address comments

* Address feedback
This commit is contained in:
Paul Gschwendtner
2017-03-17 18:42:18 +01:00
committed by mmalerba
parent 91aafffcd2
commit 5f3abae3b6
4 changed files with 145 additions and 40 deletions
+2
View File
@@ -35,6 +35,8 @@ const dgeniPackageDeps = [
let apiDocsPackage = new DgeniPackage('material2-api-docs', dgeniPackageDeps)
.processor(require('./processors/link-inherited-docs'))
// Processor that filters out symbols that should not be shown in the docs.
.processor(require('./processors/docs-private-filter'))
+72 -36
View File
@@ -9,46 +9,82 @@
module.exports = function categorizer() {
return {
$runBefore: ['docs-processed'],
$process: function(docs) {
docs.forEach(doc => {
// The typescriptPackage groups both methods and parameters into "members".
// Use the presence of `parameters` as a proxy to determine if this is a method.
if (doc.classDoc && doc.hasOwnProperty('parameters')) {
doc.isMethod = true;
// Mark methods with a `void` return type so we can omit show the return type in the docs.
doc.showReturns = doc.returnType && doc.returnType != 'void';
normalizeMethodParameters(doc);
// Maintain a list of methods on the associated class so we can
// iterate through them while rendering.
doc.classDoc.methods ?
doc.classDoc.methods.push(doc) :
doc.classDoc.methods = [doc];
} else if (isDirective(doc)) {
doc.isDirective = true;
doc.directiveExportAs = getDirectiveExportAs(doc);
} else if (isService(doc)) {
doc.isService = true;
} else if (isNgModule(doc)) {
doc.isNgModule = true;
} else if (doc.docType == 'member') {
doc.isDirectiveInput = isDirectiveInput(doc);
doc.directiveInputAlias = getDirectiveInputAlias(doc);
doc.isDirectiveOutput = isDirectiveOutput(doc);
doc.directiveOutputAlias = getDirectiveOutputAlias(doc);
doc.classDoc.properties ?
doc.classDoc.properties.push(doc) :
doc.classDoc.properties = [doc];
}
});
$process: function (docs) {
docs.filter(doc => doc.docType === 'class').forEach(doc => decorateClassDoc(doc));
}
};
/**
* Decorates all class docs inside of the dgeni pipeline.
* - Methods and properties of a class-doc will be extracted into separate variables.
* - Identifies directives, services or NgModules and marks them them in class-doc.
**/
function decorateClassDoc(classDoc) {
// Resolve all methods and properties from the classDoc. Includes inherited docs.
classDoc.methods = resolveMethods(classDoc);
classDoc.properties = resolveProperties(classDoc);
// Call decorate hooks that can modify the method and property docs.
classDoc.methods.forEach(doc => decorateMethodDoc(doc));
classDoc.properties.forEach(doc => decoratePropertyDoc(doc));
// Categorize the current visited classDoc into its Angular type.
if (isDirective(classDoc)) {
classDoc.isDirective = true;
classDoc.directiveExportAs = getDirectiveExportAs(classDoc);
} else if (isService(classDoc)) {
classDoc.isService = true;
} else if (isNgModule(classDoc)) {
classDoc.isNgModule = true;
}
}
/**
* Method that will be called for each method doc. The parameters for the method-docs
* will be normalized, so that they can be easily used inside of dgeni templates.
**/
function decorateMethodDoc(methodDoc) {
normalizeMethodParameters(methodDoc);
// Mark methods with a `void` return type so we can omit show the return type in the docs.
methodDoc.showReturns = methodDoc.returnType && methodDoc.returnType != 'void';
}
/**
* Method that will be called for each property doc. Properties that are Angular inputs or
* outputs will be marked. Aliases for the inputs or outputs will be stored as well.
**/
function decoratePropertyDoc(propertyDoc) {
propertyDoc.isDirectiveInput = isDirectiveInput(propertyDoc);
propertyDoc.directiveInputAlias = getDirectiveInputAlias(propertyDoc);
propertyDoc.isDirectiveOutput = isDirectiveOutput(propertyDoc);
propertyDoc.directiveOutputAlias = getDirectiveOutputAlias(propertyDoc);
}
};
/** Function that walks through all inherited docs and collects public methods. */
function resolveMethods(classDoc) {
let methods = classDoc.members.filter(member => member.hasOwnProperty('parameters'));
if (classDoc.inheritedDoc) {
methods = methods.concat(resolveMethods(classDoc.inheritedDoc));
}
return methods;
}
/** Function that walks through all inherited docs and collects public properties. */
function resolveProperties(classDoc) {
let properties = classDoc.members.filter(member => !member.hasOwnProperty('parameters'));
if (classDoc.inheritedDoc) {
properties = properties.concat(resolveProperties(classDoc.inheritedDoc));
}
return properties;
}
/**
* The `parameters` property are the parameters extracted from TypeScript and are strings
+18 -4
View File
@@ -28,13 +28,27 @@ const INTERNAL_METHODS = [
module.exports = function docsPrivateFilter() {
return {
$runBefore: ['docs-processed'],
$process: function(docs) {
return docs.filter(d => !(hasDocsPrivateTag(d) || INTERNAL_METHODS.includes(d.name)));
}
$runBefore: ['categorizer'],
$process: docs => docs.filter(doc => isPublicDoc(doc))
};
};
function isPublicDoc(doc) {
if (hasDocsPrivateTag(doc)) {
return false;
} else if (doc.docType === 'member') {
return !isInternalMember(doc);
} else if (doc.docType === 'class') {
doc.members = doc.members.filter(memberDoc => isPublicDoc(memberDoc));
}
return true;
}
function isInternalMember(memberDoc) {
return INTERNAL_METHODS.includes(memberDoc.name)
}
function hasDocsPrivateTag(doc) {
let tags = doc.tags && doc.tags.tags;
return tags ? tags.find(d => d.tagName == 'docs-private') : false;
@@ -0,0 +1,53 @@
/**
* Processor that iterates through all class docs and determines if a class inherits
* another class. Inherited class-docs will be linked to the original class-doc.
*/
const ts = require('typescript');
module.exports = function linkInheritedDocs(readTypeScriptModules, tsParser) {
let checker = null;
return {
$runAfter: ['readTypeScriptModules'],
$runBefore: ['categorizer'],
$process: processDocs,
};
function processDocs(docs) {
// Use the configuration from the `readTypeScriptModules` processor.
let {sourceFiles, basePath} = readTypeScriptModules;
// To be able to map the TypeScript Nodes to the according symbols we need to use the same
// TypeScript configuration as in the `readTypeScriptModules` processor.
checker = tsParser.parse(sourceFiles, basePath).typeChecker;
// Iterate through all class docs and resolve the inherited docs.
docs.filter(doc => doc.docType === 'class').forEach(classDoc => {
resolveInheritedDoc(classDoc, docs);
});
}
function resolveInheritedDoc(classDoc, docs) {
let inheritedType = resolveInheritedType(classDoc.exportSymbol);
let inheritedSymbol = inheritedType && inheritedType.symbol;
if (inheritedSymbol) {
classDoc.inheritedSymbol = inheritedSymbol;
classDoc.inheritedDoc = docs.find(doc => doc.exportSymbol === inheritedSymbol);
}
}
function resolveInheritedType(classSymbol) {
// Ensure that the symbol can be converted into a TypeScript ClassDeclaration.
if (classSymbol.flags & ~ts.SymbolFlags.Class) {
return;
}
let declaration = classSymbol.valueDeclaration || classSymbol.declarations[0];
let typeExpression = ts.getClassExtendsHeritageClauseElement(declaration);
return typeExpression ? checker.getTypeAtLocation(typeExpression) : null;
}
};