build(docs-infra): directive inputs and outputs (#25768)

PR Close #25768
This commit is contained in:
Pete Bacon Darwin 2018-09-19 21:25:30 +01:00 committed by Alex Rickabaugh
parent d0f7eadc09
commit 9889276b15
6 changed files with 67 additions and 122 deletions

View File

@ -33,6 +33,7 @@
.method-table, .option-table, .list-table {
td > code {
background-color: inherit;
white-space: pre;
}
.with-github-links {
@ -74,19 +75,6 @@
margin: 6px 0 0 10px;
}
.properties-table {
font-size: 14px;
thead th {
&:nth-child(1) {
width: 20%;
}
&:nth-child(2) {
width: 20%;
}
}
}
.parameters-table {
margin-top: 0;
font-size: 14px;
@ -126,6 +114,10 @@
}
}
}
.property-name {
font-weight: bold;
}
}
.deprecated-api-item {

View File

@ -25,62 +25,59 @@ module.exports = function matchUpDirectiveDecorators() {
doc.exportAs = stripQuotes(doc.directiveOptions.exportAs);
doc.exportAsArray = doc.exportAs ? doc.exportAs.split(',') : [];
doc.inputs = getBindingInfo(doc.directiveOptions.inputs, doc.members, 'Input');
doc.outputs = getBindingInfo(doc.directiveOptions.outputs, doc.members, 'Output');
attachBindingInfo(doc.directiveOptions.inputs, doc.members, 'Input');
attachBindingInfo(doc.directiveOptions.outputs, doc.members, 'Output');
}
});
}
};
};
function getBindingInfo(directiveBindings, members, bindingType) {
function attachBindingInfo(directiveBindings, members, bindingType) {
const bindings = {};
if (members) {
// Parse the bindings from the directive decorator
if (directiveBindings) {
directiveBindings.forEach(function(binding) {
const bindingInfo = parseBinding(binding);
const bindingInfo = parseBinding(bindingType, binding);
bindings[bindingInfo.propertyName] = bindingInfo;
});
}
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);
bindings[member.name] = createBindingInfo(bindingType, member.name, 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
// Attach binding info to the member
// Note: this may have come from the `@Directive` decorator or from a property decorator such as `@Input`.
if (bindings[member.name]) {
bindings[member.name].memberDoc = member;
member.boundTo = bindings[member.name];
}
});
}
// 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.trim().replace(/^(['"])(.*)\1$/, '$2') : value;
}
function parseBinding(option) {
function parseBinding(bindingType, 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);
return createBindingInfo(bindingType, propertyName, bindingName);
}
function createBindingInfo(propertyName, bindingName) {
function createBindingInfo(bindingType, propertyName, bindingName) {
return {
type: bindingType,
propertyName: stripQuotes(propertyName),
bindingName: stripQuotes(bindingName)
};

View File

@ -59,7 +59,7 @@ describe('matchUpDirectiveDecorators processor', () => {
});
it('should extract inputs and outputs from the directive decorator', () => {
const docs = [{
const doc = {
docType: 'directive',
directiveOptions: {
inputs: ['in1:in2', 'in3', ' in4:in5 ', ' in6 '],
@ -70,28 +70,30 @@ describe('matchUpDirectiveDecorators processor', () => {
{ name: 'in3' },
{ name: 'in4' },
{ name: 'in6' },
{ name: 'prop1' },
{ name: 'out1' },
{ name: 'out2' },
{ name: 'out4' }
{ name: 'out4' },
{ name: 'prop2' },
]
}];
processorFactory().$process(docs);
expect(docs[0].inputs).toEqual([
{ propertyName: 'in1', bindingName: 'in2', memberDoc: docs[0].members[0] },
{ propertyName: 'in3', bindingName: 'in3', memberDoc: docs[0].members[1] },
{ propertyName: 'in4', bindingName: 'in5', memberDoc: docs[0].members[2] },
{ propertyName: 'in6', bindingName: 'in6', memberDoc: docs[0].members[3] }
]);
};
expect(docs[0].outputs).toEqual([
{ propertyName: 'out1', bindingName: 'out1', memberDoc: docs[0].members[4] },
{ propertyName: 'out2', bindingName: 'out3', memberDoc: docs[0].members[5] },
{ propertyName: 'out4', bindingName: 'out4', memberDoc: docs[0].members[6] }
processorFactory().$process([doc]);
expect(doc.members).toEqual([
{ name: 'in1', boundTo: { type: 'Input', propertyName: 'in1', bindingName: 'in2' } },
{ name: 'in3', boundTo: { type: 'Input', propertyName: 'in3', bindingName: 'in3' } },
{ name: 'in4', boundTo: { type: 'Input', propertyName: 'in4', bindingName: 'in5' } },
{ name: 'in6', boundTo: { type: 'Input', propertyName: 'in6', bindingName: 'in6' }},
{ name: 'prop1' },
{ name: 'out1', boundTo: { type: 'Output', propertyName: 'out1', bindingName: 'out1' } },
{ name: 'out2', boundTo: { type: 'Output', propertyName: 'out2', bindingName: 'out3' } },
{ name: 'out4', boundTo: { type: 'Output', propertyName: 'out4', bindingName: 'out4' }},
{ name: 'prop2' },
]);
});
it('should extract inputs and outputs from decorated properties', () => {
const docs = [{
const doc = {
docType: 'directive',
directiveOptions: {},
members: [
@ -100,21 +102,18 @@ describe('matchUpDirectiveDecorators processor', () => {
{ 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] }
};
processorFactory().$process([doc]);
expect(doc.members).toEqual([
{ name: 'a1', decorators: [{ name: 'Input', arguments: ['a2'] }], boundTo: { type: 'Input', propertyName: 'a1', bindingName: 'a2' } },
{ name: 'b1', decorators: [{ name: 'Output', arguments: ['b2'] }], boundTo: { type: 'Output', propertyName: 'b1', bindingName: 'b2' } },
{ name: 'c1', decorators: [{ name: 'Input', arguments: [] }], boundTo: { type: 'Input', propertyName: 'c1', bindingName: 'c1' } },
{ name: 'd1', decorators: [{ name: 'Output', arguments: [] }], boundTo: { type: 'Output', propertyName: 'd1', bindingName: 'd1' } },
]);
});
it('should merge directive inputs/outputs with decorator property inputs/outputs', () => {
const docs = [{
const doc = {
docType: 'directive',
directiveOptions: {
inputs: ['a1:a2'],
@ -126,16 +125,13 @@ describe('matchUpDirectiveDecorators processor', () => {
{ name: 'b1' },
{ name: 'b3', decorators: [{ name: 'Output', arguments: ['b4'] }] },
]
}];
processorFactory().$process(docs);
expect(docs[0].inputs).toEqual([
{ propertyName: 'a1', bindingName: 'a2', memberDoc: docs[0].members[0] },
{ propertyName: 'a3', bindingName: 'a4', memberDoc: docs[0].members[1] }
]);
expect(docs[0].outputs).toEqual([
{ propertyName: 'b1', bindingName: 'b2', memberDoc: docs[0].members[2] },
{ propertyName: 'b3', bindingName: 'b4', memberDoc: docs[0].members[3] }
};
processorFactory().$process([doc]);
expect(doc.members).toEqual([
{ name: 'a1', boundTo: { type: 'Input', propertyName: 'a1', bindingName: 'a2' } },
{ name: 'a3', boundTo: { type: 'Input', propertyName: 'a3', bindingName: 'a4' }, decorators: [{ name: 'Input', arguments: ['a4'] }] },
{ name: 'b1', boundTo: { type: 'Output', propertyName: 'b1', bindingName: 'b2' } },
{ name: 'b3', boundTo: { type: 'Output', propertyName: 'b3', bindingName: 'b4' }, decorators: [{ name: 'Output', arguments: ['b4'] }] },
]);
});
});

View File

@ -1,4 +1,4 @@
{% import "lib/directiveHelpers.html" as directiveHelper -%}
{% import "lib/memberHelpers.html" as memberHelpers -%}
{% extends 'class.template.html' -%}
{% block overview %}{% endblock %}
@ -7,9 +7,7 @@
{% include "includes/ngmodule.html" %}
{% include "includes/selectors.html" %}
{$ directiveHelper.renderBindings(doc.inputs, 'inputs', 'input', 'Inputs') $}
{$ directiveHelper.renderBindings(doc.outputs, 'outputs', 'output', 'Outputs') $}
{$ memberHelpers.renderProperties(doc.properties, 'instance-properties', 'instance-property', 'Properties') $}
{% include "includes/export-as.html" %}
@ -21,8 +19,9 @@
</section>
{% endif %}
<h2>Class</h2>
{% include "includes/class-members.html" %}
{% endblock %}
{$ memberHelpers.renderProperties(doc.staticProperties, 'static-properties', 'static-property', 'Static properties') $}
{$ memberHelpers.renderMethodDetails(versionInfo, doc.staticMethods, 'static-methods', 'static-method', 'Static methods') $}
{$ memberHelpers.renderMethodDetails(versionInfo, doc.methods, 'instance-methods', 'instance-method', 'Methods') $}
{% endblock %}
{% block endNotes %}{% endblock %}

View File

@ -1,40 +0,0 @@
{% macro renderBindings(bindings, cssContainerClass, cssItemClass, title) -%}
{% set hasDescription = bindings | hasValues('memberDoc.description') %}
{% set hasTypes = bindings | hasValues('memberDoc.type') %}
{% if bindings.length %}
<section class="{$ cssContainerClass $}">
<h2>{$ title $}</h2>
<table class="is-full-width list-table binding-table">
<thead>
<tr>
<th>Binding</th>
{% if hasTypes %}<th>Type</th>{% endif %}
{% if hasDescription %}<th>Description</th>{% endif %}
</tr>
</thead>
<tbody>
{% for binding in bindings %}
<tr class="{$ cssItemClass $}">
<td><a href="#{$ binding.memberDoc.anchor $}">
<code>{$ binding.bindingName $}
{%- if binding.bindingName != binding.propertyName %} ({$ binding.propertyName $}){% endif-%}
</code>
</a></td>
{% if hasTypes %}
<td><label class="property-type-label"><code>{$ binding.memberDoc.type | escape $}</code></label></td>
{% endif %}
{% if hasDescription %}
<td>
{$ binding.memberDoc.shortDescription | marked $}
{$ binding.memberDoc.description | marked $}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</section>
{% endif %}
{%- endmacro %}

View File

@ -170,7 +170,6 @@
{%- macro renderProperties(properties, containerClass, propertyClass, headingText, headings) -%}
{% set nonInternalProperties = properties | filterByPropertyValue('internal', undefined) %}
{% set hasTypes = properties | hasValues('type') %}
{% if nonInternalProperties.length -%}
<section class="{$ containerClass $}">
<h2>{$ headingText $}</h2>
@ -178,15 +177,17 @@
<thead>
<tr>
<th>{$ headings[0] or 'Property' $}</th>
{% if hasTypes %}<th>{$ headings[1] or 'Type' $}</th>{% endif %}
<th>{$ headings[2] or 'Description' $}</th>
</tr>
</thead>
<tbody>
{% for property in nonInternalProperties %}
<tr class="{$ propertyClass $}">
<td><a id="{$ property.anchor $}"></a>{$ property.name $}</td>
{% if hasTypes %}<td><label class="property-type-label"><code>{$ property.type | escape $}</code></label></td>{% endif %}
<td>
<code>{%- if property.boundTo %}<span class="property-binding">@{$ property.boundTo.type $}({$ property.boundTo.bindingName $})</span>
{% endif -%}<a id="{$ property.anchor $}" class="property-name">{$ property.name $}</a>{% if property.type %}: <span class="property-type">{$ property.type | escape $}</span>{% endif -%}
</code>
</td>
<td>
{%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}<span class='read-only-property'>Read-only.</span>{% endif %}
{% if property.shortDescription %}{$ property.shortDescription | marked $}{% endif %}