1/* @internal */ 2namespace 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_in_JSX_fragment, fixID, Diagnostics.Wrap_all_unparented_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, factory.createJsxFragment(factory.createJsxOpeningFragment(), jsx, factory.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} 72