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'],
$runBefore: ['docs-processed'],
makeDecoratorCalls: [
{type: '', description: 'toplevel'},
{type: 'Prop', description: 'property'},
{type: 'Param', description: 'parameter'},
{type: '', description: 'toplevel', functionName: 'makeDecorator'},
{type: 'Prop', description: 'property', functionName: 'makePropDecorator'},
{type: 'Param', description: 'parameter', functionName: 'makeParamDecorator'},
],
$process: function(docs) {
@ -58,24 +58,25 @@ module.exports = function mergeDecoratorDocs(log) {
var docsToMerge = Object.create(null);
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) {
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
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;
}
});
// 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
@ -106,27 +107,19 @@ module.exports = function mergeDecoratorDocs(log) {
};
};
function getMakeDecoratorCall(doc, type) {
var makeDecoratorFnName = 'make' + (type || '') + 'Decorator';
var initializer = doc.declaration &&
doc.declaration.initializer;
if (initializer) {
// There appear to be two forms of initializer:
// export var 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.expression && initializer.expression.expression) {
initializer = initializer.expression;
}
if (initializer.expression && initializer.expression.text === makeDecoratorFnName) {
return initializer;
}
function getInitializer(doc) {
var initializer = doc.symbol && doc.symbol.valueDeclaration && doc.symbol.valueDeclaration.initializer;
// There appear to be two forms of initializer:
// export var 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) {
initializer = initializer.expression;
}
return initializer;
}

View File

@ -2,11 +2,11 @@ var testPackage = require('../../helpers/test-package');
var Dgeni = require('dgeni');
describe('mergeDecoratorDocs processor', () => {
var dgeni, injector, processor, moduleDoc, decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc;
let processor, moduleDoc, decoratorDoc, metadataDoc, otherDoc;
beforeEach(() => {
dgeni = new Dgeni([testPackage('angular-api-package')]);
injector = dgeni.configureInjector();
const dgeni = new Dgeni([testPackage('angular-api-package')]);
const injector = dgeni.configureInjector();
processor = injector.get('mergeDecoratorDocs');
moduleDoc = {};
@ -15,7 +15,9 @@ describe('mergeDecoratorDocs processor', () => {
name: 'Component',
docType: 'const',
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: [
{ name: 'templateUrl', description: 'A description of the templateUrl property' }
],
@ -40,45 +42,45 @@ describe('mergeDecoratorDocs processor', () => {
moduleDoc
};
decoratorDocWithTypeAssertion = {
name: 'Y',
docType: 'const',
declaration: { initializer: { expression: {type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]} } },
moduleDoc
};
otherDoc = {
name: 'Y',
docType: 'const',
declaration: {initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}},
symbol: {
valueDeclaration: { initializer: { expression: { text: 'otherCall' }, arguments: [{ text: 'param1' }] } }
},
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', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
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(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
expect(otherDoc.docType).toEqual('const');
});
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(decoratorDocWithTypeAssertion.decoratorType).toEqual('Y');
});
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.whatItDoes).toEqual('Does something cool...');
expect(decoratorDoc.howToUse).toEqual('Use it like this...');
});
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);
});
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 -%}
{% if doc.members.length %}
<section class="decorator-overview">
<h2>Metadata Overview</h2>
<h2>Metadata Overview</h2>
<code-example language="ts" hideCopy="true">
@{$ doc.name $}{$ doc.typeParams | escape $}({ {$ memberHelper.renderMembers(doc) $}
})
</code-example>
</section>
</section>
{% endif %}