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) {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
|
@ -15,18 +60,19 @@ module.exports = function mergeDecoratorDocs(log) {
|
|||
docs.forEach(function(doc) {
|
||||
|
||||
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);
|
||||
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
|
||||
// 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 (e.g. ComponentMetadataFactory) since it is not needed
|
||||
// clear the symbol type named since it is not needed
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
@ -35,11 +81,15 @@ module.exports = function mergeDecoratorDocs(log) {
|
|||
// merge the metadata docs into the decorator docs
|
||||
docs = docs.filter(function(doc) {
|
||||
if (docsToMerge[doc.name]) {
|
||||
// We have found an `XxxDecorator` document that will hold the call signature of the decorator
|
||||
var decoratorDoc = docsToMerge[doc.name];
|
||||
log.debug(
|
||||
'mergeDecoratorDocs: merging', doc.name, 'into', decoratorDoc.name,
|
||||
doc.callMember.description.substring(0, 50));
|
||||
// Merge the documentation found in this call signature into the original decorator
|
||||
decoratorDoc.description = doc.callMember.description;
|
||||
decoratorDoc.howToUse = doc.callMember.howToUse;
|
||||
decoratorDoc.whatItDoes = doc.callMember.whatItDoes;
|
||||
|
||||
// remove doc from its module doc's exports
|
||||
doc.moduleDoc.exports =
|
||||
|
|
|
@ -1,21 +1,40 @@
|
|||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('mergeDecoratorDocs processor', function() {
|
||||
var dgeni, injector, processor, decoratorDoc, decoratorDocWithTypeAssertion, otherDoc;
|
||||
describe('mergeDecoratorDocs processor', () => {
|
||||
var dgeni, injector, processor, moduleDoc, decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('mergeDecoratorDocs');
|
||||
|
||||
moduleDoc = {};
|
||||
|
||||
decoratorDoc = {
|
||||
name: 'X',
|
||||
docType: 'var',
|
||||
name: 'Component',
|
||||
docType: 'const',
|
||||
description: 'A description of the metadata for the Component decorator',
|
||||
exportSymbol: {
|
||||
valueDeclaration:
|
||||
{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 = {
|
||||
|
@ -28,7 +47,8 @@ describe('mergeDecoratorDocs processor', function() {
|
|||
{type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
moduleDoc
|
||||
};
|
||||
otherDoc = {
|
||||
name: 'Y',
|
||||
|
@ -36,22 +56,36 @@ describe('mergeDecoratorDocs processor', function() {
|
|||
exportSymbol: {
|
||||
valueDeclaration:
|
||||
{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',
|
||||
function() {
|
||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
it('should change the docType of only the docs that are initialied by a call to makeDecorator', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
expect(decoratorDoc.docType).toEqual('decorator');
|
||||
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
|
||||
expect(otherDoc.docType).toEqual('var');
|
||||
});
|
||||
|
||||
it('should extract the "type" of the decorator meta data', function() {
|
||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
it('should extract the "type" of the decorator meta data', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, 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]);
|
||||
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 -%}
|
||||
{% extends 'layout/api-base.template.html' %}
|
||||
|
||||
{% block main %}
|
||||
{% block details %}
|
||||
{% include "includes/_description.html" %}
|
||||
{% include "includes/_metadata.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
{% for metadata in doc.members %}{% if not metadata.internal %}
|
||||
<div class="metadata-member">
|
||||
<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>
|
||||
</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>
|
||||
{% if not loop.last %}<hr class="hr-margin">{% endif %}
|
||||
{% endif %}{% endfor %}
|
||||
|
|
Loading…
Reference in New Issue