Skip to content

Commit

Permalink
fix(js_parser): allow metavariables to be placed in more locations (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ah-yu authored Mar 4, 2025
1 parent ff2b6b2 commit d0d5566
Show file tree
Hide file tree
Showing 21 changed files with 183 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ mod tests {
.unwrap()
.replace_all(&formatted, "normalizer: [address redacted]");

insta::assert_snapshot!(&snapshot, @r###"
insta::assert_snapshot!(&snapshot, @r#"
CodeSnippet(
GritCodeSnippet {
patterns: [
Expand Down Expand Up @@ -1112,32 +1112,6 @@ mod tests {
},
),
),
(
JsSyntaxKind(
JSX_TEXT,
),
AstNode(
GritNodePattern {
kind: JsSyntaxKind(
JSX_TEXT,
),
args: [
GritNodePatternArg {
slot_index: 0,
pattern: AstLeafNode(
GritLeafNodePattern {
kind: JsSyntaxKind(
JSX_TEXT_LITERAL,
),
equivalence_class: None,
text: "µfn && µfn()",
},
),
},
],
},
),
),
(
JsSyntaxKind(
JS_PROPERTY_OBJECT_MEMBER,
Expand Down Expand Up @@ -1329,6 +1303,6 @@ mod tests {
),
},
)
"###);
"#);
}
}
4 changes: 4 additions & 0 deletions crates/biome_grit_patterns/tests/specs/tsx/jsx.grit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
or {
`<µ_ µ_ >µ_</µ_>`,
`<µ_ µ_ />`
}
13 changes: 13 additions & 0 deletions crates/biome_grit_patterns/tests/specs/tsx/jsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: crates/biome_grit_patterns/tests/spec_tests.rs
expression: jsx
---
SnapshotResult {
messages: [],
matched_ranges: [
"1:1-1:33",
"2:1-2:19",
],
rewritten_files: [],
created_files: [],
}
2 changes: 2 additions & 0 deletions crates/biome_grit_patterns/tests/specs/tsx/jsx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<div style={{}}>{children}</div>;
<div style={{}} />
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ impl Rule for UseSortedAttributes {
prop_groups.push(current_prop_group);
current_prop_group = PropGroup::default();
}
AnyJsxAttribute::JsMetavariable(_) => {}
}
}
prop_groups.push(current_prop_group);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ fn has_for_attribute(jsx_tag: &AnyJsxTag) -> bool {
}
})
.is_some_and(|jsx_name| for_attributes.contains(&jsx_name.text_trimmed())),
AnyJsxAttribute::JsxSpreadAttribute(_) => false,
AnyJsxAttribute::JsxSpreadAttribute(_) | AnyJsxAttribute::JsMetavariable(_) => false,
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl Rule for UseKeyWithClickEvents {
return None;
}
}
AnyJsxAttribute::JsxSpreadAttribute(_) => {
AnyJsxAttribute::JsxSpreadAttribute(_) | AnyJsxAttribute::JsMetavariable(_) => {
return None;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ impl Rule for NoUselessFragments {
AnyJsxElementName::JsxReferenceIdentifier(identifier) => {
jsx_reference_identifier_is_fragment(&identifier, model)?
}
AnyJsxElementName::JsxName(_) | AnyJsxElementName::JsxNamespaceName(_) => false,
AnyJsxElementName::JsxName(_)
| AnyJsxElementName::JsxNamespaceName(_)
| AnyJsxElementName::JsMetavariable(_) => false,
};

if is_valid_react_fragment {
Expand Down Expand Up @@ -413,7 +415,7 @@ impl Rule for NoUselessFragments {
// can't apply a code action because it will create invalid syntax
// for example `<>{...foo}</>` would become `{...foo}` which would produce
// a syntax error
AnyJsxChild::JsxSpreadChild(_) => return None,
AnyJsxChild::JsxSpreadChild(_) | AnyJsxChild::JsMetavariable(_) => return None,
};
if let Some(new_node) = new_node {
mutation.replace_element(parent.into(), new_node.into());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ impl Rule for UseGoogleFontPreconnect {
let last_attr_token = match attributes.last()? {
AnyJsxAttribute::JsxAttribute(a) => a.name_value_token().ok()?,
AnyJsxAttribute::JsxSpreadAttribute(a) => a.l_curly_token().ok()?,
AnyJsxAttribute::JsMetavariable(a) => a.value_token().ok()?,
};

let rel = if last_attr_token.has_leading_whitespace_or_newline() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ impl Rule for UseFragmentSyntax {
AnyJsxElementName::JsxReferenceIdentifier(identifier) => {
jsx_reference_identifier_is_fragment(&identifier, model)?
}
AnyJsxElementName::JsxName(_) | AnyJsxElementName::JsxNamespaceName(_) => false,
AnyJsxElementName::JsxName(_)
| AnyJsxElementName::JsxNamespaceName(_)
| AnyJsxElementName::JsMetavariable(_) => false,
};

if maybe_invalid && opening_element.attributes().is_empty() {
Expand Down
1 change: 1 addition & 0 deletions crates/biome_js_formatter/src/jsx/any/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ impl FormatRule<AnyJsxAttribute> for FormatAnyJsxAttribute {
type Context = JsFormatContext;
fn fmt(&self, node: &AnyJsxAttribute, f: &mut JsFormatter) -> FormatResult<()> {
match node {
AnyJsxAttribute::JsMetavariable(node) => node.format().fmt(f),
AnyJsxAttribute::JsxAttribute(node) => node.format().fmt(f),
AnyJsxAttribute::JsxSpreadAttribute(node) => node.format().fmt(f),
}
Expand Down
1 change: 1 addition & 0 deletions crates/biome_js_formatter/src/jsx/any/child.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ impl FormatRule<AnyJsxChild> for FormatAnyJsxChild {
type Context = JsFormatContext;
fn fmt(&self, node: &AnyJsxChild, f: &mut JsFormatter) -> FormatResult<()> {
match node {
AnyJsxChild::JsMetavariable(node) => node.format().fmt(f),
AnyJsxChild::JsxElement(node) => node.format().fmt(f),
AnyJsxChild::JsxExpressionChild(node) => node.format().fmt(f),
AnyJsxChild::JsxFragment(node) => node.format().fmt(f),
Expand Down
1 change: 1 addition & 0 deletions crates/biome_js_formatter/src/jsx/any/element_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ impl FormatRule<AnyJsxElementName> for FormatAnyJsxElementName {
type Context = JsFormatContext;
fn fmt(&self, node: &AnyJsxElementName, f: &mut JsFormatter) -> FormatResult<()> {
match node {
AnyJsxElementName::JsMetavariable(node) => node.format().fmt(f),
AnyJsxElementName::JsxMemberName(node) => node.format().fmt(f),
AnyJsxElementName::JsxName(node) => node.format().fmt(f),
AnyJsxElementName::JsxNamespaceName(node) => node.format().fmt(f),
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_js_formatter/src/jsx/tag/opening_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,6 @@ fn as_string_literal_attribute_value(attribute: &AnyJsxAttribute) -> Option<JsxS
_ => None,
})
}
JsxSpreadAttribute(_) => None,
JsxSpreadAttribute(_) | JsMetavariable(_) => None,
}
}
3 changes: 3 additions & 0 deletions crates/biome_js_parser/src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@ impl<'src> JsLexer<'src> {
b'<' => self.eat_byte(T![<]),
// `{`: empty jsx text, directly followed by an expression
b'{' => self.eat_byte(T!['{']),
_ if self.options.should_parse_metavariables() && self.is_metavariable_start() => {
self.consume_metavariable(GRIT_METAVARIABLE)
}
_ => {
while let Some(chr) = self.current_byte() {
// but not one of: { or < or > or }
Expand Down
11 changes: 10 additions & 1 deletion crates/biome_js_parser/src/syntax/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::syntax::typescript::TypeContext;
use crate::{Absent, Present};
use crate::{JsParser, ParseRecoveryTokenSet, ParsedSyntax, parser::RecoveryResult};

use super::metavariable::{is_at_metavariable, is_nth_at_metavariable, parse_metavariable};
use super::typescript::parse_ts_type_arguments;

// test jsx jsx_element_on_return
Expand Down Expand Up @@ -52,7 +53,10 @@ pub(crate) fn parse_jsx_tag_expression(p: &mut JsParser) -> ParsedSyntax {
return Absent;
}

if !p.nth_at(1, T![>]) && !is_nth_at_identifier_or_keyword(p, 1) {
if !p.nth_at(1, T![>])
&& !is_nth_at_identifier_or_keyword(p, 1)
&& !is_nth_at_metavariable(p, 1)
{
return Absent;
}

Expand Down Expand Up @@ -314,6 +318,7 @@ impl ParseNodeList for JsxChildrenList {
// </a>
T![<] => parse_any_jsx_tag(p, false),
T!['{'] => parse_jsx_expression_child(p),
JsSyntaxKind::GRIT_METAVARIABLE => parse_metavariable(p),
// test jsx jsx_text
// <a>test</a>;
// <a> whitespace handling </a>;
Expand Down Expand Up @@ -471,6 +476,8 @@ fn parse_jsx_name(p: &mut JsParser) -> ParsedSyntax {
let name = p.start();
p.bump(JSX_IDENT);
Present(name.complete(p, JSX_NAME))
} else if is_at_metavariable(p) {
parse_metavariable(p)
} else {
Absent
}
Expand All @@ -494,6 +501,8 @@ impl ParseNodeList for JsxAttributeList {
fn parse_element(&mut self, p: &mut JsParser) -> ParsedSyntax {
if matches!(p.cur(), T!['{'] | T![...]) {
parse_jsx_spread_attribute(p)
} else if is_at_metavariable(p) {
parse_metavariable(p)
} else {
parse_jsx_attribute(p)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ const { µkey: key } = { µkey: µvalue };
function µfunctionName() {}

type µType = µOtherType;

<µtag µ_>µ_</µtag>
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
---
source: crates/biome_js_parser/tests/spec_test.rs
expression: snapshot
snapshot_kind: text
---
## Input

```ts
```tsx
import µdefaultImport from µsource;
import { µnamedImport, type µnamedType } from µsource;
Expand All @@ -26,6 +25,8 @@ function µfunctionName() {}
type µType = µOtherType;
<µtag µ_>µ_</µtag>
```


Expand Down Expand Up @@ -240,19 +241,52 @@ JsModule {
},
semicolon_token: SEMICOLON@311..312 ";" [] [],
},
JsExpressionStatement {
expression: JsxTagExpression {
tag: JsxElement {
opening_element: JsxOpeningElement {
l_angle_token: L_ANGLE@312..315 "<" [Newline("\n"), Newline("\n")] [],
name: JsMetavariable {
value_token: GRIT_METAVARIABLE@315..321 "µtag" [] [Whitespace(" ")],
},
type_arguments: missing (optional),
attributes: JsxAttributeList [
JsMetavariable {
value_token: GRIT_METAVARIABLE@321..324 "µ_" [] [],
},
],
r_angle_token: R_ANGLE@324..325 ">" [] [],
},
children: JsxChildList [
JsMetavariable {
value_token: GRIT_METAVARIABLE@325..328 "µ_" [] [],
},
],
closing_element: JsxClosingElement {
l_angle_token: L_ANGLE@328..329 "<" [] [],
slash_token: SLASH@329..330 "/" [] [],
name: JsMetavariable {
value_token: GRIT_METAVARIABLE@330..335 "µtag" [] [],
},
r_angle_token: R_ANGLE@335..336 ">" [] [],
},
},
},
semicolon_token: missing (optional),
},
],
eof_token: EOF@312..313 "" [Newline("\n")] [],
eof_token: EOF@336..337 "" [Newline("\n")] [],
}
```

## CST

```
0: JS_MODULE@0..313
0: JS_MODULE@0..337
0: (empty)
1: (empty)
2: JS_DIRECTIVE_LIST@0..0
3: JS_MODULE_ITEM_LIST@0..312
3: JS_MODULE_ITEM_LIST@0..336
0: JS_IMPORT@0..37
0: IMPORT_KW@0..7 "import" [] [Whitespace(" ")]
1: JS_IMPORT_DEFAULT_CLAUSE@7..36
Expand Down Expand Up @@ -399,6 +433,28 @@ JsModule {
4: JS_METAVARIABLE@300..311
0: GRIT_METAVARIABLE@300..311 "µOtherType" [] []
5: SEMICOLON@311..312 ";" [] []
4: EOF@312..313 "" [Newline("\n")] []
8: JS_EXPRESSION_STATEMENT@312..336
0: JSX_TAG_EXPRESSION@312..336
0: JSX_ELEMENT@312..336
0: JSX_OPENING_ELEMENT@312..325
0: L_ANGLE@312..315 "<" [Newline("\n"), Newline("\n")] []
1: JS_METAVARIABLE@315..321
0: GRIT_METAVARIABLE@315..321 "µtag" [] [Whitespace(" ")]
2: (empty)
3: JSX_ATTRIBUTE_LIST@321..324
0: JS_METAVARIABLE@321..324
0: GRIT_METAVARIABLE@321..324 "µ_" [] []
4: R_ANGLE@324..325 ">" [] []
1: JSX_CHILD_LIST@325..328
0: JS_METAVARIABLE@325..328
0: GRIT_METAVARIABLE@325..328 "µ_" [] []
2: JSX_CLOSING_ELEMENT@328..336
0: L_ANGLE@328..329 "<" [] []
1: SLASH@329..330 "/" [] []
2: JS_METAVARIABLE@330..335
0: GRIT_METAVARIABLE@330..335 "µtag" [] []
3: R_ANGLE@335..336 ">" [] []
1: (empty)
4: EOF@336..337 "" [Newline("\n")] []
```
Loading

0 comments on commit d0d5566

Please sign in to comment.