From 4470b7d5dcd622104888c1025981243de21daa32 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 14 Mar 2017 18:53:59 +0100 Subject: [PATCH 1/4] 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 --- tools/dgeni/index.js | 2 + tools/dgeni/processors/categorizer.js | 95 ++++++++++++------- tools/dgeni/processors/docs-private-filter.js | 20 +++- tools/dgeni/processors/link-inherited-docs.js | 45 +++++++++ 4 files changed, 122 insertions(+), 40 deletions(-) create mode 100644 tools/dgeni/processors/link-inherited-docs.js diff --git a/tools/dgeni/index.js b/tools/dgeni/index.js index c02c8bff8eda..f37d76df3689 100644 --- a/tools/dgeni/index.js +++ b/tools/dgeni/index.js @@ -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')) diff --git a/tools/dgeni/processors/categorizer.js b/tools/dgeni/processors/categorizer.js index ddcb00ecd317..716aca108a8e 100644 --- a/tools/dgeni/processors/categorizer.js +++ b/tools/dgeni/processors/categorizer.js @@ -9,46 +9,69 @@ 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 => visitClassDoc(doc)); } }; + + function visitClassDoc(classDoc) { + // Resolve all methods and properties from the classDoc. Includes inherited docs. + classDoc.methods = resolveMethods(classDoc); + classDoc.properties = resolveProperties(classDoc); + + // Call visit hooks that can modify the method and property docs. + classDoc.methods.forEach(doc => visitMethodDoc(doc)); + classDoc.properties.forEach(doc => visitPropertyDoc(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; + } + } + + function visitMethodDoc(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'; + } + + function visitPropertyDoc(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 diff --git a/tools/dgeni/processors/docs-private-filter.js b/tools/dgeni/processors/docs-private-filter.js index 7507e58c1c1b..3776e053b671 100644 --- a/tools/dgeni/processors/docs-private-filter.js +++ b/tools/dgeni/processors/docs-private-filter.js @@ -28,13 +28,25 @@ 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 => validateDocEntry(doc)) }; }; +function validateDocEntry(doc) { + if (doc.docType === 'member') { + return validateMemberDoc(doc); + } else if (doc.docType === 'class') { + doc.members = doc.members.filter(memberDoc => validateMemberDoc(memberDoc)); + } + + return true; +} + +function validateMemberDoc(memberDoc) { + return !(hasDocsPrivateTag(memberDoc) || 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; diff --git a/tools/dgeni/processors/link-inherited-docs.js b/tools/dgeni/processors/link-inherited-docs.js new file mode 100644 index 000000000000..daed5774a19f --- /dev/null +++ b/tools/dgeni/processors/link-inherited-docs.js @@ -0,0 +1,45 @@ +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 => visitClassDoc(classDoc, docs)); + } + + function visitClassDoc(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) { + 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; + } +}; From 8d76e2b3192822fa3a2b57b14e9acb4169963f28 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 14 Mar 2017 19:17:29 +0100 Subject: [PATCH 2/4] Handle private-docs param for all docs. --- tools/dgeni/processors/docs-private-filter.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/dgeni/processors/docs-private-filter.js b/tools/dgeni/processors/docs-private-filter.js index 3776e053b671..38dcae5e5fed 100644 --- a/tools/dgeni/processors/docs-private-filter.js +++ b/tools/dgeni/processors/docs-private-filter.js @@ -34,7 +34,9 @@ module.exports = function docsPrivateFilter() { }; function validateDocEntry(doc) { - if (doc.docType === 'member') { + if (hasDocsPrivateTag(doc)) { + return false; + } else if (doc.docType === 'member') { return validateMemberDoc(doc); } else if (doc.docType === 'class') { doc.members = doc.members.filter(memberDoc => validateMemberDoc(memberDoc)); @@ -44,7 +46,7 @@ function validateDocEntry(doc) { } function validateMemberDoc(memberDoc) { - return !(hasDocsPrivateTag(memberDoc) || INTERNAL_METHODS.includes(memberDoc.name)) + return !INTERNAL_METHODS.includes(memberDoc.name) } function hasDocsPrivateTag(doc) { From a0a17367eb60373cf7e3d9a301145247a1a5c0fb Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 14 Mar 2017 19:58:48 +0100 Subject: [PATCH 3/4] Address comments --- tools/dgeni/processors/docs-private-filter.js | 12 ++++++------ tools/dgeni/processors/link-inherited-docs.js | 11 +++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/tools/dgeni/processors/docs-private-filter.js b/tools/dgeni/processors/docs-private-filter.js index 38dcae5e5fed..de6458f2afbd 100644 --- a/tools/dgeni/processors/docs-private-filter.js +++ b/tools/dgeni/processors/docs-private-filter.js @@ -29,24 +29,24 @@ const INTERNAL_METHODS = [ module.exports = function docsPrivateFilter() { return { $runBefore: ['categorizer'], - $process: docs => docs.filter(doc => validateDocEntry(doc)) + $process: docs => docs.filter(doc => isPublicDoc(doc)) }; }; -function validateDocEntry(doc) { +function isPublicDoc(doc) { if (hasDocsPrivateTag(doc)) { return false; } else if (doc.docType === 'member') { - return validateMemberDoc(doc); + return !isInternalMember(doc); } else if (doc.docType === 'class') { - doc.members = doc.members.filter(memberDoc => validateMemberDoc(memberDoc)); + doc.members = doc.members.filter(memberDoc => isPublicDoc(memberDoc)); } return true; } -function validateMemberDoc(memberDoc) { - return !INTERNAL_METHODS.includes(memberDoc.name) +function isInternalMember(memberDoc) { + return INTERNAL_METHODS.includes(memberDoc.name) } function hasDocsPrivateTag(doc) { diff --git a/tools/dgeni/processors/link-inherited-docs.js b/tools/dgeni/processors/link-inherited-docs.js index daed5774a19f..cd90dd055726 100644 --- a/tools/dgeni/processors/link-inherited-docs.js +++ b/tools/dgeni/processors/link-inherited-docs.js @@ -1,3 +1,8 @@ +/** + * 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) { @@ -19,10 +24,12 @@ module.exports = function linkInheritedDocs(readTypeScriptModules, tsParser) { checker = tsParser.parse(sourceFiles, basePath).typeChecker; // Iterate through all class docs and resolve the inherited docs. - docs.filter(doc => doc.docType === 'class').forEach(classDoc => visitClassDoc(classDoc, docs)); + docs.filter(doc => doc.docType === 'class').forEach(classDoc => { + resolveInheritedDoc(classDoc, docs); + }); } - function visitClassDoc(classDoc, docs) { + function resolveInheritedDoc(classDoc, docs) { let inheritedType = resolveInheritedType(classDoc.exportSymbol); let inheritedSymbol = inheritedType && inheritedType.symbol; From 07642f0dec6e9c1cc5be28daee6d38ca8170ba33 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 16 Mar 2017 20:40:15 +0100 Subject: [PATCH 4/4] Address feedback --- tools/dgeni/processors/categorizer.js | 27 ++++++++++++++----- tools/dgeni/processors/link-inherited-docs.js | 1 + 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/tools/dgeni/processors/categorizer.js b/tools/dgeni/processors/categorizer.js index 716aca108a8e..758d14b7d611 100644 --- a/tools/dgeni/processors/categorizer.js +++ b/tools/dgeni/processors/categorizer.js @@ -10,18 +10,23 @@ module.exports = function categorizer() { return { $runBefore: ['docs-processed'], $process: function (docs) { - docs.filter(doc => doc.docType === 'class').forEach(doc => visitClassDoc(doc)); + docs.filter(doc => doc.docType === 'class').forEach(doc => decorateClassDoc(doc)); } }; - function visitClassDoc(classDoc) { + /** + * 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 visit hooks that can modify the method and property docs. - classDoc.methods.forEach(doc => visitMethodDoc(doc)); - classDoc.properties.forEach(doc => visitPropertyDoc(doc)); + // 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)) { @@ -34,14 +39,22 @@ module.exports = function categorizer() { } } - function visitMethodDoc(methodDoc) { + /** + * 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'; } - function visitPropertyDoc(propertyDoc) { + /** + * 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); diff --git a/tools/dgeni/processors/link-inherited-docs.js b/tools/dgeni/processors/link-inherited-docs.js index cd90dd055726..73a6bc8e7c68 100644 --- a/tools/dgeni/processors/link-inherited-docs.js +++ b/tools/dgeni/processors/link-inherited-docs.js @@ -40,6 +40,7 @@ module.exports = function linkInheritedDocs(readTypeScriptModules, tsParser) { } function resolveInheritedType(classSymbol) { + // Ensure that the symbol can be converted into a TypeScript ClassDeclaration. if (classSymbol.flags & ~ts.SymbolFlags.Class) { return; }