build(docs-infra): capture all decorators in doc-gen (#41091)

In 6cff877 we broke the decorator docs because the
doc-gen no longer knew how to identify them.

This commit updates the dgeni processor responsible
for identifying the decorators in the code and ensures
that the docs are now generated correctly.

Fixes #40851

PR Close #41091
This commit is contained in:
Pete Bacon Darwin 2021-03-05 15:25:40 +00:00 committed by Andrew Kushnir
parent 7854c4ddbe
commit 2893b925c3
2 changed files with 72 additions and 28 deletions

View File

@ -56,7 +56,6 @@ module.exports = function mergeDecoratorDocs(log) {
{type: 'Param', description: 'parameter', functionName: 'makeParamDecorator'}, {type: 'Param', description: 'parameter', functionName: 'makeParamDecorator'},
], ],
$process(docs) { $process(docs) {
const decoratorDocs = Object.create(null); const decoratorDocs = Object.create(null);
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)` // find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
@ -69,7 +68,8 @@ module.exports = function mergeDecoratorDocs(log) {
// For example the `X` of `createDecorator<X>(...)`. // For example the `X` of `createDecorator<X>(...)`.
const decoratorType = initializer.arguments[0].text; const decoratorType = initializer.arguments[0].text;
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name, decoratorType); log.debug(
'mergeDecoratorDocs: found decorator', doc.docType, doc.name, decoratorType);
doc.docType = 'decorator'; doc.docType = 'decorator';
doc.decoratorLocation = call.description; doc.decoratorLocation = call.description;
@ -84,8 +84,8 @@ module.exports = function mergeDecoratorDocs(log) {
// merge the info from the associated metadata interfaces into the decorator docs // merge the info from the associated metadata interfaces into the decorator docs
docs = docs.filter(doc => { docs = docs.filter(doc => {
if (decoratorDocs[doc.name]) { if (decoratorDocs[doc.name]) {
// We have found an `XxxDecorator` document that will hold the call signature of the
// We have found an `XxxDecorator` document that will hold the call signature of the decorator // decorator
var decoratorDoc = decoratorDocs[doc.name]; var decoratorDoc = decoratorDocs[doc.name];
var callMember = doc.members.find(member => member.isCallMember); var callMember = doc.members.find(member => member.isCallMember);
@ -109,18 +109,44 @@ module.exports = function mergeDecoratorDocs(log) {
}; };
function getInitializer(doc) { function getInitializer(doc) {
var initializer = doc.symbol && doc.symbol.valueDeclaration && doc.symbol.valueDeclaration.initializer; const declaration = doc.symbol && doc.symbol.valueDeclaration;
if (!declaration || !declaration.initializer || !declaration.initializer.expression) {
return;
}
let initializer = declaration.initializer;
// There appear to be two forms of initializer: // There appear to be two forms of initializer:
// export var Injectable: InjectableFactory = //
// ```
// export const Injectable: InjectableFactory =
// <InjectableFactory>makeDecorator(InjectableMetadata); // <InjectableFactory>makeDecorator(InjectableMetadata);
// ```
//
// and // and
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator = //
// ```
// export const RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
// makeDecorator(RouteConfigAnnotation); // makeDecorator(RouteConfigAnnotation);
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an // ```
// extra level of expression //
// to hold the new type of the expression. // In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an extra
if (initializer && initializer.expression && initializer.expression.expression) { // level of expression to hold the new type of the expression.
if (initializer.type && initializer.expression.expression) {
initializer = initializer.expression; initializer = initializer.expression;
} }
// It is also possible that the decorator call is wrapped in a call to `attachInjectFlag()`:
//
// ```
// const Optional: OptionalDecorator =
// attachInjectFlag(makeParamDecorator('Optional'), InternalInjectFlags.Optional);
// ```
//
// If so, use the first argument of the call.
if (initializer.arguments && initializer.expression.text === 'attachInjectFlag') {
initializer = initializer.arguments[0];
}
return initializer; return initializer;
} }

View File

@ -1,5 +1,5 @@
var testPackage = require('../../helpers/test-package'); const testPackage = require('../../helpers/test-package');
var Dgeni = require('dgeni'); const Dgeni = require('dgeni');
describe('mergeDecoratorDocs processor', () => { describe('mergeDecoratorDocs processor', () => {
let processor, moduleDoc, decoratorDoc, metadataDoc, otherDoc; let processor, moduleDoc, decoratorDoc, metadataDoc, otherDoc;
@ -20,11 +20,10 @@ describe('mergeDecoratorDocs processor', () => {
shortDescription: 'decorator - short description', shortDescription: 'decorator - short description',
description: 'decorator - description', description: 'decorator - description',
symbol: { symbol: {
valueDeclaration: { initializer: { expression: { text: 'makeDecorator' }, arguments: [{ text: 'X' }] } } valueDeclaration:
{initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}}
}, },
members: [ members: [{name: 'templateUrl', description: 'templateUrl - description'}],
{ name: 'templateUrl', description: 'templateUrl - description' }
],
moduleDoc moduleDoc
}; };
@ -38,9 +37,7 @@ describe('mergeDecoratorDocs processor', () => {
description: 'call interface - call member - description', description: 'call interface - call member - description',
usageNotes: 'call interface - call member - usageNotes', usageNotes: 'call interface - call member - usageNotes',
}, },
{ {description: 'call interface - non call member - description'}
description: 'call interface - non call member - description'
}
], ],
moduleDoc moduleDoc
}; };
@ -49,7 +46,8 @@ describe('mergeDecoratorDocs processor', () => {
name: 'Y', name: 'Y',
docType: 'const', docType: 'const',
symbol: { symbol: {
valueDeclaration: { initializer: { expression: { text: 'otherCall' }, arguments: [{ text: 'param1' }] } } valueDeclaration:
{initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}}
}, },
moduleDoc moduleDoc
}; };
@ -58,7 +56,8 @@ describe('mergeDecoratorDocs processor', () => {
}); });
it('should change the docType of only the docs that are initialized by a call to makeDecorator', () => { it('should change the docType of only the docs that are initialized by a call to makeDecorator',
() => {
processor.$process([decoratorDoc, metadataDoc, otherDoc]); processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator'); expect(decoratorDoc.docType).toEqual('decorator');
expect(otherDoc.docType).toEqual('const'); expect(otherDoc.docType).toEqual('const');
@ -88,4 +87,23 @@ describe('mergeDecoratorDocs processor', () => {
processor.$process([decoratorDoc, metadataDoc, otherDoc]); processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator'); expect(decoratorDoc.docType).toEqual('decorator');
}); });
it('should handle a type cast before the "make decorator" call', () => {
decoratorDoc.symbol.valueDeclaration.initializer = {
expression: decoratorDoc.symbol.valueDeclaration.initializer,
type: {},
};
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator');
});
it('should handle the "make decorator" call being wrapped in a call to `attachInjectFlag()`',
() => {
decoratorDoc.symbol.valueDeclaration.initializer = {
expression: {text: 'attachInjectFlag'},
arguments: [decoratorDoc.symbol.valueDeclaration.initializer]
};
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator');
});
}); });