feat(compiler): Expression span information and error correction (#9772)
Added error correction so the parser always returns an AST Added span information to the expression parser Refactored the test to account for the difference in error reporting Added tests for error corretion Modified tests to validate the span information
This commit is contained in:
@ -15,52 +15,52 @@ function lex(text: string): any[] {
|
||||
return new Lexer().tokenize(text);
|
||||
}
|
||||
|
||||
function expectToken(token: any /** TODO #9100 */, index: any /** TODO #9100 */) {
|
||||
function expectToken(token: any, index: any) {
|
||||
expect(token instanceof Token).toBe(true);
|
||||
expect(token.index).toEqual(index);
|
||||
}
|
||||
|
||||
function expectCharacterToken(
|
||||
token: any /** TODO #9100 */, index: any /** TODO #9100 */, character: any /** TODO #9100 */) {
|
||||
function expectCharacterToken(token: any, index: any, character: any) {
|
||||
expect(character.length).toBe(1);
|
||||
expectToken(token, index);
|
||||
expect(token.isCharacter(StringWrapper.charCodeAt(character, 0))).toBe(true);
|
||||
}
|
||||
|
||||
function expectOperatorToken(
|
||||
token: any /** TODO #9100 */, index: any /** TODO #9100 */, operator: any /** TODO #9100 */) {
|
||||
function expectOperatorToken(token: any, index: any, operator: any) {
|
||||
expectToken(token, index);
|
||||
expect(token.isOperator(operator)).toBe(true);
|
||||
}
|
||||
|
||||
function expectNumberToken(
|
||||
token: any /** TODO #9100 */, index: any /** TODO #9100 */, n: any /** TODO #9100 */) {
|
||||
function expectNumberToken(token: any, index: any, n: any) {
|
||||
expectToken(token, index);
|
||||
expect(token.isNumber()).toBe(true);
|
||||
expect(token.toNumber()).toEqual(n);
|
||||
}
|
||||
|
||||
function expectStringToken(
|
||||
token: any /** TODO #9100 */, index: any /** TODO #9100 */, str: any /** TODO #9100 */) {
|
||||
function expectStringToken(token: any, index: any, str: any) {
|
||||
expectToken(token, index);
|
||||
expect(token.isString()).toBe(true);
|
||||
expect(token.toString()).toEqual(str);
|
||||
}
|
||||
|
||||
function expectIdentifierToken(
|
||||
token: any /** TODO #9100 */, index: any /** TODO #9100 */, identifier: any /** TODO #9100 */) {
|
||||
function expectIdentifierToken(token: any, index: any, identifier: any) {
|
||||
expectToken(token, index);
|
||||
expect(token.isIdentifier()).toBe(true);
|
||||
expect(token.toString()).toEqual(identifier);
|
||||
}
|
||||
|
||||
function expectKeywordToken(
|
||||
token: any /** TODO #9100 */, index: any /** TODO #9100 */, keyword: any /** TODO #9100 */) {
|
||||
function expectKeywordToken(token: any, index: any, keyword: any) {
|
||||
expectToken(token, index);
|
||||
expect(token.isKeyword()).toBe(true);
|
||||
expect(token.toString()).toEqual(keyword);
|
||||
}
|
||||
|
||||
function expectErrorToken(token: Token, index: any, message: string) {
|
||||
expectToken(token, index);
|
||||
expect(token.isError()).toBe(true);
|
||||
expect(token.toString()).toEqual(message);
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('lexer', function() {
|
||||
describe('token', function() {
|
||||
@ -228,14 +228,13 @@ export function main() {
|
||||
expectNumberToken(tokens[0], 0, 0.5E+10);
|
||||
});
|
||||
|
||||
it('should throws exception for invalid exponent', function() {
|
||||
expect(() => {
|
||||
lex('0.5E-');
|
||||
}).toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-]');
|
||||
it('should return exception for invalid exponent', function() {
|
||||
expectErrorToken(
|
||||
lex('0.5E-')[0], 4, 'Lexer Error: Invalid exponent at column 4 in expression [0.5E-]');
|
||||
|
||||
expect(() => {
|
||||
lex('0.5E-A');
|
||||
}).toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-A]');
|
||||
expectErrorToken(
|
||||
lex('0.5E-A')[0], 4,
|
||||
'Lexer Error: Invalid exponent at column 4 in expression [0.5E-A]');
|
||||
});
|
||||
|
||||
it('should tokenize number starting with a dot', function() {
|
||||
@ -244,9 +243,9 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should throw error on invalid unicode', function() {
|
||||
expect(() => { lex('\'\\u1\'\'bla\''); })
|
||||
.toThrowError(
|
||||
'Lexer Error: Invalid unicode escape [\\u1\'\'b] at column 2 in expression [\'\\u1\'\'bla\']');
|
||||
expectErrorToken(
|
||||
lex('\'\\u1\'\'bla\'')[0], 2,
|
||||
'Lexer Error: Invalid unicode escape [\\u1\'\'b] at column 2 in expression [\'\\u1\'\'bla\']');
|
||||
});
|
||||
|
||||
it('should tokenize hash as operator', function() {
|
||||
|
@ -6,64 +6,79 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AST, BindingPipe} from '@angular/compiler/src/expression_parser/ast';
|
||||
import {AST, ASTWithSource, BindingPipe, Interpolation, LiteralPrimitive, ParserError, TemplateBinding} from '@angular/compiler/src/expression_parser/ast';
|
||||
import {Lexer} from '@angular/compiler/src/expression_parser/lexer';
|
||||
import {Parser} from '@angular/compiler/src/expression_parser/parser';
|
||||
import {Parser, TemplateBindingParseResult} from '@angular/compiler/src/expression_parser/parser';
|
||||
import {beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing';
|
||||
|
||||
import {isBlank, isPresent} from '../../src/facade/lang';
|
||||
|
||||
import {Unparser} from './unparser';
|
||||
import {unparse} from './unparser';
|
||||
import {validate} from './validator';
|
||||
|
||||
export function main() {
|
||||
function createParser() { return new Parser(new Lexer()); }
|
||||
|
||||
function parseAction(text: string, location: any = null): any {
|
||||
function parseAction(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseAction(text, location);
|
||||
}
|
||||
|
||||
function parseBinding(text: string, location: any = null): any {
|
||||
function parseBinding(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseBinding(text, location);
|
||||
}
|
||||
|
||||
function parseTemplateBindings(text: string, location: any = null): any {
|
||||
return createParser().parseTemplateBindings(text, location).templateBindings;
|
||||
function parseTemplateBindingsResult(
|
||||
text: string, location: any = null): TemplateBindingParseResult {
|
||||
return createParser().parseTemplateBindings(text, location);
|
||||
}
|
||||
function parseTemplateBindings(text: string, location: any = null): TemplateBinding[] {
|
||||
return parseTemplateBindingsResult(text, location).templateBindings;
|
||||
}
|
||||
|
||||
function parseInterpolation(text: string, location: any = null): any {
|
||||
function parseInterpolation(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseInterpolation(text, location);
|
||||
}
|
||||
|
||||
function parseSimpleBinding(text: string, location: any = null): any {
|
||||
function parseSimpleBinding(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseSimpleBinding(text, location);
|
||||
}
|
||||
|
||||
function unparse(ast: AST): string { return new Unparser().unparse(ast); }
|
||||
|
||||
function checkInterpolation(exp: string, expected?: string) {
|
||||
var ast = parseInterpolation(exp);
|
||||
if (isBlank(expected)) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function checkBinding(exp: string, expected?: string) {
|
||||
var ast = parseBinding(exp);
|
||||
if (isBlank(expected)) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function checkAction(exp: string, expected?: string) {
|
||||
var ast = parseAction(exp);
|
||||
if (isBlank(expected)) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
validate(ast);
|
||||
}
|
||||
|
||||
function expectActionError(text: any /** TODO #9100 */) {
|
||||
return expect(() => parseAction(text));
|
||||
function expectError(ast: {errors: ParserError[]}, message: string) {
|
||||
for (var error of ast.errors) {
|
||||
if (error.message.indexOf(message) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw Error(`Expected an error containing "${message}" to be reported`);
|
||||
}
|
||||
|
||||
function expectBindingError(text: any /** TODO #9100 */) {
|
||||
return expect(() => parseBinding(text));
|
||||
function expectActionError(text: string, message: string) {
|
||||
expectError(validate(parseAction(text)), message);
|
||||
}
|
||||
|
||||
function expectBindingError(text: string, message: string) {
|
||||
expectError(validate(parseBinding(text)), message);
|
||||
}
|
||||
|
||||
describe('parser', () => {
|
||||
@ -140,8 +155,8 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should only allow identifier, string, or keyword as map key', () => {
|
||||
expectActionError('{(:0}').toThrowError(/expected identifier, keyword, or string/);
|
||||
expectActionError('{1234:0}').toThrowError(/expected identifier, keyword, or string/);
|
||||
expectActionError('{(:0}', 'expected identifier, keyword, or string');
|
||||
expectActionError('{1234:0}', 'expected identifier, keyword, or string');
|
||||
});
|
||||
});
|
||||
|
||||
@ -152,9 +167,9 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should only allow identifier or keyword as member names', () => {
|
||||
expectActionError('x.(').toThrowError(/identifier or keyword/);
|
||||
expectActionError('x. 1234').toThrowError(/identifier or keyword/);
|
||||
expectActionError('x."foo"').toThrowError(/identifier or keyword/);
|
||||
expectActionError('x.(', 'identifier or keyword');
|
||||
expectActionError('x. 1234', 'identifier or keyword');
|
||||
expectActionError('x."foo"', 'identifier or keyword');
|
||||
});
|
||||
|
||||
it('should parse safe field access', () => {
|
||||
@ -182,9 +197,8 @@ export function main() {
|
||||
checkAction('false ? 10 : 20');
|
||||
});
|
||||
|
||||
it('should throw on incorrect ternary operator syntax', () => {
|
||||
expectActionError('true?1').toThrowError(
|
||||
/Parser Error: Conditional expression true\?1 requires all 3 expressions/);
|
||||
it('should report incorrect ternary operator syntax', () => {
|
||||
expectActionError('true?1', 'Conditional expression true?1 requires all 3 expressions');
|
||||
});
|
||||
});
|
||||
|
||||
@ -195,39 +209,35 @@ export function main() {
|
||||
checkAction('a = 123; b = 234;');
|
||||
});
|
||||
|
||||
it('should throw on safe field assignments', () => {
|
||||
expectActionError('a?.a = 123').toThrowError(/cannot be used in the assignment/);
|
||||
});
|
||||
it('should report on safe field assignments',
|
||||
() => { expectActionError('a?.a = 123', 'cannot be used in the assignment'); });
|
||||
|
||||
it('should support array updates', () => { checkAction('a[0] = 200'); });
|
||||
});
|
||||
|
||||
it('should error when using pipes',
|
||||
() => { expectActionError('x|blah').toThrowError(/Cannot have a pipe/); });
|
||||
() => { expectActionError('x|blah', 'Cannot have a pipe'); });
|
||||
|
||||
it('should store the source in the result',
|
||||
() => { expect(parseAction('someExpr').source).toBe('someExpr'); });
|
||||
() => { expect(parseAction('someExpr', 'someExpr')); });
|
||||
|
||||
it('should store the passed-in location',
|
||||
() => { expect(parseAction('someExpr', 'location').location).toBe('location'); });
|
||||
|
||||
it('should throw when encountering interpolation', () => {
|
||||
expectActionError('{{a()}}').toThrowError(
|
||||
/Got interpolation \(\{\{\}\}\) where expression was expected/);
|
||||
it('should report when encountering interpolation', () => {
|
||||
expectActionError('{{a()}}', 'Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('general error handling', () => {
|
||||
it('should throw on an unexpected token',
|
||||
() => { expectActionError('[1,2] trac').toThrowError(/Unexpected token \'trac\'/); });
|
||||
it('should report an unexpected token',
|
||||
() => { expectActionError('[1,2] trac', 'Unexpected token \'trac\''); });
|
||||
|
||||
it('should throw a reasonable error for unconsumed tokens', () => {
|
||||
expectActionError(')').toThrowError(/Unexpected token \) at column 1 in \[\)\]/);
|
||||
});
|
||||
it('should report reasonable error for unconsumed tokens',
|
||||
() => { expectActionError(')', 'Unexpected token ) at column 1 in [)]'); });
|
||||
|
||||
it('should throw on missing expected token', () => {
|
||||
expectActionError('a(b').toThrowError(
|
||||
/Missing expected \) at the end of the expression \[a\(b\]/);
|
||||
it('should report a missing expected token', () => {
|
||||
expectActionError('a(b', 'Missing expected ) at the end of the expression [a(b]');
|
||||
});
|
||||
});
|
||||
|
||||
@ -246,9 +256,9 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should only allow identifier or keyword as formatter names', () => {
|
||||
expectBindingError('"Foo"|(').toThrowError(/identifier or keyword/);
|
||||
expectBindingError('"Foo"|1234').toThrowError(/identifier or keyword/);
|
||||
expectBindingError('"Foo"|"uppercase"').toThrowError(/identifier or keyword/);
|
||||
expectBindingError('"Foo"|(', 'identifier or keyword');
|
||||
expectBindingError('"Foo"|1234', 'identifier or keyword');
|
||||
expectBindingError('"Foo"|"uppercase"', 'identifier or keyword');
|
||||
});
|
||||
|
||||
it('should parse quoted expressions', () => { checkBinding('a:b', 'a:b'); });
|
||||
@ -259,8 +269,8 @@ export function main() {
|
||||
it('should ignore whitespace around quote prefix', () => { checkBinding(' a :b', 'a:b'); });
|
||||
|
||||
it('should refuse prefixes that are not single identifiers', () => {
|
||||
expectBindingError('a + b:c').toThrowError();
|
||||
expectBindingError('1:c').toThrowError();
|
||||
expectBindingError('a + b:c', '');
|
||||
expectBindingError('1:c', '');
|
||||
});
|
||||
});
|
||||
|
||||
@ -270,15 +280,14 @@ export function main() {
|
||||
it('should store the passed-in location',
|
||||
() => { expect(parseBinding('someExpr', 'location').location).toBe('location'); });
|
||||
|
||||
it('should throw on chain expressions',
|
||||
() => { expect(() => parseBinding('1;2')).toThrowError(/contain chained expression/); });
|
||||
it('should report chain expressions',
|
||||
() => { expectError(parseBinding('1;2'), 'contain chained expression'); });
|
||||
|
||||
it('should throw on assignment',
|
||||
() => { expect(() => parseBinding('a=2')).toThrowError(/contain assignments/); });
|
||||
it('should report assignment',
|
||||
() => { expectError(parseBinding('a=2'), 'contain assignments'); });
|
||||
|
||||
it('should throw when encountering interpolation', () => {
|
||||
expectBindingError('{{a.b}}').toThrowError(
|
||||
/Got interpolation \(\{\{\}\}\) where expression was expected/);
|
||||
it('should report when encountering interpolation', () => {
|
||||
expectBindingError('{{a.b}}', 'Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
|
||||
it('should parse conditional expression', () => { checkBinding('a < b ? a : b'); });
|
||||
@ -331,13 +340,10 @@ export function main() {
|
||||
bindings = parseTemplateBindings('a-b:\'c\'');
|
||||
expect(keys(bindings)).toEqual(['a-b']);
|
||||
|
||||
expect(() => {
|
||||
parseTemplateBindings('(:0');
|
||||
}).toThrowError(/expected identifier, keyword, or string/);
|
||||
expectError(parseTemplateBindingsResult('(:0'), 'expected identifier, keyword, or string');
|
||||
|
||||
expect(() => {
|
||||
parseTemplateBindings('1234:0');
|
||||
}).toThrowError(/expected identifier, keyword, or string/);
|
||||
expectError(
|
||||
parseTemplateBindingsResult('1234:0'), 'expected identifier, keyword, or string');
|
||||
});
|
||||
|
||||
it('should detect expressions as value', () => {
|
||||
@ -436,7 +442,7 @@ export function main() {
|
||||
() => { expect(parseInterpolation('nothing')).toBe(null); });
|
||||
|
||||
it('should parse no prefix/suffix interpolation', () => {
|
||||
var ast = parseInterpolation('{{a}}').ast;
|
||||
var ast = parseInterpolation('{{a}}').ast as Interpolation;
|
||||
expect(ast.strings).toEqual(['', '']);
|
||||
expect(ast.expressions.length).toEqual(1);
|
||||
expect(ast.expressions[0].name).toEqual('a');
|
||||
@ -445,17 +451,18 @@ export function main() {
|
||||
it('should parse prefix/suffix with multiple interpolation', () => {
|
||||
var originalExp = 'before {{ a }} middle {{ b }} after';
|
||||
var ast = parseInterpolation(originalExp).ast;
|
||||
expect(new Unparser().unparse(ast)).toEqual(originalExp);
|
||||
expect(unparse(ast)).toEqual(originalExp);
|
||||
validate(ast);
|
||||
});
|
||||
|
||||
it('should throw on empty interpolation expressions', () => {
|
||||
expect(() => parseInterpolation('{{}}'))
|
||||
.toThrowError(
|
||||
/Parser Error: Blank expressions are not allowed in interpolated strings/);
|
||||
it('should report empty interpolation expressions', () => {
|
||||
expectError(
|
||||
parseInterpolation('{{}}'),
|
||||
'Blank expressions are not allowed in interpolated strings');
|
||||
|
||||
expect(() => parseInterpolation('foo {{ }}'))
|
||||
.toThrowError(
|
||||
/Parser Error: Blank expressions are not allowed in interpolated strings/);
|
||||
expectError(
|
||||
parseInterpolation('foo {{ }}'),
|
||||
'Parser Error: Blank expressions are not allowed in interpolated strings');
|
||||
});
|
||||
|
||||
it('should parse conditional expression',
|
||||
@ -503,28 +510,47 @@ export function main() {
|
||||
it('should parse a field access', () => {
|
||||
var p = parseSimpleBinding('name');
|
||||
expect(unparse(p)).toEqual('name');
|
||||
validate(p);
|
||||
});
|
||||
|
||||
it('should parse a constant', () => {
|
||||
var p = parseSimpleBinding('[1, 2]');
|
||||
expect(unparse(p)).toEqual('[1, 2]');
|
||||
validate(p);
|
||||
});
|
||||
|
||||
it('should throw when the given expression is not just a field name', () => {
|
||||
expect(() => parseSimpleBinding('name + 1'))
|
||||
.toThrowError(/Host binding expression can only contain field access and constants/);
|
||||
it('should report when the given expression is not just a field name', () => {
|
||||
expectError(
|
||||
validate(parseSimpleBinding('name + 1')),
|
||||
'Host binding expression can only contain field access and constants');
|
||||
});
|
||||
|
||||
it('should throw when encountering interpolation', () => {
|
||||
expect(() => parseSimpleBinding('{{exp}}'))
|
||||
.toThrowError(/Got interpolation \(\{\{\}\}\) where expression was expected/);
|
||||
it('should report when encountering interpolation', () => {
|
||||
expectError(
|
||||
validate(parseSimpleBinding('{{exp}}')),
|
||||
'Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapLiteralPrimitive', () => {
|
||||
it('should wrap a literal primitive', () => {
|
||||
expect(unparse(createParser().wrapLiteralPrimitive('foo', null))).toEqual('"foo"');
|
||||
expect(unparse(validate(createParser().wrapLiteralPrimitive('foo', null))))
|
||||
.toEqual('"foo"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error recovery', () => {
|
||||
function recover(text: string, expected?: string) {
|
||||
let expr = validate(parseAction(text));
|
||||
expect(unparse(expr)).toEqual(expected || text);
|
||||
}
|
||||
it('should be able to recover from an extra paren', () => recover('((a)))', 'a'));
|
||||
it('should be able to recover from an extra bracket', () => recover('[[a]]]', '[[a]]'));
|
||||
it('should be able to recover from a missing )', () => recover('(a;b', 'a; b;'));
|
||||
it('should be able to recover from a missing ]', () => recover('[a,b', '[a, b]'));
|
||||
it('should be able to recover from a missing selector', () => recover('a.'));
|
||||
it('should be able to recover from a missing selector in a array literal',
|
||||
() => recover('[[a.], b, c]'))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -10,12 +10,12 @@ import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, Fun
|
||||
import {StringWrapper, isPresent, isString} from '../../src/facade/lang';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/interpolation_config';
|
||||
|
||||
export class Unparser implements AstVisitor {
|
||||
class Unparser implements AstVisitor {
|
||||
private static _quoteRegExp = /"/g;
|
||||
private _expression: string;
|
||||
private _interpolationConfig: InterpolationConfig;
|
||||
|
||||
unparse(ast: AST, interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
||||
unparse(ast: AST, interpolationConfig: InterpolationConfig) {
|
||||
this._expression = '';
|
||||
this._interpolationConfig = interpolationConfig;
|
||||
this._visit(ast);
|
||||
@ -180,3 +180,10 @@ export class Unparser implements AstVisitor {
|
||||
|
||||
private _visit(ast: AST) { ast.visit(this); }
|
||||
}
|
||||
|
||||
const sharedUnparser = new Unparser();
|
||||
|
||||
export function unparse(
|
||||
ast: AST, interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): string {
|
||||
return sharedUnparser.unparse(ast, interpolationConfig);
|
||||
}
|
||||
|
110
modules/@angular/compiler/test/expression_parser/validator.ts
Normal file
110
modules/@angular/compiler/test/expression_parser/validator.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import {AST, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast';
|
||||
|
||||
import {unparse} from './unparser';
|
||||
|
||||
class ASTValidator extends RecursiveAstVisitor {
|
||||
private parentSpan: ParseSpan|undefined;
|
||||
|
||||
visit(ast: AST) {
|
||||
this.parentSpan = undefined;
|
||||
ast.visit(this);
|
||||
}
|
||||
|
||||
validate(ast: AST, cb: () => void): void {
|
||||
if (!inSpan(ast.span, this.parentSpan)) {
|
||||
throw Error(
|
||||
`Invalid AST span [expected (${ast.span.start}, ${ast.span.end}) to be in (${this.parentSpan.start}, ${this.parentSpan.end}) for ${unparse(ast)}`);
|
||||
}
|
||||
const oldParent = this.parentSpan;
|
||||
this.parentSpan = ast.span;
|
||||
cb();
|
||||
this.parentSpan = oldParent;
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary, context: any): any {
|
||||
this.validate(ast, () => super.visitBinary(ast, context));
|
||||
}
|
||||
|
||||
visitChain(ast: Chain, context: any): any {
|
||||
this.validate(ast, () => super.visitChain(ast, context));
|
||||
}
|
||||
|
||||
visitConditional(ast: Conditional, context: any): any {
|
||||
this.validate(ast, () => super.visitConditional(ast, context));
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall, context: any): any {
|
||||
this.validate(ast, () => super.visitFunctionCall(ast, context));
|
||||
}
|
||||
|
||||
visitImplicitReceiver(ast: ImplicitReceiver, context: any): any {
|
||||
this.validate(ast, () => super.visitImplicitReceiver(ast, context));
|
||||
}
|
||||
|
||||
visitInterpolation(ast: Interpolation, context: any): any {
|
||||
this.validate(ast, () => super.visitInterpolation(ast, context));
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead, context: any): any {
|
||||
this.validate(ast, () => super.visitKeyedRead(ast, context));
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite, context: any): any {
|
||||
this.validate(ast, () => super.visitKeyedWrite(ast, context));
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray, context: any): any {
|
||||
this.validate(ast, () => super.visitLiteralArray(ast, context));
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap, context: any): any {
|
||||
this.validate(ast, () => super.visitLiteralMap(ast, context));
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any {
|
||||
this.validate(ast, () => super.visitLiteralPrimitive(ast, context));
|
||||
}
|
||||
|
||||
visitMethodCall(ast: MethodCall, context: any): any {
|
||||
this.validate(ast, () => super.visitMethodCall(ast, context));
|
||||
}
|
||||
|
||||
visitPipe(ast: BindingPipe, context: any): any {
|
||||
this.validate(ast, () => super.visitPipe(ast, context));
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot, context: any): any {
|
||||
this.validate(ast, () => super.visitPrefixNot(ast, context));
|
||||
}
|
||||
|
||||
visitPropertyRead(ast: PropertyRead, context: any): any {
|
||||
this.validate(ast, () => super.visitPropertyRead(ast, context));
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite, context: any): any {
|
||||
this.validate(ast, () => super.visitPropertyWrite(ast, context));
|
||||
}
|
||||
|
||||
visitQuote(ast: Quote, context: any): any {
|
||||
this.validate(ast, () => super.visitQuote(ast, context));
|
||||
}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall, context: any): any {
|
||||
this.validate(ast, () => super.visitSafeMethodCall(ast, context));
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any): any {
|
||||
this.validate(ast, () => super.visitSafePropertyRead(ast, context));
|
||||
}
|
||||
}
|
||||
|
||||
function inSpan(span: ParseSpan, parentSpan: ParseSpan | undefined): parentSpan is ParseSpan {
|
||||
return !parentSpan || (span.start >= parentSpan.start && span.end <= parentSpan.end);
|
||||
}
|
||||
|
||||
const sharedValidator = new ASTValidator();
|
||||
|
||||
export function validate<T extends AST>(ast: T): T {
|
||||
sharedValidator.visit(ast);
|
||||
return ast;
|
||||
}
|
Reference in New Issue
Block a user