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,17 +58,17 @@ 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) { makeDecoratorCalls.forEach(function(call) {
// 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)`
var makeDecorator = getMakeDecoratorCall(doc, call.type); if (initializer.expression && initializer.expression.text === call.functionName) {
if (makeDecorator) {
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name); log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name);
doc.docType = 'decorator'; doc.docType = 'decorator';
doc.decoratorLocation = call.description; doc.decoratorLocation = call.description;
// Get the type of the decorator metadata from the first "type" argument of the call. // Get the type of the decorator metadata from the first "type" argument of the call.
// For example the `X` of `createDecorator<X>(...)`. // For example the `X` of `createDecorator<X>(...)`.
doc.decoratorType = makeDecorator.arguments[0].text; doc.decoratorType = initializer.arguments[0].text;
// clear the symbol type named since it is not needed // clear the symbol type named since it is not needed
doc.symbolTypeName = undefined; doc.symbolTypeName = undefined;
@ -76,6 +76,7 @@ module.exports = function mergeDecoratorDocs(log) {
docsToMerge[doc.name + 'Decorator'] = doc; docsToMerge[doc.name + 'Decorator'] = doc;
} }
}); });
}
}); });
// merge the metadata docs into the decorator docs // merge the metadata docs into the decorator docs
@ -106,13 +107,8 @@ 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;
var initializer = doc.declaration &&
doc.declaration.initializer;
if (initializer) {
// There appear to be two forms of initializer: // There appear to be two forms of initializer:
// export var Injectable: InjectableFactory = // export var Injectable: InjectableFactory =
// <InjectableFactory>makeDecorator(InjectableMetadata); // <InjectableFactory>makeDecorator(InjectableMetadata);
@ -122,11 +118,8 @@ function getMakeDecoratorCall(doc, type) {
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an // In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
// extra level of expression // extra level of expression
// to hold the new type of the expression. // to hold the new type of the expression.
if (initializer.expression && initializer.expression.expression) { if (initializer && initializer.expression && initializer.expression.expression) {
initializer = initializer.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 %}