FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

build(docs-infra): support doc aliases via `@alias` dgeni tag (#29673) · ehtesham/angular@a4f3f3f · GitHub

Repository navigation

Commit a4f3f3f

Browse files
authored andcommitted
build(docs-infra): support doc aliases via @alias dgeni tag (angular#29673)
Now, one can add an `@alias` tag to API docs, which tells dgeni that this API element (usually a `const`) is really just an alias for some API element defined elsewhere. Dgeni will then look up this API element and copy over the properties from the alias to the current doc. For example, we would like to privately export an Enum from `@angular/core` but then publicly export this from `@angular/common`: **packages/core/private_exports.ts** ```ts /** * Description of this document. */ export enum ɵSomeEnum { ... } ``` **packages/common/public_api.ts** ```ts import {ɵSomeEnum} from '@angular/core'; /** * @alias core/ɵSomeEnum */ export const SomeEnum = ɵSomeEnum; ``` In the generated docs there will be a page for `common/SomeEnum`, which will be rendered as an enum, rather than a const, showing the description extracted from the `core/ɵSomeEnum`. --- The implementation of this feature required some refactoring of the other processing: 1. Previously `ɵ` prefixed exports were not even considered. 2. Due to 1. some processors needed to have guards added to ignore such private exports (`addMetadataAliases` and `checkContentRules`). 3. The processing of package pages had to be reworked (and split) so that it picked up the aliased export docs after their alias proeprties had been copied. See FW-1207, FW-632, angular#29249 PR Close angular#29673
1 parent 6233cd5 commit a4f3f3f

13 files changed

Lines changed: 297 additions & 93 deletions

File tree

‎aio/tools/transforms/angular-api-package/index.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
3232
.processor(require('./processors/simplifyMemberAnchors'))
3333
.processor(require('./processors/computeStability'))
3434
.processor(require('./processors/removeInjectableConstructors'))
35+
.processor(require('./processors/collectPackageContentDocs'))
3536
.processor(require('./processors/processPackages'))
3637
.processor(require('./processors/processNgModuleDocs'))
3738
.processor(require('./processors/fixupRealProjectRelativePath'))
39+
.processor(require('./processors/processAliasDocs'))
3840

3941

4042
/**
@@ -72,7 +74,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
7274

7375
// API files are typescript
7476
readTypeScriptModules.basePath = API_SOURCE_PATH;
75-
readTypeScriptModules.ignoreExportsMatching = [/^[_ɵ]|^VERSION$/];
77+
readTypeScriptModules.ignoreExportsMatching = [/^[_]|^VERSION$/];
7678
readTypeScriptModules.hidePrivateMembers = true;
7779

7880
// NOTE: This list should be in sync with tools/public_api_guard/BUILD.bazel

‎aio/tools/transforms/angular-api-package/processors/addMetadataAliases.js‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ module.exports = function addMetadataAliasesProcessor() {
1515
docs.forEach(doc => {
1616
switch(doc.docType) {
1717
case 'directive':
18-
case 'component':
19-
doc.aliases = doc.aliases.concat(extractSelectors(doc[doc.docType + 'Options'].selector));
18+
case 'component': {
19+
const selector = doc[doc.docType + 'Options'].selector;
20+
if (selector) {
21+
doc.aliases = doc.aliases.concat(extractSelectors(selector));
22+
}
2023
break;
24+
}
2125
case 'pipe':
2226
if (doc.pipeOptions.name) {
2327
doc.aliases = doc.aliases.concat(stripQuotes(doc.pipeOptions.name));

‎aio/tools/transforms/angular-api-package/processors/addMetadataAliases.spec.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,15 @@ describe('addSelectorsAsAliases processor', () => {
5454
expect(docs[11].aliases).toEqual([docs[11].name]);
5555
expect(docs[12].aliases).toEqual([docs[12].name]);
5656
});
57+
58+
it('should ignore directives and components that have no selector', () => {
59+
const processor = processorFactory();
60+
const docs = [
61+
{ docType: 'directive', name: 'MyDirective', aliases: ['MyDirective'], directiveOptions: { } },
62+
{ docType: 'component', name: 'MyComponent', aliases: ['MyComponent'], componentOptions: { } },
63+
];
64+
processor.$process(docs);
65+
expect(docs[0].aliases).toEqual([docs[0].name]);
66+
expect(docs[1].aliases).toEqual([docs[1].name]);
67+
});
5768
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { dirname } = require('canonical-path');
2+
3+
module.exports = function collectPackageContentDocsProcessor() {
4+
return {
5+
$runAfter: ['tags-extracted'],
6+
$runBefore: ['computing-ids', 'processPackages'],
7+
packageContentFiles: {},
8+
$process(docs) {
9+
return docs.filter(doc => {
10+
if (doc.docType === 'package-content') {
11+
this.packageContentFiles[dirname(doc.fileInfo.filePath)] = doc;
12+
return false;
13+
} else {
14+
return true;
15+
}
16+
});
17+
}
18+
};
19+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const testPackage = require('../../helpers/test-package');
2+
const processorFactory = require('./collectPackageContentDocs');
3+
const Dgeni = require('dgeni');
4+
5+
describe('collectPackageContentDocs processor', () => {
6+
7+
it('should be available on the injector', () => {
8+
const dgeni = new Dgeni([testPackage('angular-api-package')]);
9+
const injector = dgeni.configureInjector();
10+
const processor = injector.get('collectPackageContentDocsProcessor');
11+
expect(processor.$process).toBeDefined();
12+
expect(processor.$runAfter).toEqual(['tags-extracted']);
13+
expect(processor.$runBefore).toEqual(['computing-ids', 'processPackages']);
14+
});
15+
16+
it('should collect any `package-content` docs in the `packageContentFiles` map', () => {
17+
const docs = [
18+
{ fileInfo: { filePath: 'some/a' }, docType: 'a', id: 'a' },
19+
{ fileInfo: { filePath: 'some/x/PACKAGE.md' }, docType: 'package-content', id: 'x' },
20+
{ fileInfo: { filePath: 'some/b' }, docType: 'b', id: 'b' },
21+
{ fileInfo: { filePath: 'some/y/PACKAGE.md' }, docType: 'package-content', id: 'y' },
22+
{ fileInfo: { filePath: 'some/z/PACKAGE.md' }, docType: 'package-content', id: 'z' },
23+
];
24+
const processor = processorFactory();
25+
processor.$process(docs);
26+
27+
expect(processor.packageContentFiles).toEqual({
28+
'some/x': { fileInfo: { filePath: 'some/x/PACKAGE.md' }, docType: 'package-content', id: 'x' },
29+
'some/y': { fileInfo: { filePath: 'some/y/PACKAGE.md' }, docType: 'package-content', id: 'y' },
30+
'some/z': { fileInfo: { filePath: 'some/z/PACKAGE.md' }, docType: 'package-content', id: 'z' },
31+
});
32+
});
33+
34+
it('should filter out any `package-content` docs from the collection', () => {
35+
const docs = [
36+
{ fileInfo: { filePath: 'some/a' }, docType: 'a', id: 'a' },
37+
{ fileInfo: { filePath: 'some/x/PACKAGE.md' }, docType: 'package-content', id: 'x' },
38+
{ fileInfo: { filePath: 'some/b' }, docType: 'b', id: 'b' },
39+
{ fileInfo: { filePath: 'some/y/PACKAGE.md' }, docType: 'package-content', id: 'y' },
40+
{ fileInfo: { filePath: 'some/z/PACKAGE.md' }, docType: 'package-content', id: 'z' },
41+
];
42+
const processor = processorFactory();
43+
const newDocs = processor.$process(docs);
44+
expect(newDocs).toEqual([
45+
{ fileInfo: { filePath: 'some/a' }, docType: 'a', id: 'a' },
46+
{ fileInfo: { filePath: 'some/b' }, docType: 'b', id: 'b' },
47+
]);
48+
});
49+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copies over the properties from a doc's alias if it is marked with `@alias`.
3+
*/
4+
module.exports = function processAliasDocs(getDocFromAlias, log, createDocMessage) {
5+
return {
6+
$runAfter: ['tags-extracted', 'ids-computed'],
7+
$runBefore: ['filterPrivateDocs'],
8+
propertiesToKeep: [
9+
'name', 'id', 'aliases', 'fileInfo', 'startingLine', 'endingLine',
10+
'path', 'originalModule', 'outputPath', 'privateExport', 'moduleDoc'
11+
],
12+
$process(docs) {
13+
docs.forEach(doc => {
14+
if (doc.aliasDocId) {
15+
const aliasDocs = getDocFromAlias(doc.aliasDocId, doc);
16+
if (aliasDocs.length === 1) {
17+
const aliasDoc = aliasDocs[0];
18+
log.debug('processing alias', doc.id, doc.aliasDocId, aliasDoc.id);
19+
// Clean out the unwanted properties from the doc
20+
Object.keys(doc).forEach(key => {
21+
if (!this.propertiesToKeep.includes(key)) {
22+
delete doc[key];
23+
}
24+
});
25+
// Copy over all the properties of the alias doc.
26+
Object.keys(aliasDoc).forEach(key => {
27+
if (!this.propertiesToKeep.includes(key)) {
28+
doc[key] = aliasDoc[key];
29+
}
30+
});
31+
} else if (aliasDocs.length === 0) {
32+
throw new Error(createDocMessage(`There is no doc that matches "@alias ${doc.aliasDocId}"`, doc));
33+
} else {
34+
throw new Error(createDocMessage(`There is more than one doc that matches "@alias ${doc.aliasDocId}": ${aliasDocs.map(d => d.id).join(', ')}.`, doc));
35+
}
36+
}
37+
});
38+
}
39+
};
40+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const testPackage = require('../../helpers/test-package');
2+
const processorFactory = require('./processAliasDocs');
3+
const Dgeni = require('dgeni');
4+
const mockLogFactory = require('dgeni/lib/mocks/log');
5+
const createDocMessageFactory = require('dgeni-packages/base/services/createDocMessage');
6+
7+
describe('processAliasDocs processor', () => {
8+
9+
let getDocFromAlias, log, createDocMessage, processor;
10+
11+
beforeEach(() => {
12+
getDocFromAlias = jasmine.createSpy('getDocFromAlias');
13+
log = mockLogFactory(false);
14+
createDocMessage = createDocMessageFactory();
15+
processor = processorFactory(getDocFromAlias, log, createDocMessage);
16+
});
17+
18+
it('should be available on the injector', () => {
19+
const dgeni = new Dgeni([testPackage('angular-api-package')]);
20+
const injector = dgeni.configureInjector();
21+
const processor = injector.get('processAliasDocs');
22+
expect(processor.$process).toBeDefined();
23+
});
24+
25+
it('should run before the correct processor', () => {
26+
expect(processor.$runBefore).toEqual(['filterPrivateDocs']);
27+
});
28+
29+
it('should run after the correct processor', () => {
30+
expect(processor.$runAfter).toEqual(['tags-extracted', 'ids-computed']);
31+
});
32+
33+
it('should ignore docs that do not have an `@alias` tag', () => {
34+
const docs = [{}];
35+
getDocFromAlias.and.returnValue([{ prop1: 'prop-1', prop2: 'prop-2', prop3: 'prop-3' }]);
36+
processor.$process(docs);
37+
expect(docs).toEqual([{}]);
38+
});
39+
40+
it('should copy over properties from a valid alias doc', () => {
41+
const docs = [{ aliasDocId: 'alias-doc' }];
42+
getDocFromAlias.and.returnValue([{ prop1: 'prop-1', prop2: 'prop-2', prop3: 'prop-3' }]);
43+
processor.$process(docs);
44+
expect(docs).toEqual([
45+
{ prop1: 'prop-1', prop2: 'prop-2', prop3: 'prop-3' }
46+
]);
47+
});
48+
49+
it('should error if `@alias` does not match a doc', () => {
50+
const docs = [{ aliasDocId: 'alias-doc' }];
51+
getDocFromAlias.and.returnValue([]);
52+
expect(() => processor.$process(docs)).toThrowError('There is no doc that matches "@alias alias-doc" - doc');
53+
});
54+
55+
it('should error if `@alias` matches more than one doc', () => {
56+
const docs = [{ aliasDocId: 'alias-doc' }];
57+
getDocFromAlias.and.returnValue([{id: 'alias-1'}, {id: 'alias-2'}]);
58+
expect(() => processor.$process(docs)).toThrowError('There is more than one doc that matches "@alias alias-doc": alias-1, alias-2. - doc');
59+
});
60+
61+
it('should remove all but the specified properties from the original doc', () => {
62+
processor.propertiesToKeep = ['x', 'y'];
63+
const docs = [{ aliasDocId: 'alias-doc', x: 'original-x', z: 'original-z' }];
64+
getDocFromAlias.and.returnValue([{}]);
65+
processor.$process(docs);
66+
expect(docs).toEqual([{ x: 'original-x' }]);
67+
});
68+
69+
it('should copy over all but the specified properties from the aliased doc', () => {
70+
processor.propertiesToKeep = ['x', 'y'];
71+
const docs = [{ aliasDocId: 'alias-doc', x: 'original-x', z: 'original-z' }];
72+
getDocFromAlias.and.returnValue([{ x: 'alias-x', y: 'alias-y', z: 'alias-z' }]);
73+
processor.$process(docs);
74+
expect(docs).toEqual([{ x: 'original-x', z: 'alias-z' }]);
75+
});
76+
77+
it('should have default properties to keep', () => {
78+
expect(processor.propertiesToKeep).toEqual([
79+
'name', 'id', 'aliases', 'fileInfo', 'startingLine', 'endingLine',
80+
'path', 'originalModule', 'outputPath', 'privateExport', 'moduleDoc'
81+
]);
82+
});
83+
});

‎aio/tools/transforms/angular-api-package/processors/processPackages.js‎

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
const { dirname } = require('canonical-path');
22

3-
module.exports = function processPackages() {
3+
module.exports = function processPackages(collectPackageContentDocsProcessor) {
44
return {
5-
$runAfter: ['extractDecoratedClassesProcessor', 'computeStability'],
6-
$runBefore: ['computing-ids', 'generateKeywordsProcessor'],
5+
$runAfter: ['processAliasDocs', 'collectPackageContentDocsProcessor'],
6+
$runBefore: ['rendering-docs', 'checkContentRules'],
77
$process(docs) {
8-
const packageContentFiles = {};
8+
const packageContentFiles = collectPackageContentDocsProcessor.packageContentFiles;
99
const packageMap = {};
1010

11-
docs = docs.filter(doc => {
12-
if (doc.docType === 'package-content') {
13-
packageContentFiles[dirname(doc.fileInfo.filePath)] = doc;
14-
return false;
15-
} else {
16-
return true;
17-
}
18-
});
19-
2011
docs.forEach(doc => {
2112
if (doc.docType === 'module') {
2213
// Convert the doc type from "module" to "package"
@@ -26,15 +17,16 @@ module.exports = function processPackages() {
2617

2718
// Partition the exports into groups by type
2819
if (doc.exports) {
29-
doc.ngmodules = doc.exports.filter(doc => doc.docType === 'ngmodule').sort(byId);
30-
doc.classes = doc.exports.filter(doc => doc.docType === 'class').sort(byId);
31-
doc.decorators = doc.exports.filter(doc => doc.docType === 'decorator').sort(byId);
32-
doc.functions = doc.exports.filter(doc => doc.docType === 'function').sort(byId);
33-
doc.structures = doc.exports.filter(doc => doc.docType === 'enum' || doc.docType === 'interface').sort(byId);
34-
doc.directives = doc.exports.filter(doc => doc.docType === 'directive').sort(byId);
35-
doc.pipes = doc.exports.filter(doc => doc.docType === 'pipe').sort(byId);
36-
doc.types = doc.exports.filter(doc => doc.docType === 'type-alias' || doc.docType === 'const').sort(byId);
37-
if (doc.exports.every(doc => !!doc.deprecated)) {
20+
const publicExports = doc.exports.filter(doc => !doc.privateExport);
21+
doc.ngmodules = publicExports.filter(doc => doc.docType === 'ngmodule').sort(byId);
22+
doc.classes = publicExports.filter(doc => doc.docType === 'class').sort(byId);
23+
doc.decorators = publicExports.filter(doc => doc.docType === 'decorator').sort(byId);
24+
doc.functions = publicExports.filter(doc => doc.docType === 'function').sort(byId);
25+
doc.structures = publicExports.filter(doc => doc.docType === 'enum' || doc.docType === 'interface').sort(byId);
26+
doc.directives = publicExports.filter(doc => doc.docType === 'directive').sort(byId);
27+
doc.pipes = publicExports.filter(doc => doc.docType === 'pipe').sort(byId);
28+
doc.types = publicExports.filter(doc => doc.docType === 'type-alias' || doc.docType === 'const').sort(byId);
29+
if (publicExports.every(doc => !!doc.deprecated)) {
3830
doc.deprecated = 'all exports of this entry point are deprecated.';
3931
}
4032
}
@@ -66,8 +58,6 @@ module.exports = function processPackages() {
6658
const pkg = packageMap[key];
6759
pkg.primary.packageDeprecated = pkg.primary.deprecated !== undefined && pkg.secondary.every(entryPoint => entryPoint.deprecated !== undefined);
6860
});
69-
70-
return docs;
7161
}
7262
};
7363
};

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL