build(aio): fix matchUpDirectiveDecorators processor

This commit is contained in:
Peter Bacon Darwin 2017-06-29 22:14:45 +01:00 committed by Pete Bacon Darwin
parent 633ec30291
commit 99b38f52cb
2 changed files with 154 additions and 37 deletions

View File

@ -1,61 +1,85 @@
var _ = require('lodash');
/**
* @dgProcessor
* @description
* Directives in Angular are specified by various decorators. In particular the `@Directive()`
* decorator on the class and various other property decorators, such as `@Input`.
*
* This processor will extract this decorator information and attach it as properties to the
* directive document.
*
* Notably, the `input` and `output` binding information can be specified
* either via property decorators (`@Input()`/`@Output()`) or by properties on the metadata
* passed to the `@Directive` decorator. This processor will collect up info from both and
* merge them.
*/
module.exports = function matchUpDirectiveDecoratorsProcessor() {
module.exports = function matchUpDirectiveDecorators() {
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) {
docs.forEach(function(doc) {
if (doc.docType === 'directive') {
doc.selector = doc.directiveOptions.selector;
for (let decoratorName in decoratorMappings) {
var propertyName = decoratorMappings[decoratorName];
doc[propertyName] =
getDecoratorValues(doc.directiveOptions[propertyName], decoratorName, doc.members);
}
doc.selector = stripQuotes(doc.directiveOptions.selector);
doc.exportAs = stripQuotes(doc.directiveOptions.exportAs);
doc.inputs = getBindingInfo(doc.directiveOptions.inputs, doc.members, 'Input');
doc.outputs = getBindingInfo(doc.directiveOptions.outputs, doc.members, 'Output');
}
});
}
};
};
function getDecoratorValues(classDecoratorValues, memberDecoratorName, members) {
var decoratorValues = {};
function getBindingInfo(directiveBindings, members, bindingType) {
const bindings = {};
// 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;
// Parse the bindings from the directive decorator
if (directiveBindings) {
directiveBindings.forEach(function(binding) {
const bindingInfo = parseBinding(binding);
bindings[bindingInfo.propertyName] = bindingInfo;
});
}
decoratorValues[propertyName] = {propertyName: propertyName, bindingName: bindingName};
});
if (members) {
members.forEach(function(member) {
if (member.decorators) {
// Search for members with binding decorators
member.decorators.forEach(function(decorator) {
if (decorator.name === bindingType) {
bindings[member.name] = createBindingInfo(member.name, decorator.arguments[0] || member.name);
}
});
}
_.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
};
// Now ensure that any bindings have the associated member attached
// Note that this binding could have come from the directive decorator
if (bindings[member.name]) {
bindings[member.name].memberDoc = member;
}
});
if (decoratorValues[member.name]) {
decoratorValues[member.name].memberDoc = member;
}
});
if (Object.keys(decoratorValues).length) {
return decoratorValues;
}
// Convert the map back to an array
return Object.keys(bindings).map(function(key) { return bindings[key]; });
}
function stripQuotes(value) {
return (typeof(value) === 'string') ? value.replace(/^(['"])(.*)\1/, '$2') : value;
}
function parseBinding(option) {
// Directive decorator bindings are of the form: "propName : bindingName" (bindingName is optional)
const optionPair = option.split(':');
const propertyName = optionPair[0].trim();
const bindingName = (optionPair[1] || '').trim() || propertyName;
return createBindingInfo(propertyName, bindingName);
}
function createBindingInfo(propertyName, bindingName) {
return {
propertyName: stripQuotes(propertyName),
bindingName: stripQuotes(bindingName)
};
}

View File

@ -0,0 +1,93 @@
const testPackage = require('../../helpers/test-package');
const processorFactory = require('./matchUpDirectiveDecorators');
const Dgeni = require('dgeni');
describe('matchUpDirectiveDecorators processor', () => {
it('should be available on the injector', () => {
const dgeni = new Dgeni([testPackage('angular-api-package')]);
const injector = dgeni.configureInjector();
const processor = injector.get('matchUpDirectiveDecorators');
expect(processor.$process).toBeDefined();
expect(processor.$runAfter).toContain('ids-computed');
expect(processor.$runAfter).toContain('paths-computed');
expect(processor.$runBefore).toContain('rendering-docs');
});
it('should extract selector and exportAs from the directive decorator on directive docs', () => {
const docs = [{
docType: 'directive',
directiveOptions: { selector: 'a,b,c', exportAs: 'someExport' }
}];
processorFactory().$process(docs);
expect(docs[0].selector).toEqual('a,b,c');
expect(docs[0].exportAs).toEqual('someExport');
});
it('should ignore properties from the directive decorator on non-directive docs', () => {
const docs = [{
docType: 'class',
directiveOptions: { selector: 'a,b,c', exportAs: 'someExport' }
}];
processorFactory().$process(docs);
expect(docs[0].selector).toBeUndefined();
expect(docs[0].exportAs).toBeUndefined();
});
it('should strip quotes off directive properties', () => {
const docs = [{
docType: 'directive',
directiveOptions: { selector: '"a,b,c"', exportAs: '\'someExport\'' }
}];
processorFactory().$process(docs);
expect(docs[0].selector).toEqual('a,b,c');
expect(docs[0].exportAs).toEqual('someExport');
});
it('should extract inputs and outputs from the directive decorator', () => {
const docs = [{
docType: 'directive',
directiveOptions: {
inputs: ['a:b', 'x'],
outputs: ['foo:foo']
},
members: [
{ name: 'a' },
{ name: 'x' },
{ name: 'foo' }
]
}];
processorFactory().$process(docs);
expect(docs[0].inputs).toEqual([
{ propertyName: 'a', bindingName: 'b', memberDoc: docs[0].members[0] },
{ propertyName: 'x', bindingName: 'x', memberDoc: docs[0].members[1] }
]);
expect(docs[0].outputs).toEqual([
{ propertyName: 'foo', bindingName: 'foo', memberDoc: docs[0].members[2] }
]);
});
it('should extract inputs and outputs from decorated properties', () => {
const docs = [{
docType: 'directive',
directiveOptions: {},
members: [
{ name: 'a1', decorators: [{ name: 'Input', arguments: ['a2'] }] },
{ name: 'b1', decorators: [{ name: 'Output', arguments: ['b2'] }] },
{ name: 'c1', decorators: [{ name: 'Input', arguments: [] }] },
{ name: 'd1', decorators: [{ name: 'Output', arguments: [] }] },
]
}];
processorFactory().$process(docs);
expect(docs[0].inputs).toEqual([
{ propertyName: 'a1', bindingName: 'a2', memberDoc: docs[0].members[0] },
{ propertyName: 'c1', bindingName: 'c1', memberDoc: docs[0].members[2] }
]);
expect(docs[0].outputs).toEqual([
{ propertyName: 'b1', bindingName: 'b2', memberDoc: docs[0].members[1] },
{ propertyName: 'd1', bindingName: 'd1', memberDoc: docs[0].members[3] }
]);
});
});