Skip to content

Commit

Permalink
fix(js_parser): fix Parser is no longer progressing for an invalid ob…
Browse files Browse the repository at this point in the history
…ject member name (#4423)
  • Loading branch information
denbezrukov authored Oct 29, 2024
1 parent d4d6a93 commit 6a8ff77
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 1 deletion.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

Contributed by @fireairforce

- Fix [#342](https://github.com/biomejs/biome/issues/342), js parser is no longer progressing for an invalid object
member name:

```js
({
params: { [paramName: string]: number } = {}
})
```

Contributed by @denbezrukov

## v1.9.4 (2024-10-17)

Expand Down
13 changes: 12 additions & 1 deletion crates/biome_js_parser/src/syntax/assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::JsParser;
use crate::ParsedSyntax::{Absent, Present};
use biome_js_syntax::{JsSyntaxKind::*, *};
use biome_parser::diagnostic::expected_any;
use biome_parser::parse_recovery::ParseRecoveryTokenSet;
use biome_rowan::AstNode;

use super::metavariable::is_at_metavariable;
Expand Down Expand Up @@ -302,7 +303,17 @@ impl ParseObjectPattern for ObjectAssignmentPattern {
.or_add_diagnostic(p, expected_identifier);
JS_OBJECT_ASSIGNMENT_PATTERN_SHORTHAND_PROPERTY
} else if is_at_object_member_name(p) || p.at(T![:]) || p.nth_at(1, T![:]) {
parse_object_member_name(p).or_add_diagnostic(p, expected_object_member_name);
// If the parser is at an object member name, parse it and look for the colon token next.
// If `p.nth_at(1, T![:])` is true, it indicates an invalid object member name before the colon,
// such as `{%: 1}`. In this case, attempt to recover by finding the next colon token.
parse_object_member_name(p)
.or_recover_with_token_set(
p,
&ParseRecoveryTokenSet::new(JS_BOGUS, token_set![T![:]])
.enable_recovery_on_line_break(),
expected_object_member_name,
)
.ok();
p.expect(T![:]);
parse_assignment_pattern(p).or_add_diagnostic(p, expected_assignment_target);
JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// here we have an invalid object member name '%'
({%: y} = {})

// here we have invalid object member name
({
params: { [paramName: string]: number } = {}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
---
source: crates/biome_js_parser/tests/spec_test.rs
expression: snapshot
---
## Input

```jsx
// here we have an invalid object member name '%'
({%: y} = {})
// here we have invalid object member name
({
params: { [paramName: string]: number } = {}
})
```


## AST

```
JsModule {
bom_token: missing (optional),
interpreter_token: missing (optional),
directives: JsDirectiveList [],
items: JsModuleItemList [
JsExpressionStatement {
expression: JsCallExpression {
callee: JsParenthesizedExpression {
l_paren_token: L_PAREN@0..51 "(" [Comments("// here we have an in ..."), Newline("\n")] [],
expression: JsBogusExpression {
items: [
JsBogus {
items: [
L_CURLY@51..52 "{" [] [],
JsBogus {
items: [
JsBogus {
items: [
JsBogus {
items: [
PERCENT@52..53 "%" [] [],
],
},
COLON@53..55 ":" [] [Whitespace(" ")],
JsIdentifierAssignment {
name_token: IDENT@55..56 "y" [] [],
},
],
},
],
},
R_CURLY@56..58 "}" [] [Whitespace(" ")],
],
},
EQ@58..60 "=" [] [Whitespace(" ")],
JsObjectExpression {
l_curly_token: L_CURLY@60..61 "{" [] [],
members: JsObjectMemberList [],
r_curly_token: R_CURLY@61..62 "}" [] [],
},
],
},
r_paren_token: R_PAREN@62..63 ")" [] [],
},
optional_chain_token: missing (optional),
type_arguments: missing (optional),
arguments: JsCallArguments {
l_paren_token: L_PAREN@63..109 "(" [Newline("\n"), Newline("\n"), Comments("// here we have inval ..."), Newline("\n")] [],
args: JsCallArgumentList [
JsObjectExpression {
l_curly_token: L_CURLY@109..110 "{" [] [],
members: JsObjectMemberList [
JsPropertyObjectMember {
name: JsLiteralMemberName {
value: IDENT@110..118 "params" [Newline("\n"), Whitespace("\t")] [],
},
colon_token: COLON@118..120 ":" [] [Whitespace(" ")],
value: JsBogusExpression {
items: [
JsBogus {
items: [
L_CURLY@120..122 "{" [] [Whitespace(" ")],
JsBogus {
items: [
JsObjectAssignmentPatternProperty {
member: JsComputedMemberName {
l_brack_token: L_BRACK@122..123 "[" [] [],
expression: JsIdentifierExpression {
name: JsReferenceIdentifier {
value_token: IDENT@123..132 "paramName" [] [],
},
},
r_brack_token: missing (required),
},
colon_token: COLON@132..134 ":" [] [Whitespace(" ")],
pattern: JsIdentifierAssignment {
name_token: IDENT@134..140 "string" [] [],
},
init: missing (optional),
},
JsBogus {
items: [
JsBogus {
items: [
R_BRACK@140..141 "]" [] [],
],
},
COLON@141..143 ":" [] [Whitespace(" ")],
JsIdentifierAssignment {
name_token: IDENT@143..150 "number" [] [Whitespace(" ")],
},
],
},
],
},
R_CURLY@150..152 "}" [] [Whitespace(" ")],
],
},
EQ@152..154 "=" [] [Whitespace(" ")],
JsObjectExpression {
l_curly_token: L_CURLY@154..155 "{" [] [],
members: JsObjectMemberList [],
r_curly_token: R_CURLY@155..156 "}" [] [],
},
],
},
},
],
r_curly_token: R_CURLY@156..158 "}" [Newline("\n")] [],
},
],
r_paren_token: R_PAREN@158..159 ")" [] [],
},
},
semicolon_token: missing (optional),
},
],
eof_token: EOF@159..160 "" [Newline("\n")] [],
}
```

## CST

```
0: JS_MODULE@0..160
0: (empty)
1: (empty)
2: JS_DIRECTIVE_LIST@0..0
3: JS_MODULE_ITEM_LIST@0..159
0: JS_EXPRESSION_STATEMENT@0..159
0: JS_CALL_EXPRESSION@0..159
0: JS_PARENTHESIZED_EXPRESSION@0..63
0: L_PAREN@0..51 "(" [Comments("// here we have an in ..."), Newline("\n")] []
1: JS_BOGUS_EXPRESSION@51..62
0: JS_BOGUS@51..58
0: L_CURLY@51..52 "{" [] []
1: JS_BOGUS@52..56
0: JS_BOGUS@52..56
0: JS_BOGUS@52..53
0: PERCENT@52..53 "%" [] []
1: COLON@53..55 ":" [] [Whitespace(" ")]
2: JS_IDENTIFIER_ASSIGNMENT@55..56
0: IDENT@55..56 "y" [] []
2: R_CURLY@56..58 "}" [] [Whitespace(" ")]
1: EQ@58..60 "=" [] [Whitespace(" ")]
2: JS_OBJECT_EXPRESSION@60..62
0: L_CURLY@60..61 "{" [] []
1: JS_OBJECT_MEMBER_LIST@61..61
2: R_CURLY@61..62 "}" [] []
2: R_PAREN@62..63 ")" [] []
1: (empty)
2: (empty)
3: JS_CALL_ARGUMENTS@63..159
0: L_PAREN@63..109 "(" [Newline("\n"), Newline("\n"), Comments("// here we have inval ..."), Newline("\n")] []
1: JS_CALL_ARGUMENT_LIST@109..158
0: JS_OBJECT_EXPRESSION@109..158
0: L_CURLY@109..110 "{" [] []
1: JS_OBJECT_MEMBER_LIST@110..156
0: JS_PROPERTY_OBJECT_MEMBER@110..156
0: JS_LITERAL_MEMBER_NAME@110..118
0: IDENT@110..118 "params" [Newline("\n"), Whitespace("\t")] []
1: COLON@118..120 ":" [] [Whitespace(" ")]
2: JS_BOGUS_EXPRESSION@120..156
0: JS_BOGUS@120..152
0: L_CURLY@120..122 "{" [] [Whitespace(" ")]
1: JS_BOGUS@122..150
0: JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY@122..140
0: JS_COMPUTED_MEMBER_NAME@122..132
0: L_BRACK@122..123 "[" [] []
1: JS_IDENTIFIER_EXPRESSION@123..132
0: JS_REFERENCE_IDENTIFIER@123..132
0: IDENT@123..132 "paramName" [] []
2: (empty)
1: COLON@132..134 ":" [] [Whitespace(" ")]
2: JS_IDENTIFIER_ASSIGNMENT@134..140
0: IDENT@134..140 "string" [] []
3: (empty)
1: JS_BOGUS@140..150
0: JS_BOGUS@140..141
0: R_BRACK@140..141 "]" [] []
1: COLON@141..143 ":" [] [Whitespace(" ")]
2: JS_IDENTIFIER_ASSIGNMENT@143..150
0: IDENT@143..150 "number" [] [Whitespace(" ")]
2: R_CURLY@150..152 "}" [] [Whitespace(" ")]
1: EQ@152..154 "=" [] [Whitespace(" ")]
2: JS_OBJECT_EXPRESSION@154..156
0: L_CURLY@154..155 "{" [] []
1: JS_OBJECT_MEMBER_LIST@155..155
2: R_CURLY@155..156 "}" [] []
2: R_CURLY@156..158 "}" [Newline("\n")] []
2: R_PAREN@158..159 ")" [] []
1: (empty)
4: EOF@159..160 "" [Newline("\n")] []
```
## Diagnostics
```
property_assignment_target_err_1.js:2:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected an identifier, a string literal, a number literal, or a computed property but instead found '%'.
1// here we have an invalid object member name '%'
> 2 │ ({%: y} = {})
^
3
4// here we have invalid object member name
i Expected an identifier, a string literal, a number literal, or a computed property here.
1// here we have an invalid object member name '%'
> 2 │ ({%: y} = {})
^
3
4// here we have invalid object member name
property_assignment_target_err_1.js:6:22 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× expected `]` but instead found `:`
4// here we have invalid object member name
5 │ ({
> 6 │ params: { [paramName: string]: number } = {}
^
7 │ })
8
i Remove :
property_assignment_target_err_1.js:6:30 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× expected `,` but instead found `]`
4// here we have invalid object member name
5 │ ({
> 6 │ params: { [paramName: string]: number } = {}
^
7 │ })
8
i Remove ]
```

0 comments on commit 6a8ff77

Please sign in to comment.