Skip to content

Commit

Permalink
feat(parser): display jsx mismatch error, e.g. <Foo></Bar> (#3696)
Browse files Browse the repository at this point in the history
relates #3548 

I'll remove the closing name in a follow up PR.

The snapshot is incorrect, so I created a follow up issue:
#3697
  • Loading branch information
Boshen authored Jun 15, 2024
1 parent 4a004e2 commit d65c652
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 7 deletions.
6 changes: 6 additions & 0 deletions crates/oxc_parser/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,9 @@ pub fn static_constructor(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::error("TS1089: `static` modifier cannot appear on a constructor declaration.")
.with_labels([span0.into()])
}

#[cold]
pub fn jsx_element_no_match(span0: Span, span1: Span, name: &str) -> OxcDiagnostic {
OxcDiagnostic::error(format!("Expected corresponding JSX closing tag for '{name}'."))
.with_labels([span0.into(), span1.into()])
}
53 changes: 49 additions & 4 deletions crates/oxc_parser/src/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use oxc_allocator::{Box, Vec};
use oxc_ast::ast::*;
use oxc_diagnostics::Result;
use oxc_span::{Atom, Span};
use oxc_span::{Atom, GetSpan, Span};

use crate::{diagnostics, lexer::Kind, Context, ParserImpl};

Expand Down Expand Up @@ -62,9 +62,19 @@ impl<'a> ParserImpl<'a> {
} else {
self.parse_jsx_children()?
};
let closing_element = (!opening_element.self_closing)
.then(|| self.parse_jsx_closing_element(in_jsx_child))
.transpose()?;
let closing_element = if opening_element.self_closing {
None
} else {
let closing_element = self.parse_jsx_closing_element(in_jsx_child)?;
if !Self::jsx_element_name_eq(&opening_element.name, &closing_element.name) {
self.error(diagnostics::jsx_element_no_match(
opening_element.name.span(),
closing_element.name.span(),
opening_element.name.span().source_text(self.source_text),
));
}
Some(closing_element)
};
Ok(self.ast.jsx_element(self.end_span(span), opening_element, closing_element, children))
}

Expand Down Expand Up @@ -378,4 +388,39 @@ impl<'a> ParserImpl<'a> {
self.bump_any();
self.ast.jsx_text(self.end_span(span), value)
}

fn jsx_element_name_eq(lhs: &JSXElementName<'a>, rhs: &JSXElementName<'a>) -> bool {
match (lhs, rhs) {
(JSXElementName::Identifier(lhs), JSXElementName::Identifier(rhs)) => {
lhs.name == rhs.name
}
(JSXElementName::NamespacedName(lhs), JSXElementName::NamespacedName(rhs)) => {
lhs.namespace.name == rhs.namespace.name && lhs.property.name == rhs.property.name
}
(JSXElementName::MemberExpression(lhs), JSXElementName::MemberExpression(rhs)) => {
Self::jsx_member_expression_eq(lhs, rhs)
}
_ => false,
}
}

fn jsx_member_expression_eq(
lhs: &JSXMemberExpression<'a>,
rhs: &JSXMemberExpression<'a>,
) -> bool {
if lhs.property.name != rhs.property.name {
return false;
}
match (&lhs.object, &rhs.object) {
(
JSXMemberExpressionObject::Identifier(lhs),
JSXMemberExpressionObject::Identifier(rhs),
) => lhs.name == rhs.name,
(
JSXMemberExpressionObject::MemberExpression(lhs),
JSXMemberExpressionObject::MemberExpression(rhs),
) => Self::jsx_member_expression_eq(lhs, rhs),
_ => false,
}
}
}
5 changes: 5 additions & 0 deletions tasks/coverage/misc/fail/oxc-3528.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let a = <Apple></Banana>;

let b = <Apple:Orange></Banana>;

let c = <Apple.Orange></Banana>;
24 changes: 23 additions & 1 deletion tasks/coverage/parser_misc.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 18/18 (100.00%)
Positive Passed: 18/18 (100.00%)
Negative Passed: 9/9 (100.00%)
Negative Passed: 10/10 (100.00%)

× Unexpected token
╭─[fail/oxc-169.js:2:1]
Expand Down Expand Up @@ -105,6 +105,28 @@ Negative Passed: 9/9 (100.00%)
· ─
╰────

× Expected corresponding JSX closing tag for 'Apple'.
╭─[fail/oxc-3528.jsx:1:10]
1 │ let a = <Apple></Banana>;
· ───── ──────
2 │
╰────

× Expected corresponding JSX closing tag for 'Apple:Orange'.
╭─[fail/oxc-3528.jsx:3:10]
2 │
3 │ let b = <Apple:Orange></Banana>;
· ──────────── ──────
4 │
╰────

× Expected corresponding JSX closing tag for 'Apple.Orange'.
╭─[fail/oxc-3528.jsx:5:10]
4 │
5 │ let c = <Apple.Orange></Banana>;
· ──────────── ──────
╰────

× The keyword 'let' is reserved
╭─[fail/oxc.js:1:1]
1 │ let.a = 1;
Expand Down
51 changes: 49 additions & 2 deletions tasks/coverage/parser_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ commit: d8086f14
parser_typescript Summary:
AST Parsed : 5280/5283 (99.94%)
Positive Passed: 5273/5283 (99.81%)
Negative Passed: 1052/4875 (21.58%)
Negative Passed: 1053/4875 (21.60%)
Expect Syntax Error: "compiler/ClassDeclaration10.ts"
Expect Syntax Error: "compiler/ClassDeclaration11.ts"
Expect Syntax Error: "compiler/ClassDeclaration13.ts"
Expand Down Expand Up @@ -3026,7 +3026,6 @@ Expect Syntax Error: "conformance/jsx/tsxUnionElementType3.tsx"
Expect Syntax Error: "conformance/jsx/tsxUnionElementType4.tsx"
Expect Syntax Error: "conformance/jsx/tsxUnionElementType6.tsx"
Expect Syntax Error: "conformance/jsx/tsxUnionTypeComponent2.tsx"
Expect Syntax Error: "conformance/jsx/unicodeEscapesInJsxtags.tsx"
Expect Syntax Error: "conformance/override/override1.ts"
Expect Syntax Error: "conformance/override/override11.ts"
Expect Syntax Error: "conformance/override/override13.ts"
Expand Down Expand Up @@ -16206,6 +16205,54 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
42 │ }
╰────

× Expected corresponding JSX closing tag for '\u0061'.
╭─[conformance/jsx/unicodeEscapesInJsxtags.tsx:15:4]
14 │ // tag name:
15 │ ; <\u0061></a>
· ────── ─
16 │ ; <\u0061-b></a-b>
╰────

× Expected corresponding JSX closing tag for '\u0061-b'.
╭─[conformance/jsx/unicodeEscapesInJsxtags.tsx:16:4]
15 │ ; <\u0061></a>
16 │ ; <\u0061-b></a-b>
· ──────── ───
17 │ ; <a-\u0063></a-c>
╰────

× Expected corresponding JSX closing tag for 'a-'.
╭─[conformance/jsx/unicodeEscapesInJsxtags.tsx:17:4]
16 │ ; <\u0061-b></a-b>
17 │ ; <a-\u0063></a-c>
· ── ───
18 │ ; <Comp\u0061 x={12} />
╰────

× Expected corresponding JSX closing tag for '\u{0061}'.
╭─[conformance/jsx/unicodeEscapesInJsxtags.tsx:20:4]
19 │ ; <x.\u0076ideo />
20 │ ; <\u{0061}></a>
· ──────── ─
21 │ ; <\u{0061}-b></a-b>
╰────

× Expected corresponding JSX closing tag for '\u{0061}-b'.
╭─[conformance/jsx/unicodeEscapesInJsxtags.tsx:21:4]
20 │ ; <\u{0061}></a>
21 │ ; <\u{0061}-b></a-b>
· ────────── ───
22 │ ; <a-\u{0063}></a-c>
╰────

× Expected corresponding JSX closing tag for 'a-'.
╭─[conformance/jsx/unicodeEscapesInJsxtags.tsx:22:4]
21 │ ; <\u{0061}-b></a-b>
22 │ ; <a-\u{0063}></a-c>
· ── ───
23 │ ; <Comp\u{0061} x={12} />
╰────

× Expected `(` but found `Identifier`
╭─[conformance/override/overrideKeywordOrder.ts:12:18]
11 │ override async m1() {}
Expand Down

0 comments on commit d65c652

Please sign in to comment.