mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
chore(doc): initial version of dgeni setup for api docs (#2257)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user