build(aio): fix rendering of Decorator API docs
The templates were a bit out and we were not merging the correct properties. Added some docs and tests for the processor. Related to #16208
This commit is contained in:
parent
f5aaa55f21
commit
9a4c8d543d
|
@ -1,3 +1,48 @@
|
||||||
|
/**
|
||||||
|
* Decorators in the Angular code base are made up from three code items:
|
||||||
|
*
|
||||||
|
* 1) An interface that represents the call signature of the decorator. E.g.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* export interface ComponentDecorator {
|
||||||
|
* (obj: Component): TypeDecorator;
|
||||||
|
* new (obj: Component): Component;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2) An interface that represents the members of the object that should be passed
|
||||||
|
* into the decorator. E.g.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* export interface Component extends Directive {
|
||||||
|
* changeDetection?: ChangeDetectionStrategy;
|
||||||
|
* viewProviders?: Provider[];
|
||||||
|
* templateUrl?: string;
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 3) A constant that is created by a call to a generic function, whose type parameter is
|
||||||
|
* the call signature interface of the decorator. E.g.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* export const Component: ComponentDecorator =
|
||||||
|
* <ComponentDecorator>makeDecorator('Component', { ... }, Directive)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This processor searches for these constants (3) by looking for a call to
|
||||||
|
* `make...Decorator(...)`. (There are variations to the call for property and param
|
||||||
|
* decorators). From this call we identify the `decoratorType` (e.g. `ComponentDecorator`).
|
||||||
|
*
|
||||||
|
* Calls to `make...Decorator<X>` will return an object of type X. This type is the document
|
||||||
|
* referred to in (2). This is the primary doc that we care about for documenting the decorator.
|
||||||
|
* It holds all of the members of the metadata that is passed to the decorator call.
|
||||||
|
*
|
||||||
|
* Finally we want to capture the documentation attached to the call signature interface of the
|
||||||
|
* associated decorator (1). We copy across the properties that we care about from this call
|
||||||
|
* signature (e.g. description, whatItDoes and howToUse).
|
||||||
|
*/
|
||||||
|
|
||||||
module.exports = function mergeDecoratorDocs(log) {
|
module.exports = function mergeDecoratorDocs(log) {
|
||||||
return {
|
return {
|
||||||
$runAfter: ['processing-docs'],
|
$runAfter: ['processing-docs'],
|
||||||
|
@ -15,18 +60,19 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||||
docs.forEach(function(doc) {
|
docs.forEach(function(doc) {
|
||||||
|
|
||||||
makeDecoratorCalls.forEach(function(call) {
|
makeDecoratorCalls.forEach(function(call) {
|
||||||
// find all the decorators, signified by a call to `makeDecorator(metadata)`
|
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
|
||||||
var makeDecorator = getMakeDecoratorCall(doc, call.type);
|
var makeDecorator = getMakeDecoratorCall(doc, call.type);
|
||||||
if (makeDecorator) {
|
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
|
// 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;
|
doc.decoratorType = makeDecorator.arguments[0].text;
|
||||||
// clear the symbol type named (e.g. ComponentMetadataFactory) since it is not needed
|
// clear the symbol type named since it is not needed
|
||||||
doc.symbolTypeName = undefined;
|
doc.symbolTypeName = undefined;
|
||||||
|
|
||||||
// keep track of the names of the docs that need to be merged into this 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;
|
docsToMerge[doc.name + 'Decorator'] = doc;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -35,11 +81,15 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||||
// merge the metadata docs into the decorator docs
|
// merge the metadata docs into the decorator docs
|
||||||
docs = docs.filter(function(doc) {
|
docs = docs.filter(function(doc) {
|
||||||
if (docsToMerge[doc.name]) {
|
if (docsToMerge[doc.name]) {
|
||||||
|
// We have found an `XxxDecorator` document that will hold the call signature of the decorator
|
||||||
var decoratorDoc = docsToMerge[doc.name];
|
var decoratorDoc = docsToMerge[doc.name];
|
||||||
log.debug(
|
log.debug(
|
||||||
'mergeDecoratorDocs: merging', doc.name, 'into', decoratorDoc.name,
|
'mergeDecoratorDocs: merging', doc.name, 'into', decoratorDoc.name,
|
||||||
doc.callMember.description.substring(0, 50));
|
doc.callMember.description.substring(0, 50));
|
||||||
|
// Merge the documentation found in this call signature into the original decorator
|
||||||
decoratorDoc.description = doc.callMember.description;
|
decoratorDoc.description = doc.callMember.description;
|
||||||
|
decoratorDoc.howToUse = doc.callMember.howToUse;
|
||||||
|
decoratorDoc.whatItDoes = doc.callMember.whatItDoes;
|
||||||
|
|
||||||
// remove doc from its module doc's exports
|
// remove doc from its module doc's exports
|
||||||
doc.moduleDoc.exports =
|
doc.moduleDoc.exports =
|
||||||
|
|
|
@ -1,21 +1,40 @@
|
||||||
var testPackage = require('../../helpers/test-package');
|
var testPackage = require('../../helpers/test-package');
|
||||||
var Dgeni = require('dgeni');
|
var Dgeni = require('dgeni');
|
||||||
|
|
||||||
describe('mergeDecoratorDocs processor', function() {
|
describe('mergeDecoratorDocs processor', () => {
|
||||||
var dgeni, injector, processor, decoratorDoc, decoratorDocWithTypeAssertion, otherDoc;
|
var dgeni, injector, processor, moduleDoc, decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
dgeni = new Dgeni([testPackage('angular-api-package')]);
|
dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||||
injector = dgeni.configureInjector();
|
injector = dgeni.configureInjector();
|
||||||
processor = injector.get('mergeDecoratorDocs');
|
processor = injector.get('mergeDecoratorDocs');
|
||||||
|
|
||||||
|
moduleDoc = {};
|
||||||
|
|
||||||
decoratorDoc = {
|
decoratorDoc = {
|
||||||
name: 'X',
|
name: 'Component',
|
||||||
docType: 'var',
|
docType: 'const',
|
||||||
|
description: 'A description of the metadata for the Component decorator',
|
||||||
exportSymbol: {
|
exportSymbol: {
|
||||||
valueDeclaration:
|
valueDeclaration:
|
||||||
{initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}}
|
{initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}}
|
||||||
}
|
},
|
||||||
|
members: [
|
||||||
|
{ name: 'templateUrl', description: 'A description of the templateUrl property' }
|
||||||
|
],
|
||||||
|
moduleDoc
|
||||||
|
};
|
||||||
|
|
||||||
|
metadataDoc = {
|
||||||
|
name: 'ComponentDecorator',
|
||||||
|
docType: 'interface',
|
||||||
|
description: 'A description of the interface for the call signature for the Component decorator',
|
||||||
|
callMember: {
|
||||||
|
description: 'The actual description of the call signature',
|
||||||
|
whatItDoes: 'Does something cool...',
|
||||||
|
howToUse: 'Use it like this...'
|
||||||
|
},
|
||||||
|
moduleDoc
|
||||||
};
|
};
|
||||||
|
|
||||||
decoratorDocWithTypeAssertion = {
|
decoratorDocWithTypeAssertion = {
|
||||||
|
@ -28,7 +47,8 @@ describe('mergeDecoratorDocs processor', function() {
|
||||||
{type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]}
|
{type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
moduleDoc
|
||||||
};
|
};
|
||||||
otherDoc = {
|
otherDoc = {
|
||||||
name: 'Y',
|
name: 'Y',
|
||||||
|
@ -36,22 +56,36 @@ describe('mergeDecoratorDocs processor', function() {
|
||||||
exportSymbol: {
|
exportSymbol: {
|
||||||
valueDeclaration:
|
valueDeclaration:
|
||||||
{initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}}
|
{initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}}
|
||||||
}
|
},
|
||||||
|
moduleDoc
|
||||||
};
|
};
|
||||||
|
|
||||||
|
moduleDoc.exports = [decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, 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 initialied by a call to makeDecorator', () => {
|
||||||
function() {
|
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
|
||||||
expect(decoratorDoc.docType).toEqual('decorator');
|
expect(decoratorDoc.docType).toEqual('decorator');
|
||||||
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
|
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
|
||||||
expect(otherDoc.docType).toEqual('var');
|
expect(otherDoc.docType).toEqual('var');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract the "type" of the decorator meta data', function() {
|
it('should extract the "type" of the decorator meta data', () => {
|
||||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||||
expect(decoratorDoc.decoratorType).toEqual('X');
|
expect(decoratorDoc.decoratorType).toEqual('X');
|
||||||
expect(decoratorDocWithTypeAssertion.decoratorType).toEqual('Y');
|
expect(decoratorDocWithTypeAssertion.decoratorType).toEqual('Y');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should copy across properties from the call signature doc', () => {
|
||||||
|
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, 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]);
|
||||||
|
expect(moduleDoc.exports).not.toContain(metadataDoc);
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
{% import "lib/paramList.html" as params -%}
|
{% import "lib/paramList.html" as params -%}
|
||||||
{% extends 'layout/api-base.template.html' %}
|
{% extends 'layout/api-base.template.html' %}
|
||||||
|
|
||||||
{% block main %}
|
{% block details %}
|
||||||
{% include "includes/_description.html" %}
|
{% include "includes/_description.html" %}
|
||||||
{% include "includes/_metadata.html" %}
|
{% include "includes/_metadata.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
{% for metadata in doc.members %}{% if not metadata.internal %}
|
{% for metadata in doc.members %}{% if not metadata.internal %}
|
||||||
<div class="metadata-member">
|
<div class="metadata-member">
|
||||||
<a name="{$ metadata.name $}-anchor" class="anchor-offset"></a>
|
<a name="{$ metadata.name $}-anchor" class="anchor-offset"></a>
|
||||||
<pre class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ metadata.name $}') }">
|
<pre>
|
||||||
<code>{$ metadata.name $}{$ params.paramList(metadata.parameters) | trim $}{$ params.returnType(metadata.returnType) $}</code>
|
<code>{$ metadata.name $}{$ params.paramList(metadata.parameters) | trim $}{$ params.returnType(metadata.returnType) $}</code>
|
||||||
</pre>
|
</pre>
|
||||||
{%- if not metadata.notYetDocumented %}{$ metadata.description | replace('### Example', '') | replace('## Example', '') | replace('# Example', '') | trimBlankLines | marked $}{% endif -%}
|
{%- if not metadata.notYetDocumented %}{$ metadata.description | trimBlankLines | marked $}{% endif -%}
|
||||||
</div>
|
</div>
|
||||||
{% if not loop.last %}<hr class="hr-margin">{% endif %}
|
{% if not loop.last %}<hr class="hr-margin">{% endif %}
|
||||||
{% endif %}{% endfor %}
|
{% endif %}{% endfor %}
|
||||||
|
|
Loading…
Reference in New Issue