build(aio): ensure decorators with shared interface types are found (#19361)

Closes #19358
This commit is contained in:
Pete Bacon Darwin 2017-09-25 19:59:21 +01:00 committed by Victor Berchet
parent 0f5c70d563
commit 4ae546be1f
3 changed files with 60 additions and 63 deletions

View File

@ -48,9 +48,9 @@ module.exports = function mergeDecoratorDocs(log) {
$runAfter: ['processing-docs'], $runAfter: ['processing-docs'],
$runBefore: ['docs-processed'], $runBefore: ['docs-processed'],
makeDecoratorCalls: [ makeDecoratorCalls: [
{type: '', description: 'toplevel'}, {type: '', description: 'toplevel', functionName: 'makeDecorator'},
{type: 'Prop', description: 'property'}, {type: 'Prop', description: 'property', functionName: 'makePropDecorator'},
{type: 'Param', description: 'parameter'}, {type: 'Param', description: 'parameter', functionName: 'makeParamDecorator'},
], ],
$process: function(docs) { $process: function(docs) {
@ -58,24 +58,25 @@ module.exports = function mergeDecoratorDocs(log) {
var docsToMerge = Object.create(null); var docsToMerge = Object.create(null);
docs.forEach(function(doc) { docs.forEach(function(doc) {
const initializer = getInitializer(doc);
if (initializer) {
makeDecoratorCalls.forEach(function(call) {
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
if (initializer.expression && initializer.expression.text === call.functionName) {
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name);
doc.docType = 'decorator';
doc.decoratorLocation = call.description;
// Get the type of the decorator metadata from the first "type" argument of the call.
// For example the `X` of `createDecorator<X>(...)`.
doc.decoratorType = initializer.arguments[0].text;
// clear the symbol type named since it is not needed
doc.symbolTypeName = undefined;
makeDecoratorCalls.forEach(function(call) { // keep track of the names of the metadata interface that will need to be merged into this decorator doc
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)` docsToMerge[doc.name + 'Decorator'] = doc;
var makeDecorator = getMakeDecoratorCall(doc, call.type); }
if (makeDecorator) { });
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name); }
doc.docType = 'decorator';
doc.decoratorLocation = call.description;
// Get the type of the decorator metadata from the first "type" argument of the call.
// For example the `X` of `createDecorator<X>(...)`.
doc.decoratorType = makeDecorator.arguments[0].text;
// clear the symbol type named since it is not needed
doc.symbolTypeName = undefined;
// keep track of the names of the metadata interface that will need to be merged into this decorator doc
docsToMerge[doc.name + 'Decorator'] = doc;
}
});
}); });
// merge the metadata docs into the decorator docs // merge the metadata docs into the decorator docs
@ -106,27 +107,19 @@ module.exports = function mergeDecoratorDocs(log) {
}; };
}; };
function getMakeDecoratorCall(doc, type) { function getInitializer(doc) {
var makeDecoratorFnName = 'make' + (type || '') + 'Decorator'; var initializer = doc.symbol && doc.symbol.valueDeclaration && doc.symbol.valueDeclaration.initializer;
// There appear to be two forms of initializer:
var initializer = doc.declaration && // export var Injectable: InjectableFactory =
doc.declaration.initializer; // <InjectableFactory>makeDecorator(InjectableMetadata);
// and
if (initializer) { // export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
// There appear to be two forms of initializer: // makeDecorator(RouteConfigAnnotation);
// export var Injectable: InjectableFactory = // In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
// <InjectableFactory>makeDecorator(InjectableMetadata); // extra level of expression
// and // to hold the new type of the expression.
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator = if (initializer && initializer.expression && initializer.expression.expression) {
// makeDecorator(RouteConfigAnnotation); initializer = initializer.expression;
// 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.expression && initializer.expression.expression) {
initializer = initializer.expression;
}
if (initializer.expression && initializer.expression.text === makeDecoratorFnName) {
return initializer;
}
} }
return initializer;
} }

View File

@ -2,11 +2,11 @@ var testPackage = require('../../helpers/test-package');
var Dgeni = require('dgeni'); var Dgeni = require('dgeni');
describe('mergeDecoratorDocs processor', () => { describe('mergeDecoratorDocs processor', () => {
var dgeni, injector, processor, moduleDoc, decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc; let processor, moduleDoc, decoratorDoc, metadataDoc, otherDoc;
beforeEach(() => { beforeEach(() => {
dgeni = new Dgeni([testPackage('angular-api-package')]); const dgeni = new Dgeni([testPackage('angular-api-package')]);
injector = dgeni.configureInjector(); const injector = dgeni.configureInjector();
processor = injector.get('mergeDecoratorDocs'); processor = injector.get('mergeDecoratorDocs');
moduleDoc = {}; moduleDoc = {};
@ -15,7 +15,9 @@ describe('mergeDecoratorDocs processor', () => {
name: 'Component', name: 'Component',
docType: 'const', docType: 'const',
description: 'A description of the metadata for the Component decorator', description: 'A description of the metadata for the Component decorator',
declaration: {initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}}, symbol: {
valueDeclaration: { initializer: { expression: { text: 'makeDecorator' }, arguments: [{ text: 'X' }] } }
},
members: [ members: [
{ name: 'templateUrl', description: 'A description of the templateUrl property' } { name: 'templateUrl', description: 'A description of the templateUrl property' }
], ],
@ -40,45 +42,45 @@ describe('mergeDecoratorDocs processor', () => {
moduleDoc moduleDoc
}; };
decoratorDocWithTypeAssertion = {
name: 'Y',
docType: 'const',
declaration: { initializer: { expression: {type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]} } },
moduleDoc
};
otherDoc = { otherDoc = {
name: 'Y', name: 'Y',
docType: 'const', docType: 'const',
declaration: {initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}}, symbol: {
valueDeclaration: { initializer: { expression: { text: 'otherCall' }, arguments: [{ text: 'param1' }] } }
},
moduleDoc moduleDoc
}; };
moduleDoc.exports = [decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]; moduleDoc.exports = [decoratorDoc, metadataDoc, otherDoc];
}); });
it('should change the docType of only the docs that are initialied 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, decoratorDocWithTypeAssertion, otherDoc]); processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator'); expect(decoratorDoc.docType).toEqual('decorator');
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
expect(otherDoc.docType).toEqual('const'); expect(otherDoc.docType).toEqual('const');
}); });
it('should extract the "type" of the decorator meta data', () => { it('should extract the "type" of the decorator meta data', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]); processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.decoratorType).toEqual('X'); expect(decoratorDoc.decoratorType).toEqual('X');
expect(decoratorDocWithTypeAssertion.decoratorType).toEqual('Y');
}); });
it('should copy across properties from the call signature doc', () => { it('should copy across properties from the call signature doc', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]); processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.description).toEqual('The actual description of the call signature'); expect(decoratorDoc.description).toEqual('The actual description of the call signature');
expect(decoratorDoc.whatItDoes).toEqual('Does something cool...'); expect(decoratorDoc.whatItDoes).toEqual('Does something cool...');
expect(decoratorDoc.howToUse).toEqual('Use it like this...'); expect(decoratorDoc.howToUse).toEqual('Use it like this...');
}); });
it('should remove the metadataDoc from the module exports', () => { it('should remove the metadataDoc from the module exports', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]); processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(moduleDoc.exports).not.toContain(metadataDoc); expect(moduleDoc.exports).not.toContain(metadataDoc);
}); });
it('should cope with decorators that have type params', () => {
decoratorDoc.symbol.valueDeclaration.initializer.expression.type = {};
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator');
});
}); });

View File

@ -1,9 +1,11 @@
{% import "lib/memberHelpers.html" as memberHelper -%} {% import "lib/memberHelpers.html" as memberHelper -%}
{% if doc.members.length %}
<section class="decorator-overview"> <section class="decorator-overview">
<h2>Metadata Overview</h2> <h2>Metadata Overview</h2>
<code-example language="ts" hideCopy="true"> <code-example language="ts" hideCopy="true">
@{$ doc.name $}{$ doc.typeParams | escape $}({ {$ memberHelper.renderMembers(doc) $} @{$ doc.name $}{$ doc.typeParams | escape $}({ {$ memberHelper.renderMembers(doc) $}
}) })
</code-example> </code-example>
</section> </section>
{% endif %}