facebook / jscodeshift

A JavaScript codemod toolkit.
https://jscodeshift.com
MIT License
9.31k stars 480 forks source link

.find doesn't traverse "typeParameters" in TypeScript #389

Open Ayc0 opened 4 years ago

Ayc0 commented 4 years ago

Description

jscodeshift(file.source).find doesn't find anything in a generic.

Example

Code sandbox: https://codesandbox.io/s/unruffled-goodall-b5qkn

Edit: ASTExplorer: https://astexplorer.net/#/gist/191f77988f260ee6bef6ec402331ac0e/808e05a888e206dacc16f8dac85609a7ac09eeab

Input

const [state, setState] = React.useState<Enum.A | Enum.B | null>(null);

Traverse

root.find("TSQualifiedName")
// []

And it should match 2 things : Enum.A and Enum.B

AST

{
  "type": "Program",
  "sourceType": "module",
  "interpreter": null,
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "ArrayPattern",
            "elements": [
              {
                "type": "Identifier",
                "name": "state"
              },
              {
                "type": "Identifier",
                "name": "setState"
              }
            ]
          },
          "init": {
            "type": "CallExpression",
            "callee": {
              "type": "MemberExpression",
              "object": {
                "type": "Identifier",
                "name": "React"
              },
              "computed": false,
              "property": {
                "type": "Identifier",
                "name": "useState"
              }
            },
            "arguments": [
              {
                "type": "NullLiteral"
              }
            ],
            "typeParameters": {
              "type": "TSTypeParameterInstantiation",
              "params": [
                {
                  "type": "TSUnionType",
                  "types": [
                    {
                      "type": "TSTypeReference",
                      "typeName": {
                        "type": "TSQualifiedName",
                        "left": {
                          "type": "Identifier",
                          "name": "Enum"
                        },
                        "right": {
                          "type": "Identifier",
                          "name": "A"
                        }
                      }
                    },
                    {
                      "type": "TSTypeReference",
                      "typeName": {
                        "type": "TSQualifiedName",
                        "left": {
                          "type": "Identifier",
                          "name": "Enum"
                        },
                        "right": {
                          "type": "Identifier",
                          "name": "B"
                        }
                      }
                    },
                    {
                      "type": "TSNullKeyword"
                    }
                  ]
                }
              ]
            }
          }
        }
      ],
      "kind": "const"
    }
  ],
  "directives": []
}

Versions

Ayc0 commented 4 years ago

I tried to look into jscodeshift and recast (and sub packages) to see if I could patch the traverse / visit / walk / whatever function but every time I can only see this.traverse without defining traverse so I gave up but if someone could help me on this, I'd be glad to work on a PR

MichaelMitchell-at commented 2 years ago

I believe this is blocked by https://github.com/benjamn/ast-types/issues/343

eps1lon commented 1 year ago

Sandbox returns 404.

I switched to using @babel/traverse instead. I already use it as a parser so I might as well use their traversal helper to ensure the understanding of the tree is consistent between parsing and traversal.

jscodeshift find:

const refObjectTypeReferences = ast
    .find(j.TSTypeReference, (typeReference) => {
        const { typeName } = typeReference;
        if (typeName.type === "TSTypeParameter") {
            // TODO: What code produces this AST?
            return false;
        } else {
            const identifier =
                typeName.type === "TSQualifiedName"
                    ? /** @type {any} */ (typeName.right)
                    : typeName;
            return ["RefObject"].includes(identifier.name);
        }
    })

@babel/traverse:

/**
 * @type {import('@babel/types').TSTypeReference[]}
 */
const refObjectTypeReferences = [];
traverse(ast.get("program").value, {
    TSTypeReference({ node: typeReference }) {
        const { typeName } = typeReference;
        const identifier =
            typeName.type === "TSQualifiedName" ? typeName.right : typeName;

        if (["RefObject"].includes(identifier.name)) {
            refObjectTypeReferences.push(typeReference);
        }
    },
});

I can even get rid of an any cast and one branch (typeName.type === "TSTypeParameter") that seemed unreachable anyway.

So this feels like I'm fixing 3 issues at once by switching to @babel/traverse.

Ayc0 commented 1 year ago

Sandbox returns 404.

I don't know why it's not available anymore, here is a new sandbox in ASTExplorer: https://astexplorer.net/#/gist/191f77988f260ee6bef6ec402331ac0e/808e05a888e206dacc16f8dac85609a7ac09eeab

And if the history goes away:

With:

I get this output:

Screenshot of ASTExplorer.net showing `[]` in the console