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'},
],
$process(docs) {
const decoratorDocs = Object.create(null);
// 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>(...)`.
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.decoratorLocation = call.description;
@ -84,8 +84,8 @@ module.exports = function mergeDecoratorDocs(log) {
// merge the info from the associated metadata interfaces into the decorator docs
docs = docs.filter(doc => {
if (decoratorDocs[doc.name]) {
// We have found an `XxxDecorator` document that will hold the call signature of the decorator
// We have found an `XxxDecorator` document that will hold the call signature of the
// decorator
var decoratorDoc = decoratorDocs[doc.name];
var callMember = doc.members.find(member => member.isCallMember);
@ -109,18 +109,44 @@ module.exports = function mergeDecoratorDocs(log) {
};
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:
// export var Injectable: InjectableFactory =
// <InjectableFactory>makeDecorator(InjectableMetadata);
//
// ```
// export const Injectable: InjectableFactory =
// <InjectableFactory>makeDecorator(InjectableMetadata);
// ```
//
// and
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
// 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.
if (initializer && initializer.expression && initializer.expression.expression) {
//
// ```
// export const RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
// 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.
if (initializer.type && initializer.expression.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;
}

View File

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