mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(parser): display jsx mismatch error, e.g. <Foo></Bar> (#3696)
relates #3548 I'll remove the closing name in a follow up PR. The snapshot is incorrect, so I created a follow up issue: https://github.com/oxc-project/oxc/issues/3697
This commit is contained in:
parent
4a004e2140
commit
d65c652700
5 changed files with 132 additions and 7 deletions
|
|
@ -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()])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
@ -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
tasks/coverage/misc/fail/oxc-3528.jsx
Normal file
5
tasks/coverage/misc/fail/oxc-3528.jsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
let a = <Apple></Banana>;
|
||||
|
||||
let b = <Apple:Orange></Banana>;
|
||||
|
||||
let c = <Apple.Orange></Banana>;
|
||||
|
|
@ -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]
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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() {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue