diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index eea0910068..da7cdbd3da 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -15,6 +15,8 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) // Register the processors + .processor(require('./processors/mergeParameterInfo')) + .processor(require('./processors/processPseudoClasses')) .processor(require('./processors/splitDescription')) .processor(require('./processors/convertPrivateClassesToInterfaces')) .processor(require('./processors/generateApiListDoc')) @@ -76,7 +78,6 @@ module.exports = .config(function( readTypeScriptModules, readFilesProcessor, collectExamples, tsParser, packageContentFileReader) { - // Tell TypeScript how to load modules that start with with `@angular` tsParser.options.paths = {'@angular/*': [API_SOURCE_PATH + '/*']}; tsParser.options.baseUrl = '.'; @@ -181,14 +182,11 @@ module.exports = }) .config(function(filterMembers) { - filterMembers.notAllowedPatterns.push( - /^ɵ/ - ); + filterMembers.notAllowedPatterns.push(/^ɵ/); }) .config(function(computePathsProcessor, EXPORT_DOC_TYPES, generateApiListDoc) { - const API_SEGMENT = 'api'; generateApiListDoc.outputFolder = API_SEGMENT; diff --git a/aio/tools/transforms/angular-api-package/mocks/methodParameters.ts b/aio/tools/transforms/angular-api-package/mocks/methodParameters.ts new file mode 100644 index 0000000000..2772938049 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/mocks/methodParameters.ts @@ -0,0 +1,20 @@ +export class TestClass { + method1( + /** description of param1 */ param1: number, + /** description of param2 */ param2?: string, + /** description of param3 */ param3: object = {}, + /** description of param4 */ param4 = 'default string', + ) { + /// + } + + /** + * Some description of method 2 + * @param param5 description of param5 + * @param param6 description of param6 + * @param param7 description of param7 + */ + method2(param5: string, param6: number, param7 = 42) { + // + } +} diff --git a/aio/tools/transforms/angular-api-package/processors/mergeParameterInfo.js b/aio/tools/transforms/angular-api-package/processors/mergeParameterInfo.js new file mode 100644 index 0000000000..f07b1beb4e --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/mergeParameterInfo.js @@ -0,0 +1,31 @@ +/** + * @dgProcessor + * + * @description + * Merge the description from `@param` tags into the parameter docs + * extracted from the TypeScript + * + * This is actually an override of the processor provided by the `typescript` dgeni package. + * The original does not set the `defaultValue`. + */ +module.exports = function mergeParameterInfo() { + return { + $runAfter: ['readTypeScriptModules', 'tags-extracted'], + $runBefore: ['extra-docs-added'], + $process(docs) { + docs.forEach((doc) => { + if (doc.docType === 'parameter') { + // The `params` property comes from parsing the `@param` jsdoc tags on the container doc + const paramTag = + doc.container.params && doc.container.params.find(param => param.name === doc.name); + if (paramTag && paramTag.description) { + doc.description = paramTag.description; + if (doc.defaultValue === undefined) { + doc.defaultValue = paramTag.defaultValue; + } + } + } + }); + }, + }; +}; diff --git a/aio/tools/transforms/angular-api-package/processors/mergeParameterInfo.spec.js b/aio/tools/transforms/angular-api-package/processors/mergeParameterInfo.spec.js new file mode 100644 index 0000000000..ffbc7094f3 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/mergeParameterInfo.spec.js @@ -0,0 +1,48 @@ +const Dgeni = require('dgeni'); +const path = require('canonical-path'); +const testPackage = require('../../helpers/test-package'); + +describe('mergeParameterInfo', () => { + let injector; + let tsProcessor; + let mergeParameterInfoProcessor; + let parseTagsProcessor; + let extractTagsProcessor; + + beforeEach(() => { + const dgeni = new Dgeni([testPackage('angular-api-package')]); + injector = dgeni.configureInjector(); + + tsProcessor = injector.get('readTypeScriptModules'); + parseTagsProcessor = injector.get('parseTagsProcessor'); + extractTagsProcessor = injector.get('extractTagsProcessor'); + mergeParameterInfoProcessor = injector.get('mergeParameterInfo'); + tsProcessor.basePath = path.resolve(__dirname, '../mocks'); + tsProcessor.sourceFiles = ['methodParameters.ts']; + }); + + it('should merge the param tags into the parameter docs', () => { + const docsArray = []; + + tsProcessor.$process(docsArray); + parseTagsProcessor.$process(docsArray); + extractTagsProcessor.$process(docsArray); + mergeParameterInfoProcessor.$process(docsArray); + + const param5 = docsArray.find(doc => doc.name === 'param5' && doc.container.name === 'method2'); + expect(param5.id).toEqual('methodParameters/TestClass.method2()~param5'); + expect(param5.description).toEqual('description of param5'); + expect(param5.type).toEqual('string'); + + const param6 = docsArray.find(doc => doc.name === 'param6' && doc.container.name === 'method2'); + expect(param6.id).toEqual('methodParameters/TestClass.method2()~param6'); + expect(param6.description).toEqual('description of param6'); + expect(param6.type).toEqual('number'); + + const param7 = docsArray.find(doc => doc.name === 'param7' && doc.container.name === 'method2'); + expect(param7.id).toEqual('methodParameters/TestClass.method2()~param7'); + expect(param7.description).toEqual('description of param7'); + expect(param7.type).toEqual('number'); + expect(param7.defaultValue).toEqual('42'); + }); +}); diff --git a/aio/tools/transforms/angular-api-package/processors/processPseudoClasses.js b/aio/tools/transforms/angular-api-package/processors/processPseudoClasses.js new file mode 100644 index 0000000000..9a51ce842e --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/processPseudoClasses.js @@ -0,0 +1,27 @@ +module.exports = function processPseudoClasses(tsHost) { + return { + $runAfter: ['readTypeScriptModules'], + $runBefore: ['parsing-tags'], + $process(docs) { + docs.forEach(doc => { + if (doc.docType === 'interface' && doc.additionalDeclarations && + doc.additionalDeclarations.length > 0) { + doc.docType = 'class'; + const additionalContent = tsHost.getContent(doc.additionalDeclarations[0]); + if (!doc.content || doc.content === '@publicApi' && additionalContent) { + doc.content = additionalContent; + } + doc.members = doc.members && doc.members.filter(m => { + if (m.isNewMember) { + doc.constructorDoc = m; + doc.constructorDoc.name = 'constructor'; + return false; + } else { + return true; + } + }); + } + }); + } + }; +}; diff --git a/aio/tools/transforms/angular-api-package/processors/processPseudoClasses.spec.js b/aio/tools/transforms/angular-api-package/processors/processPseudoClasses.spec.js new file mode 100644 index 0000000000..3913f6448b --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/processPseudoClasses.spec.js @@ -0,0 +1,97 @@ +const testPackage = require('../../helpers/test-package'); +const processorFactory = require('./processPseudoClasses'); +const Dgeni = require('dgeni'); + +describe('processPseudoClasses processor', () => { + it('should be available on the injector', () => { + const dgeni = new Dgeni([testPackage('angular-api-package')]); + const injector = dgeni.configureInjector(); + const processor = injector.get('processPseudoClasses'); + expect(processor.$process).toBeDefined(); + expect(processor.$runAfter).toEqual(['readTypeScriptModules']); + expect(processor.$runBefore).toEqual(['parsing-tags']); + }); + + it('should convert "interface+const" docs to "class" docs', () => { + const processor = processorFactory(jasmine.createSpyObj(['getContent'])); + const docs = [ + {docType: 'module', id: 'a'}, + {docType: 'class', id: 'b'}, + {docType: 'interface', id: 'c'}, + {docType: 'interface', id: 'd', additionalDeclarations: []}, + {docType: 'interface', id: 'e', additionalDeclarations: [{}]}, + {docType: 'const', id: 'f'}, + {docType: 'const', id: 'g', additionalDeclarations: []}, + {docType: 'const', id: 'h', additionalDeclarations: [{}]}, + ]; + processor.$process(docs); + expect(docs).toEqual([ + jasmine.objectContaining({docType: 'module', id: 'a'}), + jasmine.objectContaining({docType: 'class', id: 'b'}), + jasmine.objectContaining({docType: 'interface', id: 'c'}), + jasmine.objectContaining({docType: 'interface', id: 'd'}), + + // This is the only one that changes + jasmine.objectContaining({docType: 'class', id: 'e'}), + + jasmine.objectContaining({docType: 'const', id: 'f'}), + jasmine.objectContaining({docType: 'const', id: 'g'}), + jasmine.objectContaining({docType: 'const', id: 'h'}), + ]); + }); + + it('should grab the content from the first additional declaration if there is no "real" content already', + () => { + const getContent = jasmine.createSpy('getContent').and.returnValue('additional content'); + const additionalDeclaration1 = {}; + const additionalDeclaration2 = {}; + const additionalDeclaration3 = {}; + const processor = processorFactory({getContent}); + const docs = [ + { + docType: 'interface', + id: 'a', + content: 'original content', + additionalDeclarations: [additionalDeclaration1] + }, + { + docType: 'interface', + id: 'b', + content: '@publicApi', // this does not count as "real" content + additionalDeclarations: [additionalDeclaration2] + }, + {docType: 'interface', id: 'c', additionalDeclarations: [additionalDeclaration3]}, + ]; + processor.$process(docs); + expect(docs[0].content).toEqual('original content'); + expect(docs[1].content).toEqual('additional content'); + expect(docs[2].content).toEqual('additional content'); + expect(getContent).toHaveBeenCalledWith(additionalDeclaration1); + expect(getContent).toHaveBeenCalledWith(additionalDeclaration2); + expect(getContent).toHaveBeenCalledWith(additionalDeclaration3); + }); + + it('should extract any __new member from the interface members', () => { + const getContent = jasmine.createSpy('getContent').and.returnValue('additional content'); + const processor = processorFactory({getContent}); + const docs = [ + {docType: 'interface', id: 'a', additionalDeclarations: [{}]}, + {docType: 'interface', id: 'b', additionalDeclarations: [{}], members: []}, + {docType: 'interface', id: 'c', additionalDeclarations: [{}], members: [{name: 'member1'}]}, + { + docType: 'interface', + id: 'd', + additionalDeclarations: [{}], + members: [{name: 'member1', isNewMember: true}] + }, + ]; + processor.$process(docs); + + expect(docs[0].members).toEqual(undefined); + expect(docs[1].members).toEqual([]); + expect(docs[2].members).toEqual([{name: 'member1'}]); + + expect(docs[3].members).toEqual([]); + expect(docs[3].constructorDoc).toEqual({name: 'constructor', isNewMember: true}); + }); +});