diff --git a/crates/angular_conformance/README.md b/crates/angular_conformance/README.md index 8302d8409d..d44d7d17c2 100644 --- a/crates/angular_conformance/README.md +++ b/crates/angular_conformance/README.md @@ -4,7 +4,7 @@ A conformance testing framework that validates the oxc Angular compiler implemen ## Overview -This crate extracts test cases from Angular's TypeScript spec files and runs them against the Rust implementation to ensure compatibility. It currently maintains a **100% pass rate** across **1,264** extracted test assertions against Angular **v22.0.0-rc.2**. +This crate extracts test cases from Angular's TypeScript spec files and runs them against the Rust implementation to ensure compatibility. It currently passes **1,430** of **1,456** extracted test assertions (the rest cover other new 22.3 features not yet ported) against Angular **v22.3.0-next.4**. ## Architecture diff --git a/crates/angular_conformance/fixtures/expression_parser_lexer_spec.json b/crates/angular_conformance/fixtures/expression_parser_lexer_spec.json index 800f28615c..5681335cde 100644 --- a/crates/angular_conformance/fixtures/expression_parser_lexer_spec.json +++ b/crates/angular_conformance/fixtures/expression_parser_lexer_spec.json @@ -4123,6 +4123,464 @@ } ] }, + { + "name": "should tokenize increment/decrement operators", + "path": "lexer/token/should tokenize increment/decrement operators", + "assertions": [ + { + "type": "ExpressionLexerTest", + "input": "++", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 2, + "value": "++" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "--", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 2, + "value": "--" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "a++", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 3, + "value": "++" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "a--", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 3, + "value": "--" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "++a", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 2, + "value": "++" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "--a", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 2, + "value": "--" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "--a", + "expected_token_count": null, + "token_assertions": [] + } + ] + }, + { + "name": "should tokenize increment/decrement operators used together with plus/minus", + "path": "lexer/token/should tokenize increment/decrement operators used together with plus/minus", + "assertions": [ + { + "type": "ExpressionLexerTest", + "input": "+++a", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 2, + "value": "++" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 2, + "end": 3, + "value": "+" + }, + { + "assertion_type": "Identifier", + "token_index": 2, + "start": 3, + "end": 4, + "value": "a" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "---a", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 2, + "value": "--" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 2, + "end": 3, + "value": "-" + }, + { + "assertion_type": "Identifier", + "token_index": 2, + "start": 3, + "end": 4, + "value": "a" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "-- foo++", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 2, + "value": "--" + }, + { + "assertion_type": "Identifier", + "token_index": 1, + "start": 3, + "end": 6, + "value": "foo" + }, + { + "assertion_type": "Operator", + "token_index": 2, + "start": 6, + "end": 8, + "value": "++" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "- - foo++", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Operator", + "token_index": 0, + "start": 0, + "end": 1, + "value": "-" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 2, + "end": 3, + "value": "-" + }, + { + "assertion_type": "Identifier", + "token_index": 2, + "start": 4, + "end": 7, + "value": "foo" + }, + { + "assertion_type": "Operator", + "token_index": 3, + "start": 7, + "end": 9, + "value": "++" + } + ] + } + ] + }, + { + "name": "should tokenize increment/decrement operators followed by other operators in an expression", + "path": "lexer/token/should tokenize increment/decrement operators followed by other operators in an expression", + "assertions": [ + { + "type": "ExpressionLexerTest", + "input": "a+++b", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Identifier", + "token_index": 0, + "start": 0, + "end": 1, + "value": "a" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 3, + "value": "++" + }, + { + "assertion_type": "Operator", + "token_index": 2, + "start": 3, + "end": 4, + "value": "+" + }, + { + "assertion_type": "Identifier", + "token_index": 3, + "start": 4, + "end": 5, + "value": "b" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "a+++++b", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Identifier", + "token_index": 0, + "start": 0, + "end": 1, + "value": "a" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 3, + "value": "++" + }, + { + "assertion_type": "Operator", + "token_index": 2, + "start": 3, + "end": 5, + "value": "++" + }, + { + "assertion_type": "Operator", + "token_index": 3, + "start": 5, + "end": 6, + "value": "+" + }, + { + "assertion_type": "Identifier", + "token_index": 4, + "start": 6, + "end": 7, + "value": "b" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "a-----b", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Identifier", + "token_index": 0, + "start": 0, + "end": 1, + "value": "a" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 3, + "value": "--" + }, + { + "assertion_type": "Operator", + "token_index": 2, + "start": 3, + "end": 5, + "value": "--" + }, + { + "assertion_type": "Operator", + "token_index": 3, + "start": 5, + "end": 6, + "value": "-" + }, + { + "assertion_type": "Identifier", + "token_index": 4, + "start": 6, + "end": 7, + "value": "b" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "a+++ +b", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Identifier", + "token_index": 0, + "start": 0, + "end": 1, + "value": "a" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 3, + "value": "++" + }, + { + "assertion_type": "Operator", + "token_index": 2, + "start": 3, + "end": 4, + "value": "+" + }, + { + "assertion_type": "Operator", + "token_index": 3, + "start": 5, + "end": 6, + "value": "+" + }, + { + "assertion_type": "Identifier", + "token_index": 4, + "start": 6, + "end": 7, + "value": "b" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "a+--b", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Identifier", + "token_index": 0, + "start": 0, + "end": 1, + "value": "a" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 2, + "value": "+" + }, + { + "assertion_type": "Operator", + "token_index": 2, + "start": 2, + "end": 4, + "value": "--" + }, + { + "assertion_type": "Identifier", + "token_index": 3, + "start": 4, + "end": 5, + "value": "b" + } + ] + }, + { + "type": "ExpressionLexerTest", + "input": "a+++=b", + "expected_token_count": null, + "token_assertions": [ + { + "assertion_type": "Identifier", + "token_index": 0, + "start": 0, + "end": 1, + "value": "a" + }, + { + "assertion_type": "Operator", + "token_index": 1, + "start": 1, + "end": 3, + "value": "++" + }, + { + "assertion_type": "Operator", + "token_index": 2, + "start": 3, + "end": 5, + "value": "+=" + }, + { + "assertion_type": "Identifier", + "token_index": 3, + "start": 5, + "end": 6, + "value": "b" + } + ] + } + ] + }, { "name": "should tokenize a spread operator", "path": "lexer/token/should tokenize a spread operator", diff --git a/crates/angular_conformance/fixtures/expression_parser_parser_spec.json b/crates/angular_conformance/fixtures/expression_parser_parser_spec.json index 3045d33f69..53726518c6 100644 --- a/crates/angular_conformance/fixtures/expression_parser_parser_spec.json +++ b/crates/angular_conformance/fixtures/expression_parser_parser_spec.json @@ -839,6 +839,282 @@ "expected": null } ] + }, + { + "name": "should report invalid increment/decrement expressions", + "path": "parser/parseAction/assignment/should report invalid increment/decrement expressions", + "assertions": [ + { + "type": "ExpectActionError", + "input": "-- foo++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++ foo--", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "a?.a++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++a?.a", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "a?.[0]++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++a?.[0]", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "5++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++5", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "foo()++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++foo()", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "---foo", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "+++foo", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "(foo++)++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++(foo++)", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++foo++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "--foo--", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++foo--", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "--foo++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++ ++foo", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "-- --foo", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++foo+++bar", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "(-foo)++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "(foo + bar)++", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "++(foo + bar)", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "\"foo\"++", + "error_contains": "cannot be used in the assignment" + } + ] + }, + { + "name": "should report unexpected token for chained postfix operators", + "path": "parser/parseAction/assignment/should report unexpected token for chained postfix operators", + "assertions": [ + { + "type": "ExpectActionError", + "input": "foo++ ++", + "error_contains": "Unexpected token '++'" + }, + { + "type": "ExpectActionError", + "input": "foo-- --", + "error_contains": "Unexpected token '--'" + }, + { + "type": "ExpectActionError", + "input": "foo+++++bar", + "error_contains": "Unexpected token '++'" + }, + { + "type": "ExpectActionError", + "input": "foo-----bar", + "error_contains": "Unexpected token '--'" + } + ] + }, + { + "name": "should report a dangling + or - left over after an update operator", + "path": "parser/parseAction/assignment/should report a dangling + or - left over after an update operator", + "assertions": [ + { + "type": "ExpectActionError", + "input": "a+++", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "a---", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "a++ +", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "a++-", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "++a+", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "a+++b+", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "foo.bar+++", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "foo[0]+++", + "error_contains": "Unexpected end of expression" + } + ] + }, + { + "name": "should report a increment/decrement expression with no target", + "path": "parser/parseAction/assignment/should report a increment/decrement expression with no target", + "assertions": [ + { + "type": "ExpectActionError", + "input": "++", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "--", + "error_contains": "Unexpected end of expression" + }, + { + "type": "ExpectActionError", + "input": "+++", + "error_contains": "Unexpected end of expression" + } + ] + }, + { + "name": "should not allow member access or calls after a postfix update", + "path": "parser/parseAction/assignment/should not allow member access or calls after a postfix update", + "assertions": [ + { + "type": "ExpectActionError", + "input": "foo++.bar", + "error_contains": "Unexpected token '.'" + }, + { + "type": "ExpectActionError", + "input": "foo++[0]", + "error_contains": "Unexpected token '['" + }, + { + "type": "ExpectActionError", + "input": "foo++()", + "error_contains": "Unexpected token '('" + }, + { + "type": "ExpectActionError", + "input": "foo--.bar", + "error_contains": "Unexpected token '.'" + }, + { + "type": "ExpectActionError", + "input": "foo-- bar", + "error_contains": "Unexpected token 'bar'" + } + ] + }, + { + "name": "should not allow assigning to an update expression", + "path": "parser/parseAction/assignment/should not allow assigning to an update expression", + "assertions": [ + { + "type": "ExpectActionError", + "input": "foo++ = 1", + "error_contains": "Unexpected token '='" + }, + { + "type": "ExpectActionError", + "input": "++foo = 1", + "error_contains": "cannot be used in the assignment" + }, + { + "type": "ExpectActionError", + "input": "foo+++=bar", + "error_contains": "Unexpected token '+='" + } + ] } ] }, @@ -964,6 +1240,11 @@ "name": "should report error if interpolation is empty", "path": "parser/parseAction/template literals/should report error if interpolation is empty", "assertions": [ + { + "type": "CheckBinding", + "input": "`hello ${}`", + "expected": null + }, { "type": "ExpectBindingError", "input": "`hello ${}`", @@ -971,6 +1252,99 @@ } ] }, + { + "name": "should not report errors for valid interpolations", + "path": "parser/parseAction/template literals/should not report errors for valid interpolations", + "assertions": [] + }, + { + "name": "should report an error if an interpolation has unexpected tokens after the expression", + "path": "parser/parseAction/template literals/should report an error if an interpolation has unexpected tokens after the expression", + "assertions": [ + { + "type": "ExpectBindingError", + "input": "`hello ${a b} world`", + "error_contains": "Missing expected }" + }, + { + "type": "ExpectBindingError", + "input": "`hello ${a & b} world`", + "error_contains": "Missing expected }" + }, + { + "type": "ExpectBindingError", + "input": "`hello ${a, b} world`", + "error_contains": "Missing expected }" + }, + { + "type": "ExpectBindingError", + "input": "`hello ${`nested ${a b}`} world`", + "error_contains": "Missing expected }" + }, + { + "type": "ExpectBindingError", + "input": "{\"a\": `hello ${b c}`}", + "error_contains": "Missing expected }" + }, + { + "type": "ExpectBindingError", + "input": "tag`hello ${a b} world`", + "error_contains": "Missing expected }" + }, + { + "type": "ExpectBindingError", + "input": "`hello ${a | pipe b} world`", + "error_contains": "Missing expected }" + }, + { + "type": "ExpectActionError", + "input": "`hello ${a; b} world`", + "error_contains": "Missing expected }" + } + ] + }, + { + "name": "should report an error if an interpolation is not closed", + "path": "parser/parseAction/template literals/should report an error if an interpolation is not closed", + "assertions": [ + { + "type": "ExpectBindingError", + "input": "`hello ${a", + "error_contains": "Missing expected } at the end of the expression" + }, + { + "type": "ExpectBindingError", + "input": "`hello ${a ", + "error_contains": "Missing expected } at the end of the expression" + } + ] + }, + { + "name": "should report an error if a template literal with an interpolation is not closed", + "path": "parser/parseAction/template literals/should report an error if a template literal with an interpolation is not closed", + "assertions": [ + { + "type": "ExpectBindingError", + "input": "`hello ${a}", + "error_contains": "Unterminated template literal" + }, + { + "type": "ExpectBindingError", + "input": "`hello ${a} world", + "error_contains": "Unterminated template literal" + }, + { + "type": "ExpectBindingError", + "input": "`hello ${a} and ${b} world", + "error_contains": "Unterminated template literal" + } + ] + }, + { + "name": "should retain all the elements and expressions of a malformed template literal", + "path": "parser/parseAction/template literals/should retain all the elements and expressions of a malformed template literal", + "assertions": [] + }, { "name": "should parse tagged template literals with no interpolations", "path": "parser/parseAction/template literals/should parse tagged template literals with no interpolations", @@ -1138,6 +1512,22 @@ } ] }, + { + "name": "should parse a regular expression literal that ends with an escaped slash", + "path": "parser/parseAction/regular expression literals/should parse a regular expression literal that ends with an escaped slash", + "assertions": [ + { + "type": "CheckBinding", + "input": "/^https?:\\/\\//", + "expected": null + }, + { + "type": "CheckBinding", + "input": "url.replace(/^\\//, \"\") //comment", + "expected": "url.replace(/^\\//, \"\")" + } + ] + }, { "name": "should report invalid regular expression flag", "path": "parser/parseAction/regular expression literals/should report invalid regular expression flag", @@ -1245,421 +1635,782 @@ "assertions": [ { "type": "CheckAction", - "input": "!true", + "input": "!true", + "expected": null + }, + { + "type": "CheckAction", + "input": "!!true", + "expected": null + }, + { + "type": "CheckAction", + "input": "!!!true", + "expected": null + } + ] + }, + { + "name": "should parse postfix ! expression", + "path": "parser/parseAction/should parse postfix ! expression", + "assertions": [ + { + "type": "CheckAction", + "input": "true!", + "expected": null + }, + { + "type": "CheckAction", + "input": "a!.b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a!!!!.b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a!()", + "expected": null + }, + { + "type": "CheckAction", + "input": "a.b!()", + "expected": null + } + ] + }, + { + "name": "should parse exponentiation expressions", + "path": "parser/parseAction/should parse exponentiation expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "1*2**3", + "expected": "1 * 2 ** 3" + } + ] + }, + { + "name": "should allow ++ and -- on the left-hand side of **", + "path": "parser/parseAction/should allow ++ and -- on the left-hand side of **", + "assertions": [ + { + "type": "CheckAction", + "input": "a++ ** 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "++a ** 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "a-- ** 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "--a ** 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "a.b++ ** 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0]++ ** 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "2 ** a++", + "expected": null + }, + { + "type": "CheckAction", + "input": "a ** b++ ** c", + "expected": null + } + ] + }, + { + "name": "should not allow other unary operators on the left-hand side of **", + "path": "parser/parseAction/should not allow other unary operators on the left-hand side of **", + "assertions": [] + }, + { + "name": "should parse multiplicative expressions", + "path": "parser/parseAction/should parse multiplicative expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "3*4/2%5", + "expected": "3 * 4 / 2 % 5" + } + ] + }, + { + "name": "should parse additive expressions", + "path": "parser/parseAction/should parse additive expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "3 + 6 - 2", + "expected": null + } + ] + }, + { + "name": "should parse relational expressions", + "path": "parser/parseAction/should parse relational expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "2 < 3", + "expected": null + }, + { + "type": "CheckAction", + "input": "2 > 3", + "expected": null + }, + { + "type": "CheckAction", + "input": "2 <= 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "2 >= 2", + "expected": null + }, + { + "type": "CheckAction", + "input": "\"key\" in obj", + "expected": null + }, + { + "type": "CheckAction", + "input": "foo instanceof Foo", + "expected": null + } + ] + }, + { + "name": "should parse equality expressions", + "path": "parser/parseAction/should parse equality expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "2 == 3", + "expected": null + }, + { + "type": "CheckAction", + "input": "2 != 3", + "expected": null + } + ] + }, + { + "name": "should parse strict equality expressions", + "path": "parser/parseAction/should parse strict equality expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "2 === 3", + "expected": null + }, + { + "type": "CheckAction", + "input": "2 !== 3", + "expected": null + } + ] + }, + { + "name": "should parse expressions", + "path": "parser/parseAction/should parse expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "true && true", + "expected": null + }, + { + "type": "CheckAction", + "input": "true || false", + "expected": null + }, + { + "type": "CheckAction", + "input": "null ?? 0", + "expected": null + }, + { + "type": "CheckAction", + "input": "null ?? undefined ?? 0", + "expected": null + } + ] + }, + { + "name": "should parse typeof expression", + "path": "parser/parseAction/should parse typeof expression", + "assertions": [ + { + "type": "CheckAction", + "input": "typeof {} === \"object\"", + "expected": null + }, + { + "type": "CheckAction", + "input": "(!(typeof {} === \"number\"))", + "expected": null + } + ] + }, + { + "name": "should parse void expression", + "path": "parser/parseAction/should parse void expression", + "assertions": [ + { + "type": "CheckAction", + "input": "void 0", + "expected": null + }, + { + "type": "CheckAction", + "input": "(!(void 0))", + "expected": null + } + ] + }, + { + "name": "should parse grouped expressions", + "path": "parser/parseAction/should parse grouped expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "(1 + 2) * 3", + "expected": null + } + ] + }, + { + "name": "should parse in expressions", + "path": "parser/parseAction/should parse in expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "'key' in obj", + "expected": "\"key\" in obj" + }, + { + "type": "CheckAction", + "input": "('key' in obj) && true", + "expected": "(\"key\" in obj) && true" + }, + { + "type": "CheckAction", + "input": "'in' in {in: foo}", + "expected": "\"in\" in {in: foo}" + } + ] + }, + { + "name": "should throw on invalid in expressions", + "path": "parser/parseAction/should throw on invalid in expressions", + "assertions": [ + { + "type": "ExpectActionError", + "input": "in", + "error_contains": "Unexpected token in" + }, + { + "type": "ExpectActionError", + "input": "in foo", + "error_contains": "Unexpected token in" + }, + { + "type": "ExpectActionError", + "input": "'foo' in", + "error_contains": "Unexpected end of expression: 'foo' in at the end of the expression ['foo' in]" + } + ] + }, + { + "name": "should ignore comments in expressions", + "path": "parser/parseAction/should ignore comments in expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "a //comment", + "expected": "a" + } + ] + }, + { + "name": "should parse instanceof expressions", + "path": "parser/parseAction/should parse instanceof expressions", + "assertions": [ + { + "type": "CheckAction", + "input": "obj instanceof MyClass", + "expected": "obj instanceof MyClass" + }, + { + "type": "CheckAction", + "input": "(obj instanceof MyClass) || false", + "expected": "(obj instanceof MyClass) || false" + } + ] + }, + { + "name": "should retain // in string literals", + "path": "parser/parseAction/should retain // in string literals", + "assertions": [ + { + "type": "CheckAction", + "input": "\"http://www.google.com\"", + "expected": "\"http://www.google.com\"" + } + ] + }, + { + "name": "should parse an empty string", + "path": "parser/parseAction/should parse an empty string", + "assertions": [ + { + "type": "CheckAction", + "input": "", + "expected": null + } + ] + }, + { + "name": "should parse assignment operators with property reads", + "path": "parser/parseAction/should parse assignment operators with property reads", + "assertions": [ + { + "type": "CheckAction", + "input": "a = b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a += b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a -= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a *= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a /= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a %= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a **= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a &&= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a ||= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a ??= b", + "expected": null + } + ] + }, + { + "name": "should parse assignment operators with keyed reads", + "path": "parser/parseAction/should parse assignment operators with keyed reads", + "assertions": [ + { + "type": "CheckAction", + "input": "a[0] = b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0] += b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0] -= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0] *= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0] /= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0] %= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0] **= b", + "expected": null + }, + { + "type": "CheckAction", + "input": "a[0] &&= b", "expected": null }, { "type": "CheckAction", - "input": "!!true", + "input": "a[0] ||= b", "expected": null }, { "type": "CheckAction", - "input": "!!!true", + "input": "a[0] ??= b", "expected": null } ] }, { - "name": "should parse postfix ! expression", - "path": "parser/parseAction/should parse postfix ! expression", + "name": "should parse prefix ++ and --", + "path": "parser/parseAction/should parse prefix ++ and --", "assertions": [ { "type": "CheckAction", - "input": "true!", + "input": "++a", "expected": null }, { "type": "CheckAction", - "input": "a!.b", + "input": "--a", "expected": null }, { "type": "CheckAction", - "input": "a!!!!.b", + "input": "++a.b", "expected": null }, { "type": "CheckAction", - "input": "a!()", + "input": "--a.b", "expected": null }, { "type": "CheckAction", - "input": "a.b!()", + "input": "++a[0]", "expected": null - } - ] - }, - { - "name": "should parse exponentiation expressions", - "path": "parser/parseAction/should parse exponentiation expressions", - "assertions": [ - { - "type": "CheckAction", - "input": "1*2**3", - "expected": "1 * 2 ** 3" - } - ] - }, - { - "name": "should parse multiplicative expressions", - "path": "parser/parseAction/should parse multiplicative expressions", - "assertions": [ - { - "type": "CheckAction", - "input": "3*4/2%5", - "expected": "3 * 4 / 2 % 5" - } - ] - }, - { - "name": "should parse additive expressions", - "path": "parser/parseAction/should parse additive expressions", - "assertions": [ + }, { "type": "CheckAction", - "input": "3 + 6 - 2", + "input": "--a[0]", "expected": null - } - ] - }, - { - "name": "should parse relational expressions", - "path": "parser/parseAction/should parse relational expressions", - "assertions": [ + }, { "type": "CheckAction", - "input": "2 < 3", - "expected": null + "input": "++this.a", + "expected": "++a" }, { "type": "CheckAction", - "input": "2 > 3", - "expected": null + "input": "--this.a", + "expected": "--a" }, { "type": "CheckAction", - "input": "2 <= 2", + "input": "++a!", "expected": null }, { "type": "CheckAction", - "input": "2 >= 2", + "input": "--a!", "expected": null }, { "type": "CheckAction", - "input": "\"key\" in obj", + "input": "++(a)", "expected": null }, { "type": "CheckAction", - "input": "foo instanceof Foo", + "input": "--(a)", "expected": null } ] }, { - "name": "should parse equality expressions", - "path": "parser/parseAction/should parse equality expressions", + "name": "should parse postfix ++ and --", + "path": "parser/parseAction/should parse postfix ++ and --", "assertions": [ { "type": "CheckAction", - "input": "2 == 3", + "input": "a++", "expected": null }, { "type": "CheckAction", - "input": "2 != 3", + "input": "a--", "expected": null - } - ] - }, - { - "name": "should parse strict equality expressions", - "path": "parser/parseAction/should parse strict equality expressions", - "assertions": [ + }, { "type": "CheckAction", - "input": "2 === 3", + "input": "a.b++", "expected": null }, { "type": "CheckAction", - "input": "2 !== 3", + "input": "a.b--", "expected": null - } - ] - }, - { - "name": "should parse expressions", - "path": "parser/parseAction/should parse expressions", - "assertions": [ + }, { "type": "CheckAction", - "input": "true && true", + "input": "a[0]++", "expected": null }, { "type": "CheckAction", - "input": "true || false", + "input": "a[0]--", "expected": null }, { "type": "CheckAction", - "input": "null ?? 0", - "expected": null + "input": "this.a++", + "expected": "a++" }, { "type": "CheckAction", - "input": "null ?? undefined ?? 0", - "expected": null - } - ] - }, - { - "name": "should parse typeof expression", - "path": "parser/parseAction/should parse typeof expression", - "assertions": [ + "input": "this.a--", + "expected": "a--" + }, { "type": "CheckAction", - "input": "typeof {} === \"object\"", + "input": "a!++", "expected": null }, { "type": "CheckAction", - "input": "(!(typeof {} === \"number\"))", + "input": "a!--", "expected": null - } - ] - }, - { - "name": "should parse void expression", - "path": "parser/parseAction/should parse void expression", - "assertions": [ + }, { "type": "CheckAction", - "input": "void 0", + "input": "(a)++", "expected": null }, { "type": "CheckAction", - "input": "(!(void 0))", + "input": "(a)--", "expected": null } ] }, { - "name": "should parse grouped expressions", - "path": "parser/parseAction/should parse grouped expressions", + "name": "should parse consecutive unary expressions", + "path": "parser/parseAction/should parse consecutive unary expressions", "assertions": [ { "type": "CheckAction", - "input": "(1 + 2) * 3", - "expected": null - } - ] - }, - { - "name": "should parse in expressions", - "path": "parser/parseAction/should parse in expressions", - "assertions": [ + "input": "- - foo++", + "expected": "- -foo++" + }, { "type": "CheckAction", - "input": "'key' in obj", - "expected": "\"key\" in obj" + "input": "+ + foo++", + "expected": "+ +foo++" }, { "type": "CheckAction", - "input": "('key' in obj) && true", - "expected": "(\"key\" in obj) && true" + "input": "- ++foo", + "expected": "-++foo" }, { "type": "CheckAction", - "input": "'in' in {in: foo}", - "expected": "\"in\" in {in: foo}" - } - ] - }, - { - "name": "should throw on invalid in expressions", - "path": "parser/parseAction/should throw on invalid in expressions", - "assertions": [ - { - "type": "ExpectActionError", - "input": "in", - "error_contains": "Unexpected token in" + "input": "+ --foo", + "expected": "+--foo" }, { - "type": "ExpectActionError", - "input": "in foo", - "error_contains": "Unexpected token in" + "type": "CheckAction", + "input": "a+++b", + "expected": "a++ + b" }, { - "type": "ExpectActionError", - "input": "'foo' in", - "error_contains": "Unexpected end of expression: 'foo' in at the end of the expression ['foo' in]" - } - ] - }, - { - "name": "should ignore comments in expressions", - "path": "parser/parseAction/should ignore comments in expressions", - "assertions": [ + "type": "CheckAction", + "input": "a---b", + "expected": "a-- - b" + }, { "type": "CheckAction", - "input": "a //comment", - "expected": "a" - } - ] - }, - { - "name": "should parse instanceof expressions", - "path": "parser/parseAction/should parse instanceof expressions", - "assertions": [ + "input": "a+++b+++c", + "expected": "a++ + b++ + c" + }, { "type": "CheckAction", - "input": "obj instanceof MyClass", - "expected": "obj instanceof MyClass" + "input": "a+++ +b", + "expected": "a++ + +b" }, { "type": "CheckAction", - "input": "(obj instanceof MyClass) || false", - "expected": "(obj instanceof MyClass) || false" - } - ] - }, - { - "name": "should retain // in string literals", - "path": "parser/parseAction/should retain // in string literals", - "assertions": [ + "input": "a+ ++b", + "expected": "a + ++b" + }, { "type": "CheckAction", - "input": "\"http://www.google.com\"", - "expected": "\"http://www.google.com\"" - } - ] - }, - { - "name": "should parse an empty string", - "path": "parser/parseAction/should parse an empty string", - "assertions": [ + "input": "a- --b", + "expected": "a - --b" + }, { "type": "CheckAction", - "input": "", + "input": "a - -b", "expected": null + }, + { + "type": "CheckAction", + "input": "a + + b", + "expected": "a + +b" } ] }, { - "name": "should parse assignment operators with property reads", - "path": "parser/parseAction/should parse assignment operators with property reads", + "name": "should bind ++ and -- tighter than the other unary operators", + "path": "parser/parseAction/should bind ++ and -- tighter than the other unary operators", "assertions": [ { "type": "CheckAction", - "input": "a = b", + "input": "-a++", "expected": null }, { "type": "CheckAction", - "input": "a += b", + "input": "+a++", "expected": null }, { "type": "CheckAction", - "input": "a -= b", + "input": "!a++", "expected": null }, { "type": "CheckAction", - "input": "a *= b", + "input": "-a--", "expected": null }, { "type": "CheckAction", - "input": "a /= b", + "input": "typeof a++", "expected": null }, { "type": "CheckAction", - "input": "a %= b", + "input": "void a++", "expected": null - }, + } + ] + }, + { + "name": "should allow ++ and -- expressions as operands of other operators", + "path": "parser/parseAction/should allow ++ and -- expressions as operands of other operators", + "assertions": [ { "type": "CheckAction", - "input": "a **= b", + "input": "a++ instanceof b", "expected": null }, { "type": "CheckAction", - "input": "a &&= b", + "input": "a++ in o", "expected": null }, { "type": "CheckAction", - "input": "a ||= b", + "input": "a++ < 3", "expected": null }, { "type": "CheckAction", - "input": "a ??= b", + "input": "a++ * 2", "expected": null - } - ] - }, - { - "name": "should parse assignment operators with keyed reads", - "path": "parser/parseAction/should parse assignment operators with keyed reads", - "assertions": [ + }, { "type": "CheckAction", - "input": "a[0] = b", + "input": "a++ ?? b", "expected": null }, { "type": "CheckAction", - "input": "a[0] += b", + "input": "a ? b++ : c--", "expected": null }, { "type": "CheckAction", - "input": "a[0] -= b", + "input": "o[a++]", "expected": null }, { "type": "CheckAction", - "input": "a[0] *= b", + "input": "f(a++)", "expected": null }, { "type": "CheckAction", - "input": "a[0] /= b", + "input": "[a++]", "expected": null }, { "type": "CheckAction", - "input": "a[0] %= b", + "input": "{x: a++}", "expected": null }, { "type": "CheckAction", - "input": "a[0] **= b", + "input": "c = a++", "expected": null }, { "type": "CheckAction", - "input": "a[0] &&= b", + "input": "c += a++", "expected": null }, { "type": "CheckAction", - "input": "a[0] ||= b", + "input": "a.b[c++].d++", "expected": null }, { "type": "CheckAction", - "input": "a[0] ??= b", + "input": "a++ + ++a", "expected": null } ] @@ -2219,6 +2970,37 @@ } ] }, + { + "name": "should parse an arrow function containing update operators", + "path": "parser/parseBinding/arrow functions/should parse an arrow function containing update operators", + "assertions": [ + { + "type": "CheckBinding", + "input": "a => a++", + "expected": null + }, + { + "type": "CheckBinding", + "input": "a => a--", + "expected": null + }, + { + "type": "CheckBinding", + "input": "a => ++a", + "expected": null + }, + { + "type": "CheckBinding", + "input": "a => --a", + "expected": null + }, + { + "type": "CheckBinding", + "input": "(a) => a++", + "expected": "a => a++" + } + ] + }, { "name": "should be able to pass an arrow function through a pipe", "path": "parser/parseBinding/arrow functions/should be able to pass an arrow function through a pipe", @@ -2260,6 +3042,11 @@ "path": "parser/parseBinding/should report assignment", "assertions": [] }, + { + "name": "should report update operators in bindings", + "path": "parser/parseBinding/should report update operators in bindings", + "assertions": [] + }, { "name": "should report when encountering interpolation", "path": "parser/parseBinding/should report when encountering interpolation", @@ -2520,6 +3307,11 @@ "path": "parser/parseInterpolation/comments/should retain // in nested, unterminated strings", "assertions": [] }, + { + "name": "should ignore escaped quotes when looking for comments", + "path": "parser/parseInterpolation/comments/should ignore escaped quotes when looking for comments", + "assertions": [] + }, { "name": "should ignore quotes inside a comment", "path": "parser/parseInterpolation/comments/should ignore quotes inside a comment", @@ -2638,8 +3430,8 @@ ] }, { - "name": "should report when encountering field write", - "path": "parser/parseSimpleBinding/should report when encountering field write", + "name": "should report when encountering field write or update operators", + "path": "parser/parseSimpleBinding/should report when encountering field write or update operators", "assertions": [] }, { diff --git a/crates/angular_conformance/fixtures/expression_parser_serializer_spec.json b/crates/angular_conformance/fixtures/expression_parser_serializer_spec.json index 609823cf95..8124dd6e4e 100644 --- a/crates/angular_conformance/fixtures/expression_parser_serializer_spec.json +++ b/crates/angular_conformance/fixtures/expression_parser_serializer_spec.json @@ -348,6 +348,54 @@ "expected": "foo instanceof Bar" } ] + }, + { + "name": "should serialize prefix increment and decrement", + "path": "serializer/serialize/should serialize prefix increment and decrement", + "assertions": [ + { + "type": "SerializeExpression", + "input": " ++ foo ", + "expected": "++foo" + }, + { + "type": "SerializeExpression", + "input": " -- foo ", + "expected": "--foo" + } + ] + }, + { + "name": "should serialize postfix increment and decrement", + "path": "serializer/serialize/should serialize postfix increment and decrement", + "assertions": [ + { + "type": "SerializeExpression", + "input": " foo ++ ", + "expected": "foo++" + }, + { + "type": "SerializeExpression", + "input": " foo -- ", + "expected": "foo--" + } + ] + }, + { + "name": "should separate unary operators next to each other", + "path": "serializer/serialize/should separate unary operators next to each other", + "assertions": [ + { + "type": "SerializeExpression", + "input": " + ++foo ", + "expected": "+ ++foo" + }, + { + "type": "SerializeExpression", + "input": " - --foo ", + "expected": "- --foo" + } + ] } ] } diff --git a/crates/angular_conformance/fixtures/ml_parser_html_parser_spec.json b/crates/angular_conformance/fixtures/ml_parser_html_parser_spec.json index 440d49118a..4c39b0f3e6 100644 --- a/crates/angular_conformance/fixtures/ml_parser_html_parser_spec.json +++ b/crates/angular_conformance/fixtures/ml_parser_html_parser_spec.json @@ -657,6 +657,27 @@ } ] }, + { + "name": "should avoid walking the prototype when looking up entity names", + "path": "HtmlParser/parse/attributes/should avoid walking the prototype when looking up entity names", + "assertions": [ + { + "type": "HumanizeDom", + "input": "

{{ &constructor; }}

", + "expected": [ + ["html.Element", "p", 0.0], + [ + "html.Text", + "{{ &constructor; }}", + 1.0, + [""], + ["{{", " &constructor; ", "}}"], + [""] + ] + ] + } + ] + }, { "name": "should normalize line endings within attribute values", "path": "HtmlParser/parse/attributes/should normalize line endings within attribute values", @@ -1318,6 +1339,34 @@ } ] }, + { + "name": "ignored syntax", + "groups": [], + "tests": [ + { + "name": "should ignore doctype declaration", + "path": "HtmlParser/parse/ignored syntax/should ignore doctype declaration", + "assertions": [ + { + "type": "HumanizeDom", + "input": "hello", + "expected": [["html.Text", "hello", 0.0, ["hello"]]] + } + ] + }, + { + "name": "should ignore processing instruction", + "path": "HtmlParser/parse/ignored syntax/should ignore processing instruction", + "assertions": [ + { + "type": "HumanizeDom", + "input": "hello", + "expected": [["html.Text", "hello", 0.0, ["hello"]]] + } + ] + } + ] + }, { "name": "source spans", "groups": [], diff --git a/crates/angular_conformance/fixtures/ml_parser_lexer_spec.json b/crates/angular_conformance/fixtures/ml_parser_lexer_spec.json index 8a0daaa991..cb4db4b224 100644 --- a/crates/angular_conformance/fixtures/ml_parser_lexer_spec.json +++ b/crates/angular_conformance/fixtures/ml_parser_lexer_spec.json @@ -327,6 +327,111 @@ } ] }, + { + "name": "processing instructions", + "groups": [], + "tests": [ + { + "name": "should parse a processing instruction", + "path": "HtmlLexer/processing instructions/should parse a processing instruction", + "assertions": [ + { + "type": "HtmlLexerTest", + "input": "", + "test_type": "HumanizeParts", + "expected": [ + ["TokenType.PROCESSING_INSTRUCTION", "xml version=\"1.0\" encoding=\"UTF-8\""], + ["TokenType.EOF"] + ], + "options": null + } + ] + }, + { + "name": "should parse a processing instruction ending with > instead of ?>", + "path": "HtmlLexer/processing instructions/should parse a processing instruction ending with > instead of ?>", + "assertions": [ + { + "type": "HtmlLexerTest", + "input": "", + "test_type": "HumanizeParts", + "expected": [ + ["TokenType.PROCESSING_INSTRUCTION", "xml version=\"1.0\" encoding=\"UTF-8\""], + ["TokenType.EOF"] + ], + "options": null + } + ] + }, + { + "name": "should parse a processing instruction surrounded by other content", + "path": "HtmlLexer/processing instructions/should parse a processing instruction surrounded by other content", + "assertions": [ + { + "type": "HtmlLexerTest", + "input": "\n hello", + "test_type": "HumanizeParts", + "expected": [ + ["TokenType.DOC_TYPE", "DOCTYPE html"], + ["TokenType.TEXT", "\n"], + ["TokenType.PROCESSING_INSTRUCTION", "xml version=\"1.0\" encoding=\"UTF-8\""], + ["TokenType.TEXT", " hello"], + ["TokenType.EOF"] + ], + "options": null + } + ] + }, + { + "name": "should parse a processing instruction that contains question marks inside quoted content", + "path": "HtmlLexer/processing instructions/should parse a processing instruction that contains question marks inside quoted content", + "assertions": [ + { + "type": "HtmlLexerTest", + "input": "-8\"?>", + "test_type": "HumanizeParts", + "expected": [ + ["TokenType.PROCESSING_INSTRUCTION", "xml version=\"?\" encoding=\"UTF?>-8\""], + ["TokenType.EOF"] + ], + "options": null + } + ] + }, + { + "name": "should store the location of a processing instruction", + "path": "HtmlLexer/processing instructions/should store the location of a processing instruction", + "assertions": [ + { + "type": "HtmlLexerTest", + "input": "", + "test_type": "HumanizeSourceSpans", + "expected": [ + [ + "TokenType.PROCESSING_INSTRUCTION", + "" + ], + ["TokenType.EOF", ""] + ], + "options": null + } + ] + }, + { + "name": "should report missing end of a processing instruction", + "path": "HtmlLexer/processing instructions/should report missing end of a processing instruction", + "assertions": [ + { + "type": "HtmlLexerTest", + "input": "Content\n } @error (when err instanceof SpecificError) {\n
Specific Error
\n } @error (let err) {\n
General Error {{ err }}
\n }\n ", + "expected": [ + ["BoundaryBlock"], + ["Element", "div"], + ["Text", "Content"], + ["BoundaryErrorBlock", "err instanceof SpecificError"], + ["Variable", "$error", "$error"], + ["Variable", "$reset", "$reset"], + ["Element", "div"], + ["Text", "Specific Error"], + ["BoundaryErrorBlock", null], + ["Variable", "$error", "$error"], + ["Variable", "$reset", "$reset"], + ["Variable", "err", "$error"], + ["Element", "div"], + ["BoundText", "General Error {{ err }}"] + ], + "ignore_error": false + } + ] + } + ] + }, { "name": "Nodes without binding", "groups": [], @@ -279,6 +312,22 @@ } ] }, + { + "name": "should parse bound classes with dots in the name", + "path": "R3 template transform/Bound attributes/should parse bound classes with dots in the name", + "assertions": [ + { + "type": "ExpectFromHtml", + "input": "
", + "expected": [ + ["Element", "div"], + ["BoundAttribute", 2, "px-3.5", "v"], + ["BoundAttribute", 2, "a.b.c", "w"] + ], + "ignore_error": false + } + ] + }, { "name": "should parse mixed case bound styles", "path": "R3 template transform/Bound attributes/should parse mixed case bound styles", @@ -311,6 +360,11 @@ "ignore_error": false } ] + }, + { + "name": "should not attach i18n metadata for bound properties named after Object.prototype members", + "path": "R3 template transform/Bound attributes/should not attach i18n metadata for bound properties named after Object.prototype members", + "assertions": [] } ] }, @@ -958,6 +1012,23 @@ } ] }, + { + "name": "should ignore safe", + "expected": [ + ["Element", ":xhtml:div"], + ["TextAttribute", ":xmlns:xhtml", "http://www.w3.org/1999/xhtml"], + ["Element", ":xhtml:span"], + ["Text", "safe"] + ], + "ignore_error": false + } + ] + }, { "name": "should ignore