• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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