refactor(compiler-cli): move the expression expression type checker (#16562)
The expression type checker moved from the language service to the compiler-cli in preparation to using it to check template expressions.
This commit is contained in:

committed by
Jason Aden

parent
9e661e58d1
commit
bb0902c592
48
packages/compiler/src/ast_path.ts
Normal file
48
packages/compiler/src/ast_path.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* A path is an ordered set of elements. Typically a path is to a
|
||||
* particular offset in a source file. The head of the list is the top
|
||||
* most node. The tail is the node that contains the offset directly.
|
||||
*
|
||||
* For example, the expresion `a + b + c` might have an ast that looks
|
||||
* like:
|
||||
* +
|
||||
* / \
|
||||
* a +
|
||||
* / \
|
||||
* b c
|
||||
*
|
||||
* The path to the node at offset 9 would be `['+' at 1-10, '+' at 7-10,
|
||||
* 'c' at 9-10]` and the path the node at offset 1 would be
|
||||
* `['+' at 1-10, 'a' at 1-2]`.
|
||||
*/
|
||||
export class AstPath<T> {
|
||||
constructor(private path: T[], public position: number = -1) {}
|
||||
|
||||
get empty(): boolean { return !this.path || !this.path.length; }
|
||||
get head(): T|undefined { return this.path[0]; }
|
||||
get tail(): T|undefined { return this.path[this.path.length - 1]; }
|
||||
|
||||
parentOf(node: T|undefined): T|undefined {
|
||||
return node && this.path[this.path.indexOf(node) - 1];
|
||||
}
|
||||
childOf(node: T): T|undefined { return this.path[this.path.indexOf(node) + 1]; }
|
||||
|
||||
first<N extends T>(ctor: {new (...args: any[]): N}): N|undefined {
|
||||
for (let i = this.path.length - 1; i >= 0; i--) {
|
||||
let item = this.path[i];
|
||||
if (item instanceof ctor) return <N>item;
|
||||
}
|
||||
}
|
||||
|
||||
push(node: T) { this.path.push(node); }
|
||||
|
||||
pop(): T { return this.path.pop() !; }
|
||||
}
|
@ -36,6 +36,7 @@ export * from './aot/static_reflection_capabilities';
|
||||
export * from './aot/static_symbol';
|
||||
export * from './aot/static_symbol_resolver';
|
||||
export * from './aot/summary_resolver';
|
||||
export * from './ast_path';
|
||||
export * from './summary_resolver';
|
||||
export {JitCompiler} from './jit/compiler';
|
||||
export * from './jit/compiler_factory';
|
||||
|
@ -239,7 +239,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
return convertToStatementIfNeeded(
|
||||
mode,
|
||||
new o.BinaryOperatorExpr(
|
||||
op, this.visit(ast.left, _Mode.Expression), this.visit(ast.right, _Mode.Expression)));
|
||||
op, this._visit(ast.left, _Mode.Expression), this._visit(ast.right, _Mode.Expression)));
|
||||
}
|
||||
|
||||
visitChain(ast: cdAst.Chain, mode: _Mode): any {
|
||||
@ -248,11 +248,11 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
|
||||
visitConditional(ast: cdAst.Conditional, mode: _Mode): any {
|
||||
const value: o.Expression = this.visit(ast.condition, _Mode.Expression);
|
||||
const value: o.Expression = this._visit(ast.condition, _Mode.Expression);
|
||||
return convertToStatementIfNeeded(
|
||||
mode,
|
||||
value.conditional(
|
||||
this.visit(ast.trueExp, _Mode.Expression), this.visit(ast.falseExp, _Mode.Expression)));
|
||||
mode, value.conditional(
|
||||
this._visit(ast.trueExp, _Mode.Expression),
|
||||
this._visit(ast.falseExp, _Mode.Expression)));
|
||||
}
|
||||
|
||||
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
|
||||
@ -266,7 +266,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
if (ast instanceof BuiltinFunctionCall) {
|
||||
fnResult = ast.converter(convertedArgs);
|
||||
} else {
|
||||
fnResult = this.visit(ast.target !, _Mode.Expression).callFn(convertedArgs);
|
||||
fnResult = this._visit(ast.target !, _Mode.Expression).callFn(convertedArgs);
|
||||
}
|
||||
return convertToStatementIfNeeded(mode, fnResult);
|
||||
}
|
||||
@ -281,7 +281,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
const args = [o.literal(ast.expressions.length)];
|
||||
for (let i = 0; i < ast.strings.length - 1; i++) {
|
||||
args.push(o.literal(ast.strings[i]));
|
||||
args.push(this.visit(ast.expressions[i], _Mode.Expression));
|
||||
args.push(this._visit(ast.expressions[i], _Mode.Expression));
|
||||
}
|
||||
args.push(o.literal(ast.strings[ast.strings.length - 1]));
|
||||
|
||||
@ -298,14 +298,14 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
||||
} else {
|
||||
return convertToStatementIfNeeded(
|
||||
mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression)));
|
||||
mode, this._visit(ast.obj, _Mode.Expression).key(this._visit(ast.key, _Mode.Expression)));
|
||||
}
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any {
|
||||
const obj: o.Expression = this.visit(ast.obj, _Mode.Expression);
|
||||
const key: o.Expression = this.visit(ast.key, _Mode.Expression);
|
||||
const value: o.Expression = this.visit(ast.value, _Mode.Expression);
|
||||
const obj: o.Expression = this._visit(ast.obj, _Mode.Expression);
|
||||
const key: o.Expression = this._visit(ast.key, _Mode.Expression);
|
||||
const value: o.Expression = this._visit(ast.value, _Mode.Expression);
|
||||
return convertToStatementIfNeeded(mode, obj.key(key).set(value));
|
||||
}
|
||||
|
||||
@ -330,7 +330,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
} else {
|
||||
const args = this.visitAll(ast.args, _Mode.Expression);
|
||||
let result: any = null;
|
||||
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||
const receiver = this._visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
const varExpr = this._getLocal(ast.name);
|
||||
if (varExpr) {
|
||||
@ -345,7 +345,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: cdAst.PrefixNot, mode: _Mode): any {
|
||||
return convertToStatementIfNeeded(mode, o.not(this.visit(ast.expression, _Mode.Expression)));
|
||||
return convertToStatementIfNeeded(mode, o.not(this._visit(ast.expression, _Mode.Expression)));
|
||||
}
|
||||
|
||||
visitPropertyRead(ast: cdAst.PropertyRead, mode: _Mode): any {
|
||||
@ -354,7 +354,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
||||
} else {
|
||||
let result: any = null;
|
||||
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||
const receiver = this._visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
result = this._getLocal(ast.name);
|
||||
}
|
||||
@ -366,7 +366,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
|
||||
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
||||
const receiver: o.Expression = this._visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
const varExpr = this._getLocal(ast.name);
|
||||
if (varExpr) {
|
||||
@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
}
|
||||
return convertToStatementIfNeeded(
|
||||
mode, receiver.prop(ast.name).set(this.visit(ast.value, _Mode.Expression)));
|
||||
mode, receiver.prop(ast.name).set(this._visit(ast.value, _Mode.Expression)));
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: cdAst.SafePropertyRead, mode: _Mode): any {
|
||||
@ -385,14 +385,14 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode);
|
||||
}
|
||||
|
||||
visitAll(asts: cdAst.AST[], mode: _Mode): any { return asts.map(ast => this.visit(ast, mode)); }
|
||||
visitAll(asts: cdAst.AST[], mode: _Mode): any { return asts.map(ast => this._visit(ast, mode)); }
|
||||
|
||||
visitQuote(ast: cdAst.Quote, mode: _Mode): any {
|
||||
throw new Error(`Quotes are not supported for evaluation!
|
||||
Statement: ${ast.uninterpretedExpression} located at ${ast.location}`);
|
||||
}
|
||||
|
||||
private visit(ast: cdAst.AST, mode: _Mode): any {
|
||||
private _visit(ast: cdAst.AST, mode: _Mode): any {
|
||||
const result = this._resultMap.get(ast);
|
||||
if (result) return result;
|
||||
return (this._nodeMap.get(ast) || ast).visit(this, mode);
|
||||
@ -439,7 +439,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
// Notice that the first guard condition is the left hand of the left most safe access node
|
||||
// which comes in as leftMostSafe to this routine.
|
||||
|
||||
let guardedExpression = this.visit(leftMostSafe.receiver, _Mode.Expression);
|
||||
let guardedExpression = this._visit(leftMostSafe.receiver, _Mode.Expression);
|
||||
let temporary: o.ReadVarExpr = undefined !;
|
||||
if (this.needsTemporary(leftMostSafe.receiver)) {
|
||||
// If the expression has method calls or pipes then we need to save the result into a
|
||||
@ -468,7 +468,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
|
||||
// Recursively convert the node now without the guarded member access.
|
||||
const access = this.visit(ast, _Mode.Expression);
|
||||
const access = this._visit(ast, _Mode.Expression);
|
||||
|
||||
// Remove the mapping. This is not strictly required as the converter only traverses each node
|
||||
// once but is safer if the conversion is changed to traverse the nodes more than once.
|
||||
|
@ -227,6 +227,29 @@ export interface AstVisitor {
|
||||
visitQuote(ast: Quote, context: any): any;
|
||||
visitSafeMethodCall(ast: SafeMethodCall, context: any): any;
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any): any;
|
||||
visit?(ast: AST, context?: any): any;
|
||||
}
|
||||
|
||||
export class NullAstVisitor {
|
||||
visitBinary(ast: Binary, context: any): any {}
|
||||
visitChain(ast: Chain, context: any): any {}
|
||||
visitConditional(ast: Conditional, context: any): any {}
|
||||
visitFunctionCall(ast: FunctionCall, context: any): any {}
|
||||
visitImplicitReceiver(ast: ImplicitReceiver, context: any): any {}
|
||||
visitInterpolation(ast: Interpolation, context: any): any {}
|
||||
visitKeyedRead(ast: KeyedRead, context: any): any {}
|
||||
visitKeyedWrite(ast: KeyedWrite, context: any): any {}
|
||||
visitLiteralArray(ast: LiteralArray, context: any): any {}
|
||||
visitLiteralMap(ast: LiteralMap, context: any): any {}
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any {}
|
||||
visitMethodCall(ast: MethodCall, context: any): any {}
|
||||
visitPipe(ast: BindingPipe, context: any): any {}
|
||||
visitPrefixNot(ast: PrefixNot, context: any): any {}
|
||||
visitPropertyRead(ast: PropertyRead, context: any): any {}
|
||||
visitPropertyWrite(ast: PropertyWrite, context: any): any {}
|
||||
visitQuote(ast: Quote, context: any): any {}
|
||||
visitSafeMethodCall(ast: SafeMethodCall, context: any): any {}
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any): any {}
|
||||
}
|
||||
|
||||
export class RecursiveAstVisitor implements AstVisitor {
|
||||
@ -390,3 +413,64 @@ export class AstTransformer implements AstVisitor {
|
||||
return new Quote(ast.span, ast.prefix, ast.uninterpretedExpression, ast.location);
|
||||
}
|
||||
}
|
||||
|
||||
export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
|
||||
function visit(ast: AST) {
|
||||
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);
|
||||
}
|
||||
|
||||
function visitAll<T extends AST>(asts: T[]) { asts.forEach(visit); }
|
||||
|
||||
ast.visit({
|
||||
visitBinary(ast) {
|
||||
visit(ast.left);
|
||||
visit(ast.right);
|
||||
},
|
||||
visitChain(ast) { visitAll(ast.expressions); },
|
||||
visitConditional(ast) {
|
||||
visit(ast.condition);
|
||||
visit(ast.trueExp);
|
||||
visit(ast.falseExp);
|
||||
},
|
||||
visitFunctionCall(ast) {
|
||||
if (ast.target) {
|
||||
visit(ast.target);
|
||||
}
|
||||
visitAll(ast.args);
|
||||
},
|
||||
visitImplicitReceiver(ast) {},
|
||||
visitInterpolation(ast) { visitAll(ast.expressions); },
|
||||
visitKeyedRead(ast) {
|
||||
visit(ast.obj);
|
||||
visit(ast.key);
|
||||
},
|
||||
visitKeyedWrite(ast) {
|
||||
visit(ast.obj);
|
||||
visit(ast.key);
|
||||
visit(ast.obj);
|
||||
},
|
||||
visitLiteralArray(ast) { visitAll(ast.expressions); },
|
||||
visitLiteralMap(ast) {},
|
||||
visitLiteralPrimitive(ast) {},
|
||||
visitMethodCall(ast) {
|
||||
visit(ast.receiver);
|
||||
visitAll(ast.args);
|
||||
},
|
||||
visitPipe(ast) {
|
||||
visit(ast.exp);
|
||||
visitAll(ast.args);
|
||||
},
|
||||
visitPrefixNot(ast) { visit(ast.expression); },
|
||||
visitPropertyRead(ast) { visit(ast.receiver); },
|
||||
visitPropertyWrite(ast) {
|
||||
visit(ast.receiver);
|
||||
visit(ast.value);
|
||||
},
|
||||
visitQuote(ast) {},
|
||||
visitSafeMethodCall(ast) {
|
||||
visit(ast.receiver);
|
||||
visitAll(ast.args);
|
||||
},
|
||||
visitSafePropertyRead(ast) { visit(ast.receiver); },
|
||||
});
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AstPath} from '../ast_path';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
export interface Node {
|
||||
@ -80,3 +81,70 @@ export function visitAll(visitor: Visitor, nodes: Node[], context: any = null):
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export class RecursiveVisitor implements Visitor {
|
||||
constructor() {}
|
||||
|
||||
visitElement(ast: Element, context: any): any {
|
||||
this.visitChildren(context, visit => {
|
||||
visit(ast.attrs);
|
||||
visit(ast.children);
|
||||
});
|
||||
}
|
||||
|
||||
visitAttribute(ast: Attribute, context: any): any {}
|
||||
visitText(ast: Text, context: any): any {}
|
||||
visitComment(ast: Comment, context: any): any {}
|
||||
|
||||
visitExpansion(ast: Expansion, context: any): any {
|
||||
return this.visitChildren(context, visit => { visit(ast.cases); });
|
||||
}
|
||||
|
||||
visitExpansionCase(ast: ExpansionCase, context: any): any {}
|
||||
|
||||
private visitChildren<T extends Node>(
|
||||
context: any, cb: (visit: (<V extends Node>(children: V[]|undefined) => void)) => void) {
|
||||
let results: any[][] = [];
|
||||
let t = this;
|
||||
function visit<T extends Node>(children: T[] | undefined) {
|
||||
if (children) results.push(visitAll(t, children, context));
|
||||
}
|
||||
cb(visit);
|
||||
return [].concat.apply([], results);
|
||||
}
|
||||
}
|
||||
|
||||
export type HtmlAstPath = AstPath<Node>;
|
||||
|
||||
function spanOf(ast: Node) {
|
||||
const start = ast.sourceSpan.start.offset;
|
||||
let end = ast.sourceSpan.end.offset;
|
||||
if (ast instanceof Element) {
|
||||
if (ast.endSourceSpan) {
|
||||
end = ast.endSourceSpan.end.offset;
|
||||
} else if (ast.children && ast.children.length) {
|
||||
end = spanOf(ast.children[ast.children.length - 1]).end;
|
||||
}
|
||||
}
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
export function findNode(nodes: Node[], position: number): HtmlAstPath {
|
||||
const path: Node[] = [];
|
||||
|
||||
const visitor = new class extends RecursiveVisitor {
|
||||
visit(ast: Node, context: any): any {
|
||||
const span = spanOf(ast);
|
||||
if (span.start <= position && position < span.end) {
|
||||
path.push(ast);
|
||||
} else {
|
||||
// Returning a value here will result in the children being skipped.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitAll(visitor, nodes);
|
||||
|
||||
return new AstPath<Node>(path, position);
|
||||
}
|
@ -8,10 +8,12 @@
|
||||
|
||||
import {SecurityContext, ɵLifecycleHooks as LifecycleHooks} from '@angular/core';
|
||||
|
||||
import {AstPath} from '../ast_path';
|
||||
import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
|
||||
/**
|
||||
* An Abstract Syntax Tree node representing part of a parsed Angular template.
|
||||
*/
|
||||
@ -268,6 +270,77 @@ export interface TemplateAstVisitor {
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visitor that accepts each node but doesn't do anything. It is intended to be used
|
||||
* as the base class for a visitor that is only interested in a subset of the node types.
|
||||
*/
|
||||
export class NullTemplateVisitor implements TemplateAstVisitor {
|
||||
visitNgContent(ast: NgContentAst, context: any): void {}
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): void {}
|
||||
visitElement(ast: ElementAst, context: any): void {}
|
||||
visitReference(ast: ReferenceAst, context: any): void {}
|
||||
visitVariable(ast: VariableAst, context: any): void {}
|
||||
visitEvent(ast: BoundEventAst, context: any): void {}
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): void {}
|
||||
visitAttr(ast: AttrAst, context: any): void {}
|
||||
visitBoundText(ast: BoundTextAst, context: any): void {}
|
||||
visitText(ast: TextAst, context: any): void {}
|
||||
visitDirective(ast: DirectiveAst, context: any): void {}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class that can be used to build a visitor that visits each node
|
||||
* in an template ast recursively.
|
||||
*/
|
||||
export class RecursiveTemplateAstVisitor extends NullTemplateVisitor implements TemplateAstVisitor {
|
||||
constructor() { super(); }
|
||||
|
||||
// Nodes with children
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
return this.visitChildren(context, visit => {
|
||||
visit(ast.attrs);
|
||||
visit(ast.references);
|
||||
visit(ast.variables);
|
||||
visit(ast.directives);
|
||||
visit(ast.providers);
|
||||
visit(ast.children);
|
||||
});
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
return this.visitChildren(context, visit => {
|
||||
visit(ast.attrs);
|
||||
visit(ast.inputs);
|
||||
visit(ast.outputs);
|
||||
visit(ast.references);
|
||||
visit(ast.directives);
|
||||
visit(ast.providers);
|
||||
visit(ast.children);
|
||||
});
|
||||
}
|
||||
|
||||
visitDirective(ast: DirectiveAst, context: any): any {
|
||||
return this.visitChildren(context, visit => {
|
||||
visit(ast.inputs);
|
||||
visit(ast.hostProperties);
|
||||
visit(ast.hostEvents);
|
||||
});
|
||||
}
|
||||
|
||||
protected visitChildren<T extends TemplateAst>(
|
||||
context: any,
|
||||
cb: (visit: (<V extends TemplateAst>(children: V[]|undefined) => void)) => void) {
|
||||
let results: any[][] = [];
|
||||
let t = this;
|
||||
function visit<T extends TemplateAst>(children: T[] | undefined) {
|
||||
if (children && children.length) results.push(templateVisitAll(t, children, context));
|
||||
}
|
||||
cb(visit);
|
||||
return [].concat.apply([], results);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit every node in a list of {@link TemplateAst}s with the given {@link TemplateAstVisitor}.
|
||||
*/
|
||||
@ -285,3 +358,5 @@ export function templateVisitAll(
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export type TemplateAstPath = AstPath<TemplateAst>;
|
||||
|
Reference in New Issue
Block a user