From 9a4c8d543dfd4c4971271eb2ff411b6a29a07ded Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Mon, 24 Apr 2017 15:04:52 +0100 Subject: [PATCH] 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 --- .../processors/mergeDecoratorDocs.js | 58 +++++++++++++-- .../processors/mergeDecoratorDocs.spec.js | 70 ++++++++++++++----- .../templates/decorator.template.html | 2 +- .../templates/includes/_metadata.html | 4 +- 4 files changed, 109 insertions(+), 25 deletions(-) diff --git a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js index 7fde771509..4d0b4065ac 100644 --- a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js +++ b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js @@ -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 = + * 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` 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(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(...)`. 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 = diff --git a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js index 6024978c18..da982f15db 100644 --- a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js @@ -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]); - expect(decoratorDoc.docType).toEqual('decorator'); - expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator'); - expect(otherDoc.docType).toEqual('var'); - }); + 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'); }); -}); \ No newline at end of file + + 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); + }); +}); diff --git a/aio/tools/transforms/templates/decorator.template.html b/aio/tools/transforms/templates/decorator.template.html index 5c012ced97..12586023d5 100644 --- a/aio/tools/transforms/templates/decorator.template.html +++ b/aio/tools/transforms/templates/decorator.template.html @@ -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 %} diff --git a/aio/tools/transforms/templates/includes/_metadata.html b/aio/tools/transforms/templates/includes/_metadata.html index d3d1a09e24..a18967ee5f 100644 --- a/aio/tools/transforms/templates/includes/_metadata.html +++ b/aio/tools/transforms/templates/includes/_metadata.html @@ -4,10 +4,10 @@ {% for metadata in doc.members %}{% if not metadata.internal %} {% if not loop.last %}
{% endif %} {% endif %}{% endfor %}