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:
Chuck Jazdzewski
2017-05-09 16:16:50 -07:00
committed by Jason Aden
parent 9e661e58d1
commit bb0902c592
23 changed files with 2891 additions and 2270 deletions

View 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() !; }
}

View File

@ -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';

View File

@ -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.

View File

@ -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); },
});
}

View File

@ -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);
}

View File

@ -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>;