feat(compiler): support safe keyed read expressions (#41911)
Currently we support safe property (`a?.b`) and method (`a?.b()`) accesses, but we don't handle safe keyed reads (`a?.[0]`) which is inconsistent. These changes expand the compiler in order to support safe key read expressions as well. PR Close #41911
This commit is contained in:
committed by
Jessica Janiuk
parent
47270d9e63
commit
ba084857ea
@@ -127,6 +127,14 @@ function expectErrorToken(token: Token, index: any, end: number, message: string
|
||||
expectCharacterToken(tokens[3], 3, 4, ']');
|
||||
});
|
||||
|
||||
it('should tokenize a safe indexed operator', () => {
|
||||
const tokens: number[] = lex('j?.[k]');
|
||||
expect(tokens.length).toBe(5);
|
||||
expectOperatorToken(tokens[1], 1, 3, '?.');
|
||||
expectCharacterToken(tokens[2], 3, 4, '[');
|
||||
expectCharacterToken(tokens[4], 5, 6, ']');
|
||||
});
|
||||
|
||||
it('should tokenize numbers', () => {
|
||||
const tokens: number[] = lex('88');
|
||||
expect(tokens.length).toEqual(1);
|
||||
|
||||
@@ -215,9 +215,16 @@ describe('parser', () => {
|
||||
|
||||
describe('keyed read', () => {
|
||||
it('should parse keyed reads', () => {
|
||||
checkAction('a["a"]');
|
||||
checkAction('this.a["a"]', 'a["a"]');
|
||||
checkAction('a.a["a"]');
|
||||
checkBinding('a["a"]');
|
||||
checkBinding('this.a["a"]', 'a["a"]');
|
||||
checkBinding('a.a["a"]');
|
||||
});
|
||||
|
||||
it('should parse safe keyed reads', () => {
|
||||
checkBinding('a?.["a"]');
|
||||
checkBinding('this.a?.["a"]', 'a?.["a"]');
|
||||
checkBinding('a.a?.["a"]');
|
||||
checkBinding('a.a?.["a" | foo]', 'a.a?.[("a" | foo)]');
|
||||
});
|
||||
|
||||
describe('malformed keyed reads', () => {
|
||||
@@ -248,6 +255,10 @@ describe('parser', () => {
|
||||
checkAction('a.a["a"] = 1 + 2');
|
||||
});
|
||||
|
||||
it('should report on safe keyed writes', () => {
|
||||
expectActionError('a?.["a"] = 123', 'cannot be used in the assignment');
|
||||
});
|
||||
|
||||
describe('malformed keyed writes', () => {
|
||||
it('should recover on empty rvalues', () => {
|
||||
checkActionWithError('a["a"] = ', 'a["a"] = ', 'Unexpected end of expression');
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead, ThisReceiver, Unary} from '../../../src/expression_parser/ast';
|
||||
import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeKeyedRead, SafeMethodCall, SafePropertyRead, ThisReceiver, Unary} from '../../../src/expression_parser/ast';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config';
|
||||
|
||||
class Unparser implements AstVisitor {
|
||||
@@ -101,14 +101,14 @@ class Unparser implements AstVisitor {
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead, context: any) {
|
||||
this._visit(ast.obj);
|
||||
this._visit(ast.receiver);
|
||||
this._expression += '[';
|
||||
this._visit(ast.key);
|
||||
this._expression += ']';
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite, context: any) {
|
||||
this._visit(ast.obj);
|
||||
this._visit(ast.receiver);
|
||||
this._expression += '[';
|
||||
this._visit(ast.key);
|
||||
this._expression += '] = ';
|
||||
@@ -193,6 +193,13 @@ class Unparser implements AstVisitor {
|
||||
this._expression += `${ast.prefix}:${ast.uninterpretedExpression}`;
|
||||
}
|
||||
|
||||
visitSafeKeyedRead(ast: SafeKeyedRead, context: any) {
|
||||
this._visit(ast.receiver);
|
||||
this._expression += '?.[';
|
||||
this._visit(ast.key);
|
||||
this._expression += ']';
|
||||
}
|
||||
|
||||
private _visit(ast: AST) {
|
||||
ast.visit(this);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AST, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead, Unary} from '../../../src/expression_parser/ast';
|
||||
import {AST, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeKeyedRead, SafeMethodCall, SafePropertyRead, Unary} from '../../../src/expression_parser/ast';
|
||||
|
||||
import {unparse} from './unparser';
|
||||
|
||||
@@ -113,6 +113,10 @@ class ASTValidator extends RecursiveAstVisitor {
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any): any {
|
||||
this.validate(ast, () => super.visitSafePropertyRead(ast, context));
|
||||
}
|
||||
|
||||
visitSafeKeyedRead(ast: SafeKeyedRead, context: any): any {
|
||||
this.validate(ast, () => super.visitSafeKeyedRead(ast, context));
|
||||
}
|
||||
}
|
||||
|
||||
function inSpan(span: ParseSpan, parentSpan: ParseSpan|undefined): parentSpan is ParseSpan {
|
||||
|
||||
Reference in New Issue
Block a user