Skip to content

Commit e610faa

Browse files
committed
feat: add code fix for 2657
1 parent 89e675e commit e610faa

File tree

5 files changed

+95
-1
lines changed

5 files changed

+95
-1
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5641,7 +5641,15 @@
56415641
"category": "Message",
56425642
"code": 95116
56435643
},
5644-
5644+
"Wrap JSX in JSX Fragment": {
5645+
"category": "Message",
5646+
"code": 95117
5647+
},
5648+
"Wrap all JSX in JSX Fragment": {
5649+
"category": "Message",
5650+
"code": 95118
5651+
},
5652+
56455653
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
56465654
"category": "Error",
56475655
"code": 18004
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixID = "wrapJsxInFragment";
4+
const errorCodes = [Diagnostics.JSX_expressions_must_have_one_parent_element.code];
5+
registerCodeFix({
6+
errorCodes,
7+
getCodeActions: context => {
8+
const { jsx } = context.program.getCompilerOptions();
9+
if (jsx !== JsxEmit.React && jsx !== JsxEmit.ReactNative) {
10+
return undefined;
11+
}
12+
const { sourceFile, span } = context;
13+
const node = findNodeToFix(sourceFile, span.start);
14+
if (!node) return undefined;
15+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node));
16+
return [createCodeFixAction(fixID, changes, Diagnostics.Wrap_JSX_in_JSX_Fragment, fixID, Diagnostics.Wrap_all_JSX_in_JSX_Fragment)];
17+
},
18+
fixIds: [fixID],
19+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
20+
const node = findNodeToFix(context.sourceFile, diag.start);
21+
if (!node) return undefined;
22+
doChange(changes, context.sourceFile, node);
23+
}),
24+
});
25+
26+
function findNodeToFix(sourceFile: SourceFile, pos: number): BinaryExpression | undefined {
27+
// The error always at 1st token that is "<" in "<a /><a />"
28+
const lessThanToken = getTokenAtPosition(sourceFile, pos);
29+
const firstJsxElementOrOpenElement = lessThanToken.parent;
30+
let binaryExpr = firstJsxElementOrOpenElement.parent;
31+
if (!isBinaryExpression(binaryExpr)) {
32+
// In case the start element is a JsxSelfClosingElement, it the end.
33+
// For JsxOpenElement, find one more parent
34+
binaryExpr = binaryExpr.parent;
35+
if (!isBinaryExpression(binaryExpr)) return undefined;
36+
}
37+
if (!nodeIsMissing(binaryExpr.operatorToken)) return undefined;
38+
return binaryExpr;
39+
}
40+
41+
function doChange(changeTracker: textChanges.ChangeTracker, sf: SourceFile, node: Node) {
42+
const jsx = flattenInvalidBinaryExpr(node);
43+
if (jsx) changeTracker.replaceNode(sf, node, createJsxFragment(createJsxOpeningFragment(), jsx, createJsxJsxClosingFragment()));
44+
}
45+
// The invalid syntax is constructed as
46+
// InvalidJsxTree :: One of
47+
// JsxElement CommaToken InvalidJsxTree
48+
// JsxElement CommaToken JsxElement
49+
function flattenInvalidBinaryExpr(node: Node): JsxChild[] | undefined {
50+
const children: JsxChild[] = [];
51+
let current = node;
52+
while (true) {
53+
if (isBinaryExpression(current) && nodeIsMissing(current.operatorToken) && current.operatorToken.kind === SyntaxKind.CommaToken) {
54+
children.push(<JsxChild>current.left);
55+
if (isJsxChild(current.right)) {
56+
children.push(current.right);
57+
// Indicates the tree has go to the bottom
58+
return children;
59+
}
60+
else if (isBinaryExpression(current.right)) {
61+
current = current.right;
62+
continue;
63+
}
64+
// Unreachable case
65+
else return undefined;
66+
}
67+
// Unreachable case
68+
else return undefined;
69+
}
70+
}
71+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"codefixes/useDefaultImport.ts",
9494
"codefixes/useBigintLiteral.ts",
9595
"codefixes/fixAddModuleReferTypeMissingTypeof.ts",
96+
"codefixes/wrapJsxInFragment.ts",
9697
"codefixes/convertToMappedObjectType.ts",
9798
"codefixes/removeUnnecessaryAwait.ts",
9899
"codefixes/splitTypeOnlyImport.ts",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @jsx: react
4+
// @Filename: /a.tsx
5+
////[|<a /><a />|]
6+
7+
verify.rangeAfterCodeFix(`<><a /><a /></>`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @jsx: react
4+
// @Filename: /a.tsx
5+
////[|<a></a><a />|]
6+
7+
verify.rangeAfterCodeFix(`<><a></a><a /></>`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0);

0 commit comments

Comments
 (0)