refactor(compiler): remove a dependency from the IVY AST to the template AST (#23476)

PR Close #23476
This commit is contained in:
Victor Berchet
2018-04-20 11:28:34 -07:00
parent 0b47902ad7
commit 6761a64522
8 changed files with 316 additions and 267 deletions

View File

@ -7,7 +7,7 @@
*/
import {SecurityContext} from '../core';
import {AST} from '../expression_parser/ast';
import {AST, BoundElementBindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast';
import {ParseSourceSpan} from '../parse_util';
export interface Node {
@ -32,42 +32,17 @@ export class TextAttribute implements Node {
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitAttribute(this); }
}
/**
* Enumeration of types of property bindings.
*/
export enum PropertyBindingType {
/**
* A normal binding to a property (e.g. `[property]="expression"`).
*/
Property,
/**
* A binding to an element attribute (e.g. `[attr.name]="expression"`).
*/
Attribute,
/**
* A binding to a CSS class (e.g. `[class.name]="condition"`).
*/
Class,
/**
* A binding to a style rule (e.g. `[style.rule]="expression"`).
*/
Style,
/**
* A binding to an animation reference (e.g. `[animate.key]="expression"`).
*/
Animation
}
export class BoundAttribute implements Node {
constructor(
public name: string, public type: PropertyBindingType,
public name: string, public type: BoundElementBindingType,
public securityContext: SecurityContext, public value: AST, public unit: string|null,
public sourceSpan: ParseSourceSpan) {}
static fromBoundElementProperty(prop: BoundElementProperty) {
return new BoundAttribute(
prop.name, prop.type, prop.securityContext, prop.value, prop.unit, prop.sourceSpan);
}
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundAttribute(this); }
}
@ -75,6 +50,14 @@ export class BoundEvent implements Node {
constructor(
public name: string, public handler: AST, public target: string|null,
public phase: string|null, public sourceSpan: ParseSourceSpan) {}
static fromParsedEvent(event: ParsedEvent) {
const target: string|null = event.type === ParsedEventType.Regular ? event.targetOrPhase : null;
const phase: string|null =
event.type === ParsedEventType.Animation ? event.targetOrPhase : null;
return new BoundEvent(event.name, event.handler, target, phase, event.sourceSpan);
}
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundEvent(this); }
}

View File

@ -6,14 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ParsedEvent, ParsedProperty, ParsedVariable} from '../expression_parser/ast';
import * as html from '../ml_parser/ast';
import {replaceNgsp} from '../ml_parser/html_whitespaces';
import {isNgTemplate} from '../ml_parser/tags';
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
import {isStyleUrlResolvable} from '../style_url_resolver';
import {BindingParser, BoundProperty} from '../template_parser/binding_parser';
// TODO(chuckj): Refactor binding parser to not have a dependency on template_ast.
import {BoundEventAst, VariableAst} from '../template_parser/template_ast';
import {BindingParser} from '../template_parser/binding_parser';
import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser';
import * as t from './r3_ast';
@ -78,7 +77,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
const isTemplateElement = isNgTemplate(element.name);
const matchableAttributes: [string, string][] = [];
const boundProperties: BoundProperty[] = [];
const parsedProperties: ParsedProperty[] = [];
const boundEvents: t.BoundEvent[] = [];
const variables: t.Variable[] = [];
const references: t.Reference[] = [];
@ -86,7 +85,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
const templateMatchableAttributes: [string, string][] = [];
let inlineTemplateSourceSpan: ParseSourceSpan;
const templateBoundProperties: BoundProperty[] = [];
const templateParsedProperties: ParsedProperty[] = [];
const templateVariables: t.Variable[] = [];
// Whether the element has any *-attribute
@ -110,20 +109,15 @@ export class HtmlToTemplateTransform implements html.Visitor {
const templateValue = attribute.value;
const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
const oldVariables: VariableAst[] = [];
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
this.bindingParser.parseInlineTemplateBinding(
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
templateBoundProperties, oldVariables);
templateVariables.push(
...oldVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
templateParsedProperties, templateVariables);
} else {
// Check for variables, events, property bindings, interpolation
hasBinding = this.parseAttribute(
isTemplateElement, attribute, matchableAttributes, boundProperties, boundEvents,
isTemplateElement, attribute, matchableAttributes, parsedProperties, boundEvents,
variables, references);
}
@ -158,12 +152,12 @@ export class HtmlToTemplateTransform implements html.Visitor {
parsedElement = new t.Content(selectorIndex, attributes, element.sourceSpan);
} else if (isTemplateElement) {
// `<ng-template>`
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
parsedElement = new t.Template(
attributes, boundAttributes, children, references, variables, element.sourceSpan,
element.startSourceSpan, element.endSourceSpan);
} else {
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
parsedElement = new t.Element(
element.name, attributes, boundAttributes, boundEvents, children, references,
@ -177,7 +171,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
([name, value]) =>
attributes.push(new t.TextAttribute(name, value, inlineTemplateSourceSpan)));
const boundAttributes = this.createBoundAttributes('ng-template', templateBoundProperties);
const boundAttributes = this.createBoundAttributes('ng-template', templateParsedProperties);
parsedElement = new t.Template(
attributes, boundAttributes, [parsedElement], [], templateVariables, element.sourceSpan,
element.startSourceSpan, element.endSourceSpan);
@ -202,22 +196,16 @@ export class HtmlToTemplateTransform implements html.Visitor {
visitExpansionCase(expansionCase: html.ExpansionCase): null { return null; }
private createBoundAttributes(elementName: string, boundProperties: BoundProperty[]):
private createBoundAttributes(elementName: string, properties: ParsedProperty[]):
t.BoundAttribute[] {
const literalProperties = boundProperties.filter(prop => !prop.isLiteral);
return literalProperties.map(property => {
// TODO(vicb): get ride of the boundProperty (from TemplateAst)
const boundProp = this.bindingParser.createElementPropertyAst(elementName, property);
return new t.BoundAttribute(
boundProp.name, boundProp.type as any as t.PropertyBindingType, boundProp.securityContext,
boundProp.value, boundProp.unit, boundProp.sourceSpan);
});
return properties.filter(prop => !prop.isLiteral)
.map(prop => this.bindingParser.createBoundElementProperty(elementName, prop))
.map(prop => t.BoundAttribute.fromBoundElementProperty(prop));
}
private parseAttribute(
isTemplateElement: boolean, attribute: html.Attribute, matchableAttributes: string[][],
boundProperties: BoundProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
parsedProperties: ParsedProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
references: t.Reference[]) {
const name = normalizeAttributeName(attribute.name);
const value = attribute.value;
@ -230,7 +218,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
hasBinding = true;
if (bindParts[KW_BIND_IDX] != null) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
} else if (bindParts[KW_LET_IDX]) {
if (isTemplateElement) {
@ -245,40 +233,40 @@ export class HtmlToTemplateTransform implements html.Visitor {
this.parseReference(identifier, value, srcSpan, references);
} else if (bindParts[KW_ON_IDX]) {
const events: BoundEventAst[] = [];
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, events);
addEvents(events, boundEvents);
} else if (bindParts[KW_BINDON_IDX]) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
this.parseAssignmentEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, boundEvents);
} else if (bindParts[KW_AT_IDX]) {
this.bindingParser.parseLiteralAttr(
name, value, srcSpan, matchableAttributes, boundProperties);
name, value, srcSpan, matchableAttributes, parsedProperties);
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, matchableAttributes,
boundProperties);
parsedProperties);
this.parseAssignmentEvent(
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, matchableAttributes, boundEvents);
} else if (bindParts[IDENT_PROPERTY_IDX]) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, matchableAttributes,
boundProperties);
parsedProperties);
} else if (bindParts[IDENT_EVENT_IDX]) {
const events: BoundEventAst[] = [];
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
bindParts[IDENT_EVENT_IDX], value, srcSpan, matchableAttributes, events);
addEvents(events, boundEvents);
}
} else {
hasBinding = this.bindingParser.parsePropertyInterpolation(
name, value, srcSpan, matchableAttributes, boundProperties);
name, value, srcSpan, matchableAttributes, parsedProperties);
}
return hasBinding;
@ -305,7 +293,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
private parseAssignmentEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], boundEvents: t.BoundEvent[]) {
const events: BoundEventAst[] = [];
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, events);
addEvents(events, boundEvents);
@ -356,9 +344,8 @@ function normalizeAttributeName(attrName: string): string {
return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
}
function addEvents(events: BoundEventAst[], boundEvents: t.BoundEvent[]) {
boundEvents.push(
...events.map(e => new t.BoundEvent(e.name, e.handler, e.target, e.phase, e.sourceSpan)));
function addEvents(events: ParsedEvent[], boundEvents: t.BoundEvent[]) {
boundEvents.push(...events.map(e => t.BoundEvent.fromParsedEvent(e)));
}
function isEmptyTextNode(node: html.Node): boolean {

View File

@ -12,6 +12,7 @@ import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, c
import {ConstantPool, DefinitionKind} from '../constant_pool';
import * as core from '../core';
import {AST, AstMemoryEfficientTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../expression_parser/ast';
import {BoundElementBindingType} from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
import {LifecycleHooks} from '../lifecycle_reflector';
import * as o from '../output/output_ast';
@ -24,6 +25,7 @@ import * as t from './r3_ast';
import {Identifiers as R3} from './r3_identifiers';
/** Name of the context parameter passed into a template function */
const CONTEXT_NAME = 'ctx';
@ -213,10 +215,10 @@ function unsupported(feature: string): never {
}
const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = {
[t.PropertyBindingType.Property]: R3.elementProperty,
[t.PropertyBindingType.Attribute]: R3.elementAttribute,
[t.PropertyBindingType.Class]: R3.elementClassNamed,
[t.PropertyBindingType.Style]: R3.elementStyleNamed,
[BoundElementBindingType.Property]: R3.elementProperty,
[BoundElementBindingType.Attribute]: R3.elementAttribute,
[BoundElementBindingType.Class]: R3.elementClassNamed,
[BoundElementBindingType.Style]: R3.elementStyleNamed,
};
function interpolate(args: o.Expression[]): o.Expression {
@ -679,7 +681,7 @@ class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
// Generate element input bindings
element.inputs.forEach((input: t.BoundAttribute) => {
if (input.type === t.PropertyBindingType.Animation) {
if (input.type === BoundElementBindingType.Animation) {
this._unsupported('animations');
}
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
@ -691,7 +693,7 @@ class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
o.literal(input.name), value);
} else {
this._unsupported(`binding ${t.PropertyBindingType[input.type]}`);
this._unsupported(`binding type ${input.type}`);
}
});