Skip to content

Commit

Permalink
build: show inherited members in dgeni (#3599)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
devversion authored and mmalerba committed Mar 17, 2017
1 parent 91aafff commit 5f3abae
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 40 deletions.
2 changes: 2 additions & 0 deletions tools/dgeni/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'))

Expand Down
108 changes: 72 additions & 36 deletions tools/dgeni/processors/categorizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 18 additions & 4 deletions tools/dgeni/processors/docs-private-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
53 changes: 53 additions & 0 deletions tools/dgeni/processors/link-inherited-docs.js
Original file line number Diff line number Diff line change
@@ -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;
}
};

0 comments on commit 5f3abae

Please sign in to comment.