Skip to content

Commit 5f3abae

Browse files
devversionmmalerba
authored andcommitted
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
1 parent 91aafff commit 5f3abae

File tree

4 files changed

+145
-40
lines changed

4 files changed

+145
-40
lines changed

tools/dgeni/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const dgeniPackageDeps = [
3535

3636
let apiDocsPackage = new DgeniPackage('material2-api-docs', dgeniPackageDeps)
3737

38+
.processor(require('./processors/link-inherited-docs'))
39+
3840
// Processor that filters out symbols that should not be shown in the docs.
3941
.processor(require('./processors/docs-private-filter'))
4042

tools/dgeni/processors/categorizer.js

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,82 @@
99
module.exports = function categorizer() {
1010
return {
1111
$runBefore: ['docs-processed'],
12-
$process: function(docs) {
13-
docs.forEach(doc => {
14-
// The typescriptPackage groups both methods and parameters into "members".
15-
// Use the presence of `parameters` as a proxy to determine if this is a method.
16-
if (doc.classDoc && doc.hasOwnProperty('parameters')) {
17-
doc.isMethod = true;
18-
19-
// Mark methods with a `void` return type so we can omit show the return type in the docs.
20-
doc.showReturns = doc.returnType && doc.returnType != 'void';
21-
22-
normalizeMethodParameters(doc);
23-
24-
// Maintain a list of methods on the associated class so we can
25-
// iterate through them while rendering.
26-
doc.classDoc.methods ?
27-
doc.classDoc.methods.push(doc) :
28-
doc.classDoc.methods = [doc];
29-
} else if (isDirective(doc)) {
30-
doc.isDirective = true;
31-
doc.directiveExportAs = getDirectiveExportAs(doc);
32-
} else if (isService(doc)) {
33-
doc.isService = true;
34-
} else if (isNgModule(doc)) {
35-
doc.isNgModule = true;
36-
} else if (doc.docType == 'member') {
37-
doc.isDirectiveInput = isDirectiveInput(doc);
38-
doc.directiveInputAlias = getDirectiveInputAlias(doc);
39-
40-
doc.isDirectiveOutput = isDirectiveOutput(doc);
41-
doc.directiveOutputAlias = getDirectiveOutputAlias(doc);
42-
43-
doc.classDoc.properties ?
44-
doc.classDoc.properties.push(doc) :
45-
doc.classDoc.properties = [doc];
46-
}
47-
});
12+
$process: function (docs) {
13+
docs.filter(doc => doc.docType === 'class').forEach(doc => decorateClassDoc(doc));
4814
}
4915
};
16+
17+
/**
18+
* Decorates all class docs inside of the dgeni pipeline.
19+
* - Methods and properties of a class-doc will be extracted into separate variables.
20+
* - Identifies directives, services or NgModules and marks them them in class-doc.
21+
**/
22+
function decorateClassDoc(classDoc) {
23+
// Resolve all methods and properties from the classDoc. Includes inherited docs.
24+
classDoc.methods = resolveMethods(classDoc);
25+
classDoc.properties = resolveProperties(classDoc);
26+
27+
// Call decorate hooks that can modify the method and property docs.
28+
classDoc.methods.forEach(doc => decorateMethodDoc(doc));
29+
classDoc.properties.forEach(doc => decoratePropertyDoc(doc));
30+
31+
// Categorize the current visited classDoc into its Angular type.
32+
if (isDirective(classDoc)) {
33+
classDoc.isDirective = true;
34+
classDoc.directiveExportAs = getDirectiveExportAs(classDoc);
35+
} else if (isService(classDoc)) {
36+
classDoc.isService = true;
37+
} else if (isNgModule(classDoc)) {
38+
classDoc.isNgModule = true;
39+
}
40+
}
41+
42+
/**
43+
* Method that will be called for each method doc. The parameters for the method-docs
44+
* will be normalized, so that they can be easily used inside of dgeni templates.
45+
**/
46+
function decorateMethodDoc(methodDoc) {
47+
normalizeMethodParameters(methodDoc);
48+
49+
// Mark methods with a `void` return type so we can omit show the return type in the docs.
50+
methodDoc.showReturns = methodDoc.returnType && methodDoc.returnType != 'void';
51+
}
52+
53+
/**
54+
* Method that will be called for each property doc. Properties that are Angular inputs or
55+
* outputs will be marked. Aliases for the inputs or outputs will be stored as well.
56+
**/
57+
function decoratePropertyDoc(propertyDoc) {
58+
propertyDoc.isDirectiveInput = isDirectiveInput(propertyDoc);
59+
propertyDoc.directiveInputAlias = getDirectiveInputAlias(propertyDoc);
60+
61+
propertyDoc.isDirectiveOutput = isDirectiveOutput(propertyDoc);
62+
propertyDoc.directiveOutputAlias = getDirectiveOutputAlias(propertyDoc);
63+
}
5064
};
5165

66+
/** Function that walks through all inherited docs and collects public methods. */
67+
function resolveMethods(classDoc) {
68+
let methods = classDoc.members.filter(member => member.hasOwnProperty('parameters'));
69+
70+
if (classDoc.inheritedDoc) {
71+
methods = methods.concat(resolveMethods(classDoc.inheritedDoc));
72+
}
73+
74+
return methods;
75+
}
76+
77+
/** Function that walks through all inherited docs and collects public properties. */
78+
function resolveProperties(classDoc) {
79+
let properties = classDoc.members.filter(member => !member.hasOwnProperty('parameters'));
80+
81+
if (classDoc.inheritedDoc) {
82+
properties = properties.concat(resolveProperties(classDoc.inheritedDoc));
83+
}
84+
85+
return properties;
86+
}
87+
5288

5389
/**
5490
* The `parameters` property are the parameters extracted from TypeScript and are strings

tools/dgeni/processors/docs-private-filter.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,27 @@ const INTERNAL_METHODS = [
2828

2929
module.exports = function docsPrivateFilter() {
3030
return {
31-
$runBefore: ['docs-processed'],
32-
$process: function(docs) {
33-
return docs.filter(d => !(hasDocsPrivateTag(d) || INTERNAL_METHODS.includes(d.name)));
34-
}
31+
$runBefore: ['categorizer'],
32+
$process: docs => docs.filter(doc => isPublicDoc(doc))
3533
};
3634
};
3735

36+
function isPublicDoc(doc) {
37+
if (hasDocsPrivateTag(doc)) {
38+
return false;
39+
} else if (doc.docType === 'member') {
40+
return !isInternalMember(doc);
41+
} else if (doc.docType === 'class') {
42+
doc.members = doc.members.filter(memberDoc => isPublicDoc(memberDoc));
43+
}
44+
45+
return true;
46+
}
47+
48+
function isInternalMember(memberDoc) {
49+
return INTERNAL_METHODS.includes(memberDoc.name)
50+
}
51+
3852
function hasDocsPrivateTag(doc) {
3953
let tags = doc.tags && doc.tags.tags;
4054
return tags ? tags.find(d => d.tagName == 'docs-private') : false;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Processor that iterates through all class docs and determines if a class inherits
3+
* another class. Inherited class-docs will be linked to the original class-doc.
4+
*/
5+
6+
const ts = require('typescript');
7+
8+
module.exports = function linkInheritedDocs(readTypeScriptModules, tsParser) {
9+
10+
let checker = null;
11+
12+
return {
13+
$runAfter: ['readTypeScriptModules'],
14+
$runBefore: ['categorizer'],
15+
$process: processDocs,
16+
};
17+
18+
function processDocs(docs) {
19+
// Use the configuration from the `readTypeScriptModules` processor.
20+
let {sourceFiles, basePath} = readTypeScriptModules;
21+
22+
// To be able to map the TypeScript Nodes to the according symbols we need to use the same
23+
// TypeScript configuration as in the `readTypeScriptModules` processor.
24+
checker = tsParser.parse(sourceFiles, basePath).typeChecker;
25+
26+
// Iterate through all class docs and resolve the inherited docs.
27+
docs.filter(doc => doc.docType === 'class').forEach(classDoc => {
28+
resolveInheritedDoc(classDoc, docs);
29+
});
30+
}
31+
32+
function resolveInheritedDoc(classDoc, docs) {
33+
let inheritedType = resolveInheritedType(classDoc.exportSymbol);
34+
let inheritedSymbol = inheritedType && inheritedType.symbol;
35+
36+
if (inheritedSymbol) {
37+
classDoc.inheritedSymbol = inheritedSymbol;
38+
classDoc.inheritedDoc = docs.find(doc => doc.exportSymbol === inheritedSymbol);
39+
}
40+
}
41+
42+
function resolveInheritedType(classSymbol) {
43+
// Ensure that the symbol can be converted into a TypeScript ClassDeclaration.
44+
if (classSymbol.flags & ~ts.SymbolFlags.Class) {
45+
return;
46+
}
47+
48+
let declaration = classSymbol.valueDeclaration || classSymbol.declarations[0];
49+
let typeExpression = ts.getClassExtendsHeritageClauseElement(declaration);
50+
51+
return typeExpression ? checker.getTypeAtLocation(typeExpression) : null;
52+
}
53+
};

0 commit comments

Comments
 (0)