Skip to content

Commit cc07efd

Browse files
fix(ast/estree): Fix JSXOpeningFragment (#10208)
Fix `JSXOpeningFragment` node estree TS serialization by skipping `selfClosing` and `attributes` fields when AST is TS. Part of our broader work to align our AST's ESTree output with that of TS-ESLint's. Relates to #9705 --------- Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
1 parent 48ed6a1 commit cc07efd

File tree

7 files changed

+49
-42
lines changed

7 files changed

+49
-42
lines changed

crates/oxc_ast/src/ast/jsx.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub struct JSXFragment<'a> {
128128
#[ast(visit)]
129129
#[derive(Debug)]
130130
#[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)]
131-
#[estree(add_fields(attributes = JSXOpeningFragmentAttributes, selfClosing = False))]
131+
#[estree(via = JSXOpeningFragmentConverter, add_fields(attributes = JSXOpeningFragmentAttributes, selfClosing = TsFalse))]
132132
pub struct JSXOpeningFragment {
133133
/// Node location in source code
134134
pub span: Span,

crates/oxc_ast/src/generated/derive_estree.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,13 +2026,7 @@ impl ESTree for JSXFragment<'_> {
20262026

20272027
impl ESTree for JSXOpeningFragment {
20282028
fn serialize<S: Serializer>(&self, serializer: S) {
2029-
let mut state = serializer.serialize_struct();
2030-
state.serialize_field("type", &JsonSafeString("JSXOpeningFragment"));
2031-
state.serialize_field("start", &self.span.start);
2032-
state.serialize_field("end", &self.span.end);
2033-
state.serialize_field("attributes", &crate::serialize::JSXOpeningFragmentAttributes(self));
2034-
state.serialize_field("selfClosing", &crate::serialize::False(self));
2035-
state.end();
2029+
crate::serialize::JSXOpeningFragmentConverter(self).serialize(serializer)
20362030
}
20372031
}
20382032

crates/oxc_ast/src/serialize.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,17 @@ impl ESTree for JSXOpeningElementSelfClosing<'_, '_> {
870870
}
871871
}
872872

873+
#[ast_meta]
874+
#[estree(ts_type = "Array<JSXAttributeItem>", raw_deser = "[]")]
875+
#[ts]
876+
pub struct JSXOpeningFragmentAttributes<'b>(#[expect(dead_code)] pub &'b JSXOpeningFragment);
877+
878+
impl ESTree for JSXOpeningFragmentAttributes<'_> {
879+
fn serialize<S: Serializer>(&self, serializer: S) {
880+
[(); 0].serialize(serializer);
881+
}
882+
}
883+
873884
/// Serializer for `IdentifierReference` variant of `JSXElementName` and `JSXMemberExpressionObject`.
874885
///
875886
/// Convert to `JSXIdentifier`.
@@ -908,13 +919,38 @@ impl ESTree for JSXElementThisExpression<'_> {
908919
}
909920
}
910921

922+
/// Serializer for `JSXOpeningFragment`.
923+
///
924+
/// Add `attributes` and `selfClosing` fields in JS AST, but not in TS AST.
925+
/// Acorn-JSX has these fields, but TS-ESLint parser does not.
926+
//
927+
// TODO: Find a better way to do this.
911928
#[ast_meta]
912-
#[estree(ts_type = "Array<JSXAttributeItem>", raw_deser = "[]")]
913-
pub struct JSXOpeningFragmentAttributes<'b>(#[expect(dead_code)] pub &'b JSXOpeningFragment);
929+
#[estree(raw_deser = "
930+
const node = {
931+
type: 'JSXOpeningFragment',
932+
start: DESER[u32](POS_OFFSET.span.start),
933+
end: DESER[u32](POS_OFFSET.span.end),
934+
/* IF_JS */
935+
attributes: [],
936+
selfClosing: false,
937+
/* END_IF_JS */
938+
};
939+
node
940+
")]
941+
pub struct JSXOpeningFragmentConverter<'b>(pub &'b JSXOpeningFragment);
914942

915-
impl ESTree for JSXOpeningFragmentAttributes<'_> {
943+
impl ESTree for JSXOpeningFragmentConverter<'_> {
916944
fn serialize<S: Serializer>(&self, serializer: S) {
917-
[(); 0].serialize(serializer);
945+
let mut state = serializer.serialize_struct();
946+
state.serialize_field("type", &JsonSafeString("JSXOpeningFragment"));
947+
state.serialize_field("start", &self.0.span.start);
948+
state.serialize_field("end", &self.0.span.end);
949+
if !S::INCLUDE_TS_FIELDS {
950+
state.serialize_field("attributes", &EmptyArray(()));
951+
state.serialize_field("selfClosing", &False(()));
952+
}
953+
state.end();
918954
}
919955
}
920956

napi/parser/deserialize-js.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1175,13 +1175,14 @@ function deserializeJSXFragment(pos) {
11751175
}
11761176

11771177
function deserializeJSXOpeningFragment(pos) {
1178-
return {
1178+
const node = {
11791179
type: 'JSXOpeningFragment',
11801180
start: deserializeU32(pos),
11811181
end: deserializeU32(pos + 4),
11821182
attributes: [],
11831183
selfClosing: false,
11841184
};
1185+
return node;
11851186
}
11861187

11871188
function deserializeJSXClosingFragment(pos) {

napi/parser/deserialize-ts.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,13 +1253,12 @@ function deserializeJSXFragment(pos) {
12531253
}
12541254

12551255
function deserializeJSXOpeningFragment(pos) {
1256-
return {
1256+
const node = {
12571257
type: 'JSXOpeningFragment',
12581258
start: deserializeU32(pos),
12591259
end: deserializeU32(pos + 4),
1260-
attributes: [],
1261-
selfClosing: false,
12621260
};
1261+
return node;
12631262
}
12641263

12651264
function deserializeJSXClosingFragment(pos) {

npm/oxc-types/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -858,8 +858,8 @@ export interface JSXFragment extends Span {
858858

859859
export interface JSXOpeningFragment extends Span {
860860
type: 'JSXOpeningFragment';
861-
attributes: Array<JSXAttributeItem>;
862-
selfClosing: false;
861+
attributes?: Array<JSXAttributeItem>;
862+
selfClosing?: false;
863863
}
864864

865865
export interface JSXClosingFragment extends Span {

tasks/coverage/snapshots/estree_typescript.snap

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ commit: 15392346
22

33
estree_typescript Summary:
44
AST Parsed : 10618/10725 (99.00%)
5-
Positive Passed: 8024/10725 (74.82%)
5+
Positive Passed: 8047/10725 (75.03%)
66
Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/ClassDeclarationWithInvalidConstOnPropertyDeclaration.ts
77
A class member cannot have the 'const' keyword.
88
Mismatch: tasks/coverage/typescript/tests/cases/compiler/abstractPropertyInConstructor.ts
@@ -689,7 +689,6 @@ Unexpected estree file content error: 1 != 2
689689
Mismatch: tasks/coverage/typescript/tests/cases/compiler/genericFunctionInference2.ts
690690
Mismatch: tasks/coverage/typescript/tests/cases/compiler/genericFunctionsAndConditionalInference.ts
691691
Mismatch: tasks/coverage/typescript/tests/cases/compiler/genericIndexedAccessMethodIntersectionCanBeAccessed.ts
692-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/genericInferenceDefaultTypeParameterJsxReact.tsx
693692
Mismatch: tasks/coverage/typescript/tests/cases/compiler/genericInstanceOf.ts
694693
Mismatch: tasks/coverage/typescript/tests/cases/compiler/genericIsNeverEmptyObject.ts
695694
Mismatch: tasks/coverage/typescript/tests/cases/compiler/genericMappedTypeAsClause.ts
@@ -978,18 +977,8 @@ Unexpected estree file content error: 1 != 2
978977
tasks/coverage/typescript/tests/cases/compiler/jsdocReferenceGlobalTypeInCommonJs.ts
979978
Unexpected estree file content error: 2 != 3
980979
981-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxChildrenIndividualErrorElaborations.tsx
982980
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxElementClassTooManyParams.tsx
983981
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxElementTypeLiteralWithGeneric.tsx
984-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFactoryAndJsxFragmentFactory.tsx
985-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFactoryAndJsxFragmentFactoryErrorNotIdentifier.tsx
986-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFactoryAndJsxFragmentFactoryNull.tsx
987-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFactoryButNoJsxFragmentFactory.tsx
988-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFragReactReferenceErrors.tsx
989-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFragmentAndFactoryUsedOnFragmentUse.tsx
990-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFragmentFactoryNoUnusedLocals.tsx
991-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFragmentFactoryReference.tsx
992-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxFragmentWrongType.tsx
993982
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxInferenceProducesLiteralAsExpected.tsx
994983
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxNamespaceImplicitImportJSXNamespaceFromConfigPickedOverGlobalOne.tsx
995984
Mismatch: tasks/coverage/typescript/tests/cases/compiler/jsxNamespaceImplicitImportJSXNamespaceFromPragmaPickedOverGlobalOne.tsx
@@ -1542,7 +1531,6 @@ Unexpected estree file content error: 1 != 3
15421531
15431532
Mismatch: tasks/coverage/typescript/tests/cases/compiler/tsxAttributesHasInferrableIndex.tsx
15441533
Mismatch: tasks/coverage/typescript/tests/cases/compiler/tsxDefaultImports.ts
1545-
Mismatch: tasks/coverage/typescript/tests/cases/compiler/tsxNotUsingApparentTypeOfSFC.tsx
15461534
Mismatch: tasks/coverage/typescript/tests/cases/compiler/tupleTypeInference.ts
15471535
Mismatch: tasks/coverage/typescript/tests/cases/compiler/twiceNestedKeyofIndexInference.ts
15481536
Mismatch: tasks/coverage/typescript/tests/cases/compiler/typeConstraintsWithConstructSignatures.ts
@@ -2743,39 +2731,29 @@ Unexpected estree file content error: 1 != 5
27432731
tasks/coverage/typescript/tests/cases/conformance/jsdoc/typedefMultipleTypeParameters.ts
27442732
Unexpected estree file content error: 1 != 2
27452733

2746-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/checkJsxChildrenProperty14.tsx
27472734
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/checkJsxChildrenProperty16.tsx
27482735
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/commentEmittingInPreserveJsx1.tsx
27492736
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/correctlyMarkAliasAsReferences1.tsx
27502737
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/correctlyMarkAliasAsReferences2.tsx
27512738
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/correctlyMarkAliasAsReferences3.tsx
27522739
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/correctlyMarkAliasAsReferences4.tsx
2753-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/inline/inlineJsxAndJsxFragPragma.tsx
2754-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/inline/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.tsx
27552740
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/inline/inlineJsxFactoryDeclarationsLocalTypes.tsx
2756-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/inline/inlineJsxFactoryWithFragmentIsError.tsx
27572741
tasks/coverage/typescript/tests/cases/conformance/jsx/jsxCheckJsxNoTypeArgumentsAllowed.tsx
27582742
Unexpected estree file content error: 1 != 2
27592743

27602744
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/jsx/jsxParsingError1.tsx
27612745
JSX expressions may not use the comma operator
27622746
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/jsx/jsxReactTestSuite.tsx
27632747
JSX expressions may not use the comma operator
2764-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformCustomImport.tsx
2765-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformCustomImportPragma.tsx
2766-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/jsxs/jsxJsxsCjsTransformSubstitutesNamesFragment.tsx
27672748
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxAttributeResolution14.tsx
27682749
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxAttributeResolution15.tsx
27692750
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxAttributeResolution16.tsx
27702751
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxCorrectlyParseLessThanComparison1.tsx
2771-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxFragmentPreserveEmit.tsx
2772-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxFragmentReactEmit.tsx
27732752
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxLibraryManagedAttributes.tsx
27742753
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxOpeningClosingNames.tsx
27752754
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxReactEmitEntities.tsx
27762755
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxReactEmitNesting.tsx
27772756
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxStatelessFunctionComponentsWithTypeArguments5.tsx
2778-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxTypeArgumentsJsxPreserveOutput.tsx
27792757
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxUnionElementType1.tsx
27802758
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxUnionElementType2.tsx
27812759
Mismatch: tasks/coverage/typescript/tests/cases/conformance/jsx/tsxUnionElementType3.tsx
@@ -3511,7 +3489,6 @@ Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationsh
35113489
Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationships/typeInference/genericContextualTypes2.ts
35123490
Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationships/typeInference/genericContextualTypes3.ts
35133491
Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationships/typeInference/intraExpressionInferences.ts
3514-
Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationships/typeInference/intraExpressionInferencesJsx.tsx
35153492
Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationships/typeInference/keyofInferenceLowerPriorityThanReturn.ts
35163493
Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationships/typeInference/noInfer.ts
35173494
Mismatch: tasks/coverage/typescript/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts

0 commit comments

Comments
 (0)