api-templates: extract and render directive info distinct from classes

Closes #221
This commit is contained in:
Peter Bacon Darwin 2015-09-30 09:13:23 +01:00
parent b176f42302
commit e0f5261d3b
11 changed files with 291 additions and 4 deletions

View File

@ -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'))

View File

@ -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;
}
};
};

View File

@ -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']
});
});
});

View File

@ -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;
}

View File

@ -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

View File

@ -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 %}

View File

@ -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'))

View File

@ -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;
}
};
};

View File

@ -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']
});
});
});

View File

@ -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
});

View File

@ -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);