From e0f5261d3b4f7195aaaf0a7991474964f9a44c0e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 30 Sep 2015 09:13:23 +0100 Subject: [PATCH] api-templates: extract and render directive info distinct from classes Closes #221 --- .../api-builder/angular.io-package/index.js | 3 + .../processors/extractDirectiveClasses.js | 30 +++++++++ .../extractDirectiveClasses.spec.js | 51 +++++++++++++++ .../processors/matchUpDirectiveDecorators.js | 65 +++++++++++++++++++ .../templates/class.template.html | 5 +- .../templates/directive.template.html | 33 ++++++++++ public/api-builder/docs-package/index.js | 1 + .../processors/extractDirectiveClasses.js | 32 +++++++++ .../extractDirectiveClasses.spec.js | 45 +++++++++++++ .../api-builder/typescript-package/index.js | 6 +- .../processors/readTypeScriptModules.js | 24 ++++++- 11 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 public/api-builder/angular.io-package/processors/extractDirectiveClasses.js create mode 100644 public/api-builder/angular.io-package/processors/extractDirectiveClasses.spec.js create mode 100644 public/api-builder/angular.io-package/processors/matchUpDirectiveDecorators.js create mode 100644 public/api-builder/angular.io-package/templates/directive.template.html create mode 100644 public/api-builder/docs-package/processors/extractDirectiveClasses.js create mode 100644 public/api-builder/docs-package/processors/extractDirectiveClasses.spec.js diff --git a/public/api-builder/angular.io-package/index.js b/public/api-builder/angular.io-package/index.js index 32ff362a83..4884d52d27 100644 --- a/public/api-builder/angular.io-package/index.js +++ b/public/api-builder/angular.io-package/index.js @@ -7,6 +7,9 @@ module.exports = new Package('angular.io', [basePackage]) .factory(require('./services/renderMarkdown')) .processor(require('./processors/addJadeDataDocsProcessor')) .processor(require('./processors/filterUnwantedDecorators')) +.processor(require('./processors/extractDirectiveClasses')) +.processor(require('./processors/matchUpDirectiveDecorators')) + // overrides base packageInfo and returns the one for the 'angular/angular' repo. .factory(require('./services/packageInfo')) diff --git a/public/api-builder/angular.io-package/processors/extractDirectiveClasses.js b/public/api-builder/angular.io-package/processors/extractDirectiveClasses.js new file mode 100644 index 0000000000..668dfcd7d4 --- /dev/null +++ b/public/api-builder/angular.io-package/processors/extractDirectiveClasses.js @@ -0,0 +1,30 @@ +var _ = require('lodash'); + +module.exports = function extractDirectiveClassesProcessor(EXPORT_DOC_TYPES) { + + // Add the "directive" docType into those that can be exported from a module + EXPORT_DOC_TYPES.push('directive'); + + return { + $runAfter: ['processing-docs'], + $runBefore: ['docs-processed'], + decoratorTypes: ['Directive', 'Component', 'View'], + $process: function(docs) { + var decoratorTypes = this.decoratorTypes; + + _.forEach(docs, function(doc) { + + _.forEach(doc.decorators, function(decorator) { + + if (decoratorTypes.indexOf(decorator.name) !== -1) { + doc.docType = 'directive'; + + doc[decorator.name.toLowerCase() + 'Options'] = decorator.argumentInfo[0]; + } + }); + }); + + return docs; + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/angular.io-package/processors/extractDirectiveClasses.spec.js b/public/api-builder/angular.io-package/processors/extractDirectiveClasses.spec.js new file mode 100644 index 0000000000..c6b6d6d8b9 --- /dev/null +++ b/public/api-builder/angular.io-package/processors/extractDirectiveClasses.spec.js @@ -0,0 +1,51 @@ +var mockPackage = require('../mocks/mockPackage'); +var Dgeni = require('dgeni'); + +describe('extractDirectiveClasses processor', function() { + var dgeni, injector, processor; + + beforeEach(function() { + dgeni = new Dgeni([mockPackage()]); + injector = dgeni.configureInjector(); + processor = injector.get('extractDirectiveClassesProcessor'); + }); + + it('should extract specified decorator arguments', function() { + var doc = { + id: 'angular2/angular2.ngFor', + name: 'ngFor', + docType: 'class', + decorators: [ + { + name: 'Directive', + arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}'], + argumentInfo: [ + { selector: '[ng-for][ng-for-of]', properties: ['ngForOf'] } + ] + } + ] + }; + + var docs = processor.$process([doc]); + + expect(doc).toEqual(jasmine.objectContaining({ + id: 'angular2/angular2.ngFor', + name: 'ngFor', + docType: 'directive', + decorators: [ + { + name: 'Directive', + arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}'], + argumentInfo: [ + { selector: '[ng-for][ng-for-of]', properties: ['ngForOf'] } + ] + } + ] + })); + + expect(doc.directiveOptions).toEqual({ + selector: '[ng-for][ng-for-of]', + properties: ['ngForOf'] + }); + }); +}); \ No newline at end of file diff --git a/public/api-builder/angular.io-package/processors/matchUpDirectiveDecorators.js b/public/api-builder/angular.io-package/processors/matchUpDirectiveDecorators.js new file mode 100644 index 0000000000..6424959e92 --- /dev/null +++ b/public/api-builder/angular.io-package/processors/matchUpDirectiveDecorators.js @@ -0,0 +1,65 @@ +var _ = require('lodash'); + +/** + * @dgProcessor + * @description + * + */ +module.exports = function matchUpDirectiveDecoratorsProcessor(aliasMap) { + + return { + $runAfter: ['ids-computed', 'paths-computed'], + $runBefore: ['rendering-docs'], + decoratorMappings: { + 'Inputs' : 'inputs', + 'Outputs' : 'outputs' + }, + $process: function(docs) { + var decoratorMappings = this.decoratorMappings; + _.forEach(docs, function(doc) { + if (doc.docType === 'directive') { + doc.selector = doc.directiveOptions.selector; + + for(decoratorName in decoratorMappings) { + var propertyName = decoratorMappings[decoratorName]; + doc[propertyName] = getDecoratorValues(doc.directiveOptions[propertyName], decoratorName, doc.members); + } + } + }); + } + }; +}; + +function getDecoratorValues(classDecoratorValues, memberDecoratorName, members) { + var optionMap = {}; + var decoratorValues = {}; + + // Parse the class decorator + _.forEach(classDecoratorValues, function(option) { + // Options are of the form: "propName : bindingName" (bindingName is optional) + var optionPair = option.split(':'); + var propertyName = optionPair.shift().trim(); + var bindingName = (optionPair.shift() || '').trim() || propertyName; + + decoratorValues[propertyName] = { + propertyName: propertyName, + bindingName: bindingName + }; + }); + + _.forEach(members, function(member) { + _.forEach(member.decorators, function(decorator) { + if (decorator.name === memberDecoratorName) { + decoratorValues[member.name] = { + propertyName: member.name, + bindingName: decorator.arguments[0] || member.name + }; + } + }); + if (decoratorValues[member.name]) { + decoratorValues[member.name].memberDoc = member; + } + }); + + return decoratorValues; +} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/class.template.html b/public/api-builder/angular.io-package/templates/class.template.html index e27e786553..a28fd6e33f 100644 --- a/public/api-builder/angular.io-package/templates/class.template.html +++ b/public/api-builder/angular.io-package/templates/class.template.html @@ -19,7 +19,10 @@ p.location-badge. *Not Yet Documented* {% else %} {$ doc.description | indentForMarkdown(2) | trimBlankLines $} -{% endif -%} +{% endif %} + +{% block additional %} +{% endblock %} {%- if doc.decorators.length %} .l-main-section diff --git a/public/api-builder/angular.io-package/templates/directive.template.html b/public/api-builder/angular.io-package/templates/directive.template.html new file mode 100644 index 0000000000..d57a9ad8a7 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/directive.template.html @@ -0,0 +1,33 @@ +{% include "lib/githubLinks.html" -%} +{% include "lib/paramList.html" -%} +{% extends 'class.template.html' -%} + +{% block additional %} +.l-main-section + h2 Selector + .l-sub-section + h3.selector + code {$ doc.directiveOptions.selector $} + +.l-main-section + h2 Inputs + .l-sub-section + h3.input {% for binding, property in doc.inputs %} + code {$ property.bindingName | dashCase $} + |  bound to  + code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $} + :markdown +{$ property.memberDoc.description | indentForMarkdown(2) | trimBlankLines $} + {% endfor %} + +.l-main-section + h2 Outputs + .l-sub-section + h3.input {% for binding, property in doc.outputs %} + code {$ property.bindingName | dashCase $} + |  bound to  + code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $} + :markdown +{$ event.memberDoc.description | indentForMarkdown(2) | trimBlankLines $} + {% endfor %} +{% endblock %} diff --git a/public/api-builder/docs-package/index.js b/public/api-builder/docs-package/index.js index a5dd5bdff5..033e181b0e 100644 --- a/public/api-builder/docs-package/index.js +++ b/public/api-builder/docs-package/index.js @@ -15,6 +15,7 @@ module.exports = new Package('angular-v2-docs', [jsdocPackage, nunjucksPackage, // Register the processors .processor(require('./processors/convertPrivateClassesToInterfaces')) +.processor(require('./processors/extractDirectiveClasses')) .processor(require('./processors/generateNavigationDoc')) .processor(require('./processors/extractTitleFromGuides')) .processor(require('./processors/createOverviewDump')) diff --git a/public/api-builder/docs-package/processors/extractDirectiveClasses.js b/public/api-builder/docs-package/processors/extractDirectiveClasses.js new file mode 100644 index 0000000000..91f83a06bd --- /dev/null +++ b/public/api-builder/docs-package/processors/extractDirectiveClasses.js @@ -0,0 +1,32 @@ +var _ = require('lodash'); +var vm = require('vm'); + +module.exports = function extractDirectiveClassesProcessor() { + return { + $runAfter: ['processing-docs'], + $runBefore: ['docs-processed'], + decoratorTypes: ['Directive', 'Component', 'View'], + $process: function(docs) { + var decoratorTypes = this.decoratorTypes; + + _.forEach(docs, function(doc) { + + _.forEach(doc.decorators, function(decorator) { + + if (decoratorTypes.indexOf(decorator.name) !== -1) { + + // We use this sneaky vm trick to extract the object literal + // argument from the decorator's constructor call + var args = decorator.arguments ? + vm.runInNewContext('dummy = ' + decorator.arguments[0]) : {}; + + doc[decorator.name.toLowerCase() + 'Options'] = args; + doc.docType = 'directive'; + } + }); + }); + + return docs; + } + }; +}; diff --git a/public/api-builder/docs-package/processors/extractDirectiveClasses.spec.js b/public/api-builder/docs-package/processors/extractDirectiveClasses.spec.js new file mode 100644 index 0000000000..ed0c9ed959 --- /dev/null +++ b/public/api-builder/docs-package/processors/extractDirectiveClasses.spec.js @@ -0,0 +1,45 @@ +var mockPackage = require('../mocks/mockPackage'); +var Dgeni = require('dgeni'); + +describe('extractDirectiveClasses processor', function() { + var dgeni, injector, processor; + + beforeEach(function() { + dgeni = new Dgeni([mockPackage()]); + injector = dgeni.configureInjector(); + processor = injector.get('extractDirectiveClassesProcessor'); + }); + + it('should extract specified decorator arguments', function() { + var doc = { + id: 'angular2/angular2.ngFor', + name: 'ngFor', + docType: 'class', + decorators: [ + { + name: 'Directive', + arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}'] + } + ] + }; + + var docs = processor.$process([doc]); + + expect(doc).toEqual(jasmine.objectContaining({ + id: 'angular2/angular2.ngFor', + name: 'ngFor', + docType: 'directive', + decorators: [ + { + name: 'Directive', + arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}'] + } + ] + })); + + expect(doc.directiveOptions).toEqual({ + selector: '[ng-for][ng-for-of]', + properties: ['ngForOf'] + }); + }); +}); \ No newline at end of file diff --git a/public/api-builder/typescript-package/index.js b/public/api-builder/typescript-package/index.js index def48537ae..981ad06d64 100644 --- a/public/api-builder/typescript-package/index.js +++ b/public/api-builder/typescript-package/index.js @@ -44,12 +44,14 @@ module.exports = new Package('typescript-parsing', [basePackage]) computeIdsProcessor.idTemplates.push({ docTypes: ['member'], idTemplate: '${classDoc.id}.${name}', - getAliases: function(doc) { return [doc.id]; } + getAliases: function(doc) { + return doc.classDoc.aliases.map(function(alias) { return alias + '.' + doc.name; }); + } }); computePathsProcessor.pathTemplates.push({ docTypes: ['member'], - pathTemplate: '${classDoc.path}/${name}', + pathTemplate: '${classDoc.path}#${name}', getOutputPath: function() {} // These docs are not written to their own file, instead they are part of their class doc }); diff --git a/public/api-builder/typescript-package/processors/readTypeScriptModules.js b/public/api-builder/typescript-package/processors/readTypeScriptModules.js index 979edd14b0..00af64e3e8 100644 --- a/public/api-builder/typescript-package/processors/readTypeScriptModules.js +++ b/public/api-builder/typescript-package/processors/readTypeScriptModules.js @@ -301,6 +301,7 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, function getDecorators(symbol) { + var declaration = symbol.valueDeclaration || symbol.declarations[0]; var sourceFile = ts.getSourceFileOfNode(declaration); @@ -310,12 +311,33 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, name: decorator.expression ? decorator.expression.text : decorator.text, arguments: decorator.arguments && decorator.arguments.map(function(argument) { return getText(sourceFile, argument).trim(); - }) + }), + argumentInfo: decorator.arguments && decorator.arguments.map(function(argument) { + return parseArgument(argument); + }), + expression: decorator }; }); return decorators; } + function parseProperties(properties) { + var result = {}; + _.forEach(properties, function(property) { + result[property.name.text] = parseArgument(property.initializer); + }); + return result; + } + + function parseArgument(argument) { + if (argument.text) return argument.text; + if (argument.properties) return parseProperties(argument.properties); + if (argument.elements) return argument.elements.map(function(element) { return element.text; }); + var sourceFile = ts.getSourceFileOfNode(argument); + var text = getText(sourceFile, argument).trim(); + return text; + } + function getParameters(typeChecker, symbol) { var declaration = symbol.valueDeclaration || symbol.declarations[0]; var sourceFile = ts.getSourceFileOfNode(declaration);