jscodeshift

repository·main·Indexed 27 days ago

https://github.com/facebook/jscodeshift

A toolkit for running codemods over multiple JavaScript or TypeScript files. It provides a CLI runner and a JavaScript API to execute transforms, utilizing a wrapper around Recast to offer a jQuery-like API for AST transformations while preserving code style. Version 17.4.0.

Tokens
5.9K
Snippets
11
Records
24
Agent score
94%

What's inside jscodeshift

  1. Overview of jscodeshift core concepts

    main

    jscodeshift is a toolkit for building and running codemods over multiple JavaScript or TypeScript files. It consists of two primary components:

    1. A runner: Executes a provided transform for each file passed to it and outputs a summary of files that were not transformed.
    2. A recast wrapper: Provides an API for recast, an AST-to-AST transform tool that attempts to preserve the original code style.

    The workflow follows three steps:

    1. Parsing: Converts JavaScript code into an Abstract Syntax Tree (AST).
    2. Transforming: Navigates the AST to apply changes (e.g., renaming functions or changing parameters).
    3. Generating: Converts the transformed AST back into JavaScript code.
  2. Project structure for documentation

    main

    The documentation site follows a specific structure for content and assets:

    • Documentation files: Place .md or .mdx files in src/content/docs/. Each file is exposed as a route based on its filename.
    • Images: Add images to src/assets/ and embed them in Markdown using relative links.
    • Static assets: Place files like favicons in the public/ directory.
  3. Debug jscodeshift transforms in VSCode

    main

    To debug your codemod transforms using the VSCode IDE, follow these steps:

    1. Install jscodeshift locally: Ensure jscodeshift is installed in your project via npm install --save jscodeshift. The debugger relies on the local node_modules binary.
    2. Configure launch.json: Add the provided VSCode debugging configuration to your .vscode/launch.json file. This configuration includes two modes: Debug Transform (for running a transform on a specific file) and Debug All JSCodeshift Jest Tests.
    3. Select target file: In the VSCode file tree, click on the file you want to transform (e.g., foo.js).
    4. Start Debugging:
      • Open the Run and Debug menu and select Debug Transform.
      • Click the Start Debugging button.
      • When prompted, enter the path to your transform file (defaults to transform.js).
      • Select the appropriate parser from the list (e.g., babel, ts, tsx).
    5. Review Results: The transform will run and stop at any set breakpoints. Because the configuration uses the --dry flag, the original file will not be modified; instead, the transformed output will be printed to the VSCode Debug Console.
    {
        // Use IntelliSense to learn about possible attributes.
        // Hover to view descriptions of existing attributes.
        // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
            {
                "type": "pwa-node",
                "request": "launch",
                "name": "Debug Transform",
                "skipFiles": [
                    "<node_internals>/**"
                ],
                "program": "${workspaceRoot}/node_modules/.bin/jscodeshift",
                "stopOnEntry": false,
                "args": ["--dry", "--print", "-t", "${input:transformFile}", "--parser", "${input:parser}", "--run-in-band", "${file}"],
                "preLaunchTask": null,
                "runtimeExecutable": null,
                "runtimeArgs": [
                    "--nolazy"
                ],
                "console": "internalConsole",
                "sourceMaps": true,
                "outFiles": []
            },
            {
                "name": "Debug All JSCodeshift Jest Tests",
                "type": "node",
                "request": "launch",
                "runtimeArgs": [
                    "--inspect-brk",
                    "${workspaceRoot}/node_modules/jest/bin/jest.js",
                    "--runInBand",
                    "--testPathPattern=${fileBasenameNoExtension}"
                ],
                "console": "integratedTerminal",
                "internalConsoleOptions": "neverOpen",
                "port": 9229
            }
        ],
        "inputs": [
            {
              "type": "pickString",
              "id": "parser",
              "description": "jscodeshift parser",
              "options": [
                "babel",
                "babylon",
                "flow",
                "ts",
                "tsx",
              ],
              "default": "babel"
            },
            {
                "type": "promptString",
                "id": "transformFile",
                "description": "jscodeshift transform file",
                "default": "transform.js"
            }
        ]
    }
  4. Retain leading comments when replacing the first statement

    main

    When removing or replacing the first statement in a file, jscodeshift may remove leading comments at the top of the file. To prevent this, you must manually copy the comments array from the original first node to the new node that becomes the first statement in the file.

    1. Identify the first node in the Program body.
    2. Extract its comments property.
    3. Perform your transformations.
    4. Identify the new first node and reattach the saved comments array to it.
    export default function transformer(file, api) {
      const j = api.jscodeshift;
      const root = j(file.source);
    
      const getFirstNode = () => root.find(j.Program).get('body', 0).node;
    
      // Save the comments attached to the first node
      const firstNode = getFirstNode();
      const { comments } = firstNode;
    
      root.find(j.VariableDeclaration).replaceWith(
        j.expressionStatement(j.callExpression(
            j.identifier('foo'),
            []
        ))
      );
    
      // If the first node has been modified or deleted, reattach the comments
      const firstNode2 = getFirstNode();
      if (firstNode2 !== firstNode) {
        firstNode2.comments = comments;
      }
    
      return root.toSource();
    };
  5. Create a Transform Module

    main

    A transform is a module that exports a function. This function receives fileInfo, api, and options as arguments. It should return the transformed source code as a string.

    module.exports = function(fileInfo, api, options) {
      // transform `fileInfo.source` here
      // ...
      // return changed source
      return source;
    };
  6. Core Concepts: AST Nodes, Path Objects, and Builders

    main

    To use the jscodeshift API, you must understand three core concepts:

    1. AST Nodes: Plain JavaScript objects representing code structure (e.g., a Literal node for strings). You can identify nodes via their type field.
    2. Path Objects: Wrappers around AST nodes provided by ast-types. Unlike plain nodes, paths allow you to traverse the tree upwards via path.parent.
    3. Builders: Helper methods provided by jscodeshift (via ast-types) to safely create new AST nodes.

    Use the AST Explorer to inspect the AST of any JavaScript code.

  7. Unit Test Transforms with testUtils

    main

    jscodeshift provides a testUtils module to simplify testing transforms with Jest. It assumes a specific directory structure:

    • Tests: /__tests__/MyTransform-test.js
    • Fixtures: /__testfixtures__/MyTransform.input.js and /__testfixtures__/MyTransform.output.js

    Available helpers:

    • defineTest(dir, name): Uses file-based fixtures.
    • defineInlineTest(transform, options, input, expected, name): Uses inline strings for input/output.
    • defineSnapshotTest(transform, options, input, name): Uses Jest snapshots.
    • defineSnapshotTestFromFixture(dir, transform, options, fixtureName, name): Uses snapshot testing with file-based fixtures.
    • applyTransform(transform, options, input): Executes the transform manually (returns a string or Promise).
    // Example: defineTest
    const defineTest = require('jscodeshift/dist/testUtils').defineTest;
    defineTest(__dirname, 'MyTransform');
    
    // Example: defineInlineTest
    const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
    const transform = require('../myTransform');
    const transformOptions = {};
    defineInlineTest(transform, transformOptions, 'input', 'expected output', 'test name (optional)');
  8. Run jscodeshift codemods via CLI

    main

    The jscodeshift CLI provides four ways to execute codemods against target files or directories:

    1. Default Transform: Uses a transform.js file located in the current working directory.
    2. Specific Transform File: Use the -t or --transform flag to point to a local file.
    3. Transform via URL: Use the -t or --transform flag to point to a remote URL.
    4. Standard Input: Use the --stdin flag to process files listed in standard input (e.g., from a text file).
  9. Use Collections and Traversal for AST Transformation

    main

    jscodeshift uses collections of paths to provide a fluent interface for traversing and transforming the AST. Instead of manual visitor patterns, you can use .find() to locate specific node types and .forEach() to operate on them.

    Note that collections are "typed": a collection of Identifier nodes only supports methods applicable to identifiers.

    // jscodeshift approach
    jscodeshift(src)
      .find(jscodeshift.Identifier)
      .forEach(function(path) {
        // do something with path
      });
  10. Extend Collections with registerMethods

    main

    You can extend jscodeshift collections by registering custom methods. There are two types:

    1. Generic extensions: Applicable to all collections (e.g., a method that searches for something specific from any node).
    2. Type-specific extensions: Only callable on collections of a specific node type.

    Use jscodeshift.registerMethods(methods, [type]) to add functionality.

    // Adding a method to all Identifiers
    jscodeshift.registerMethods({
      logNames: function() {
        return this.forEach(function(path) {
          console.log(path.node.name);
        });
      }
    }, jscodeshift.Identifier);
    
    // Adding a method to all collections
    jscodeshift.registerMethods({
      findIdentifiers: function() {
        return this.find(jscodeshift.Identifier);
      }
    });
    
    // Usage
    jscodeshift(ast).findIdentifiers().logNames();