Skip to content

Commit 21221ff

Browse files
authored
chore(doc): initial version of dgeni setup for api docs (#2257)
1 parent 2855cc3 commit 21221ff

File tree

8 files changed

+461
-1
lines changed

8 files changed

+461
-1
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
"axe-core": "^2.0.7",
5555
"axe-webdriverjs": "^0.4.0",
5656
"conventional-changelog": "^1.1.0",
57+
"dgeni": "^0.4.2",
58+
"dgeni-packages": "^0.16.2",
5759
"express": "^4.14.0",
5860
"firebase-tools": "^2.2.1",
5961
"fs-extra": "^0.26.5",

tools/dgeni/index.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
const path = require('path');
2+
const fs = require('fs');
3+
const Dgeni = require('dgeni');
4+
const DgeniPackage = Dgeni.Package;
5+
6+
// dgeni packages
7+
const jsdocPackage = require('dgeni-packages/jsdoc');
8+
const nunjucksPackage = require('dgeni-packages/nunjucks');
9+
const typescriptPackage = require('dgeni-packages/typescript');
10+
11+
12+
// Project configuration.
13+
const projectRootDir = path.resolve(__dirname, '../..');
14+
const sourceDir = path.resolve(projectRootDir, 'src/lib');
15+
const outputDir = path.resolve(projectRootDir, 'dist/docs');
16+
const templateDir = path.resolve(__dirname, './templates');
17+
18+
// Package definition for material2 api docs. This only *defines* the package- it does not yet
19+
// actually *run* anything.
20+
//
21+
// A dgeni package is very similar to an Angular 1 module. Modules contain:
22+
// "services" (injectables)
23+
// "processors" (injectables that conform to a specific interface)
24+
// "templates": nunjucks templates that can be used to render content
25+
//
26+
// A dgeni package also has a `config` method, similar to an Angular 1 module.
27+
// A config block can inject any services/processors and configure them before
28+
// docs processing begins.
29+
30+
const dgeniPackageDeps = [
31+
jsdocPackage,
32+
nunjucksPackage,
33+
typescriptPackage,
34+
];
35+
36+
let apiDocsPackage = new DgeniPackage('material2-api-docs', dgeniPackageDeps)
37+
38+
// Processor that filters out symbols that should not be shown in the docs.
39+
.processor(require('./processors/docs-private-filter'))
40+
41+
// Processor that appends categorization flags to the docs, e.g. `isDirective`, `isNgModule`, etc.
42+
.processor(require('./processors/categorizer'))
43+
44+
// Processor to group components into top-level groups such as "Tabs", "Sidenav", etc.
45+
.processor(require('./processors/component-grouper'))
46+
47+
.config(function(log) {
48+
log.level = 'info';
49+
})
50+
51+
// Configure the processor for reading files from the file system.
52+
.config(function(readFilesProcessor, writeFilesProcessor) {
53+
readFilesProcessor.basePath = sourceDir;
54+
readFilesProcessor.$enabled = false; // disable for now as we are using readTypeScriptModules
55+
56+
writeFilesProcessor.outputFolder = outputDir;
57+
})
58+
59+
// Configure the output path for written files (i.e., file names).
60+
.config(function(computePathsProcessor) {
61+
computePathsProcessor.pathTemplates = [{
62+
docTypes: ['componentGroup'],
63+
pathTemplate: '${name}',
64+
outputPathTemplate: '${name}.html',
65+
}];
66+
})
67+
68+
// Configure custom JsDoc tags.
69+
.config(function(parseTagsProcessor) {
70+
parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat([
71+
{name: 'docs-private'}
72+
]);
73+
})
74+
75+
// Configure the processor for understanding TypeScript.
76+
.config(function(readTypeScriptModules) {
77+
console.log(sourceDir);
78+
readTypeScriptModules.basePath = sourceDir;
79+
readTypeScriptModules.ignoreExportsMatching = [/^_/];
80+
readTypeScriptModules.hidePrivateMembers = true;
81+
82+
// Entry points for docs generation. All publically exported symbols found through these
83+
// files will have docs generated.
84+
readTypeScriptModules.sourceFiles = [
85+
'autocomplete/index.ts',
86+
'button/index.ts',
87+
'button-toggle/index.ts',
88+
'card/index.ts',
89+
'checkbox/index.ts',
90+
'chips/index.ts',
91+
'core/index.ts',
92+
'dialog/index.ts',
93+
'grid-list/index.ts',
94+
'icon/index.ts',
95+
'input/index.ts',
96+
'list/index.ts',
97+
'menu/index.ts',
98+
'progress-bar/index.ts',
99+
'progress-circle/index.ts',
100+
'radio/index.ts',
101+
'select/index.ts',
102+
'sidenav/index.ts',
103+
'slide-toggle/index.ts',
104+
'slider/index.ts',
105+
'snack-bar/index.ts',
106+
'tabs/index.ts',
107+
'toolbar/index.ts',
108+
'tooltip/index.ts',
109+
];
110+
})
111+
112+
113+
// Configure processor for finding nunjucks templates.
114+
.config(function(templateFinder, templateEngine) {
115+
// Where to find the templates for the doc rendering
116+
templateFinder.templateFolders = [templateDir];
117+
118+
// Standard patterns for matching docs to templates
119+
templateFinder.templatePatterns = [
120+
'${ doc.template }',
121+
'${ doc.id }.${ doc.docType }.template.html',
122+
'${ doc.id }.template.html',
123+
'${ doc.docType }.template.html',
124+
'${ doc.id }.${ doc.docType }.template.js',
125+
'${ doc.id }.template.js',
126+
'${ doc.docType }.template.js',
127+
'${ doc.id }.${ doc.docType }.template.json',
128+
'${ doc.id }.template.json',
129+
'${ doc.docType }.template.json',
130+
'common.template.html'
131+
];
132+
133+
// Nunjucks and Angular conflict in their template bindings so change Nunjucks
134+
templateEngine.config.tags = {
135+
variableStart: '{$',
136+
variableEnd: '$}'
137+
};
138+
});
139+
140+
141+
module.exports = apiDocsPackage;
142+
143+
// Run the dgeni pipeline, generating documentation.
144+
// TODO(jelbourn): remove this once the process is more final in favor of gulp.
145+
let dgeni = new Dgeni([apiDocsPackage]);
146+
dgeni.generate().then(docs => {
147+
console.log(docs);
148+
});

tools/dgeni/processors/categorizer.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* Processor to add properties to docs objects.
3+
*
4+
* isMethod | Whether the doc is for a method on a class.
5+
* isDirective | Whether the doc is for a @Component or a @Directive
6+
* isService | Whether the doc is for an @Injectable
7+
* isNgModule | Whether the doc is for an NgModule
8+
*/
9+
module.exports = function categorizer() {
10+
return {
11+
$runBefore: ['docs-processed'],
12+
$process: function(docs) {
13+
docs.forEach(doc => {
14+
// The typescriptPackage groups both methods and parameters into "members".
15+
// Use the presence of `parameters` as a proxy to determine if this is a method.
16+
if (doc.classDoc && doc.hasOwnProperty('parameters')) {
17+
doc.isMethod = true;
18+
19+
// Mark methods with a `void` return type so we can omit show the return type in the docs.
20+
doc.showReturns = doc.returnType && doc.returnType != 'void';
21+
22+
normalizeMethodParameters(doc);
23+
24+
// Maintain a list of methods on the associated class so we can
25+
// iterate through them while rendering.
26+
doc.classDoc.methods ?
27+
doc.classDoc.methods.push(doc) :
28+
doc.classDoc.methods = [doc];
29+
} else if (isDirective(doc)) {
30+
doc.isDirective = true;
31+
} else if (isService(doc)) {
32+
doc.isService = true;
33+
} else if (isNgModule(doc)) {
34+
doc.isNgModule = true;
35+
} else if (doc.docType == 'member') {
36+
doc.isDirectiveInput = isDirectiveInput(doc);
37+
doc.isDirectiveOutput = isDirectiveOutput(doc);
38+
39+
doc.classDoc.properties ?
40+
doc.classDoc.properties.push(doc) :
41+
doc.classDoc.properties = [doc];
42+
}
43+
});
44+
}
45+
};
46+
};
47+
48+
49+
/**
50+
* The `parameters` property are the parameters extracted from TypeScript and are strings
51+
* of the form "propertyName: propertyType" (literally what's written in the source).
52+
*
53+
* The `params` property is pulled from the `@param` JsDoc tag. We need to merge
54+
* the information of these to get name + type + description.
55+
*
56+
* We will use the `params` property to store the final normalized form since it is already
57+
* an object.
58+
*/
59+
function normalizeMethodParameters(method) {
60+
if (method.parameters) {
61+
method.parameters.forEach(parameter => {
62+
let [parameterName, parameterType] = parameter.split(':');
63+
64+
if (!method.params) {
65+
method.params = [];
66+
}
67+
68+
let jsDocParam = method.params.find(p => p.name == parameterName);
69+
70+
if (!jsDocParam) {
71+
jsDocParam = {name: parameterName};
72+
method.params.push(jsDocParam);
73+
}
74+
75+
jsDocParam.type = parameterType.trim();
76+
});
77+
}
78+
}
79+
80+
function isDirective(doc) {
81+
return hasClassDecorator(doc, 'Component') || hasClassDecorator(doc, 'Directive');
82+
}
83+
84+
function isService(doc) {
85+
return hasClassDecorator(doc, 'Injectable')
86+
}
87+
88+
function isNgModule(doc) {
89+
return hasClassDecorator(doc, 'NgModule');
90+
}
91+
92+
function isDirectiveOutput(doc) {
93+
return hasMemberDecorator(doc, 'Output');
94+
}
95+
96+
function isDirectiveInput(doc) {
97+
return hasMemberDecorator(doc, 'Input');
98+
}
99+
100+
function hasMemberDecorator(doc, decoratorName) {
101+
return doc.docType == 'member' && hasDecorator(doc, decoratorName);
102+
}
103+
104+
function hasClassDecorator(doc, decoratorName) {
105+
return doc.docType == 'class' && hasDecorator(doc, decoratorName);
106+
}
107+
108+
function hasDecorator(doc, decoratorName) {
109+
return doc.decorators &&
110+
doc.decorators.length &&
111+
doc.decorators.some(d => d.name == decoratorName);
112+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const path = require('path');
2+
3+
/**
4+
* Processor to group docs into top-level "Components" WRT material design, e.g., "Button", "Tabs",
5+
* where each group may conists of several directives and services.
6+
*/
7+
8+
9+
/** Component group data structure. */
10+
class ComponentGroup {
11+
constructor(name) {
12+
this.name = name;
13+
this.id = `component-group-${name}`;
14+
this.docType = 'componentGroup';
15+
this.directives = [];
16+
this.services = [];
17+
this.ngModule = null;
18+
}
19+
}
20+
21+
module.exports = function componentGrouper() {
22+
return {
23+
$runBefore: ['docs-processed'],
24+
$process: function(docs) {
25+
// Map of group name to group instance.
26+
let groups = new Map();
27+
28+
docs.forEach(doc => {
29+
// Full path to the file for this doc.
30+
let basePath = doc.fileInfo.basePath;
31+
let filePath = doc.fileInfo.filePath;
32+
33+
// All of the component documentation is under `src/lib`, which will be the basePath.
34+
// We group the docs up by the directory immediately under `src/lib` (e.g., "button").
35+
let groupName = path.relative(basePath, filePath).split(path.sep)[0];
36+
37+
// Get the group for this doc, or, if one does not exist, create it.
38+
let group;
39+
if (groups.has(groupName)) {
40+
group = groups.get(groupName);
41+
} else {
42+
group = new ComponentGroup(groupName);
43+
groups.set(groupName, group);
44+
}
45+
46+
// Put this doc into the appropriate list in this group.
47+
if (doc.isDirective) {
48+
group.directives.push(doc);
49+
} else if (doc.isService) {
50+
group.services.push(doc);
51+
} else if (doc.isNgModule) {
52+
group.ngModule = doc;
53+
}
54+
});
55+
56+
return Array.from(groups.values());
57+
}
58+
};
59+
};
60+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Processor to filter out symbols that should not be shown in the Material docs.
3+
*/
4+
5+
const INTERNAL_METHODS = [
6+
// Lifecycle methods
7+
'ngOnInit',
8+
'ngOnChanges',
9+
'ngDoCheck',
10+
'ngAfterContentInit',
11+
'ngAfterContentChecked',
12+
'ngAfterViewInit',
13+
'ngAfterViewChecked',
14+
'ngOnDestroy',
15+
16+
// ControlValueAccessor methods
17+
'writeValue',
18+
'registerOnChange',
19+
'registerOnTouched',
20+
'setDisabledState',
21+
22+
// Don't ever need to document constructors
23+
'constructor',
24+
25+
// tabIndex exists on all elements, no need to document it
26+
'tabIndex',
27+
];
28+
29+
module.exports = function docsPrivateFilter() {
30+
return {
31+
$runBefore: ['docs-processed'],
32+
$process: function(docs) {
33+
return docs.filter(d => !(hasDocsPrivateTag(d) || INTERNAL_METHODS.includes(d.name)));
34+
}
35+
};
36+
};
37+
38+
function hasDocsPrivateTag(doc) {
39+
let tags = doc.tags && doc.tags.tags;
40+
return tags ? tags.find(d => d.tagName == 'docs-private') : false;
41+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## {$ doc.name $} ({$ doc.docType $})

0 commit comments

Comments
 (0)