build(docs-infra): process and render ngmodule exports (#25734)
All directives and pipes must now be tagged with one ore more public NgModule, from which they are exported. If an item is exported transitively via a re-exported internal NgModule then it may be that the item appears to be exported from more than one public NgModule. For example, there are shared directives that are exported in this way from `FormsModule` and `ReactiveFormsModule`. The doc-gen will error and fail if a directive or pipe is not tagged correctly. NgModule pages now list all the directives and pipes that are exported from it. Directive and Pipe pages now list any NgModule from which they are exported. Packages also now list any NgModules that are contained - previously they were missed. PR Close #25734
This commit is contained in:
parent
bc5cb8153e
commit
b94436d86c
|
@ -108,4 +108,9 @@
|
|||
font-style: italic;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.ngmodule-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module.exports = function processNgModuleDocs() {
|
||||
module.exports = function processNgModuleDocs(getDocFromAlias, createDocMessage, log) {
|
||||
return {
|
||||
$runAfter: ['extractDecoratedClassesProcessor'],
|
||||
$runBefore: ['docs-processed'],
|
||||
$runAfter: ['extractDecoratedClassesProcessor', 'computeIdsProcessor'],
|
||||
$runBefore: ['createSitemap'],
|
||||
$process(docs) {
|
||||
docs.forEach(doc => {
|
||||
if (doc.docType === 'ngmodule') {
|
||||
|
@ -13,6 +13,43 @@ module.exports = function processNgModuleDocs() {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Match all the directives/pipes to their module
|
||||
const errors = [];
|
||||
docs.forEach(doc => {
|
||||
if (['directive', 'pipe'].indexOf(doc.docType) !== -1) {
|
||||
if (!doc.ngModules || doc.ngModules.length === 0) {
|
||||
errors.push(createDocMessage(`"${doc.id}" has no @ngModule tag. Docs of type "${doc.docType}" must have this tag.`, doc));
|
||||
return;
|
||||
}
|
||||
|
||||
doc.ngModules.forEach((ngModule, index) => {
|
||||
|
||||
const ngModuleDocs = getDocFromAlias(ngModule, doc);
|
||||
|
||||
if (ngModuleDocs.length === 0) {
|
||||
errors.push(createDocMessage(`"@ngModule ${ngModule}" does not match a public NgModule`, doc));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngModuleDocs.length > 1) {
|
||||
errors.push(createDocMessage(`"@ngModule ${ngModule}" is ambiguous. Matches: ${ngModuleDocs.map(d => d.id).join(', ')}`, doc));
|
||||
return;
|
||||
}
|
||||
|
||||
const ngModuleDoc = ngModuleDocs[0];
|
||||
const container = ngModuleDoc[doc.docType + 's'] = ngModuleDoc[doc.docType + 's'] || [];
|
||||
container.push(doc);
|
||||
|
||||
doc.ngModules[index] = ngModuleDoc;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
errors.forEach(error => log.error(error));
|
||||
throw new Error('Failed to process NgModule relationships.');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,9 +3,10 @@ const Dgeni = require('dgeni');
|
|||
|
||||
describe('processNgModuleDocs processor', () => {
|
||||
let processor;
|
||||
let injector;
|
||||
beforeEach(() => {
|
||||
const dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||
const injector = dgeni.configureInjector();
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('processNgModuleDocs');
|
||||
});
|
||||
|
||||
|
@ -14,11 +15,105 @@ describe('processNgModuleDocs processor', () => {
|
|||
});
|
||||
|
||||
it('should run before the correct processor', () => {
|
||||
expect(processor.$runBefore).toEqual(['docs-processed']);
|
||||
expect(processor.$runBefore).toEqual(['createSitemap']);
|
||||
});
|
||||
|
||||
it('should run after the correct processor', () => {
|
||||
expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor']);
|
||||
expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor', 'computeIdsProcessor']);
|
||||
});
|
||||
|
||||
it('should non-arrayNgModule options to arrays', () => {
|
||||
const docs = [{
|
||||
docType: 'ngmodule',
|
||||
ngmoduleOptions: {
|
||||
a: ['AAA'],
|
||||
b: 'BBB',
|
||||
c: 42
|
||||
}
|
||||
}];
|
||||
processor.$process(docs);
|
||||
expect(docs[0].ngmoduleOptions.a).toEqual(['AAA']);
|
||||
expect(docs[0].ngmoduleOptions.b).toEqual(['BBB']);
|
||||
expect(docs[0].ngmoduleOptions.c).toEqual([42]);
|
||||
});
|
||||
|
||||
it('should link directive/pipe docs with their NgModule docs', () => {
|
||||
const aliasMap = injector.get('aliasMap');
|
||||
const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModule1'], ngmoduleOptions: {}};
|
||||
const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModule2'], ngmoduleOptions: {}};
|
||||
const directive1 = { docType: 'directive', id: 'Directive1', ngModules: ['NgModule1']};
|
||||
const directive2 = { docType: 'directive', id: 'Directive2', ngModules: ['NgModule2']};
|
||||
const directive3 = { docType: 'directive', id: 'Directive3', ngModules: ['NgModule1', 'NgModule2']};
|
||||
const pipe1 = { docType: 'pipe', id: 'Pipe1', ngModules: ['NgModule1']};
|
||||
const pipe2 = { docType: 'pipe', id: 'Pipe2', ngModules: ['NgModule2']};
|
||||
const pipe3 = { docType: 'pipe', id: 'Pipe3', ngModules: ['NgModule1', 'NgModule2']};
|
||||
|
||||
aliasMap.addDoc(ngModule1);
|
||||
aliasMap.addDoc(ngModule2);
|
||||
processor.$process([ngModule1, ngModule2, directive1, directive2, directive3, pipe1, pipe2, pipe3]);
|
||||
|
||||
expect(ngModule1.directives).toEqual([directive1, directive3]);
|
||||
expect(ngModule1.pipes).toEqual([pipe1, pipe3]);
|
||||
expect(ngModule2.directives).toEqual([directive2, directive3]);
|
||||
expect(ngModule2.pipes).toEqual([pipe2, pipe3]);
|
||||
|
||||
expect(directive1.ngModules).toEqual([ngModule1]);
|
||||
expect(directive2.ngModules).toEqual([ngModule2]);
|
||||
expect(directive3.ngModules).toEqual([ngModule1, ngModule2]);
|
||||
|
||||
expect(pipe1.ngModules).toEqual([ngModule1]);
|
||||
expect(pipe2.ngModules).toEqual([ngModule2]);
|
||||
expect(pipe3.ngModules).toEqual([ngModule1, ngModule2]);
|
||||
});
|
||||
|
||||
it('should error if a pipe/directive does not have a `@ngModule` tag', () => {
|
||||
const log = injector.get('log');
|
||||
expect(() => {
|
||||
processor.$process([{ docType: 'directive', id: 'Directive1' }]);
|
||||
}).toThrowError('Failed to process NgModule relationships.');
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'"Directive1" has no @ngModule tag. Docs of type "directive" must have this tag. - doc "Directive1" (directive) ');
|
||||
|
||||
expect(() => {
|
||||
processor.$process([{ docType: 'pipe', id: 'Pipe1' }]);
|
||||
}).toThrowError('Failed to process NgModule relationships.');
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'"Pipe1" has no @ngModule tag. Docs of type "pipe" must have this tag. - doc "Pipe1" (pipe) ');
|
||||
});
|
||||
|
||||
it('should error if a pipe/directive has an @ngModule tag that does not match an NgModule doc', () => {
|
||||
const log = injector.get('log');
|
||||
expect(() => {
|
||||
processor.$process([{ docType: 'directive', id: 'Directive1', ngModules: ['MissingNgModule'] }]);
|
||||
}).toThrowError('Failed to process NgModule relationships.');
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'"@ngModule MissingNgModule" does not match a public NgModule - doc "Directive1" (directive) ');
|
||||
|
||||
expect(() => {
|
||||
processor.$process([{ docType: 'pipe', id: 'Pipe1', ngModules: ['MissingNgModule'] }]);
|
||||
}).toThrowError('Failed to process NgModule relationships.');
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'"@ngModule MissingNgModule" does not match a public NgModule - doc "Pipe1" (pipe) ');
|
||||
});
|
||||
|
||||
it('should error if a pipe/directive has an @ngModule tag that matches more than one NgModule doc', () => {
|
||||
const aliasMap = injector.get('aliasMap');
|
||||
const log = injector.get('log');
|
||||
const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModuleAlias'], ngmoduleOptions: {}};
|
||||
const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModuleAlias'], ngmoduleOptions: {}};
|
||||
aliasMap.addDoc(ngModule1);
|
||||
aliasMap.addDoc(ngModule2);
|
||||
|
||||
expect(() => {
|
||||
processor.$process([{ docType: 'directive', id: 'Directive1', ngModules: ['NgModuleAlias'] }]);
|
||||
}).toThrowError('Failed to process NgModule relationships.');
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'"@ngModule NgModuleAlias" is ambiguous. Matches: NgModule1, NgModule2 - doc "Directive1" (directive) ');
|
||||
|
||||
expect(() => {
|
||||
processor.$process([{ docType: 'pipe', id: 'Pipe1', ngModules: ['NgModuleAlias'] }]);
|
||||
}).toThrowError('Failed to process NgModule relationships.');
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'"@ngModule NgModuleAlias" is ambiguous. Matches: NgModule1, NgModule2 - doc "Pipe1" (pipe) ');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ module.exports = function processPackages() {
|
|||
|
||||
// Partition the exports into groups by type
|
||||
if (doc.exports) {
|
||||
doc.ngmodules = doc.exports.filter(doc => doc.docType === 'ngmodule');
|
||||
doc.classes = doc.exports.filter(doc => doc.docType === 'class');
|
||||
doc.decorators = doc.exports.filter(doc => doc.docType === 'decorator');
|
||||
doc.functions = doc.exports.filter(doc => doc.docType === 'function');
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
module.exports = function() {
|
||||
return {name: 'ngModule'};
|
||||
return {
|
||||
name: 'ngModule',
|
||||
docProperty: 'ngModules',
|
||||
multi: true,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
{% block overview %}{% include "includes/directive-overview.html" %}{% endblock %}
|
||||
{% block additional -%}
|
||||
{% include "includes/ngmodule.html" %}
|
||||
{% include "includes/selectors.html" %}
|
||||
{$ directiveHelper.renderBindings(doc.inputs, 'inputs', 'input', 'Inputs') $}
|
||||
{$ directiveHelper.renderBindings(doc.outputs, 'outputs', 'output', 'Outputs') $}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<h2>NgModules</h2>
|
||||
<ul class="ngmodule-list">
|
||||
{% for ngModule in doc.ngModules %}
|
||||
<li>
|
||||
<a href="{$ ngModule.path $}">
|
||||
<code-example language="ts" hideCopy="true" linenums="false" class="no-box">{$ ngModule.name | escape $}</code-example>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
@ -10,6 +10,8 @@
|
|||
{%- if param.isOptional or param.defaultValue !== undefined %} ]{% endif %}
|
||||
{%- endfor %} }}</code-example>
|
||||
|
||||
{% include "includes/ngmodule.html" %}
|
||||
|
||||
{% if doc.valueParam.type %}
|
||||
<h2>Input value</h2>
|
||||
{$ params.renderParameters([doc.valueParam], 'pipe-parameters', 'pipe-parameter', true) $}
|
||||
|
|
|
@ -27,6 +27,31 @@
|
|||
</section>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro renderExports(items, cssClass, itemType) %}
|
||||
<section class="{$ cssClass $}s">
|
||||
<h2>{$ itemType $}</h2>
|
||||
<table class="is-full-width list-table {$ cssClass $}-table">
|
||||
<thead>
|
||||
<tr><th>Name</th><th>Description</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{$ item.path $}">
|
||||
<code-example language="ts" hideCopy="true" linenums="false" class="no-box">{$ item.name | escape $}</code-example>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{$ item.shortDescription | marked $}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endmacro %}
|
||||
|
||||
{% block overview %}
|
||||
{% include "includes/class-overview.html" %}
|
||||
{% endblock %}
|
||||
|
@ -47,8 +72,11 @@
|
|||
{$ renderTable(doc.ngmoduleOptions.providers, 'providers', 'Providers', 'Provider') $}
|
||||
{% endif %}
|
||||
|
||||
{% if doc.ngmoduleOptions.exports %}
|
||||
{$ renderTable(doc.ngmoduleOptions.exports, 'exports', 'Exports', 'Export') $}
|
||||
{% if doc.directives.length %}
|
||||
{$ renderExports(doc.directives, 'directive', 'Directives') $}
|
||||
{% endif %}
|
||||
{% if doc.pipes.length %}
|
||||
{$ renderExports(doc.pipes, 'pipe', 'Pipes') $}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
{% endif %}
|
||||
|
||||
<h2>{% if doc.isPrimaryPackage %}Primary entry{% else %}Entry{% endif %} point exports</h2>
|
||||
{$ listItems(doc.ngmodules, 'NgModules') $}
|
||||
{$ listItems(doc.classes, 'Classes') $}
|
||||
{$ listItems(doc.decorators, 'Decorators') $}
|
||||
{$ listItems(doc.functions, 'Functions') $}
|
||||
|
|
Loading…
Reference in New Issue