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; + } +};