/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {SecurityContext} from '../core'; import {AST, BindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast'; import {ParseSourceSpan} from '../parse_util'; export interface Node { sourceSpan: ParseSourceSpan; visit(visitor: Visitor): Result; } export class Text implements Node { constructor(public value: string, public sourceSpan: ParseSourceSpan) {} visit(visitor: Visitor): Result { return visitor.visitText(this); } } export class BoundText implements Node { constructor(public value: AST, public sourceSpan: ParseSourceSpan) {} visit(visitor: Visitor): Result { return visitor.visitBoundText(this); } } export class TextAttribute implements Node { constructor( public name: string, public value: string, public sourceSpan: ParseSourceSpan, public valueSpan?: ParseSourceSpan) {} visit(visitor: Visitor): Result { return visitor.visitTextAttribute(this); } } export class BoundAttribute implements Node { constructor( public name: string, public type: BindingType, 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(visitor: Visitor): Result { return visitor.visitBoundAttribute(this); } } 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(visitor: Visitor): Result { return visitor.visitBoundEvent(this); } } export class Element implements Node { constructor( public name: string, public attributes: TextAttribute[], public inputs: BoundAttribute[], public outputs: BoundEvent[], public children: Node[], public references: Reference[], public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null, public endSourceSpan: ParseSourceSpan|null) {} visit(visitor: Visitor): Result { return visitor.visitElement(this); } } export class Template implements Node { constructor( public attributes: TextAttribute[], public inputs: BoundAttribute[], public children: Node[], public references: Reference[], public variables: Variable[], public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null, public endSourceSpan: ParseSourceSpan|null) {} visit(visitor: Visitor): Result { return visitor.visitTemplate(this); } } export class Content implements Node { constructor( public selectorIndex: number, public attributes: TextAttribute[], public sourceSpan: ParseSourceSpan) {} visit(visitor: Visitor): Result { return visitor.visitContent(this); } } export class Variable implements Node { constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} visit(visitor: Visitor): Result { return visitor.visitVariable(this); } } export class Reference implements Node { constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} visit(visitor: Visitor): Result { return visitor.visitReference(this); } } export interface Visitor { // Returning a truthy value from `visit()` will prevent `visitAll()` from the call to the typed // method and result returned will become the result included in `visitAll()`s result array. visit?(node: Node): Result; visitElement(element: Element): Result; visitTemplate(template: Template): Result; visitContent(content: Content): Result; visitVariable(variable: Variable): Result; visitReference(reference: Reference): Result; visitTextAttribute(attribute: TextAttribute): Result; visitBoundAttribute(attribute: BoundAttribute): Result; visitBoundEvent(attribute: BoundEvent): Result; visitText(text: Text): Result; visitBoundText(text: BoundText): Result; } export class NullVisitor implements Visitor { visitElement(element: Element): void {} visitTemplate(template: Template): void {} visitContent(content: Content): void {} visitVariable(variable: Variable): void {} visitReference(reference: Reference): void {} visitTextAttribute(attribute: TextAttribute): void {} visitBoundAttribute(attribute: BoundAttribute): void {} visitBoundEvent(attribute: BoundEvent): void {} visitText(text: Text): void {} visitBoundText(text: BoundText): void {} } export class RecursiveVisitor implements Visitor { visitElement(element: Element): void { visitAll(this, element.attributes); visitAll(this, element.children); visitAll(this, element.references); } visitTemplate(template: Template): void { visitAll(this, template.attributes); visitAll(this, template.children); visitAll(this, template.references); visitAll(this, template.variables); } visitContent(content: Content): void {} visitVariable(variable: Variable): void {} visitReference(reference: Reference): void {} visitTextAttribute(attribute: TextAttribute): void {} visitBoundAttribute(attribute: BoundAttribute): void {} visitBoundEvent(attribute: BoundEvent): void {} visitText(text: Text): void {} visitBoundText(text: BoundText): void {} } export class TransformVisitor implements Visitor { visitElement(element: Element): Node { const newAttributes = transformAll(this, element.attributes); const newInputs = transformAll(this, element.inputs); const newOutputs = transformAll(this, element.outputs); const newChildren = transformAll(this, element.children); const newReferences = transformAll(this, element.references); if (newAttributes != element.attributes || newInputs != element.inputs || newOutputs != element.outputs || newChildren != element.children || newReferences != element.references) { return new Element( element.name, newAttributes, newInputs, newOutputs, newChildren, newReferences, element.sourceSpan, element.startSourceSpan, element.endSourceSpan); } return element; } visitTemplate(template: Template): Node { const newAttributes = transformAll(this, template.attributes); const newInputs = transformAll(this, template.inputs); const newChildren = transformAll(this, template.children); const newReferences = transformAll(this, template.references); const newVariables = transformAll(this, template.variables); if (newAttributes != template.attributes || newInputs != template.inputs || newChildren != template.children || newVariables != template.variables || newReferences != template.references) { return new Template( newAttributes, newInputs, newChildren, newReferences, newVariables, template.sourceSpan, template.startSourceSpan, template.endSourceSpan); } return template; } visitContent(content: Content): Node { return content; } visitVariable(variable: Variable): Node { return variable; } visitReference(reference: Reference): Node { return reference; } visitTextAttribute(attribute: TextAttribute): Node { return attribute; } visitBoundAttribute(attribute: BoundAttribute): Node { return attribute; } visitBoundEvent(attribute: BoundEvent): Node { return attribute; } visitText(text: Text): Node { return text; } visitBoundText(text: BoundText): Node { return text; } } export function visitAll(visitor: Visitor, nodes: Node[]): Result[] { const result: Result[] = []; if (visitor.visit) { for (const node of nodes) { const newNode = visitor.visit(node) || node.visit(visitor); } } else { for (const node of nodes) { const newNode = node.visit(visitor); if (newNode) { result.push(newNode); } } } return result; } export function transformAll( visitor: Visitor, nodes: Result[]): Result[] { const result: Result[] = []; let changed = false; for (const node of nodes) { const newNode = node.visit(visitor); if (newNode) { result.push(newNode as Result); } changed = changed || newNode != node; } return changed ? result : nodes; }