Skip to content

Commit

Permalink
build: show inherited members in dgeni
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
devversion committed Mar 16, 2017
1 parent aa3360a commit 4470b7d
Show file tree
Hide file tree
Showing 4 changed files with 122 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
95 changes: 59 additions & 36 deletions tools/dgeni/processors/categorizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 16 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,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;
Expand Down
45 changes: 45 additions & 0 deletions tools/dgeni/processors/link-inherited-docs.js
Original file line number Diff line number Diff line change
@@ -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;
}
};

0 comments on commit 4470b7d

Please sign in to comment.