build(aio): ensure decorators with shared interface types are found (#19361)
Closes #19358
This commit is contained in:
parent
0f5c70d563
commit
4ae546be1f
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
{% endif %}
|
Loading…
Reference in New Issue