refactor(ivy): use absolute source spans in type checker (#34417)
Previously, the type checker would compute an absolute source span by combining an expression AST node's `ParseSpan` (relative to the start of the expression) together with the absolute offset of the expression as represented in a `ParseSourceSpan`, to arrive at a span relative to the start of the file. This information is now directly available on an expression AST node in the `AST.sourceSpan` property, which can be used instead. PR Close #34417
This commit is contained in:
parent
23595272fe
commit
8c6468a025
|
@ -5,7 +5,7 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {AbsoluteSourceSpan, ParseSourceSpan, ParseSpan, Position} from '@angular/compiler';
|
import {AbsoluteSourceSpan, ParseSourceSpan} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {getTokenAtPosition} from '../../util/src/typescript';
|
import {getTokenAtPosition} from '../../util/src/typescript';
|
||||||
|
@ -35,26 +35,6 @@ export interface TcbSourceResolver {
|
||||||
sourceLocationToSpan(location: SourceLocation): ParseSourceSpan|null;
|
sourceLocationToSpan(location: SourceLocation): ParseSourceSpan|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An `AbsoluteSpan` is the result of translating the `ParseSpan` of `AST` template expression nodes
|
|
||||||
* to their absolute positions, as the `ParseSpan` is always relative to the start of the
|
|
||||||
* expression, not the full template.
|
|
||||||
*/
|
|
||||||
export interface AbsoluteSpan {
|
|
||||||
__brand__: 'AbsoluteSpan';
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translates a `ParseSpan` into an `AbsoluteSpan` by incorporating the location information that
|
|
||||||
* the `ParseSourceSpan` represents.
|
|
||||||
*/
|
|
||||||
export function toAbsoluteSpan(span: ParseSpan, sourceSpan: ParseSourceSpan): AbsoluteSpan {
|
|
||||||
const offset = sourceSpan.start.offset;
|
|
||||||
return <AbsoluteSpan>{start: span.start + offset, end: span.end + offset};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function absoluteSourceSpanToSourceLocation(
|
export function absoluteSourceSpanToSourceLocation(
|
||||||
id: string, span: AbsoluteSourceSpan): SourceLocation {
|
id: string, span: AbsoluteSourceSpan): SourceLocation {
|
||||||
return {id, ...span};
|
return {id, ...span};
|
||||||
|
@ -78,20 +58,15 @@ export function wrapForDiagnostics(expr: ts.Expression): ts.Expression {
|
||||||
* Adds a synthetic comment to the expression that represents the parse span of the provided node.
|
* Adds a synthetic comment to the expression that represents the parse span of the provided node.
|
||||||
* This comment can later be retrieved as trivia of a node to recover original source locations.
|
* This comment can later be retrieved as trivia of a node to recover original source locations.
|
||||||
*/
|
*/
|
||||||
export function addParseSpanInfo(node: ts.Node, span: AbsoluteSpan | ParseSourceSpan): void {
|
export function addParseSpanInfo(node: ts.Node, span: AbsoluteSourceSpan | ParseSourceSpan): void {
|
||||||
let commentText: string;
|
let commentText: string;
|
||||||
if (isAbsoluteSpan(span)) {
|
if (span instanceof AbsoluteSourceSpan) {
|
||||||
commentText = `${span.start},${span.end}`;
|
commentText = `${span.start},${span.end}`;
|
||||||
} else {
|
} else {
|
||||||
commentText = `${span.start.offset},${span.end.offset}`;
|
commentText = `${span.start.offset},${span.end.offset}`;
|
||||||
}
|
}
|
||||||
ts.addSyntheticTrailingComment(
|
ts.addSyntheticTrailingComment(
|
||||||
node, ts.SyntaxKind.MultiLineCommentTrivia, commentText,
|
node, ts.SyntaxKind.MultiLineCommentTrivia, commentText, /* hasTrailingNewLine */ false);
|
||||||
/* hasTrailingNewLine */ false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAbsoluteSpan(span: AbsoluteSpan | ParseSourceSpan): span is AbsoluteSpan {
|
|
||||||
return typeof span.start === 'number';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -245,18 +220,18 @@ export function makeTemplateDiagnostic(
|
||||||
function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null {
|
function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null {
|
||||||
// Search for comments until the TCB's function declaration is encountered.
|
// Search for comments until the TCB's function declaration is encountered.
|
||||||
while (node !== undefined && !ts.isFunctionDeclaration(node)) {
|
while (node !== undefined && !ts.isFunctionDeclaration(node)) {
|
||||||
const parseSpan =
|
const sourceSpan =
|
||||||
ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
|
ts.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
|
||||||
if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
if (kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const commentText = sourceFile.text.substring(pos, end);
|
const commentText = sourceFile.text.substring(pos, end);
|
||||||
return parseParseSpanComment(commentText);
|
return parseSourceSpanComment(commentText);
|
||||||
}) || null;
|
}) || null;
|
||||||
if (parseSpan !== null) {
|
if (sourceSpan !== null) {
|
||||||
// Once the positional information has been extracted, search further up the TCB to extract
|
// Once the positional information has been extracted, search further up the TCB to extract
|
||||||
// the file information that is attached with the TCB's function declaration.
|
// the file information that is attached with the TCB's function declaration.
|
||||||
return toSourceLocation(parseSpan, node, sourceFile);
|
return toSourceLocation(sourceSpan, node, sourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.parent;
|
node = node.parent;
|
||||||
|
@ -266,7 +241,7 @@ function findSourceLocation(node: ts.Node, sourceFile: ts.SourceFile): SourceLoc
|
||||||
}
|
}
|
||||||
|
|
||||||
function toSourceLocation(
|
function toSourceLocation(
|
||||||
parseSpan: ParseSpan, node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null {
|
sourceSpan: AbsoluteSourceSpan, node: ts.Node, sourceFile: ts.SourceFile): SourceLocation|null {
|
||||||
// Walk up to the function declaration of the TCB, the file information is attached there.
|
// Walk up to the function declaration of the TCB, the file information is attached there.
|
||||||
let tcb = node;
|
let tcb = node;
|
||||||
while (!ts.isFunctionDeclaration(tcb)) {
|
while (!ts.isFunctionDeclaration(tcb)) {
|
||||||
|
@ -292,18 +267,18 @@ function toSourceLocation(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
start: parseSpan.start,
|
start: sourceSpan.start,
|
||||||
end: parseSpan.end,
|
end: sourceSpan.end,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseSpanComment = /^\/\*(\d+),(\d+)\*\/$/;
|
const parseSpanComment = /^\/\*(\d+),(\d+)\*\/$/;
|
||||||
|
|
||||||
function parseParseSpanComment(commentText: string): ParseSpan|null {
|
function parseSourceSpanComment(commentText: string): AbsoluteSourceSpan|null {
|
||||||
const match = commentText.match(parseSpanComment);
|
const match = commentText.match(parseSpanComment);
|
||||||
if (match === null) {
|
if (match === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ParseSpan(+match[1], +match[2]);
|
return new AbsoluteSourceSpan(+match[1], +match[2]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '@angular/compiler';
|
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {TypeCheckingConfig} from './api';
|
import {TypeCheckingConfig} from './api';
|
||||||
import {AbsoluteSpan, addParseSpanInfo, wrapForDiagnostics} from './diagnostics';
|
import {addParseSpanInfo, wrapForDiagnostics} from './diagnostics';
|
||||||
|
|
||||||
export const NULL_AS_ANY =
|
export const NULL_AS_ANY =
|
||||||
ts.createAsExpression(ts.createNull(), ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
|
ts.createAsExpression(ts.createNull(), ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
|
||||||
|
@ -41,17 +41,16 @@ const BINARY_OPS = new Map<string, ts.SyntaxKind>([
|
||||||
* AST.
|
* AST.
|
||||||
*/
|
*/
|
||||||
export function astToTypescript(
|
export function astToTypescript(
|
||||||
ast: AST, maybeResolve: (ast: AST) => (ts.Expression | null), config: TypeCheckingConfig,
|
ast: AST, maybeResolve: (ast: AST) => (ts.Expression | null),
|
||||||
translateSpan: (span: ParseSpan) => AbsoluteSpan): ts.Expression {
|
config: TypeCheckingConfig): ts.Expression {
|
||||||
const translator = new AstTranslator(maybeResolve, config, translateSpan);
|
const translator = new AstTranslator(maybeResolve, config);
|
||||||
return translator.translate(ast);
|
return translator.translate(ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AstTranslator implements AstVisitor {
|
class AstTranslator implements AstVisitor {
|
||||||
constructor(
|
constructor(
|
||||||
private maybeResolve: (ast: AST) => (ts.Expression | null),
|
private maybeResolve: (ast: AST) => (ts.Expression | null),
|
||||||
private config: TypeCheckingConfig,
|
private config: TypeCheckingConfig) {}
|
||||||
private translateSpan: (span: ParseSpan) => AbsoluteSpan) {}
|
|
||||||
|
|
||||||
translate(ast: AST): ts.Expression {
|
translate(ast: AST): ts.Expression {
|
||||||
// Skip over an `ASTWithSource` as its `visit` method calls directly into its ast's `visit`,
|
// Skip over an `ASTWithSource` as its `visit` method calls directly into its ast's `visit`,
|
||||||
|
@ -82,14 +81,14 @@ class AstTranslator implements AstVisitor {
|
||||||
throw new Error(`Unsupported Binary.operation: ${ast.operation}`);
|
throw new Error(`Unsupported Binary.operation: ${ast.operation}`);
|
||||||
}
|
}
|
||||||
const node = ts.createBinary(lhs, op as any, rhs);
|
const node = ts.createBinary(lhs, op as any, rhs);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitChain(ast: Chain): ts.Expression {
|
visitChain(ast: Chain): ts.Expression {
|
||||||
const elements = ast.expressions.map(expr => this.translate(expr));
|
const elements = ast.expressions.map(expr => this.translate(expr));
|
||||||
const node = wrapForDiagnostics(ts.createCommaList(elements));
|
const node = wrapForDiagnostics(ts.createCommaList(elements));
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +97,7 @@ class AstTranslator implements AstVisitor {
|
||||||
const trueExpr = this.translate(ast.trueExp);
|
const trueExpr = this.translate(ast.trueExp);
|
||||||
const falseExpr = this.translate(ast.falseExp);
|
const falseExpr = this.translate(ast.falseExp);
|
||||||
const node = ts.createParen(ts.createConditional(condExpr, trueExpr, falseExpr));
|
const node = ts.createParen(ts.createConditional(condExpr, trueExpr, falseExpr));
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +105,7 @@ class AstTranslator implements AstVisitor {
|
||||||
const receiver = wrapForDiagnostics(this.translate(ast.target !));
|
const receiver = wrapForDiagnostics(this.translate(ast.target !));
|
||||||
const args = ast.args.map(expr => this.translate(expr));
|
const args = ast.args.map(expr => this.translate(expr));
|
||||||
const node = ts.createCall(receiver, undefined, args);
|
const node = ts.createCall(receiver, undefined, args);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +126,7 @@ class AstTranslator implements AstVisitor {
|
||||||
const receiver = wrapForDiagnostics(this.translate(ast.obj));
|
const receiver = wrapForDiagnostics(this.translate(ast.obj));
|
||||||
const key = this.translate(ast.key);
|
const key = this.translate(ast.key);
|
||||||
const node = ts.createElementAccess(receiver, key);
|
const node = ts.createElementAccess(receiver, key);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,14 +137,14 @@ class AstTranslator implements AstVisitor {
|
||||||
// available on `ast`.
|
// available on `ast`.
|
||||||
const right = this.translate(ast.value);
|
const right = this.translate(ast.value);
|
||||||
const node = wrapForDiagnostics(ts.createBinary(left, ts.SyntaxKind.EqualsToken, right));
|
const node = wrapForDiagnostics(ts.createBinary(left, ts.SyntaxKind.EqualsToken, right));
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLiteralArray(ast: LiteralArray): ts.Expression {
|
visitLiteralArray(ast: LiteralArray): ts.Expression {
|
||||||
const elements = ast.expressions.map(expr => this.translate(expr));
|
const elements = ast.expressions.map(expr => this.translate(expr));
|
||||||
const node = ts.createArrayLiteral(elements);
|
const node = ts.createArrayLiteral(elements);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +154,7 @@ class AstTranslator implements AstVisitor {
|
||||||
return ts.createPropertyAssignment(ts.createStringLiteral(key), value);
|
return ts.createPropertyAssignment(ts.createStringLiteral(key), value);
|
||||||
});
|
});
|
||||||
const node = ts.createObjectLiteral(properties, true);
|
const node = ts.createObjectLiteral(properties, true);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +167,7 @@ class AstTranslator implements AstVisitor {
|
||||||
} else {
|
} else {
|
||||||
node = ts.createLiteral(ast.value);
|
node = ts.createLiteral(ast.value);
|
||||||
}
|
}
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,14 +176,14 @@ class AstTranslator implements AstVisitor {
|
||||||
const method = ts.createPropertyAccess(receiver, ast.name);
|
const method = ts.createPropertyAccess(receiver, ast.name);
|
||||||
const args = ast.args.map(expr => this.translate(expr));
|
const args = ast.args.map(expr => this.translate(expr));
|
||||||
const node = ts.createCall(method, undefined, args);
|
const node = ts.createCall(method, undefined, args);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitNonNullAssert(ast: NonNullAssert): ts.Expression {
|
visitNonNullAssert(ast: NonNullAssert): ts.Expression {
|
||||||
const expr = wrapForDiagnostics(this.translate(ast.expression));
|
const expr = wrapForDiagnostics(this.translate(ast.expression));
|
||||||
const node = ts.createNonNullExpression(expr);
|
const node = ts.createNonNullExpression(expr);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +192,7 @@ class AstTranslator implements AstVisitor {
|
||||||
visitPrefixNot(ast: PrefixNot): ts.Expression {
|
visitPrefixNot(ast: PrefixNot): ts.Expression {
|
||||||
const expression = wrapForDiagnostics(this.translate(ast.expression));
|
const expression = wrapForDiagnostics(this.translate(ast.expression));
|
||||||
const node = ts.createLogicalNot(expression);
|
const node = ts.createLogicalNot(expression);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +201,7 @@ class AstTranslator implements AstVisitor {
|
||||||
// TypeScript expression to read the property.
|
// TypeScript expression to read the property.
|
||||||
const receiver = wrapForDiagnostics(this.translate(ast.receiver));
|
const receiver = wrapForDiagnostics(this.translate(ast.receiver));
|
||||||
const node = ts.createPropertyAccess(receiver, ast.name);
|
const node = ts.createPropertyAccess(receiver, ast.name);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +212,7 @@ class AstTranslator implements AstVisitor {
|
||||||
// available on `ast`.
|
// available on `ast`.
|
||||||
const right = this.translate(ast.value);
|
const right = this.translate(ast.value);
|
||||||
const node = wrapForDiagnostics(ts.createBinary(left, ts.SyntaxKind.EqualsToken, right));
|
const node = wrapForDiagnostics(ts.createBinary(left, ts.SyntaxKind.EqualsToken, right));
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +227,7 @@ class AstTranslator implements AstVisitor {
|
||||||
const expr = ts.createCall(method, undefined, args);
|
const expr = ts.createCall(method, undefined, args);
|
||||||
const whenNull = this.config.strictSafeNavigationTypes ? UNDEFINED : NULL_AS_ANY;
|
const whenNull = this.config.strictSafeNavigationTypes ? UNDEFINED : NULL_AS_ANY;
|
||||||
const node = safeTernary(receiver, expr, whenNull);
|
const node = safeTernary(receiver, expr, whenNull);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +240,7 @@ class AstTranslator implements AstVisitor {
|
||||||
const expr = ts.createPropertyAccess(ts.createNonNullExpression(receiver), ast.name);
|
const expr = ts.createPropertyAccess(ts.createNonNullExpression(receiver), ast.name);
|
||||||
const whenNull = this.config.strictSafeNavigationTypes ? UNDEFINED : NULL_AS_ANY;
|
const whenNull = this.config.strictSafeNavigationTypes ? UNDEFINED : NULL_AS_ANY;
|
||||||
const node = safeTernary(receiver, expr, whenNull);
|
const node = safeTernary(receiver, expr, whenNull);
|
||||||
addParseSpanInfo(node, this.translateSpan(ast.span));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,15 +44,11 @@ export interface OutOfBandDiagnosticRecorder {
|
||||||
* @param templateId the template type-checking ID of the template which contains the unknown
|
* @param templateId the template type-checking ID of the template which contains the unknown
|
||||||
* pipe.
|
* pipe.
|
||||||
* @param ast the `BindingPipe` invocation of the pipe which could not be found.
|
* @param ast the `BindingPipe` invocation of the pipe which could not be found.
|
||||||
* @param sourceSpan the `AbsoluteSourceSpan` of the pipe invocation (ideally, this should be the
|
|
||||||
* source span of the pipe's name). This depends on the source span of the `BindingPipe` itself
|
|
||||||
* plus span of the larger expression context.
|
|
||||||
*/
|
*/
|
||||||
missingPipe(templateId: string, ast: BindingPipe, sourceSpan: AbsoluteSourceSpan): void;
|
missingPipe(templateId: string, ast: BindingPipe): void;
|
||||||
|
|
||||||
illegalAssignmentToTemplateVar(
|
illegalAssignmentToTemplateVar(
|
||||||
templateId: string, assignment: PropertyWrite, assignmentSpan: AbsoluteSourceSpan,
|
templateId: string, assignment: PropertyWrite, target: TmplAstVariable): void;
|
||||||
target: TmplAstVariable): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder {
|
export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecorder {
|
||||||
|
@ -72,11 +68,11 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
|
||||||
ngErrorCode(ErrorCode.MISSING_REFERENCE_TARGET), errorMsg));
|
ngErrorCode(ErrorCode.MISSING_REFERENCE_TARGET), errorMsg));
|
||||||
}
|
}
|
||||||
|
|
||||||
missingPipe(templateId: string, ast: BindingPipe, absSpan: AbsoluteSourceSpan): void {
|
missingPipe(templateId: string, ast: BindingPipe): void {
|
||||||
const mapping = this.resolver.getSourceMapping(templateId);
|
const mapping = this.resolver.getSourceMapping(templateId);
|
||||||
const errorMsg = `No pipe found with name '${ast.name}'.`;
|
const errorMsg = `No pipe found with name '${ast.name}'.`;
|
||||||
|
|
||||||
const location = absoluteSourceSpanToSourceLocation(templateId, absSpan);
|
const location = absoluteSourceSpanToSourceLocation(templateId, ast.nameSpan);
|
||||||
const sourceSpan = this.resolver.sourceLocationToSpan(location);
|
const sourceSpan = this.resolver.sourceLocationToSpan(location);
|
||||||
if (sourceSpan === null) {
|
if (sourceSpan === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -88,13 +84,12 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
|
||||||
}
|
}
|
||||||
|
|
||||||
illegalAssignmentToTemplateVar(
|
illegalAssignmentToTemplateVar(
|
||||||
templateId: string, assignment: PropertyWrite, assignmentSpan: AbsoluteSourceSpan,
|
templateId: string, assignment: PropertyWrite, target: TmplAstVariable): void {
|
||||||
target: TmplAstVariable): void {
|
|
||||||
const mapping = this.resolver.getSourceMapping(templateId);
|
const mapping = this.resolver.getSourceMapping(templateId);
|
||||||
const errorMsg =
|
const errorMsg =
|
||||||
`Cannot use variable '${assignment.name}' as the left-hand side of an assignment expression. Template variables are read-only.`;
|
`Cannot use variable '${assignment.name}' as the left-hand side of an assignment expression. Template variables are read-only.`;
|
||||||
|
|
||||||
const location = absoluteSourceSpanToSourceLocation(templateId, assignmentSpan);
|
const location = absoluteSourceSpanToSourceLocation(templateId, assignment.sourceSpan);
|
||||||
const sourceSpan = this.resolver.sourceLocationToSpan(location);
|
const sourceSpan = this.resolver.sourceLocationToSpan(location);
|
||||||
if (sourceSpan === null) {
|
if (sourceSpan === null) {
|
||||||
throw new Error(`Assertion failure: no SourceLocation found for property binding.`);
|
throw new Error(`Assertion failure: no SourceLocation found for property binding.`);
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AST, BoundTarget, ImplicitReceiver, ParseSourceSpan, PropertyWrite, RecursiveAstVisitor, TmplAstVariable} from '@angular/compiler';
|
import {AST, BoundTarget, ImplicitReceiver, PropertyWrite, RecursiveAstVisitor, TmplAstVariable} from '@angular/compiler';
|
||||||
|
|
||||||
import {toAbsoluteSpan} from './diagnostics';
|
|
||||||
import {OutOfBandDiagnosticRecorder} from './oob';
|
import {OutOfBandDiagnosticRecorder} from './oob';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +16,7 @@ import {OutOfBandDiagnosticRecorder} from './oob';
|
||||||
export class ExpressionSemanticVisitor extends RecursiveAstVisitor {
|
export class ExpressionSemanticVisitor extends RecursiveAstVisitor {
|
||||||
constructor(
|
constructor(
|
||||||
private templateId: string, private boundTarget: BoundTarget<any>,
|
private templateId: string, private boundTarget: BoundTarget<any>,
|
||||||
private oob: OutOfBandDiagnosticRecorder, private sourceSpan: ParseSourceSpan) {
|
private oob: OutOfBandDiagnosticRecorder) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,14 +30,12 @@ export class ExpressionSemanticVisitor extends RecursiveAstVisitor {
|
||||||
const target = this.boundTarget.getExpressionTarget(ast);
|
const target = this.boundTarget.getExpressionTarget(ast);
|
||||||
if (target instanceof TmplAstVariable) {
|
if (target instanceof TmplAstVariable) {
|
||||||
// Template variables are read-only.
|
// Template variables are read-only.
|
||||||
const astSpan = toAbsoluteSpan(ast.span, this.sourceSpan);
|
this.oob.illegalAssignmentToTemplateVar(this.templateId, ast, target);
|
||||||
this.oob.illegalAssignmentToTemplateVar(this.templateId, ast, astSpan, target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static visit(
|
static visit(
|
||||||
ast: AST, sourceSpan: ParseSourceSpan, id: string, boundTarget: BoundTarget<any>,
|
ast: AST, id: string, boundTarget: BoundTarget<any>, oob: OutOfBandDiagnosticRecorder): void {
|
||||||
oob: OutOfBandDiagnosticRecorder): void {
|
ast.visit(new ExpressionSemanticVisitor(id, boundTarget, oob));
|
||||||
ast.visit(new ExpressionSemanticVisitor(id, boundTarget, oob, sourceSpan));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AST, BindingPipe, BindingType, BoundTarget, DYNAMIC_TYPE, ImplicitReceiver, MethodCall, ParseSourceSpan, ParseSpan, ParsedEventType, PropertyRead, PropertyWrite, SchemaMetadata, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
|
import {AST, BindingPipe, BindingType, BoundTarget, DYNAMIC_TYPE, ImplicitReceiver, MethodCall, ParseSourceSpan, ParsedEventType, PropertyRead, PropertyWrite, SchemaMetadata, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
|
||||||
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta} from './api';
|
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta} from './api';
|
||||||
import {addParseSpanInfo, addSourceId, toAbsoluteSpan, wrapForDiagnostics} from './diagnostics';
|
import {addParseSpanInfo, addSourceId, wrapForDiagnostics} from './diagnostics';
|
||||||
import {DomSchemaChecker} from './dom';
|
import {DomSchemaChecker} from './dom';
|
||||||
import {Environment} from './environment';
|
import {Environment} from './environment';
|
||||||
import {NULL_AS_ANY, astToTypescript} from './expression';
|
import {NULL_AS_ANY, astToTypescript} from './expression';
|
||||||
|
@ -215,9 +215,7 @@ class TcbTemplateBodyOp extends TcbOp {
|
||||||
i instanceof TmplAstBoundAttribute && i.name === guard.inputName);
|
i instanceof TmplAstBoundAttribute && i.name === guard.inputName);
|
||||||
if (boundInput !== undefined) {
|
if (boundInput !== undefined) {
|
||||||
// If there is such a binding, generate an expression for it.
|
// If there is such a binding, generate an expression for it.
|
||||||
const expr = tcbExpression(
|
const expr = tcbExpression(boundInput.value, this.tcb, this.scope);
|
||||||
boundInput.value, this.tcb, this.scope,
|
|
||||||
boundInput.valueSpan || boundInput.sourceSpan);
|
|
||||||
|
|
||||||
if (guard.type === 'binding') {
|
if (guard.type === 'binding') {
|
||||||
// Use the binding expression itself as guard.
|
// Use the binding expression itself as guard.
|
||||||
|
@ -229,8 +227,7 @@ class TcbTemplateBodyOp extends TcbOp {
|
||||||
dirInstId,
|
dirInstId,
|
||||||
expr,
|
expr,
|
||||||
]);
|
]);
|
||||||
addParseSpanInfo(
|
addParseSpanInfo(guardInvoke, boundInput.value.sourceSpan);
|
||||||
guardInvoke, toAbsoluteSpan(boundInput.value.span, boundInput.sourceSpan));
|
|
||||||
directiveGuards.push(guardInvoke);
|
directiveGuards.push(guardInvoke);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,7 +278,7 @@ class TcbTextInterpolationOp extends TcbOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(): null {
|
execute(): null {
|
||||||
const expr = tcbExpression(this.binding.value, this.tcb, this.scope, this.binding.sourceSpan);
|
const expr = tcbExpression(this.binding.value, this.tcb, this.scope);
|
||||||
this.scope.addStatement(ts.createExpressionStatement(expr));
|
this.scope.addStatement(ts.createExpressionStatement(expr));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -400,8 +397,7 @@ class TcbUnclaimedInputsOp extends TcbOp {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let expr = tcbExpression(
|
let expr = tcbExpression(binding.value, this.tcb, this.scope);
|
||||||
binding.value, this.tcb, this.scope, binding.valueSpan || binding.sourceSpan);
|
|
||||||
if (!this.tcb.env.config.checkTypeOfInputBindings) {
|
if (!this.tcb.env.config.checkTypeOfInputBindings) {
|
||||||
// If checking the type of bindings is disabled, cast the resulting expression to 'any'
|
// If checking the type of bindings is disabled, cast the resulting expression to 'any'
|
||||||
// before the assignment.
|
// before the assignment.
|
||||||
|
@ -494,8 +490,7 @@ class TcbDirectiveOutputsOp extends TcbOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpressionSemanticVisitor.visit(
|
ExpressionSemanticVisitor.visit(
|
||||||
output.handler, output.handlerSpan, this.tcb.id, this.tcb.boundTarget,
|
output.handler, this.tcb.id, this.tcb.boundTarget, this.tcb.oobRecorder);
|
||||||
this.tcb.oobRecorder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -556,8 +551,7 @@ class TcbUnclaimedOutputsOp extends TcbOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpressionSemanticVisitor.visit(
|
ExpressionSemanticVisitor.visit(
|
||||||
output.handler, output.handlerSpan, this.tcb.id, this.tcb.boundTarget,
|
output.handler, this.tcb.id, this.tcb.boundTarget, this.tcb.oobRecorder);
|
||||||
this.tcb.oobRecorder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -949,23 +943,19 @@ function tcbCtxParam(
|
||||||
* Process an `AST` expression and convert it into a `ts.Expression`, generating references to the
|
* Process an `AST` expression and convert it into a `ts.Expression`, generating references to the
|
||||||
* correct identifiers in the current scope.
|
* correct identifiers in the current scope.
|
||||||
*/
|
*/
|
||||||
function tcbExpression(
|
function tcbExpression(ast: AST, tcb: Context, scope: Scope): ts.Expression {
|
||||||
ast: AST, tcb: Context, scope: Scope, sourceSpan: ParseSourceSpan): ts.Expression {
|
const translator = new TcbExpressionTranslator(tcb, scope);
|
||||||
const translator = new TcbExpressionTranslator(tcb, scope, sourceSpan);
|
|
||||||
return translator.translate(ast);
|
return translator.translate(ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TcbExpressionTranslator {
|
class TcbExpressionTranslator {
|
||||||
constructor(
|
constructor(protected tcb: Context, protected scope: Scope) {}
|
||||||
protected tcb: Context, protected scope: Scope, protected sourceSpan: ParseSourceSpan) {}
|
|
||||||
|
|
||||||
translate(ast: AST): ts.Expression {
|
translate(ast: AST): ts.Expression {
|
||||||
// `astToTypescript` actually does the conversion. A special resolver `tcbResolve` is passed
|
// `astToTypescript` actually does the conversion. A special resolver `tcbResolve` is passed
|
||||||
// which interprets specific expression nodes that interact with the `ImplicitReceiver`. These
|
// which interprets specific expression nodes that interact with the `ImplicitReceiver`. These
|
||||||
// nodes actually refer to identifiers within the current scope.
|
// nodes actually refer to identifiers within the current scope.
|
||||||
return astToTypescript(
|
return astToTypescript(ast, ast => this.resolve(ast), this.tcb.env.config);
|
||||||
ast, ast => this.resolve(ast), this.tcb.env.config,
|
|
||||||
(span: ParseSpan) => toAbsoluteSpan(span, this.sourceSpan));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -989,7 +979,7 @@ class TcbExpressionTranslator {
|
||||||
|
|
||||||
const expr = this.translate(ast.value);
|
const expr = this.translate(ast.value);
|
||||||
const result = ts.createParen(ts.createBinary(target, ts.SyntaxKind.EqualsToken, expr));
|
const result = ts.createParen(ts.createBinary(target, ts.SyntaxKind.EqualsToken, expr));
|
||||||
addParseSpanInfo(result, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(result, ast.sourceSpan);
|
||||||
return result;
|
return result;
|
||||||
} else if (ast instanceof ImplicitReceiver) {
|
} else if (ast instanceof ImplicitReceiver) {
|
||||||
// AST instances representing variables and references look very similar to property reads
|
// AST instances representing variables and references look very similar to property reads
|
||||||
|
@ -1012,8 +1002,7 @@ class TcbExpressionTranslator {
|
||||||
pipe = this.tcb.getPipeByName(ast.name);
|
pipe = this.tcb.getPipeByName(ast.name);
|
||||||
if (pipe === null) {
|
if (pipe === null) {
|
||||||
// No pipe by that name exists in scope. Record this as an error.
|
// No pipe by that name exists in scope. Record this as an error.
|
||||||
const nameAbsoluteSpan = toAbsoluteSpan(ast.nameSpan, this.sourceSpan);
|
this.tcb.oobRecorder.missingPipe(this.tcb.id, ast);
|
||||||
this.tcb.oobRecorder.missingPipe(this.tcb.id, ast, nameAbsoluteSpan);
|
|
||||||
|
|
||||||
// Return an 'any' value to at least allow the rest of the expression to be checked.
|
// Return an 'any' value to at least allow the rest of the expression to be checked.
|
||||||
pipe = NULL_AS_ANY;
|
pipe = NULL_AS_ANY;
|
||||||
|
@ -1024,7 +1013,7 @@ class TcbExpressionTranslator {
|
||||||
}
|
}
|
||||||
const args = ast.args.map(arg => this.translate(arg));
|
const args = ast.args.map(arg => this.translate(arg));
|
||||||
const result = tsCallMethod(pipe, 'transform', [expr, ...args]);
|
const result = tsCallMethod(pipe, 'transform', [expr, ...args]);
|
||||||
addParseSpanInfo(result, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(result, ast.sourceSpan);
|
||||||
return result;
|
return result;
|
||||||
} else if (ast instanceof MethodCall && ast.receiver instanceof ImplicitReceiver) {
|
} else if (ast instanceof MethodCall && ast.receiver instanceof ImplicitReceiver) {
|
||||||
// Resolve the special `$any(expr)` syntax to insert a cast of the argument to type `any`.
|
// Resolve the special `$any(expr)` syntax to insert a cast of the argument to type `any`.
|
||||||
|
@ -1033,7 +1022,7 @@ class TcbExpressionTranslator {
|
||||||
const exprAsAny =
|
const exprAsAny =
|
||||||
ts.createAsExpression(expr, ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
|
ts.createAsExpression(expr, ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
|
||||||
const result = ts.createParen(exprAsAny);
|
const result = ts.createParen(exprAsAny);
|
||||||
addParseSpanInfo(result, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(result, ast.sourceSpan);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1049,7 +1038,7 @@ class TcbExpressionTranslator {
|
||||||
const method = ts.createPropertyAccess(wrapForDiagnostics(receiver), ast.name);
|
const method = ts.createPropertyAccess(wrapForDiagnostics(receiver), ast.name);
|
||||||
const args = ast.args.map(arg => this.translate(arg));
|
const args = ast.args.map(arg => this.translate(arg));
|
||||||
const node = ts.createCall(method, undefined, args);
|
const node = ts.createCall(method, undefined, args);
|
||||||
addParseSpanInfo(node, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(node, ast.sourceSpan);
|
||||||
return node;
|
return node;
|
||||||
} else {
|
} else {
|
||||||
// This AST isn't special after all.
|
// This AST isn't special after all.
|
||||||
|
@ -1071,7 +1060,7 @@ class TcbExpressionTranslator {
|
||||||
// This expression has a binding to some variable or reference in the template. Resolve it.
|
// This expression has a binding to some variable or reference in the template. Resolve it.
|
||||||
if (binding instanceof TmplAstVariable) {
|
if (binding instanceof TmplAstVariable) {
|
||||||
const expr = ts.getMutableClone(this.scope.resolve(binding));
|
const expr = ts.getMutableClone(this.scope.resolve(binding));
|
||||||
addParseSpanInfo(expr, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(expr, ast.sourceSpan);
|
||||||
return expr;
|
return expr;
|
||||||
} else if (binding instanceof TmplAstReference) {
|
} else if (binding instanceof TmplAstReference) {
|
||||||
const target = this.tcb.boundTarget.getReferenceTarget(binding);
|
const target = this.tcb.boundTarget.getReferenceTarget(binding);
|
||||||
|
@ -1092,7 +1081,7 @@ class TcbExpressionTranslator {
|
||||||
}
|
}
|
||||||
|
|
||||||
const expr = ts.getMutableClone(this.scope.resolve(target));
|
const expr = ts.getMutableClone(this.scope.resolve(target));
|
||||||
addParseSpanInfo(expr, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(expr, ast.sourceSpan);
|
||||||
return expr;
|
return expr;
|
||||||
} else if (target instanceof TmplAstTemplate) {
|
} else if (target instanceof TmplAstTemplate) {
|
||||||
if (!this.tcb.env.config.checkTypeOfNonDomReferences) {
|
if (!this.tcb.env.config.checkTypeOfNonDomReferences) {
|
||||||
|
@ -1109,7 +1098,7 @@ class TcbExpressionTranslator {
|
||||||
value,
|
value,
|
||||||
this.tcb.env.referenceExternalType('@angular/core', 'TemplateRef', [DYNAMIC_TYPE]));
|
this.tcb.env.referenceExternalType('@angular/core', 'TemplateRef', [DYNAMIC_TYPE]));
|
||||||
value = ts.createParen(value);
|
value = ts.createParen(value);
|
||||||
addParseSpanInfo(value, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(value, ast.sourceSpan);
|
||||||
return value;
|
return value;
|
||||||
} else {
|
} else {
|
||||||
if (!this.tcb.env.config.checkTypeOfNonDomReferences) {
|
if (!this.tcb.env.config.checkTypeOfNonDomReferences) {
|
||||||
|
@ -1118,7 +1107,7 @@ class TcbExpressionTranslator {
|
||||||
}
|
}
|
||||||
|
|
||||||
const expr = ts.getMutableClone(this.scope.resolve(target.node, target.directive));
|
const expr = ts.getMutableClone(this.scope.resolve(target.node, target.directive));
|
||||||
addParseSpanInfo(expr, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(expr, ast.sourceSpan);
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1236,7 +1225,7 @@ function tcbGetDirectiveInputs(
|
||||||
let expr: ts.Expression;
|
let expr: ts.Expression;
|
||||||
if (attr instanceof TmplAstBoundAttribute) {
|
if (attr instanceof TmplAstBoundAttribute) {
|
||||||
// Produce an expression representing the value of the binding.
|
// Produce an expression representing the value of the binding.
|
||||||
expr = tcbExpression(attr.value, tcb, scope, attr.valueSpan || attr.sourceSpan);
|
expr = tcbExpression(attr.value, tcb, scope);
|
||||||
} else {
|
} else {
|
||||||
// For regular attributes with a static string value, use the represented string literal.
|
// For regular attributes with a static string value, use the represented string literal.
|
||||||
expr = ts.createStringLiteral(attr.value);
|
expr = ts.createStringLiteral(attr.value);
|
||||||
|
@ -1275,7 +1264,7 @@ const enum EventParamType {
|
||||||
function tcbCreateEventHandler(
|
function tcbCreateEventHandler(
|
||||||
event: TmplAstBoundEvent, tcb: Context, scope: Scope,
|
event: TmplAstBoundEvent, tcb: Context, scope: Scope,
|
||||||
eventType: EventParamType | ts.TypeNode): ts.ArrowFunction {
|
eventType: EventParamType | ts.TypeNode): ts.ArrowFunction {
|
||||||
const handler = tcbEventHandlerExpression(event.handler, tcb, scope, event.handlerSpan);
|
const handler = tcbEventHandlerExpression(event.handler, tcb, scope);
|
||||||
|
|
||||||
let eventParamType: ts.TypeNode|undefined;
|
let eventParamType: ts.TypeNode|undefined;
|
||||||
if (eventType === EventParamType.Infer) {
|
if (eventType === EventParamType.Infer) {
|
||||||
|
@ -1307,9 +1296,8 @@ function tcbCreateEventHandler(
|
||||||
* `ts.Expression`, with special handling of the `$event` variable that can be used within event
|
* `ts.Expression`, with special handling of the `$event` variable that can be used within event
|
||||||
* bindings.
|
* bindings.
|
||||||
*/
|
*/
|
||||||
function tcbEventHandlerExpression(
|
function tcbEventHandlerExpression(ast: AST, tcb: Context, scope: Scope): ts.Expression {
|
||||||
ast: AST, tcb: Context, scope: Scope, sourceSpan: ParseSourceSpan): ts.Expression {
|
const translator = new TcbEventHandlerTranslator(tcb, scope);
|
||||||
const translator = new TcbEventHandlerTranslator(tcb, scope, sourceSpan);
|
|
||||||
return translator.translate(ast);
|
return translator.translate(ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1322,7 +1310,7 @@ class TcbEventHandlerTranslator extends TcbExpressionTranslator {
|
||||||
if (ast instanceof PropertyRead && ast.receiver instanceof ImplicitReceiver &&
|
if (ast instanceof PropertyRead && ast.receiver instanceof ImplicitReceiver &&
|
||||||
ast.name === EVENT_PARAMETER) {
|
ast.name === EVENT_PARAMETER) {
|
||||||
const event = ts.createIdentifier(EVENT_PARAMETER);
|
const event = ts.createIdentifier(EVENT_PARAMETER);
|
||||||
addParseSpanInfo(event, toAbsoluteSpan(ast.span, this.sourceSpan));
|
addParseSpanInfo(event, ast.sourceSpan);
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ export class AST {
|
||||||
/**
|
/**
|
||||||
* Absolute location of the expression AST in a source code file.
|
* Absolute location of the expression AST in a source code file.
|
||||||
*/
|
*/
|
||||||
public sourceSpan: Readonly<AbsoluteSourceSpan>) {}
|
public sourceSpan: AbsoluteSourceSpan) {}
|
||||||
visit(visitor: AstVisitor, context: any = null): any { return null; }
|
visit(visitor: AstVisitor, context: any = null): any { return null; }
|
||||||
toString(): string { return 'AST'; }
|
toString(): string { return 'AST'; }
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ export class KeyedWrite extends AST {
|
||||||
export class BindingPipe extends AST {
|
export class BindingPipe extends AST {
|
||||||
constructor(
|
constructor(
|
||||||
span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public exp: AST, public name: string,
|
span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public exp: AST, public name: string,
|
||||||
public args: any[], public nameSpan: ParseSpan) {
|
public args: any[], public nameSpan: AbsoluteSourceSpan) {
|
||||||
super(span, sourceSpan);
|
super(span, sourceSpan);
|
||||||
}
|
}
|
||||||
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitPipe(this, context); }
|
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitPipe(this, context); }
|
||||||
|
|
|
@ -359,7 +359,7 @@ export class _ParseAST {
|
||||||
do {
|
do {
|
||||||
const nameStart = this.inputIndex;
|
const nameStart = this.inputIndex;
|
||||||
const name = this.expectIdentifierOrKeyword();
|
const name = this.expectIdentifierOrKeyword();
|
||||||
const nameSpan = this.span(nameStart);
|
const nameSpan = this.sourceSpan(nameStart);
|
||||||
const args: AST[] = [];
|
const args: AST[] = [];
|
||||||
while (this.optionalCharacter(chars.$COLON)) {
|
while (this.optionalCharacter(chars.$COLON)) {
|
||||||
args.push(this.parseExpression());
|
args.push(this.parseExpression());
|
||||||
|
|
Loading…
Reference in New Issue