chore(doc): initial version of dgeni setup for api docs (#2257)

This commit is contained in:
Jeremy Elbourn
2016-12-16 14:33:13 -08:00
committed by GitHub
parent 2855cc33ca
commit 21221ff29d
8 changed files with 461 additions and 1 deletions
+2
View File
@@ -54,6 +54,8 @@
"axe-core": "^2.0.7",
"axe-webdriverjs": "^0.4.0",
"conventional-changelog": "^1.1.0",
"dgeni": "^0.4.2",
"dgeni-packages": "^0.16.2",
"express": "^4.14.0",
"firebase-tools": "^2.2.1",
"fs-extra": "^0.26.5",
+148
View File
@@ -0,0 +1,148 @@
const path = require('path');
const fs = require('fs');
const Dgeni = require('dgeni');
const DgeniPackage = Dgeni.Package;
// dgeni packages
const jsdocPackage = require('dgeni-packages/jsdoc');
const nunjucksPackage = require('dgeni-packages/nunjucks');
const typescriptPackage = require('dgeni-packages/typescript');
// Project configuration.
const projectRootDir = path.resolve(__dirname, '../..');
const sourceDir = path.resolve(projectRootDir, 'src/lib');
const outputDir = path.resolve(projectRootDir, 'dist/docs');
const templateDir = path.resolve(__dirname, './templates');
// Package definition for material2 api docs. This only *defines* the package- it does not yet
// actually *run* anything.
//
// A dgeni package is very similar to an Angular 1 module. Modules contain:
// "services" (injectables)
// "processors" (injectables that conform to a specific interface)
// "templates": nunjucks templates that can be used to render content
//
// A dgeni package also has a `config` method, similar to an Angular 1 module.
// A config block can inject any services/processors and configure them before
// docs processing begins.
const dgeniPackageDeps = [
jsdocPackage,
nunjucksPackage,
typescriptPackage,
];
let apiDocsPackage = new DgeniPackage('material2-api-docs', dgeniPackageDeps)
// Processor that filters out symbols that should not be shown in the docs.
.processor(require('./processors/docs-private-filter'))
// Processor that appends categorization flags to the docs, e.g. `isDirective`, `isNgModule`, etc.
.processor(require('./processors/categorizer'))
// Processor to group components into top-level groups such as "Tabs", "Sidenav", etc.
.processor(require('./processors/component-grouper'))
.config(function(log) {
log.level = 'info';
})
// Configure the processor for reading files from the file system.
.config(function(readFilesProcessor, writeFilesProcessor) {
readFilesProcessor.basePath = sourceDir;
readFilesProcessor.$enabled = false; // disable for now as we are using readTypeScriptModules
writeFilesProcessor.outputFolder = outputDir;
})
// Configure the output path for written files (i.e., file names).
.config(function(computePathsProcessor) {
computePathsProcessor.pathTemplates = [{
docTypes: ['componentGroup'],
pathTemplate: '${name}',
outputPathTemplate: '${name}.html',
}];
})
// Configure custom JsDoc tags.
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat([
{name: 'docs-private'}
]);
})
// Configure the processor for understanding TypeScript.
.config(function(readTypeScriptModules) {
console.log(sourceDir);
readTypeScriptModules.basePath = sourceDir;
readTypeScriptModules.ignoreExportsMatching = [/^_/];
readTypeScriptModules.hidePrivateMembers = true;
// Entry points for docs generation. All publically exported symbols found through these
// files will have docs generated.
readTypeScriptModules.sourceFiles = [
'autocomplete/index.ts',
'button/index.ts',
'button-toggle/index.ts',
'card/index.ts',
'checkbox/index.ts',
'chips/index.ts',
'core/index.ts',
'dialog/index.ts',
'grid-list/index.ts',
'icon/index.ts',
'input/index.ts',
'list/index.ts',
'menu/index.ts',
'progress-bar/index.ts',
'progress-circle/index.ts',
'radio/index.ts',
'select/index.ts',
'sidenav/index.ts',
'slide-toggle/index.ts',
'slider/index.ts',
'snack-bar/index.ts',
'tabs/index.ts',
'toolbar/index.ts',
'tooltip/index.ts',
];
})
// Configure processor for finding nunjucks templates.
.config(function(templateFinder, templateEngine) {
// Where to find the templates for the doc rendering
templateFinder.templateFolders = [templateDir];
// Standard patterns for matching docs to templates
templateFinder.templatePatterns = [
'${ doc.template }',
'${ doc.id }.${ doc.docType }.template.html',
'${ doc.id }.template.html',
'${ doc.docType }.template.html',
'${ doc.id }.${ doc.docType }.template.js',
'${ doc.id }.template.js',
'${ doc.docType }.template.js',
'${ doc.id }.${ doc.docType }.template.json',
'${ doc.id }.template.json',
'${ doc.docType }.template.json',
'common.template.html'
];
// Nunjucks and Angular conflict in their template bindings so change Nunjucks
templateEngine.config.tags = {
variableStart: '{$',
variableEnd: '$}'
};
});
module.exports = apiDocsPackage;
// Run the dgeni pipeline, generating documentation.
// TODO(jelbourn): remove this once the process is more final in favor of gulp.
let dgeni = new Dgeni([apiDocsPackage]);
dgeni.generate().then(docs => {
console.log(docs);
});
+112
View File
@@ -0,0 +1,112 @@
/**
* Processor to add properties to docs objects.
*
* isMethod | Whether the doc is for a method on a class.
* isDirective | Whether the doc is for a @Component or a @Directive
* isService | Whether the doc is for an @Injectable
* isNgModule | Whether the doc is for an NgModule
*/
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;
} else if (isService(doc)) {
doc.isService = true;
} else if (isNgModule(doc)) {
doc.isNgModule = true;
} else if (doc.docType == 'member') {
doc.isDirectiveInput = isDirectiveInput(doc);
doc.isDirectiveOutput = isDirectiveOutput(doc);
doc.classDoc.properties ?
doc.classDoc.properties.push(doc) :
doc.classDoc.properties = [doc];
}
});
}
};
};
/**
* The `parameters` property are the parameters extracted from TypeScript and are strings
* of the form "propertyName: propertyType" (literally what's written in the source).
*
* The `params` property is pulled from the `@param` JsDoc tag. We need to merge
* the information of these to get name + type + description.
*
* We will use the `params` property to store the final normalized form since it is already
* an object.
*/
function normalizeMethodParameters(method) {
if (method.parameters) {
method.parameters.forEach(parameter => {
let [parameterName, parameterType] = parameter.split(':');
if (!method.params) {
method.params = [];
}
let jsDocParam = method.params.find(p => p.name == parameterName);
if (!jsDocParam) {
jsDocParam = {name: parameterName};
method.params.push(jsDocParam);
}
jsDocParam.type = parameterType.trim();
});
}
}
function isDirective(doc) {
return hasClassDecorator(doc, 'Component') || hasClassDecorator(doc, 'Directive');
}
function isService(doc) {
return hasClassDecorator(doc, 'Injectable')
}
function isNgModule(doc) {
return hasClassDecorator(doc, 'NgModule');
}
function isDirectiveOutput(doc) {
return hasMemberDecorator(doc, 'Output');
}
function isDirectiveInput(doc) {
return hasMemberDecorator(doc, 'Input');
}
function hasMemberDecorator(doc, decoratorName) {
return doc.docType == 'member' && hasDecorator(doc, decoratorName);
}
function hasClassDecorator(doc, decoratorName) {
return doc.docType == 'class' && hasDecorator(doc, decoratorName);
}
function hasDecorator(doc, decoratorName) {
return doc.decorators &&
doc.decorators.length &&
doc.decorators.some(d => d.name == decoratorName);
}
@@ -0,0 +1,60 @@
const path = require('path');
/**
* Processor to group docs into top-level "Components" WRT material design, e.g., "Button", "Tabs",
* where each group may conists of several directives and services.
*/
/** Component group data structure. */
class ComponentGroup {
constructor(name) {
this.name = name;
this.id = `component-group-${name}`;
this.docType = 'componentGroup';
this.directives = [];
this.services = [];
this.ngModule = null;
}
}
module.exports = function componentGrouper() {
return {
$runBefore: ['docs-processed'],
$process: function(docs) {
// Map of group name to group instance.
let groups = new Map();
docs.forEach(doc => {
// Full path to the file for this doc.
let basePath = doc.fileInfo.basePath;
let filePath = doc.fileInfo.filePath;
// All of the component documentation is under `src/lib`, which will be the basePath.
// We group the docs up by the directory immediately under `src/lib` (e.g., "button").
let groupName = path.relative(basePath, filePath).split(path.sep)[0];
// Get the group for this doc, or, if one does not exist, create it.
let group;
if (groups.has(groupName)) {
group = groups.get(groupName);
} else {
group = new ComponentGroup(groupName);
groups.set(groupName, group);
}
// Put this doc into the appropriate list in this group.
if (doc.isDirective) {
group.directives.push(doc);
} else if (doc.isService) {
group.services.push(doc);
} else if (doc.isNgModule) {
group.ngModule = doc;
}
});
return Array.from(groups.values());
}
};
};
@@ -0,0 +1,41 @@
/**
* Processor to filter out symbols that should not be shown in the Material docs.
*/
const INTERNAL_METHODS = [
// Lifecycle methods
'ngOnInit',
'ngOnChanges',
'ngDoCheck',
'ngAfterContentInit',
'ngAfterContentChecked',
'ngAfterViewInit',
'ngAfterViewChecked',
'ngOnDestroy',
// ControlValueAccessor methods
'writeValue',
'registerOnChange',
'registerOnTouched',
'setDisabledState',
// Don't ever need to document constructors
'constructor',
// tabIndex exists on all elements, no need to document it
'tabIndex',
];
module.exports = function docsPrivateFilter() {
return {
$runBefore: ['docs-processed'],
$process: function(docs) {
return docs.filter(d => !(hasDocsPrivateTag(d) || INTERNAL_METHODS.includes(d.name)));
}
};
};
function hasDocsPrivateTag(doc) {
let tags = doc.tags && doc.tags.tags;
return tags ? tags.find(d => d.tagName == 'docs-private') : false;
}
@@ -0,0 +1 @@
## {$ doc.name $} ({$ doc.docType $})
@@ -0,0 +1,91 @@
<style>
table {
border-collapse: collapse;
margin: 10px;
min-width: 300px;
}
td, th {
border: 1px solid black;
padding: 4px;
}
</style>
<h2>{$ doc.name $}</h2>
<p> Module: <code>{$ doc.ngModule.name $}</code> </p>
<h3>Directives</h3>
{% for directive in doc.directives %}
<h4>{$ directive.name $}</h4>
<p> {$ directive.description $} </p>
{%- if directive.properties.length -%}
<h5> Properties </h5>
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
{% for property in directive.properties %}
<tr>
<td>
{%- if property.isDirectiveInput -%}
<p>@Input</p>
{%- endif -%}
<p>{$ property.name $}</p>
<p><code>{$ property.type $}</code></p>
</td>
<td> {$ property.description $}</td>
</tr>
{% endfor %}
</table>
{%- endif -%}
{%- if directive.methods.length -%}
<h5> Methods </h5>
{% for method in directive.methods %}
<table>
<tr>
<th colspan="2">{$ method.name $}</th>
</tr>
<tr>
<td colspan="2"> {$ method.description $} </td>
</tr>
{%- if method.params.length -%}
<tr>
<th colspan="2"> Parameters </th>
</tr>
{% for parameter in method.params %}
<tr>
<td>
<p>{$ parameter.name $}</p>
<p>{$ parameter.type $}</p>
</td>
<td>
{$ parameter.description $}
</td>
</tr>
{% endfor %}
{%- endif -%}
{%- if method.showReturns -%}
<tr>
<th colspan="2"> Returns </th>
</tr>
<tr>
<td>{$ method.returnType $}</td>
<td>{$ method.returns.description $}</td>
</tr>
{%- endif -%}
</table>
{% endfor %}
{%- endif -%}
{% endfor %}
+6 -1
View File
@@ -26,4 +26,9 @@ gulp.task('docs', () => {
.pipe(gulp.dest('dist/docs'));
});
task('api', execNodeTask('typedoc', ['--options', typedocPath, './src/lib']));
task('api', () => {
const Dgeni = require('dgeni');
const docsPackage = require(path.resolve(__dirname, '../../dgeni'));
const dgeni = new Dgeni([docsPackage]);
return dgeni.generate();
});