From 2893b925c3efc2226efd4bf4b8328de2c1dc1876 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 5 Mar 2021 15:25:40 +0000 Subject: [PATCH] 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 --- .../processors/mergeDecoratorDocs.js | 52 ++++++++++++++----- .../processors/mergeDecoratorDocs.spec.js | 48 +++++++++++------ 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js index 414e742b62..3ee25a8cb0 100644 --- a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js +++ b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js @@ -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(metadata)` @@ -69,7 +68,8 @@ module.exports = function mergeDecoratorDocs(log) { // For example the `X` of `createDecorator(...)`. 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 = - // makeDecorator(InjectableMetadata); + // + // ``` + // export const Injectable: InjectableFactory = + // makeDecorator(InjectableMetadata); + // ``` + // // and - // export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator = - // makeDecorator(RouteConfigAnnotation); - // In the first case, the type assertion `` 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 `` 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; } diff --git a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js index 77bc9f8141..9940fb9d9e 100644 --- a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js @@ -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'); + }); });