Repository: facebook/jscodeshift Branch: main Commit: f1f0b9f0a44c Files: 98 Total size: 311.4 KB Directory structure: gitextract_10vd5d0w/ ├── .changeset/ │ ├── README.md │ └── config.json ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── cr.yml │ ├── pull_request.yml │ ├── push.yml │ └── test.yml ├── .gitignore ├── .node-version ├── .npmignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin/ │ ├── __tests__/ │ │ └── jscodeshift-test.js │ ├── jscodeshift.js │ └── jscodeshift.sh ├── index.js ├── package.json ├── parser/ │ ├── __tests__/ │ │ ├── .eslintrc │ │ ├── __snapshots__/ │ │ │ └── tsx-test.js.snap │ │ └── tsx-test.js │ ├── babel5Compat.js │ ├── babylon.js │ ├── flow.js │ ├── ts.js │ ├── tsOptions.js │ └── tsx.js ├── recipes/ │ └── retain-first-comment.md ├── sample/ │ ├── __testfixtures__/ │ │ ├── reverse-identifiers.input.js │ │ ├── reverse-identifiers.output.js │ │ └── typescript/ │ │ ├── reverse-identifiers.input.ts │ │ └── reverse-identifiers.output.ts │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── reverse-identifiers-test.js.snap │ │ └── reverse-identifiers-test.js │ └── reverse-identifiers.js ├── src/ │ ├── Collection.js │ ├── Runner.js │ ├── Worker.js │ ├── __testfixtures__/ │ │ ├── test-async-transform.input.js │ │ ├── test-async-transform.js │ │ ├── test-async-transform.output.js │ │ ├── test-sync-transform.input.js │ │ ├── test-sync-transform.js │ │ └── test-sync-transform.output.js │ ├── __tests__/ │ │ ├── .eslintrc │ │ ├── Collection-test.js │ │ ├── Worker-test.js │ │ ├── __snapshots__/ │ │ │ └── testUtils-test.js.snap │ │ ├── argsParser-test.js │ │ ├── core-test.js │ │ ├── matchNode-test.js │ │ ├── template-test.js │ │ ├── testUtils-test.js │ │ └── ts-decorator-auto-accessor-test.js │ ├── argsParser.js │ ├── collections/ │ │ ├── ImportDeclaration.js │ │ ├── JSXElement.js │ │ ├── Node.js │ │ ├── VariableDeclarator.js │ │ ├── __tests__/ │ │ │ ├── .eslintrc │ │ │ ├── .jshintrc │ │ │ ├── ImportDeclaration-test.js │ │ │ ├── JSXElement-test.js │ │ │ ├── Node-test.js │ │ │ └── VariableDeclarator-test.js │ │ └── index.js │ ├── core.js │ ├── getParser.js │ ├── ignoreFiles.js │ ├── matchNode.js │ ├── template.js │ ├── testUtils.js │ └── utils/ │ ├── __tests__/ │ │ ├── intersection-test.js │ │ ├── once-test.js │ │ └── union-test.js │ ├── intersection.js │ ├── once.js │ └── union.js ├── utils/ │ ├── requirePackage.js │ └── testUtils.js └── website/ ├── .astro/ │ ├── settings.json │ └── types.d.ts ├── README.md ├── astro.config.mjs ├── package.json ├── src/ │ ├── content/ │ │ ├── config.ts │ │ └── docs/ │ │ ├── build/ │ │ │ ├── api-reference.mdx │ │ │ └── ast-grammar.mdx │ │ ├── index.mdx │ │ ├── overview/ │ │ │ └── introduction.mdx │ │ └── run/ │ │ └── cli.mdx │ └── env.d.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .changeset/README.md ================================================ # Changesets Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [] } ================================================ FILE: .editorconfig ================================================ root = true [*] end_of_line = lf indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .eslintignore ================================================ /dist/ /docs/ /sample/__testfixtures__/ ================================================ FILE: .eslintrc ================================================ { "extends": "eslint:recommended", "parser": "@babel/eslint-parser", "rules": { "comma-dangle": 0, "no-underscore-dangle": 0, "quotes": [2, "single", "avoid-escape"], "strict": 0 }, "env": { "jest": true, "node": true, "es6": true }, "globals": { "Promise": true } } ================================================ FILE: .gitattributes ================================================ bin/jscodeshift.js eol=lf ================================================ FILE: .github/workflows/cr.yml ================================================ name: Continuous Releases on: push: pull_request: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: 20.x cache: yarn - name: Install dependencies run: yarn --frozen-lockfile - name: Preview run: npx pkg-pr-new@0.0.24 publish --compact ================================================ FILE: .github/workflows/pull_request.yml ================================================ name: pull_request on: pull_request: branches: [main] jobs: call-test: uses: ./.github/workflows/test.yml ================================================ FILE: .github/workflows/push.yml ================================================ name: push on: push: branches: [main] jobs: call-test: uses: ./.github/workflows/test.yml release: needs: call-test runs-on: ubuntu-latest permissions: contents: write id-token: write pull-requests: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: cache: yarn - run: yarn - name: Create Release Pull Request or Publish to npm id: changesets uses: changesets/action@v1 with: publish: yarn release title: Publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/test.yml ================================================ name: test on: workflow_call: jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [16.x, 18.x, 20.x] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: yarn - name: Install dependencies run: yarn --frozen-lockfile - name: Run test run: yarn test ================================================ FILE: .gitignore ================================================ /dist node_modules/ ================================================ FILE: .node-version ================================================ 16.20.2 ================================================ FILE: .npmignore ================================================ /docs/ /test/ /sample/ /recipes/ .gitignore .eslintrc .eslintrc.yaml .jshintrc .module-cache __tests__ yarn.lock ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 17.3.0 ### Minor Changes - 6c2ff57: Bumps recast to allow parsing of Typescript type arguments on tagged template literals ## 17.2.0 ### Minor Changes - e5fe5be: Bumps recast to resolve a bug where JSX elements are wrapped in two pairs of parenthesis ## 17.1.2 ### Patch Changes - 8f60fbf: Enable async tranformers in test utils. All notable changes to this project will be documented in this file. ## [17.1.1] 2024-10-31 ### Fixed - Republished with `temp` dependency properly removed (#638, thanks @trivikr for reporting) ## [17.1.0] 2024-10-30 ### Added - `pkg.pr.new` will now be used to build an npm pakage for each commit to the repo, allowing you to more easily test changes or use new features before an official release is cut. (#622, @Aslemammad) ### Changed - Replaced `temp` library with `tmp` (#633, @r4zendev) ### Fixed - Removed old `docs` command from `package.json` since the new docs are in the `website` folder, which has instructions in its README. ## [17.0.0] 2024-08-06 We needed to go [from v0.x to a major release](https://github.com/facebook/jscodeshift/issues/593), and it may as well happen now. jscodeshift has been around for nine years though, so going to v1.0.0 didn't feel quite right. I've instead promoted the minor version number to a major version number, similar to what React did when it went from 0.14 to 15.0. ### Fixed - Node.js 16 is now explicitly specified as the minimum required version. It was _already_ implicitly required due to some dependencies requiring it, but this wasn't explicitly specified until now. (#607, @trivikr) ### Added - A new [jscodeshift website](https://jscodeshift.com/) has launched, thanks to the team at [Codemod](https://codemod.com/). (#592, @mohab-sameh with some tweaks by @morinokami) - Added collection functions for import declarations, including finding imports and inserting new imports (#617, @danieldelcore) ### Changed - Enabled TypeScript `importAttributes` (#585, @benasher44) and `decoratorAutoAccessors` (#594, @syi0808) plugins - Removed or replaced various outdated and unused dependencies (#601, #605, #609, #613, @trivikr) - Started using Corepack to manage Yarn version (#599, @trivikr) ## [0.16.1] 2024-06-25 ### Fixed - Removed old `babel-core` dependency that was unused but caused security scanners to flag vulnerabilities. ## [0.16.0] 2024-06-18 ### Added - Added a `--gitignore` flag to avoid transforming any files listed in `.gitignore` (#508, @ElonVolo) ### Changed - Updated various dependencies to latest version (#588, @ElonVolo) ## [0.15.2] 2024-02-21 ### Fixed - Process all supported extensions by default (#584, @trivikr) ## [0.15.1] 2023-10-28 ### Changed - Upgraded to recast 0.23.3 (#564, @ashsearle) - Enable `@babel/plugin-proposal-private-methods` in worker (#568, @sibelius) - Upgraded Babel packages (#570, @dartess) ### Fixed - Respect extensions cli option when filtering individual files (#562, @robcmills) - Fixed unit test after #562 broke them (#575, @ElonVolo) ## [0.15.0] 2023-05-07 ### Changed - Upgraded to recast 0.23.1 (#544, @ryanrhee) - Make @babel/preset-env optional (#480, @SimenB) ### Fixed - Force LF line ending in bin/jscodeshift.sh (#555, @jakeboone02) - Use transform's exported parser in testUitls (#528, @CrispyBacon12) - Ensure jscodeshift doesn't load Babel config file (#460, @raon0211) ## [0.14.0] 2022-10-04 ### Added - Added a `defineSnapshotTestFromFixture` test util (#471, @shriuken) - Added `renameTo` filters for Babel 6+ node types (#412 and #504, @elonvolo and @henryqdineen) - Added `childNodesOfType` to JSX traversal methods (#415, @j13huang) ### Changed - Bumped dependency versions - Allow arguments in `--help` to be listed in an order other than alphabetically, so they can instead be grouped thematically (#507, @elonvolo) - Allow the `j` shortcut in test utils (#515, @no23reason) ## [0.13.1] 2022-01-10 ### Changed - Switched from `colors` to `chalk` to mitigate a security vulnerability in `colors@1.4.1`. ## [0.13.0] 2021-06-26 ### Added - Added a `--fail-on-error` flag to return a `1` error code when errors were found (#416, @marcodejongh) - Created `template.asyncExpression` (#405, @jedwards1211) ### Changed - Removed lodash dependency from tsx parser (#432, @JHilker and @robyoder) ## [0.12.0] 2021-04-21 ### Changed - Allow transform to be a Promise (#237, @rektide) - Support newer TypeScript syntax by upgrading to newer Babel parser (#410, @wdoug and @mfeckie) ## [0.11.0] 2020-09-01 ### Changed - Updated `recast` to latest ## [0.10.0] 2020-06-01 ### Changed - Updated `flow-parser` to latest, and enabled Flow Enums parsing by default when using Flow parser ## [0.8.0] 2020-05-03 ### Changed - Dropped support for Node versions 6 and 8 ## [0.7.0] 2019-12-11 ## Added - Added jest snapshot utils (#297, @dogoku) ### Changed - Moved from BSD to MIT license ### Fixed - No longer throw an error when calling jscodeshift on a non-existent path (#334, @threepointone) - Preserve the original file extension in remote files (#317, @samselikoff) ## [0.6.4] 2019-04-30 ### Changed - Allow writing tests in TypeScript ([PR #308](https://github.com/facebook/jscodeshift/pull/308)) - Better handling of `.gitingore` files: Ignore comments and support `\r\n` line breaks ([PR #306](https://github.com/facebook/jscodeshift/pull/306)) ## [0.6.3] 2019-01-18 ### Fixed - Don't throw an error when jscodeshift processes an empty set of files (#295, @skovhus). - `renameTo` should not rename class properties (#296, @henryqdineen). - Custom/unknown CLI parameters are parsed as JSON, just like nomnom used to do. ## [0.6.2] 2018-12-05 ### Changed - `@babel/register`/`@babel/preset-env` is configured to not transpile any language features that the running Node process supports. That means if you use features in your transform code supported by the Node version you are running, they will be left as is. Most of ES2015 is actually supported since Node v6. - Do not transpile object rest/spread in transform code if supported by running Node version. ### Fixed - Presets and plugins passed to `@babel/register` are now properly named and loaded. ## [0.6.1] 2018-12-04 ### Added - Tranform files can be written in Typescript. If the file extension of the transform file is `.ts` or `.tsx`, `@babel/preset-typescript` is used to convert them. This requires the `--babel` option to be set (which it is by default). ( #287 , @brieb ) ### Changed - The preset and plugins for converting the transform file itself via babeljs have been updated to work with babel v7. This included removing `babel-preset-es2015` and `babel-preset-stage-1` in favor of `@babel/preset-env`. Only `@babel/proposal-class-properties` and `@babel/proposal-object-rest-spread` are enabled as experimental features. If you want to use other's in your transform file, please create a PR. ### Fixed - Typescript parses use `@babel/parser` instead of Babylon ( #291, @elliottsj ) ### Bumped - `micromatch` => v3.1.10, which doesn't (indirectly) depend on `randomatic` < v3 anymore (see #292). ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to jscodeshift We want to make contributing to this project as easy and transparent as possible. ## Code of Conduct The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) ## Our Development Process The majority of development on jscodeshift will occur through GitHub. Accordingly, the process for contributing will follow standard GitHub protocol. ## Pull Requests We actively welcome your pull requests. 1. Fork the repo and create your branch from `main`. 1. Run `corepack enable` to set up `yarn` package manager. 1. If you've added code that should be tested, add tests. 1. If you've changed APIs, update the documentation. 1. Ensure the test suite passes. 1. Make sure your code lints. 1. If you haven't already, complete the Contributor License Agreement ("CLA"). ## Contributor License Agreement ("CLA") In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. Complete your CLA here: ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue. ## Coding Style * Use semicolons; * Commas last, * 2 spaces for indentation (no tabs) * Prefer `'` over `"` * `'use strict';` * 80 character line length * "Attractive" ### License jscodeshift is [MIT licensed](./LICENSE). ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # [jscodeshift](https://jscodeshift.com/) [![Support Ukraine](https://img.shields.io/badge/Support-Ukraine-FFD500?style=flat&labelColor=005BBB)](https://opensource.fb.com/support-ukraine) [![Build Status](https://img.shields.io/github/checks-status/facebook/jscodeshift/main?label=build)](https://github.com/facebook/jscodeshift/actions/workflows/test.yml?query=branch%3Amain) jscodeshift is a toolkit for running codemods over multiple JavaScript or TypeScript files. It provides: - A runner, which executes the provided transform for each file passed to it. It also outputs a summary of how many files have (not) been transformed. - A wrapper around [recast][], providing a different API. Recast is an AST-to-AST transform tool and also tries to preserve the style of original code as much as possible. ## Install Get jscodeshift from [npm](https://www.npmjs.com/package/jscodeshift): ``` $ npm install -g jscodeshift ``` This will install the runner as `jscodeshift`. ## VSCode Debugger [Configure VSCode to debug codemods](#vscode-debugging) ## Usage (CLI) See [the website](https://jscodeshift.com/) for full documentation. The CLI provides the following options: ``` $ jscodeshift --help Usage: jscodeshift [OPTION]... PATH... or: jscodeshift [OPTION]... -t TRANSFORM_PATH PATH... or: jscodeshift [OPTION]... -t URL PATH... or: jscodeshift [OPTION]... --stdin < file_list.txt Apply transform logic in TRANSFORM_PATH (recursively) to every PATH. If --stdin is set, each line of the standard input is used as a path. Options: "..." behind an option means that it can be supplied multiple times. All options are also passed to the transformer, which means you can supply custom options that are not listed here. --(no-)babel apply babeljs to the transform file (default: true) -c, --cpus=N start at most N child processes to process source files (default: max(all - 1, 1)) -d, --(no-)dry dry run (no changes are made to files) (default: false) --extensions=EXT transform files with these file extensions (comma separated list) (default: js) -h, --help print this help and exit --ignore-config=FILE ... ignore files if they match patterns sourced from a configuration file (e.g. a .gitignore) --ignore-pattern=GLOB ... ignore files that match a provided glob expression --(no-)gitignore adds entries the current directory's .gitignore file (default: false) --parser=babel|babylon|flow|ts|tsx the parser to use for parsing the source files (default: babel) --parser-config=FILE path to a JSON file containing a custom parser configuration for flow or babylon -p, --(no-)print print transformed files to stdout, useful for development (default: false) --(no-)run-in-band run serially in the current process (default: false) -s, --(no-)silent do not write to stdout or stderr (default: false) --(no-)stdin read file/directory list from stdin (default: false) -t, --transform=FILE path to the transform file. Can be either a local path or url (default: ./transform.js) -v, --verbose=0|1|2 show more information about the transform process (default: 0) --version print version and exit --fail-on-error return a 1 exit code when errors were found during execution of codemods ``` This passes the source of all passed through the transform module specified with `-t` or `--transform` (defaults to `transform.js` in the current directory). The next section explains the structure of the transform module. ## Usage (JS) ```js const {run: jscodeshift} = require('jscodeshift/src/Runner') const path = require('node:path'); const transformPath = path.resolve('transform.js') const paths = ['foo.js', 'bar'] const options = { dry: true, print: true, verbose: 1, // ... } const res = await jscodeshift(transformPath, paths, options) console.log(res) /* { stats: {}, timeElapsed: '0.001', error: 0, ok: 0, nochange: 0, skip: 0 } */ ``` ## Transform module The transform is simply a module that exports a function of the form: ```js module.exports = function(fileInfo, api, options) { // transform `fileInfo.source` here // ... // return changed source return source; }; ``` As of v0.6.1, this module can also be written in TypeScript. ### Arguments #### `fileInfo` Holds information about the currently processed file. Property | Description ------------|------------ path | File path source | File content #### `api` This object exposes the `jscodeshift` library and helper functions from the runner. Property | Description ------------|------------ jscodeshift | A reference to the jscodeshift library stats | A function to collect statistics during `--dry` runs report | Prints the passed string to stdout `jscodeshift` is a reference to the wrapper around recast and provides a jQuery-like API to navigate and transform the AST. Here is a quick example, a more detailed description can be found below. ```js /** * This replaces every occurrence of variable "foo". */ module.exports = function(fileInfo, api, options) { return api.jscodeshift(fileInfo.source) .findVariableDeclarators('foo') .renameTo('bar') .toSource(); } ``` **Note:** This API is exposed for convenience, but you don't have to use it. You can use any tool to modify the source. `stats` is a function that only works when the `--dry` options is set. It accepts a string, and will simply count how often it was called with that value. At the end, the CLI will report those values. This can be useful while developing the transform, e.g. to find out how often a certain construct appears in the source(s). **`report`** allows you to print arbitrary strings to stdout. This can be useful when other tools consume the output of jscodeshift. The reason to not directly use `process.stdout` in transform code is to avoid mangled output when many files are processed. #### `options` Contains all options that have been passed to runner. This allows you to pass additional options to the transform. For example, if the CLI is called with ``` $ jscodeshift -t myTransforms fileA fileB --foo=bar ``` `options` would contain `{foo: 'bar'}`. ### Return value The return value of the function determines the status of the transformation: - If a string is returned and it is different from passed source, the transform is considered to be successful. - If a string is returned but it's the same as the source, the transform is considered to be unsuccessful. - If nothing is returned, the file is not supposed to be transformed (which is ok). The CLI provides a summary of the transformation at the end. You can get more detailed information by setting the `-v` option to `1` or `2`. You can collect even more stats via the `stats` function as explained above. ### Parser The transform file can let jscodeshift know with which parser to parse the source files (and features like templates). To do that, the transform module can export `parser`, which can either be one of the strings `"babel"`, `"babylon"`, `"flow"`, `"ts"`, or `"tsx"`, or it can be a parser object that is compatible with recast and follows the estree spec. __Example: specifying parser type string in the transform file__ ```js module.exports = function transformer(file, api, options) { const j = api.jscodeshift; const rootSource = j(file.source); // whatever other code... return rootSource.toSource(); } // use the flow parser module.exports.parser = 'flow'; ``` __Example: specifying a custom parser object in the transform file__ ```js module.exports = function transformer(file, api, options) { const j = api.jscodeshift; const rootSource = j(file.source); // whatever other code... return rootSource.toSource(); } module.exports.parser = { parse: function(source) { // return estree compatible AST }, }; ``` ### Example output ```text $ jscodeshift -t myTransform.js src Processing 10 files... Spawning 2 workers with 5 files each... All workers done. Results: 0 errors 2 unmodified 3 skipped 5 ok ``` ## The jscodeshift API As already mentioned, jscodeshift also provides a wrapper around [recast][]. In order to properly use the jscodeshift API, one has to understand the basic building blocks of recast (and ASTs) as well. ### Core Concepts #### AST nodes An AST node is a plain JavaScript object with a specific set of fields, in accordance with the [Mozilla Parser API][]. The primary way to identify nodes is via their `type`. For example, string literals are represented via `Literal` nodes, which have the structure ```js // "foo" { type: 'Literal', value: 'foo', raw: '"foo"' } ``` It's OK to not know the structure of every AST node type. The [(esprima) AST explorer][ast-explorer] is an online tool to inspect the AST for a given piece of JS code. #### Path objects Recast itself relies heavily on [ast-types][] which defines methods to traverse the AST, access node fields and build new nodes. ast-types wraps every AST node into a *path object*. Paths contain meta-information and helper methods to process AST nodes. For example, the child-parent relationship between two nodes is not explicitly defined. Given a plain AST node, it is not possible to traverse the tree *up*. Given a path object however, the parent can be traversed to via `path.parent`. For more information about the path object API, please have a look at [ast-types][]. #### Builders To make creating AST nodes a bit simpler and "safer", ast-types defines a couple of *builder methods*, which are also exposed on `jscodeshift`. For example, the following creates an AST equivalent to `foo(bar)`: ```js // inside a module transform var j = jscodeshift; // foo(bar); var ast = j.callExpression( j.identifier('foo'), [j.identifier('bar')] ); ``` The signature of each builder function is best learned by having a look at the [definition files](https://github.com/benjamn/ast-types/tree/master/src/def) or in the babel/types [docs](https://babeljs.io/docs/en/babel-types). ### Collections and Traversal In order to transform the AST, you have to traverse it and find the nodes that need to be changed. jscodeshift is built around the idea of **collections** of paths and thus provides a different way of processing an AST than recast or ast-types. A collection has methods to process the nodes inside a collection, often resulting in a new collection. This results in a fluent interface, which can make the transform more readable. Collections are "typed" which means that the type of a collection is the "lowest" type all AST nodes in the collection have in common. That means you cannot call a method for a `FunctionExpression` collection on an `Identifier` collection. Here is an example of how one would find/traverse all `Identifier` nodes with jscodeshift and with recast: ```js // recast var ast = recast.parse(src); recast.visit(ast, { visitIdentifier: function(path) { // do something with path return false; } }); // jscodeshift jscodeshift(src) .find(jscodeshift.Identifier) .forEach(function(path) { // do something with path }); ``` To learn about the provided methods, have a look at the [Collection.js](src/Collection.js) and its [extensions](src/collections/). ### Extensibility jscodeshift provides an API to extend collections. By moving common operators into helper functions (which can be stored separately in other modules), a transform can be made more readable. There are two types of extensions: generic extensions and type-specific extensions. **Generic extensions** are applicable to all collections. As such, they typically don't access specific node data, but rather traverse the AST from the nodes in the collection. **Type-specific** extensions work only on specific node types and are not callable on differently typed collections. #### Examples ```js // 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); } }); jscodeshift(ast).findIdentifiers().logNames(); jscodeshift(ast).logNames(); // error, unless `ast` only consists of Identifier nodes ``` ### Ignoring files and directories Sometimes there are files and directories that you want to avoid running transforms on. For example, the node_modules/ directory, where the project's installed local npm packages reside, can introduce bugs if any files in it are accidentally transformed by jscodeshift. The simplest way to avoid many of these unwanted transforms is to pass jscodeshift the __--gitignore__ flag, which uses the glob patterns specified in your project’s .gitignore file to avoid transforming anything in directories such as node_modules/, dist/, etc. In most cases anything you want git to ignore you proabably are also going to want jscodeshift to ignore as well. _Please note that the .gitignore file use will be taken from the current working directory from which jscodeshift is being run._ ``` jscodeshift --gitignore mytransform.js ``` For more custom ignore functionality, the __--ignore-pattern__ and the __--ignore-config__ arguments can be used. __--ignore-pattern__ takes a [.gitignore format](https://git-scm.com/docs/gitignore#_pattern_format) glob pattern that specifies file and directory patterns to ignore ``` jscodeshift --ignore-pattern="js_configuration_files/**/*” mytransform.js // More than one ignore jscodeshift --ignore-pattern="first_ignored_dir/**/*” -—ignore-pattern="second_ignored_dir/**/*” mytransform.js ``` __--ignore-config__ takes one or more paths to files containing lines with [.gitignore format](https://git-scm.com/docs/gitignore#_pattern_format) glob patterns. ``` // note: .gitignore is a random made-up filename extension for this example jscodeshift --ignore-config="MyIgnoreFile.gitignore" mytransform.js // More than one ignore file jscodeshift --ignore-pattern="first_ignore_file.gitignore” --ignore-pattern="second_ignore_file.gitignore” mytransform.js ``` ### Passing options to [recast] You may want to change some of the output settings (like setting `'` instead of `"` or changing the end-of-line terminator to `\r\n` for Windows files). This can be done by passing config options to [recast]. ```js .toSource({quote: 'single'}); // sets strings to use single quotes in transformed code. // or .toSource({lineTerminator: '\r\n'}); // sets EOL to use CRLF. ``` You can also pass options to recast's `parse` method by passing an object to jscodeshift as second argument: ```js jscodeshift(source, {...}) ``` For more details on recast config options, see [here](https://github.com/benjamn/recast/blob/master/lib/options.ts). ### Unit Testing jscodeshift comes with a simple utility to allow easy unit testing with [Jest](https://facebook.github.io/jest/), without having to write a lot of boilerplate code. This utility makes some assumptions in order to reduce the amount of configuration required: - The test is located in a subdirectory under the directory the transform itself is located in (eg. `__tests__`) - Test fixtures are located in a `__testfixtures__` directory This results in a directory structure like this: ``` /MyTransform.js /__tests__/MyTransform-test.js /__testfixtures__/MyTransform.input.js /__testfixtures__/MyTransform.output.js ``` A simple example of unit tests is bundled in the [sample directory](sample). The `testUtils` module exposes a number of useful helpers for unit testing. #### `defineTest` Defines a Jest/Jasmine test for a jscodeshift transform which depends on fixtures ```js jest.autoMockOff(); const defineTest = require('jscodeshift/dist/testUtils').defineTest; defineTest(__dirname, 'MyTransform'); ``` An alternate fixture filename can be provided as the fourth argument to `defineTest`. This also means that multiple test fixtures can be provided: ```js defineTest(__dirname, 'MyTransform', null, 'FirstFixture'); defineTest(__dirname, 'MyTransform', null, 'SecondFixture'); ``` This will run two tests: - `__testfixtures__/FirstFixture.input.js` - `__testfixtures__/SecondFixture.input.js` #### `defineInlineTest` Defines a Jest/Jasmine test suite for a jscodeshift transform which accepts inline values This is a more flexible alternative to `defineTest`, as this allows to also provide options to your transform ```js const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest; const transform = require('../myTransform'); const transformOptions = {}; defineInlineTest(transform, transformOptions, 'input', 'expected output', 'test name (optional)'); ``` #### `defineSnapshotTest` Similar to `defineInlineTest` but instead of requiring an output value, it uses Jest's `toMatchSnapshot()` ```js const defineSnapshotTest = require('jscodeshift/dist/testUtils').defineSnapshotTest; const transform = require('../myTransform'); const transformOptions = {}; defineSnapshotTest(transform, transformOptions, 'input', 'test name (optional)'); ``` For more information on snapshots, check out [Jest's docs](https://jestjs.io/docs/en/snapshot-testing) #### `defineSnapshotTestFromFixture` Similar to `defineSnapshotTest` but will load the file using same file-directory defaults as `defineTest` ```js const defineSnapshotTestDefault = require('jscodeshift/dist/testUtils').defineSnapshotTestDefault; const transform = require('../myTransform'); const transformOptions = {}; defineSnapshotTestFromFixture(__dirname, transform, transformOptions, 'FirstFixture', 'test name (optional)'); ``` #### `applyTransform` Executes your transform using the options and the input given and returns the result. This function is used internally by the other helpers, but it can prove useful in other cases. (bear in mind the `transform` module can be asynchronous. In that case, `applyTransform` will return a `Promise` with the transformed code. Otherwise, it will directly return the transformed code as a `string`). ```js const applyTransform = require('jscodeshift/dist/testUtils').applyTransform; const transform = require('../myTransform'); const transformOptions = {}; const output = applyTransform(transform, transformOptions, 'input'); ``` #### ES modules If you're authoring your transforms and tests using ES modules, make sure to import the transform's parser (if specified) in your tests: ```js // MyTransform.js export const parser = 'flow' export default function MyTransform(fileInfo, api, options) { // ... } ``` ```js // __tests__/MyTransform-test.js import { defineInlineTest } from 'jscodeshift/dist/testUtils import * as transform from '../MyTransform console.log(transform.parser) // 'flow' defineInlineTest(transform, /* ... */) ``` ### Example Codemods - [react-codemod](https://github.com/reactjs/react-codemod) - React codemod scripts to update React APIs. - [js-codemod](https://github.com/cpojer/js-codemod/) - Codemod scripts to transform code to next generation JS. - [js-transforms](https://github.com/jhgg/js-transforms) - Some documented codemod experiments to help you learn. - [fix-js](https://github.com/anshckr/fix-js) - Codemods to fix some ESLint issues ### Local Documentation Server To update docs in `/docs`, use `npm run docs`. To view these docs locally, use `npx http-server ./docs` ## VSCode Debugging It's recommended that you set up your codemod project to all debugging via the VSCode IDE. When you open your project in VSCode, add the following configuration to your launch.json debugging configuration. ```jsonc { // 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": [ "/**" ], "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" } ] } ``` Once this has been added to the configuration 1. Install jscodeshift as a package if you haven't done so already by running the command **npm install --save jscodeshift**. The debug configuration will not work otherwise. 2. Once the jscodeshift local package has been installed, go to the VSCode file tree and select the file on which you want to run the transform. For example, if you wanted to run codemod transforms of foo.js file, you would click on the entry for foo.js file in your project tree. 3. Select "Debug Transform" from the debugging menu's options menu. 4. Click the **"Start Debugging"** button on the VSCode debugger. 5. You will be then prompted for the name of jscodeshift transform file. Enter in the name of the transform file to use. If no name is given it will default to **transform.js** 6. Select the parser to use from the presented selection list of parsers. The transform will otherwise default to using the **babel** parser. 7. The transform will then be run, stopping at any breakpoints that have been set. 8. If there are no errors and the transform is complete, then the results of the transform will be printed in the VSCode debugging console. The file with the contents that have been transformed will not be changed, as the debug configuration makes use the jscodeshift **--dry** option. ### Recipes - [Retain leading comment(s) in file when replacing/removing first statement](recipes/retain-first-comment.md) [npm]: https://www.npmjs.com/ [Mozilla Parser API]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API [recast]: https://github.com/benjamn/recast [ast-types]: https://github.com/benjamn/ast-types [ast-explorer]: http://astexplorer.net/ ================================================ FILE: bin/__tests__/jscodeshift-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /*global jest, jasmine, xdescribe, it, expect, beforeEach*/ /*eslint camelcase: 0, no-unused-vars: 0*/ jest.autoMockOff(); // Increase default timeout (5000ms) for Travis jest.setTimeout(600000); // 10 minutes const child_process = require('child_process'); const fs = require('fs'); const path = require('path'); const testUtils = require('../../utils/testUtils'); const createTransformWith = testUtils.createTransformWith; const createTempFileWith = testUtils.createTempFileWith; function readFile(path) { return fs.readFileSync(path).toString(); } function run(args, stdin, cwd) { return new Promise(resolve => { const jscodeshift = child_process.spawn( path.join(__dirname, '../jscodeshift.sh'), args, { cwd, } ); let stdout = ''; let stderr = ''; jscodeshift.stdout.on('data', data => stdout += data); jscodeshift.stderr.on('data', data => stderr += data); jscodeshift.on('close', () => resolve([stdout, stderr])); if (stdin) { jscodeshift.stdin.write(stdin); } jscodeshift.stdin.end(); }); } describe('jscodeshift CLI', () => { it('calls the transform and file information', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const sourceB = createTempFileWith('b\n', 'sourceB', '.js'); const sourceC = createTempFileWith('c', 'sourceC', '.js'); const transformA = createTransformWith( 'return "transform" + fileInfo.source;' ); const transformB = createTransformWith( 'return fileInfo.path;' ); return Promise.all([ run(['-t', transformA, sourceA, sourceB]).then( out => { expect(out[1]).toBe(''); expect(readFile(sourceA)).toBe('transforma'); expect(readFile(sourceB)).toBe('transformb\n'); } ), run(['-t', transformB, sourceC]).then( out => { expect(out[1]).toBe(''); expect(readFile(sourceC)).toBe(sourceC); } ) ]); }); it('takes file list from stdin if --stdin is set', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const sourceB = createTempFileWith('b\n', 'sourceB', '.js'); const sourceC = createTempFileWith('c', 'sourceC', '.js'); const transformA = createTransformWith( 'return "transform" + fileInfo.source;' ); return run(['--stdin', '-t', transformA], [sourceA, sourceB, sourceC].join('\n')).then( out => { expect(out[1]).toBe(''); expect(readFile(sourceA)).toBe('transforma'); expect(readFile(sourceB)).toBe('transformb\n'); expect(readFile(sourceC)).toBe('transformc'); } ); }); it('does not transform files in a dry run', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith( 'return "transform" + fileInfo.source;' ); return run(['-t', transform, '-d', sourceA]).then( () => { expect(readFile(sourceA).toString()).toBe('a'); } ); }); describe('Babel', () => { // Verifiers that ES6 features are supported either natively or via Babel it('supports ES6 features in transform files', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith( 'const a = 42; return a;' ); return Promise.all([ run(['-t', transform, sourceA]).then( () => { expect(readFile(sourceA).toString()) .toEqual('42'); } ), ]); }); // Verifies that spread is supported, either natively over via Babel it('supports property spread in transform files', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith( 'const a = {...{foo: 42}, bar: 21}; return a.foo;' ); return Promise.all([ run(['-t', transform, sourceA]).then( () => { expect(readFile(sourceA).toString()) .toEqual('42'); } ), ]); }); it('supports class properties in transform files', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith(` return (class { x = 42; }).toString(); `); return Promise.all([ run(['-t', transform, sourceA]).then( () => { expect(readFile(sourceA).toString()) .toMatch(/\(this,\s*['"]x['"]/); } ), ]); }); it('supports flow type annotations in transform files', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith( 'return (function() { "use strict"; const a: number = 42; }).toString();' ); return Promise.all([ run(['-t', transform, sourceA]).then( () => { expect(readFile(sourceA).toString()) .toMatch(/a\s*=\s*42/); } ), ]); }); it('supports Typescript type annotations in transform files', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith( 'return (function() { "use strict"; function foo(x: string): x is string {}}).toString();', '.ts' ); return Promise.all([ run(['-t', transform, sourceA]).then( args => { expect(readFile(sourceA).toString()) .toMatch(/function\s+foo\(x\)\s*{}/); } ), ]); }); it('transpiles imported Typescript files in transform files', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const helper = createTempFileWith( 'module.exports = function(x: string): x is string {};', undefined, '.ts' ); const transform = createTransformWith( `return require('${helper}').toString();`, '.ts' ); return Promise.all([ run(['-t', transform, sourceA]).then( args => { expect(readFile(sourceA).toString()) .toMatch(/function\s*\(x\)\s*{}/); } ), ]); }); }); it('passes jscodeshift and stats the transform function', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith([ ' return String(', ' typeof api.jscodeshift === "function" &&', ' typeof api.stats === "function"', ' );', ].join('\n')); return run(['-t', transform, sourceA]).then( () => { expect(readFile(sourceA).toString()).toBe('true'); } ); }); it('passes options along to the transform', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith('return options.foo;'); return run(['-t', transform, '--foo=42', sourceA]).then( () => { expect(readFile(sourceA).toString()).toBe('42'); } ); }); it('does not stall with too many files', () => { const sources = []; for (let i = 0; i < 100; i++) { sources.push(createTempFileWith('a')); } const transform = createTransformWith(''); return run(['-t', transform, '--foo=42'].concat(sources)).then( () => { expect(true).toBe(true); } ); }); describe('ignoring', () => { const transform = createTransformWith( 'return "transform" + fileInfo.source;' ); let sources = []; beforeEach(() => { sources = []; const sourceA = createTempFileWith('a', 'sourceA', '.js'); const testIgnoreFile = createTempFileWith('a', 'a-test', '.js'); sources.push(sourceA); sources.push(testIgnoreFile); // sources.push(createTempFileWith('b', 'src/lib/b.js')); }); it('supports basic glob', () => { const pattern = '*-test.js'; return run(['-t', transform, '--ignore-pattern', pattern].concat(sources)).then( () => { expect(readFile(sources[0]).toString()).toBe('transforma'); expect(readFile(sources[1]).toString()).toBe('a'); } ); }); it('supports filename match', () => { const pattern = 'sourceA.js'; return run(['-t', transform, '--ignore-pattern', pattern].concat(sources)).then( () => { expect(readFile(sources[0]).toString()).toBe('a'); expect(readFile(sources[1]).toString()).toBe('transforma'); } ); }); it('accepts a list of patterns', () => { const patterns = ['--ignore-pattern', 'sourceA.js', '--ignore-pattern', '*-test.js']; return run(['-t', transform].concat(patterns).concat(sources)).then( () => { expect(readFile(sources[0]).toString()).toBe('a'); expect(readFile(sources[1]).toString()).toBe('a'); } ); }); it('sources ignore patterns from configuration file', () => { const patterns = ['sub/dir/', '*-test.js']; const gitignore = createTempFileWith(patterns.join('\n'), '.gitignore'); sources.push(createTempFileWith('subfile', 'sub/dir/file.js')); return run(['-t', transform, '--ignore-config', gitignore].concat(sources)).then( () => { expect(readFile(sources[0]).toString()).toBe('transforma'); expect(readFile(sources[1]).toString()).toBe('a'); expect(readFile(sources[2]).toString()).toBe('subfile'); } ); }); it('sources ignore patterns from configuration file', () => { const patterns = ['sub/dir/', '*-test.js']; const gitignore = createTempFileWith(patterns.join('\n'), '.gitignore'); sources.push(createTempFileWith('subfile', 'sub/dir/file.js')); return run(['-t', transform, '--ignore-config', gitignore].concat(sources)).then( () => { expect(readFile(sources[0]).toString()).toBe('transforma'); expect(readFile(sources[1]).toString()).toBe('a'); expect(readFile(sources[2]).toString()).toBe('subfile'); } ); }); it('sources ignore patterns from a root directory\'s .gitignore file', () => { // This test is a little different from the one above only in that we have // to simulate automatically hitting up the .gitignore file from the current // directory that the codeshift process is running from const patterns = ['sub/dir/', '*-test.js']; const gitignore = createTempFileWith(patterns.join('\n'), '.gitignore'); sources.push(createTempFileWith('subfile', 'sub/dir/file.js')); // Make the temp directory with our test files the current working directory let currPath = process.cwd(); let tempDirPath = path.dirname(sources[0]); process.chdir(tempDirPath);; return run(['-t', transform, '--gitignore'].concat(sources)).then( () => { expect(readFile(sources[0]).toString()).toBe('transforma'); expect(readFile(sources[1]).toString()).toBe('a'); expect(readFile(sources[2]).toString()).toBe('subfile'); } ) .catch((err) => { console.log(err); }) .finally(() => { process.chdir(currPath); }) }); it('accepts a list of configuration files', () => { const gitignore = createTempFileWith(['sub/dir/'].join('\n'), '.gitignore'); const eslintignore = createTempFileWith(['**/*test.js', 'sourceA.js'].join('\n'), '.eslintignore'); const configs = ['--ignore-config', gitignore, '--ignore-config', eslintignore]; sources.push(createTempFileWith('subfile', 'sub/dir/file.js')); return run(['-t', transform].concat(configs).concat(sources)).then( () => { expect(readFile(sources[0]).toString()).toBe('a'); expect(readFile(sources[1]).toString()).toBe('a'); expect(readFile(sources[2]).toString()).toBe('subfile'); } ); }); }); describe('output', () => { it('shows workers info and stats at the end by default', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith('return null;'); return run(['-t', transform, sourceA]).then( out => { expect(out[0]).toContain('Processing 1 files...'); expect(out[0]).toContain('Spawning 1 workers...'); expect(out[0]).toContain('Sending 1 files to free worker...'); expect(out[0]).toContain('All done.'); expect(out[0]).toContain('Results: '); expect(out[0]).toContain('Time elapsed: '); } ); }); it('does not ouput anything in silent mode', () => { const sourceA = createTempFileWith('a', 'sourceA', '.js'); const transform = createTransformWith('return null;'); return run(['-t', transform, '-s', sourceA]).then( out => { expect(out[0]).toEqual(''); } ); }); }); xdescribe('--parser=ts', () => { it('parses TypeScript sources', () => { const source = createTempFileWith('type Foo = string | string[];', 'source', '.ts'); const transform = createTransformWith( 'api.jscodeshift(fileInfo.source)\n { return "changed" };' ); return run([ '-t', transform, '--parser', 'ts', '--run-in-band', source, ]).then( out => { expect(out[0]).not.toContain('Transformation error'); expect(readFile(source)).toEqual('changed'); } ); }); }); describe('--parser-config', () => { it('allows custom parser settings to be passed', () => { // @decorators before export are not supported in the current default // config const source = createTempFileWith('@foo\nexport class Bar {}', 'source', '.js'); const parserConfig = createTempFileWith(JSON.stringify({ sourceType: 'module', tokens: true, plugins: [ ['decorators', {decoratorsBeforeExport: true}], ], })); const transform = createTransformWith( 'api.jscodeshift(fileInfo.source)\n { return "changed" };' ); return run([ '-t', transform, '--parser-config', parserConfig, '--parser', 'babylon', '--run-in-band', source, ]).then( out => { expect(out[0]).not.toContain('Transformation error'); expect(readFile(source)).toEqual('changed'); } ); }); }); }); ================================================ FILE: bin/jscodeshift.js ================================================ #!/usr/bin/env node /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const Runner = require('../src/Runner.js'); const fs = require('fs'); const path = require('path'); const pkg = require('../package.json'); const { DEFAULT_EXTENSIONS } = require('@babel/core'); const defaultExtensions = DEFAULT_EXTENSIONS.concat(['ts', 'tsx']).map( (ext) => (ext.startsWith('.') ? ext.substring(1) : ext) ).sort().join(','); const parser = require('../src/argsParser') .options({ transform: { display_index: 15, abbr: 't', default: './transform.js', help: 'path to the transform file. Can be either a local path or url', metavar: 'FILE', required: true }, cpus: { display_index: 1, abbr: 'c', help: 'start at most N child processes to process source files', defaultHelp: 'max(all - 1, 1)', metavar: 'N', process: Number, }, verbose: { display_index: 16, abbr: 'v', choices: [0, 1, 2], default: 0, help: 'show more information about the transform process', metavar: 'N', process: Number, }, dry: { display_index: 2, abbr: 'd', flag: true, default: false, help: 'dry run (no changes are made to files)' }, print: { display_index: 11, abbr: 'p', flag: true, default: false, help: 'print transformed files to stdout, useful for development' }, babel: { display_index: 0, flag: true, default: true, help: 'apply babeljs to the transform file' }, extensions: { display_index: 3, default: defaultExtensions, help: 'transform files with these file extensions (comma separated list)', metavar: 'EXT', }, ignorePattern: { display_index: 7, full: 'ignore-pattern', list: true, help: 'ignore files that match a provided glob expression', metavar: 'GLOB', }, ignoreConfig: { display_index: 6, full: 'ignore-config', list: true, help: 'ignore files if they match patterns sourced from a configuration file (e.g. a .gitignore)', metavar: 'FILE' }, gitignore: { display_index: 8, flag: true, default: false, help: 'adds entries the current directory\'s .gitignore file', }, runInBand: { display_index: 12, flag: true, default: false, full: 'run-in-band', help: 'run serially in the current process' }, silent: { display_index: 13, abbr: 's', flag: true, default: false, help: 'do not write to stdout or stderr' }, parser: { display_index: 9, choices: ['babel', 'babylon', 'flow', 'ts', 'tsx'], default: 'babel', help: 'the parser to use for parsing the source files' }, parserConfig: { display_index: 10, full: 'parser-config', help: 'path to a JSON file containing a custom parser configuration for flow or babylon', metavar: 'FILE', process: file => JSON.parse(fs.readFileSync(file)), }, failOnError: { display_index: 4, flag: true, help: 'Return a non-zero code when there are errors', full: 'fail-on-error', default: false, }, version: { display_index: 17, help: 'print version and exit', callback: function() { const requirePackage = require('../utils/requirePackage'); return [ `jscodeshift: ${pkg.version}`, ` - babel: ${require('@babel/core').version}`, ` - babylon: ${requirePackage('@babel/parser').version}`, ` - flow: ${requirePackage('flow-parser').version}`, ` - recast: ${requirePackage('recast').version}\n`, ].join('\n'); }, }, stdin: { display_index: 14, help: 'read file/directory list from stdin', flag: true, default: false, }, }); let options, positionalArguments; try { ({options, positionalArguments} = parser.parse()); if (positionalArguments.length === 0 && !options.stdin) { process.stderr.write( 'Error: You have to provide at least one file/directory to transform.' + '\n\n---\n\n' + parser.getHelpText() ); process.exit(1); } } catch(e) { const exitCode = e.exitCode === undefined ? 1 : e.exitCode; (exitCode ? process.stderr : process.stdout).write(e.message); process.exit(exitCode); } function run(paths, options) { Runner.run( /^https?/.test(options.transform) ? options.transform : path.resolve(options.transform), paths, options ); } if (options.stdin) { let buffer = ''; process.stdin.on('data', data => buffer += data); process.stdin.on('end', () => run(buffer.split('\n'), options)); } else { run(positionalArguments, options); } ================================================ FILE: bin/jscodeshift.sh ================================================ #!/usr/bin/env node /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; require('./jscodeshift.js'); ================================================ FILE: index.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ module.exports = require('./src/core'); ================================================ FILE: package.json ================================================ { "name": "jscodeshift", "version": "17.3.0", "description": "A toolkit for JavaScript codemods", "repository": { "type": "git", "url": "https://github.com/facebook/jscodeshift.git" }, "bugs": "https://github.com/facebook/jscodeshift/issues", "main": "index.js", "scripts": { "clean": "rm -rf dist/", "prepare": "yarn clean && cp -R src/ dist/", "test": "jest --bail", "release": "changeset publish" }, "bin": { "jscodeshift": "./bin/jscodeshift.js" }, "keywords": [ "codemod", "recast", "babel" ], "author": "Felix Kling", "license": "MIT", "dependencies": { "@babel/core": "^7.24.7", "@babel/parser": "^7.24.7", "@babel/plugin-transform-class-properties": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/preset-flow": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", "flow-parser": "0.*", "graceful-fs": "^4.2.4", "micromatch": "^4.0.7", "neo-async": "^2.5.0", "picocolors": "^1.0.1", "recast": "^0.23.11", "tmp": "^0.2.3", "write-file-atomic": "^5.0.1" }, "peerDependencies": { "@babel/preset-env": "^7.1.6" }, "peerDependenciesMeta": { "@babel/preset-env": { "optional": true } }, "devDependencies": { "@babel/eslint-parser": "^7.24.7", "@changesets/cli": "^2.27.8", "eslint": "8.56.0", "jest": "^29.7.0", "jsdoc": "^4.0.3" }, "jest": { "roots": [ "src", "bin", "parser", "sample" ] }, "engines": { "node": ">=16" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } ================================================ FILE: parser/__tests__/.eslintrc ================================================ { "globals": { "jest": true }, "env": { "jasmine": true } } ================================================ FILE: parser/__tests__/__snapshots__/tsx-test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`tsxParser parse extends the ts config with jsx support 1`] = ` [ ""mock content";", { "allowImportExportEverywhere": true, "allowReturnOutsideFunction": true, "plugins": [ "jsx", "asyncGenerators", "decoratorAutoAccessors", "bigInt", "classPrivateMethods", "classPrivateProperties", "classProperties", "decorators-legacy", "doExpressions", "dynamicImport", "exportDefaultFrom", "exportExtensions", "exportNamespaceFrom", "functionBind", "functionSent", "importAttributes", "importMeta", "nullishCoalescingOperator", "numericSeparator", "objectRestSpread", "optionalCatchBinding", "optionalChaining", [ "pipelineOperator", { "proposal": "minimal", }, ], "throwExpressions", "typescript", ], "sourceType": "module", "startLine": 1, "tokens": true, }, ] `; ================================================ FILE: parser/__tests__/tsx-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /*global jest, describe, it, expect*/ 'use strict'; jest.mock('@babel/parser') const babylon = require('@babel/parser'); const tsxParser = require('../tsx'); describe('tsxParser', function() { describe('parse', function() { it('extends the ts config with jsx support', function() { const parser = tsxParser(); parser.parse('"mock content";'); expect(babylon.parse).toHaveBeenCalledTimes(1); expect(babylon.parse.mock.calls[0]).toMatchSnapshot(); }); }); }); ================================================ FILE: parser/babel5Compat.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const babylon = require('@babel/parser'); // These are the options that were the default of the Babel5 parse function // see https://github.com/babel/babel/blob/5.x/packages/babel/src/api/node.js#L81 const options = { sourceType: 'module', allowHashBang: true, ecmaVersion: Infinity, allowImportExportEverywhere: true, allowReturnOutsideFunction: true, startLine: 1, tokens: true, plugins: [ 'estree', 'jsx', 'asyncGenerators', 'classProperties', 'doExpressions', 'exportExtensions', 'functionBind', 'functionSent', 'objectRestSpread', 'dynamicImport', 'nullishCoalescingOperator', 'optionalChaining', ['decorators', {decoratorsBeforeExport: false}], ], }; /** * Wrapper to set default options. Doesn't accept custom options because in that * case babylon should be used instead. */ module.exports = function() { return { parse(code) { return babylon.parse(code, options); }, }; }; ================================================ FILE: parser/babylon.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const babylon = require('@babel/parser'); const defaultOptions = { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, startLine: 1, tokens: true, plugins: [ ['flow', {all: true}], 'flowComments', 'jsx', 'asyncGenerators', 'bigInt', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', ['decorators', {decoratorsBeforeExport: false}], 'doExpressions', 'dynamicImport', 'exportDefaultFrom', 'exportNamespaceFrom', 'functionBind', 'functionSent', 'importMeta', 'logicalAssignment', 'nullishCoalescingOperator', 'numericSeparator', 'objectRestSpread', 'optionalCatchBinding', 'optionalChaining', ['pipelineOperator', {proposal: 'minimal'}], 'throwExpressions', ], }; /** * Wrapper to set default options */ module.exports = function(options=defaultOptions) { return { parse(code) { return babylon.parse(code, options); }, }; }; ================================================ FILE: parser/flow.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const flowParser = require('flow-parser'); const defaultOptions = { enums: true, esproposal_class_instance_fields: true, esproposal_class_static_fields: true, esproposal_decorators: true, esproposal_export_star_as: true, esproposal_optional_chaining: true, esproposal_nullish_coalescing: true, tokens: true, types: true, }; /** * Wrapper to set default options */ module.exports = function(options=defaultOptions) { return { parse(code) { return flowParser.parse(code, options); }, }; }; ================================================ FILE: parser/ts.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const babylon = require('@babel/parser'); const options = require('./tsOptions'); /** * Doesn't accept custom options because babylon should be used directly in * that case. */ module.exports = function() { return { parse(code) { return babylon.parse(code, options); }, }; }; ================================================ FILE: parser/tsOptions.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; /** * Options shared by the TypeScript and TSX parsers. */ module.exports = { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, startLine: 1, tokens: true, plugins: [ 'asyncGenerators', 'decoratorAutoAccessors', 'bigInt', 'classPrivateMethods', 'classPrivateProperties', 'classProperties', 'decorators-legacy', 'doExpressions', 'dynamicImport', 'exportDefaultFrom', 'exportExtensions', 'exportNamespaceFrom', 'functionBind', 'functionSent', 'importAttributes', 'importMeta', 'nullishCoalescingOperator', 'numericSeparator', 'objectRestSpread', 'optionalCatchBinding', 'optionalChaining', ['pipelineOperator', { proposal: 'minimal' }], 'throwExpressions', 'typescript' ], }; ================================================ FILE: parser/tsx.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const babylon = require('@babel/parser'); const baseOptions = require('./tsOptions'); const options = Object.assign({}, baseOptions); options.plugins = ['jsx'].concat(baseOptions.plugins); /** * Doesn't accept custom options because babylon should be used directly in * that case. */ module.exports = function() { return { parse(code) { return babylon.parse(code, options); }, }; }; ================================================ FILE: recipes/retain-first-comment.md ================================================ # Retain comment on first line ## Problem When removing or replacing the first statement in a file, it is possible for [leading comments at the top of the file to be removed](https://github.com/facebook/jscodeshift/issues/44). ## Solution To retain the leading comments during a transformation, the `comments` array on the statement's `node` _must_ be copied to the next statement's `node` that will be at the top of the file. ## Examples #### Bad ##### Transform ```javascript export default function transformer(file, api) { const j = api.jscodeshift; return j(file.source) .find(j.VariableDeclaration) .replaceWith( j.expressionStatement(j.callExpression( j.identifier('foo'), [] ) ) ) .toSource(); }; ``` ##### In ```javascript // Comment on first line const firstStatement = require('some-module'); ``` ##### Out ```javascript foo(); ``` #### Good ##### Transform ```javascript 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(); }; ``` ##### In ```javascript // Comment on first line const firstStatement = require('some-module'); ``` ##### Out ```javascript // Comment on first line foo(); ``` ================================================ FILE: sample/__testfixtures__/reverse-identifiers.input.js ================================================ var firstWord = 'Hello '; var secondWord = 'world'; var message = firstWord + secondWord; class Foo { @decorated *bar() { } } ================================================ FILE: sample/__testfixtures__/reverse-identifiers.output.js ================================================ var droWtsrif = 'Hello '; var droWdnoces = 'world'; var egassem = droWtsrif + droWdnoces; class ooF { @detaroced *rab() { } } ================================================ FILE: sample/__testfixtures__/typescript/reverse-identifiers.input.ts ================================================ import type { Stream } from 'stream'; const firstWord = 'Hello '; const secondWord = 'world'; const message = firstWord + secondWord; const getMessage = (): string => message ================================================ FILE: sample/__testfixtures__/typescript/reverse-identifiers.output.ts ================================================ import type { maertS } from 'stream'; const droWtsrif = 'Hello '; const droWdnoces = 'world'; const egassem = droWtsrif + droWdnoces; const egasseMteg = (): string => egassem ================================================ FILE: sample/__tests__/__snapshots__/reverse-identifiers-test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`transforms correctly 1`] = ` "var droWtsrif = 'Hello '; var droWdnoces = 'world'; var egassem = droWtsrif + droWdnoces; class ooF { @detaroced *rab() { } }" `; ================================================ FILE: sample/__tests__/reverse-identifiers-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * An example of writing a unit test for a jscodeshift script using the * `defineTest` helper bundled with jscodeshift. This will run the * reverse-identifiers.js transform with the input specified in the * reverse-identifiers-input file, and expect the output to be the same as that * in reverse-identifiers-output. */ 'use strict'; jest.autoMockOff(); const defineTest = require('../../src/testUtils').defineTest; const defineInlineTest = require('../../src/testUtils').defineInlineTest; const defineSnapshotTestFromFixture = require('../../src/testUtils').defineSnapshotTestFromFixture; const transform = require('../reverse-identifiers'); defineTest(__dirname, 'reverse-identifiers'); defineTest(__dirname, 'reverse-identifiers', null, 'typescript/reverse-identifiers', { parser: 'ts' }); describe('reverse-identifiers', () => { defineInlineTest(transform, {}, ` var firstWord = 'Hello '; var secondWord = 'world'; var message = firstWord + secondWord;`,` var droWtsrif = 'Hello '; var droWdnoces = 'world'; var egassem = droWtsrif + droWdnoces; `); defineInlineTest(transform, {}, 'function aFunction() {};', 'function noitcnuFa() {};', 'Reverses function names' ); }); // the snapshot output of this file should be the same as reverse-identifiers.output.js defineSnapshotTestFromFixture(__dirname, transform, {}, 'reverse-identifiers'); ================================================ FILE: sample/reverse-identifiers.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Example jscodeshift transformer. Simply reverses the names of all * identifiers. */ function transformer(file, api) { const j = api.jscodeshift; return j(file.source) .find(j.Identifier) .replaceWith( p => j.identifier(p.node.name.split('').reverse().join('')) ) .toSource(); } module.exports = transformer; ================================================ FILE: src/Collection.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const assert = require('assert'); const intersection = require('./utils/intersection'); const recast = require('recast'); const union = require('./utils/union'); const astTypes = recast.types; var types = astTypes.namedTypes; const NodePath = astTypes.NodePath; const Node = types.Node; /** * This represents a generic collection of node paths. It only has a generic * API to access and process the elements of the list. It doesn't know anything * about AST types. * * @mixes traversalMethods * @mixes mutationMethods * @mixes transformMethods * @mixes globalMethods */ class Collection { /** * @param {Array} paths An array of AST paths * @param {Collection} parent A parent collection * @param {Array} types An array of types all the paths in the collection * have in common. If not passed, it will be inferred from the paths. * @return {Collection} */ constructor(paths, parent, types) { assert.ok(Array.isArray(paths), 'Collection is passed an array'); assert.ok( paths.every(p => p instanceof NodePath), 'Array contains only paths' ); this._parent = parent; this.__paths = paths; if (types && !Array.isArray(types)) { types = _toTypeArray(types); } else if (!types || Array.isArray(types) && types.length === 0) { types = _inferTypes(paths); } this._types = types.length === 0 ? _defaultType : types; } /** * Returns a new collection containing the nodes for which the callback * returns true. * * @param {function} callback * @return {Collection} */ filter(callback) { return new this.constructor(this.__paths.filter(callback), this); } /** * Executes callback for each node/path in the collection. * * @param {function} callback * @return {Collection} The collection itself */ forEach(callback) { this.__paths.forEach( (path, i, paths) => callback.call(path, path, i, paths) ); return this; } /** * Tests whether at-least one path passes the test implemented by the provided callback. * * @param {function} callback * @return {boolean} */ some(callback) { return this.__paths.some( (path, i, paths) => callback.call(path, path, i, paths) ); } /** * Tests whether all paths pass the test implemented by the provided callback. * * @param {function} callback * @return {boolean} */ every(callback) { return this.__paths.every( (path, i, paths) => callback.call(path, path, i, paths) ); } /** * Executes the callback for every path in the collection and returns a new * collection from the return values (which must be paths). * * The callback can return null to indicate to exclude the element from the * new collection. * * If an array is returned, the array will be flattened into the result * collection. * * @param {function} callback * @param {Type} type Force the new collection to be of a specific type */ map(callback, type) { const paths = []; this.forEach(function(path) { /*jshint eqnull:true*/ let result = callback.apply(path, arguments); if (result == null) return; if (!Array.isArray(result)) { result = [result]; } for (let i = 0; i < result.length; i++) { if (paths.indexOf(result[i]) === -1) { paths.push(result[i]); } } }); return fromPaths(paths, this, type); } /** * Returns the number of elements in this collection. * * @return {number} */ size() { return this.__paths.length; } /** * Returns the number of elements in this collection. * * @return {number} */ get length() { return this.__paths.length; } /** * Returns an array of AST nodes in this collection. * * @return {Array} */ nodes() { return this.__paths.map(p => p.value); } paths() { return this.__paths; } getAST() { if (this._parent) { return this._parent.getAST(); } return this.__paths; } toSource(options) { if (this._parent) { return this._parent.toSource(options); } if (this.__paths.length === 1) { return recast.print(this.__paths[0], options).code; } else { return this.__paths.map(p => recast.print(p, options).code); } } /** * Returns a new collection containing only the element at position index. * * In case of a negative index, the element is taken from the end: * * .at(0) - first element * .at(-1) - last element * * @param {number} index * @return {Collection} */ at(index) { return fromPaths( this.__paths.slice( index, index === -1 ? undefined : index + 1 ), this ); } /** * Proxies to NodePath#get of the first path. * * @param {string|number} ...fields */ get() { const path = this.__paths[0]; if (!path) { throw Error( 'You cannot call "get" on a collection with no paths. ' + 'Instead, check the "length" property first to verify at least 1 path exists.' ); } return path.get.apply(path, arguments); } /** * Returns the type(s) of the collection. This is only used for unit tests, * I don't think other consumers would need it. * * @return {Array} */ getTypes() { return this._types; } /** * Returns true if this collection has the type 'type'. * * @param {Type} type * @return {boolean} */ isOfType(type) { return !!type && this._types.indexOf(type.toString()) > -1; } } /** * Given a set of paths, this infers the common types of all paths. * @private * @param {Array} paths An array of paths. * @return {Type} type An AST type */ function _inferTypes(paths) { let _types = []; if (paths.length > 0 && Node.check(paths[0].node)) { const nodeType = types[paths[0].node.type]; const sameType = paths.length === 1 || paths.every(path => nodeType.check(path.node)); if (sameType) { _types = [nodeType.toString()].concat( astTypes.getSupertypeNames(nodeType.toString()) ); } else { // try to find a common type _types = intersection( paths.map(path => astTypes.getSupertypeNames(path.node.type)) ); } } return _types; } function _toTypeArray(value) { value = !Array.isArray(value) ? [value] : value; value = value.map(v => v.toString()); if (value.length > 1) { return union( [value].concat(intersection(value.map(_getSupertypeNames))) ); } else { return value.concat(_getSupertypeNames(value[0])); } } function _getSupertypeNames(type) { try { return astTypes.getSupertypeNames(type); } catch(error) { if (error.message === '') { // Likely the case that the passed type wasn't found in the definition // list. Maybe a typo. ast-types doesn't throw a useful error in that // case :( throw new Error( '"' + type + '" is not a known AST node type. Maybe a typo?' ); } throw error; } } /** * Creates a new collection from an array of node paths. * * If type is passed, it will create a typed collection if such a collection * exists. The nodes or path values must be of the same type. * * Otherwise it will try to infer the type from the path list. If every * element has the same type, a typed collection is created (if it exists), * otherwise, a generic collection will be created. * * @ignore * @param {Array} paths An array of paths * @param {Collection} parent A parent collection * @param {Type} type An AST type * @return {Collection} */ function fromPaths(paths, parent, type) { assert.ok( paths.every(n => n instanceof NodePath), 'Every element in the array should be a NodePath' ); return new Collection(paths, parent, type); } /** * Creates a new collection from an array of nodes. This is a convenience * method which converts the nodes to node paths first and calls * * Collections.fromPaths(paths, parent, type) * * @ignore * @param {Array} nodes An array of AST nodes * @param {Collection} parent A parent collection * @param {Type} type An AST type * @return {Collection} */ function fromNodes(nodes, parent, type) { assert.ok( nodes.every(n => Node.check(n)), 'Every element in the array should be a Node' ); return fromPaths( nodes.map(n => new NodePath(n)), parent, type ); } const CPt = Collection.prototype; /** * This function adds the provided methods to the prototype of the corresponding * typed collection. If no type is passed, the methods are added to * Collection.prototype and are available for all collections. * * @param {Object} methods Methods to add to the prototype * @param {Type=} type Optional type to add the methods to */ function registerMethods(methods, type) { for (const methodName in methods) { if (!methods.hasOwnProperty(methodName)) { return; } if (hasConflictingRegistration(methodName, type)) { let msg = `There is a conflicting registration for method with name "${methodName}".\nYou tried to register an additional method with `; if (type) { msg += `type "${type.toString()}".` } else { msg += 'universal type.' } msg += '\nThere are existing registrations for that method with '; const conflictingRegistrations = CPt[methodName].typedRegistrations; if (conflictingRegistrations) { msg += `type ${Object.keys(conflictingRegistrations).join(', ')}.`; } else { msg += 'universal type.'; } throw Error(msg); } if (!type) { CPt[methodName] = methods[methodName]; } else { type = type.toString(); if (!CPt.hasOwnProperty(methodName)) { installTypedMethod(methodName); } var registrations = CPt[methodName].typedRegistrations; registrations[type] = methods[methodName]; astTypes.getSupertypeNames(type).forEach(function (name) { registrations[name] = false; }); } } } function installTypedMethod(methodName) { if (CPt.hasOwnProperty(methodName)) { throw new Error(`Internal Error: "${methodName}" method is already installed`); } const registrations = {}; function typedMethod() { const types = Object.keys(registrations); for (let i = 0; i < types.length; i++) { const currentType = types[i]; if (registrations[currentType] && this.isOfType(currentType)) { return registrations[currentType].apply(this, arguments); } } throw Error( `You have a collection of type [${this.getTypes()}]. ` + `"${methodName}" is only defined for one of [${types.join('|')}].` ); } typedMethod.typedRegistrations = registrations; CPt[methodName] = typedMethod; } function hasConflictingRegistration(methodName, type) { if (!type) { return CPt.hasOwnProperty(methodName); } if (!CPt.hasOwnProperty(methodName)) { return false; } const registrations = CPt[methodName] && CPt[methodName].typedRegistrations; if (!registrations) { return true; } type = type.toString(); if (registrations.hasOwnProperty(type)) { return true; } return astTypes.getSupertypeNames(type.toString()).some(function (name) { return !!registrations[name]; }); } var _defaultType = []; /** * Sets the default collection type. In case a collection is created form an * empty set of paths and no type is specified, we return a collection of this * type. * * @ignore * @param {Type} type */ function setDefaultCollectionType(type) { _defaultType = _toTypeArray(type); } exports.fromPaths = fromPaths; exports.fromNodes = fromNodes; exports.registerMethods = registerMethods; exports.hasConflictingRegistration = hasConflictingRegistration; exports.setDefaultCollectionType = setDefaultCollectionType; ================================================ FILE: src/Runner.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const child_process = require('child_process'); const pc = require('picocolors'); const fs = require('graceful-fs'); const path = require('path'); const http = require('http'); const https = require('https'); const ignores = require('./ignoreFiles'); const tmp = require('tmp'); tmp.setGracefulCleanup(); const availableCpus = Math.max(require('os').cpus().length - 1, 1); const CHUNK_SIZE = 50; function lineBreak(str) { return /\n$/.test(str) ? str : str + '\n'; } const bufferedWrite = (function() { const buffer = []; let buffering = false; process.stdout.on('drain', () => { if (!buffering) return; while (buffer.length > 0 && process.stdout.write(buffer.shift()) !== false); if (buffer.length === 0) { buffering = false; } }); return function write(msg) { if (buffering) { buffer.push(msg); } if (process.stdout.write(msg) === false) { buffering = true; } }; }()); const log = { ok(msg, verbose) { verbose >= 2 && bufferedWrite(pc.bgGreen(pc.white(' OKK ')) + msg); }, nochange(msg, verbose) { verbose >= 1 && bufferedWrite(pc.bgYellow(pc.white(' NOC ')) + msg); }, skip(msg, verbose) { verbose >= 1 && bufferedWrite(pc.bgYellow(pc.white(' SKIP ')) + msg); }, error(msg, verbose) { verbose >= 0 && bufferedWrite(pc.bgRed(pc.white(' ERR ')) + msg); }, }; function report({file, msg}) { bufferedWrite(lineBreak(`${pc.bgBlue(pc.white(' REP '))}${file} ${msg}`)); } function concatAll(arrays) { const result = []; for (const array of arrays) { for (const element of array) { result.push(element); } } return result; } function showFileStats(fileStats) { process.stdout.write( 'Results: \n'+ pc.red(fileStats.error + ' errors\n')+ pc.yellow(fileStats.nochange + ' unmodified\n')+ pc.yellow(fileStats.skip + ' skipped\n')+ pc.green(fileStats.ok + ' ok\n') ); } function showStats(stats) { const names = Object.keys(stats).sort(); if (names.length) { process.stdout.write(pc.blue('Stats: \n')); } names.forEach(name => process.stdout.write(name + ': ' + stats[name] + '\n')); } function dirFiles (dir, callback, acc) { // acc stores files found so far and counts remaining paths to be processed acc = acc || { files: [], remaining: 1 }; function done() { // decrement count and return if there are no more paths left to process if (!--acc.remaining) { callback(acc.files); } } fs.readdir(dir, (err, files) => { // if dir does not exist or is not a directory, bail // (this should not happen as long as calls do the necessary checks) if (err) throw err; acc.remaining += files.length; files.forEach(file => { let name = path.join(dir, file); fs.stat(name, (err, stats) => { if (err) { // probably a symlink issue process.stdout.write( 'Skipping path "' + name + '" which does not exist.\n' ); done(); } else if (ignores.shouldIgnore(name)) { // ignore the path done(); } else if (stats.isDirectory()) { dirFiles(name + '/', callback, acc); } else { acc.files.push(name); done(); } }); }); done(); }); } function getAllFiles(paths, filter) { return Promise.all( paths.map(file => new Promise(resolve => { fs.lstat(file, (err, stat) => { if (err) { process.stderr.write('Skipping path ' + file + ' which does not exist. \n'); resolve([]); return; } if (stat.isDirectory()) { dirFiles( file, list => resolve(list.filter(filter)) ); } else if (!filter(file) || ignores.shouldIgnore(file)) { // ignoring the file resolve([]); } else { resolve([file]); } }) })) ).then(concatAll); } function run(transformFile, paths, options) { const cpus = options.cpus ? Math.min(availableCpus, options.cpus) : availableCpus; const extensions = options.extensions && options.extensions.split(',').map(ext => '.' + ext); const fileCounters = {error: 0, ok: 0, nochange: 0, skip: 0}; const statsCounter = {}; const startTime = process.hrtime(); ignores.add(options.ignoreSet); ignores.add(options.ignorePattern); ignores.addFromFile(options.ignoreConfig); if (options.gitignore) { let currDirectory = process.cwd(); let gitIgnorePath = path.join(currDirectory, '.gitignore'); ignores.addFromFile(gitIgnorePath); } if (/^http/.test(transformFile)) { return new Promise((resolve, reject) => { // call the correct `http` or `https` implementation (transformFile.indexOf('https') !== 0 ? http : https).get(transformFile, (res) => { let contents = ''; res .on('data', (d) => { contents += d.toString(); }) .on('end', () => { const ext = path.extname(transformFile); tmp.file({ prefix: 'jscodeshift', postfix: ext }, (err, path, fd) => { if (err) return reject(err); fs.write(fd, contents, function (err) { if (err) return reject(err); fs.close(fd, function(err) { if (err) return reject(err); transform(path).then(resolve, reject); }); }); }); }) }) .on('error', (e) => { reject(e); }); }); } else if (!fs.existsSync(transformFile)) { process.stderr.write( pc.bgRed(pc.white('ERROR')) + ' Transform file ' + transformFile + ' does not exist \n' ); return; } else { return transform(transformFile); } function transform(transformFile) { return getAllFiles( paths, name => !extensions || extensions.indexOf(path.extname(name)) != -1 ).then(files => { const numFiles = files.length; if (numFiles === 0) { process.stdout.write('No files selected, nothing to do. \n'); return []; } const processes = options.runInBand ? 1 : Math.min(numFiles, cpus); const chunkSize = processes > 1 ? Math.min(Math.ceil(numFiles / processes), CHUNK_SIZE) : numFiles; let index = 0; // return the next chunk of work for a free worker function next() { if (!options.silent && !options.runInBand && index < numFiles) { process.stdout.write( 'Sending ' + Math.min(chunkSize, numFiles-index) + ' files to free worker...\n' ); } return files.slice(index, index += chunkSize); } if (!options.silent) { process.stdout.write('Processing ' + files.length + ' files... \n'); if (!options.runInBand) { process.stdout.write( 'Spawning ' + processes +' workers...\n' ); } if (options.dry) { process.stdout.write( pc.green('Running in dry mode, no files will be written! \n') ); } } const args = [transformFile, options.babel ? 'babel' : 'no-babel']; const workers = []; for (let i = 0; i < processes; i++) { workers.push(options.runInBand ? require('./Worker')(args) : child_process.fork(require.resolve('./Worker'), args) ); } return workers.map(child => { child.send({files: next(), options}); child.on('message', message => { switch (message.action) { case 'status': fileCounters[message.status] += 1; log[message.status](lineBreak(message.msg), options.verbose); break; case 'update': if (!statsCounter[message.name]) { statsCounter[message.name] = 0; } statsCounter[message.name] += message.quantity; break; case 'free': child.send({files: next(), options}); break; case 'report': report(message); break; } }); return new Promise(resolve => child.on('disconnect', resolve)); }); }) .then(pendingWorkers => Promise.all(pendingWorkers).then(() => { const endTime = process.hrtime(startTime); const timeElapsed = (endTime[0] + endTime[1]/1e9).toFixed(3); if (!options.silent) { process.stdout.write('All done. \n'); showFileStats(fileCounters); showStats(statsCounter); process.stdout.write( 'Time elapsed: ' + timeElapsed + 'seconds \n' ); if (options.failOnError && fileCounters.error > 0) { process.exit(1); } } return Object.assign({ stats: statsCounter, timeElapsed: timeElapsed }, fileCounters); }) ); } } exports.run = run; ================================================ FILE: src/Worker.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const EventEmitter = require('events').EventEmitter; const async = require('neo-async'); const fs = require('graceful-fs'); const writeFileAtomic = require('write-file-atomic'); const { DEFAULT_EXTENSIONS } = require('@babel/core'); const getParser = require('./getParser'); const jscodeshift = require('./core'); let presetEnv; try { presetEnv = require('@babel/preset-env'); } catch (_) {} let emitter; let finish; let notify; let transform; let parserFromTransform; if (module.parent) { emitter = new EventEmitter(); emitter.send = (data) => { run(data); }; finish = () => { emitter.emit('disconnect'); }; notify = (data) => { emitter.emit('message', data); }; module.exports = (args) => { setup(args[0], args[1]); return emitter; }; } else { finish = () => setImmediate(() => process.disconnect()); notify = (data) => { process.send(data); }; process.on('message', (data) => { run(data); }); setup(process.argv[2], process.argv[3]); } function prepareJscodeshift(options) { const parser = parserFromTransform || getParser(options.parser, options.parserConfig); return jscodeshift.withParser(parser); } function setup(tr, babel) { if (babel === 'babel') { const presets = []; if (presetEnv) { presets.push([ presetEnv.default, {targets: {node: true}}, ]); } presets.push( /\.tsx?$/.test(tr) ? require('@babel/preset-typescript').default : require('@babel/preset-flow').default ); require('@babel/register')({ configFile: false, babelrc: false, presets, plugins: [ require('@babel/plugin-transform-class-properties').default, require('@babel/plugin-transform-nullish-coalescing-operator').default, require('@babel/plugin-transform-optional-chaining').default, require('@babel/plugin-transform-modules-commonjs').default, require('@babel/plugin-transform-private-methods').default, ], extensions: [...DEFAULT_EXTENSIONS, '.ts', '.tsx'], // By default, babel register only compiles things inside the current working directory. // https://github.com/babel/babel/blob/2a4f16236656178e84b05b8915aab9261c55782c/packages/babel-register/src/node.js#L140-L157 ignore: [ // Ignore parser related files /@babel\/parser/, /\/flow-parser\//, /\/recast\//, /\/ast-types\//, ], }); } const module = require(tr); transform = typeof module.default === 'function' ? module.default : module; if (module.parser) { parserFromTransform = typeof module.parser === 'string' ? getParser(module.parser) : module.parser; } } function free() { notify({action: 'free'}); } function updateStatus(status, file, msg) { msg = msg ? file + ' ' + msg : file; notify({action: 'status', status: status, msg: msg}); } function report(file, msg) { notify({action: 'report', file, msg}); } function empty() {} function stats(name, quantity) { quantity = typeof quantity !== 'number' ? 1 : quantity; notify({action: 'update', name: name, quantity: quantity}); } function trimStackTrace(trace) { if (!trace) { return ''; } // Remove this file from the stack trace of an error thrown in the transformer const lines = trace.split('\n'); const result = []; lines.every(function(line) { if (line.indexOf(__filename) === -1) { result.push(line); return true; } }); return result.join('\n'); } function run(data) { const files = data.files; const options = data.options || {}; if (!files.length) { finish(); return; } async.each( files, function(file, callback) { fs.readFile(file, async function(err, source) { if (err) { updateStatus('error', file, 'File error: ' + err); callback(); return; } source = source.toString(); try { const jscodeshift = prepareJscodeshift(options); const out = await transform( { path: file, source: source, }, { j: jscodeshift, jscodeshift: jscodeshift, stats: options.dry ? stats : empty, report: msg => report(file, msg), }, options ); if (!out || out === source) { updateStatus(out ? 'nochange' : 'skip', file); callback(); return; } if (options.print) { console.log(out); // eslint-disable-line no-console } if (!options.dry) { writeFileAtomic(file, out, function(err) { if (err) { updateStatus('error', file, 'File writer error: ' + err); } else { updateStatus('ok', file); } callback(); }); } else { updateStatus('ok', file); callback(); } } catch(err) { updateStatus( 'error', file, 'Transformation error ('+ err.message.replace(/\n/g, ' ') + ')\n' + trimStackTrace(err.stack) ); callback(); } }); }, function(err) { if (err) { updateStatus('error', '', 'This should never be shown!'); } free(); } ); } ================================================ FILE: src/__testfixtures__/test-async-transform.input.js ================================================ export const sum = (a, b) => a + b; ================================================ FILE: src/__testfixtures__/test-async-transform.js ================================================ const synchronousTestTransform = (fileInfo, api, options) => { return new Promise(resolve => { setTimeout(() => { resolve(api.jscodeshift(fileInfo.source) .findVariableDeclarators('sum') .renameTo('addition') .toSource()); }, 100); }); } module.exports = synchronousTestTransform; ================================================ FILE: src/__testfixtures__/test-async-transform.output.js ================================================ export const addition = (a, b) => a + b; ================================================ FILE: src/__testfixtures__/test-sync-transform.input.js ================================================ export const sum = (a, b) => a + b; ================================================ FILE: src/__testfixtures__/test-sync-transform.js ================================================ const synchronousTestTransform = (fileInfo, api, options) => { return api.jscodeshift(fileInfo.source) .findVariableDeclarators('sum') .renameTo('addition') .toSource(); } module.exports = synchronousTestTransform; ================================================ FILE: src/__testfixtures__/test-sync-transform.output.js ================================================ export const addition = (a, b) => a + b; ================================================ FILE: src/__tests__/.eslintrc ================================================ { "globals": { "jest": true }, "env": { "jasmine": true } } ================================================ FILE: src/__tests__/Collection-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; describe('Collection API', function() { let nodes; let Collection; let recast; let NodePath; let types; let b; beforeEach(function() { jest.resetModules(); Collection = require('../Collection'); recast = require('recast'); NodePath = recast.types.NodePath; types = recast.types.namedTypes; b = recast.types.builders; nodes = [b.identifier('foo'), b.identifier('bar')]; }); describe('Instantiation', function() { it('should create a collection from an array of nodes', function() { expect(Collection.fromNodes(nodes).getTypes()).toContain('Identifier'); }); it('should create a collection from an array of paths', function() { const paths = [ new NodePath(b.identifier('foo')), new NodePath(b.identifier('bar')), ]; expect(Collection.fromPaths(paths).getTypes()).toContain('Identifier'); }); it('accepts an empty array as input', function() { const values = []; expect(() => Collection.fromPaths(values)).not.toThrow(); expect(() => Collection.fromNodes(values)).not.toThrow(); }); it('throws if it is passed an array of mixed values', function() { const values = [ new NodePath(b.identifier('foo')), b.identifier('bar'), ]; expect(() => Collection.fromPaths(values)).toThrow(); expect(() => Collection.fromNodes(values)).toThrow(); }); it('returns a collection of the closest common type', function() { let nodes = [ b.identifier('foo'), b.sequenceExpression([]), ]; expect(Collection.fromNodes(nodes).getTypes()) .toContain('Expression'); nodes = [ b.identifier('foo'), b.blockStatement([]), ]; expect(Collection.fromNodes(nodes).getTypes()) .toContain('Node'); }); }); describe('Method extensions', function() { it('handles method extensions for types', function() { const Collection = require('../Collection'); const getNames = jest.fn(function() { expect(this.nodes()).toEqual(nodes); }); Collection.registerMethods({getNames: getNames}, types.Identifier); const collection = Collection.fromNodes(nodes); expect(collection.getNames).toBeDefined(); collection.getNames(); expect(getNames).toBeCalled(); }); it('throws if a method is called for the wrong node type', function() { const Collection = require('../Collection'); const getNames = jest.fn(); Collection.registerMethods({getNames: getNames}, types.Identifier); const collection = Collection.fromNodes([ b.blockStatement([]) ]); expect(() => collection.getNames()).toThrow(); }); it('adds "global" methods to all types', function() { const Collection = require('../Collection'); const getNames = jest.fn(); Collection.registerMethods({getNames: getNames}); expect(Collection.fromNodes([b.blockStatement([])]).getNames).toBeDefined(); expect(Collection.fromNodes(nodes).getNames).toBeDefined(); expect(Collection.fromNodes([]).getNames).toBeDefined(); }); it('handles type inheritance chains', function() { const Collection = require('../Collection'); const nodeMethod = function() {}; const identifierMethod = function() {}; Collection.registerMethods({nodeMethod: nodeMethod}, types.Node); Collection.registerMethods( {identifierMethod: identifierMethod}, types.Identifier ); const collection = Collection.fromNodes([b.identifier('foo')]); expect(() => collection.identifierMethod()).not.toThrow(); expect(() => collection.nodeMethod()).not.toThrow(); }); it('handles type inheritance with multiple parents', function() { Collection.registerMethods( {expressionMethod: function() {}}, types.Expression ); const collection = Collection.fromNodes([ b.functionExpression(null, [], b.blockStatement([])) ]); expect(() => collection.expressionMethod()).not.toThrow(); }); it('allows multiple registrations for non-conflicting types', function () { Collection.registerMethods( {foo: function () {}}, types.FunctionExpression ); Collection.registerMethods( {foo: function () {}}, types.BinaryExpression ); const collection = Collection.fromNodes([ b.functionExpression(null, [], b.blockStatement([])), b.functionExpression(null, [], b.blockStatement([])), b.binaryExpression('+', b.identifier('a'), b.identifier('b')) ]); function typeFilter(type) { return function (path) { return type.check(path.value); }; } // allowed if collection contents match one of the registered types. collection.filter(typeFilter(types.BinaryExpression)).foo(); collection.filter(typeFilter(types.FunctionExpression)).foo(); // not allowed if there is mixed types (even though all types match one function or the other). expect(function () { collection.foo(); }).toThrow(); }); describe('hasConflictingRegistration', function () { function register(methodName, type) { const methods = {}; methods[methodName] = function () {}; if (!types[type]) { throw new Error(type + ' is not a valid type'); } Collection.registerMethods(methods, types[type]); } it('true if supertype is registered', function () { register('supertypeIsRegistered', 'Expression'); expect(Collection.hasConflictingRegistration('supertypeIsRegistered', 'FunctionExpression')).toBe(true); }); it('true if subtype is registered', function () { register('subtypeIsRegistered', 'FunctionExpression'); expect(Collection.hasConflictingRegistration('subtypeIsRegistered', 'Expression')).toBe(true); }); it('false if only a sibling type is registered', function () { register('siblingIsRegistered', 'FunctionExpression'); expect(Collection.hasConflictingRegistration('siblingIsRegistered', 'BinaryExpression')).toBe(false); }); }); }); describe('Processing functions', function() { describe('filter', function() { it('lets you filter with custom logic', function() { const filter = jest.fn(function(path) { return path.value.name === 'foo'; }); const fooVariables = Collection.fromNodes(nodes).filter(filter); expect(filter.mock.calls.length).toBe(2); expect(fooVariables.length).toBe(1); }); }); describe('forEach', function() { it('lets you iterate over each element of an collection', function() { const each = jest.fn(); Collection.fromNodes(nodes).forEach(each); expect(each.mock.calls.length).toBe(2); expect(each.mock.calls[0][0].value).toBe(nodes[0]); expect(each.mock.calls[1][0].value).toBe(nodes[1]); }); it('returns the collection itself', function() { const fVariables = Collection.fromNodes(nodes); const result = fVariables.forEach(function(){}); expect(result).toBe(fVariables); }); }); describe('some', function() { it('lets you test each element of a collection and stops when one passes the test', function() { const each = jest.fn().mockImplementation(() => true); Collection.fromNodes(nodes).some(each); expect(each.mock.calls.length).toBe(1); expect(each.mock.calls[0][0].value).toBe(nodes[0]); }); it('returns true if at least one element passes the test', function() { const result = Collection.fromNodes(nodes).some((_, i) => i === 1); expect(result).toBe(true); }); it('returns false if no elements pass the test', function() { const result = Collection.fromNodes(nodes).some(() => false); expect(result).toBe(false); }); }); describe('every', function() { it('lets you test each element of a collection and stops when one fails the test', function() { const each = jest.fn().mockImplementation(() => false); Collection.fromNodes(nodes).every(each); expect(each.mock.calls.length).toBe(1); expect(each.mock.calls[0][0].value).toBe(nodes[0]); }); it('returns true if all elements pass the test', function() { const result = Collection.fromNodes(nodes).every(() => true); expect(result).toBe(true); }); it('returns false if at least one element does not pass the test', function() { const result = Collection.fromNodes(nodes).every((_, i) => i === 1); expect(result).toBe(false); }); }); describe('map', function() { it('returns a new collection with mapped values', function() { const root = Collection.fromNodes(nodes); const mapped = root.map((_, i) => new NodePath(nodes[+!i])); expect(root).not.toBe(mapped); expect(mapped.length).toBe(2); expect(mapped.nodes()[0]).toBe(nodes[1]); expect(mapped.nodes()[1]).toBe(nodes[0]); }); it('dedupes elements', function() { const path = new NodePath(nodes[0]); const root = Collection.fromNodes(nodes); const mapped = root.map(() => path); expect(root).not.toBe(mapped); expect(mapped.length).toBe(1); expect(mapped.paths()[0]).toBe(path); }); }); describe('at', function() { it('should work with positive indecies', function() { const root = Collection.fromNodes(nodes); expect(root.at(0).nodes()[0]).toEqual(nodes[0]); expect(root.at(1).nodes()[0]).toEqual(nodes[1]); }); it('should work with negative indecies', function() { const root = Collection.fromNodes(nodes); expect(root.at(-1).nodes()[0]).toEqual(nodes[nodes.length - 1]); expect(root.at(-2).nodes()[0]).toEqual(nodes[nodes.length - 2]); }); }); describe('get', function() { it('should throw descriptive error when no paths are present', function() { const root = Collection.fromNodes([]); expect(() => root.get()).toThrowError(/cannot call "get" on a collection with no paths/); }); }); }); }); ================================================ FILE: src/__tests__/Worker-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const testUtils = require('../../utils/testUtils'); const createTransformWith = testUtils.createTransformWith; const createTempFileWith = testUtils.createTempFileWith; const getFileContent = testUtils.getFileContent; describe('Worker API', () => { it('transforms files', done => { const worker = require('../Worker'); const transformPath = createTransformWith('return fileInfo.source + " changed";'); const sourcePath = createTempFileWith('foo'); const emitter = worker([transformPath]); emitter.send({files: [sourcePath]}); emitter.once('message', (data) => { expect(data.status).toBe('ok'); expect(data.msg).toBe(sourcePath); expect(getFileContent(sourcePath)).toBe('foo changed'); done(); }); }); it('passes j as argument', done => { const worker = require('../Worker'); const transformPath = createTempFileWith( `module.exports = function (file, api) { return api.j(file.source).toSource() + ' changed'; }` ); const sourcePath = createTempFileWith('const x = 10;'); const emitter = worker([transformPath]); emitter.send({files: [sourcePath]}); emitter.once('message', (data) => { expect(data.status).toBe('ok'); expect(getFileContent(sourcePath)).toBe( 'const x = 10;' + ' changed' ); done(); }); }); describe('custom parser', () => { function getTransformForParser(parser) { return createTempFileWith( `function transform(fileInfo, api) { api.jscodeshift(fileInfo.source); return "changed"; } ${parser ? `transform.parser = '${parser}';` : ''} module.exports = transform; ` ); } function getSourceFile() { // This code cannot be parsed by Babel v5 return createTempFileWith( 'const x = (a: Object, b: string): void => {}' ); } it('errors if new flow type code is parsed with babel v5', done => { const worker = require('../Worker'); const transformPath = createTransformWith( 'api.jscodeshift(fileInfo.source); return "changed";' ); const sourcePath = getSourceFile(); const emitter = worker([transformPath]); emitter.send({files: [sourcePath]}); emitter.once('message', (data) => { expect(data.status).toBe('error'); expect(data.msg).toMatch('SyntaxError'); done(); }); }); ['flow', 'babylon'].forEach(parser => { it(`uses ${parser} if configured as such`, done => { const worker = require('../Worker'); const transformPath = getTransformForParser(parser); const sourcePath = getSourceFile(); const emitter = worker([transformPath]); emitter.send({files: [sourcePath]}); emitter.once('message', (data) => { expect(data.status).toBe('ok'); expect(getFileContent(sourcePath)).toBe('changed'); done(); }); }); }); ['babylon', 'flow', 'tsx'].forEach(parser => { it(`can parse JSX with ${parser}`, done => { const worker = require('../Worker'); const transformPath = getTransformForParser(parser); const sourcePath = createTempFileWith( 'var component =
{foobar}
;' ); const emitter = worker([transformPath]); emitter.send({files: [sourcePath]}); emitter.once('message', (data) => { expect(data.status).toBe('ok'); expect(getFileContent(sourcePath)).toBe('changed'); done(); }); }); }); it('can parse enums with flow', done => { const worker = require('../Worker'); const transformPath = getTransformForParser('flow'); const sourcePath = createTempFileWith( 'enum E {A, B}' ); const emitter = worker([transformPath]); emitter.send({files: [sourcePath]}); emitter.once('message', (data) => { expect(data.status).toBe('ok'); expect(getFileContent(sourcePath)).toBe('changed'); done(); }); }); }); }); ================================================ FILE: src/__tests__/__snapshots__/testUtils-test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`testUtils async should run async defineSnapshotTest 1`] = `"export const addition = (a, b) => a + b;"`; exports[`testUtils async should run async defineSnapshotTestFromFixture 1`] = `"export const addition = (a, b) => a + b;"`; exports[`testUtils async should run snapshot test 1`] = `"export const addition = (a, b) => a + b;"`; exports[`testUtils synchronous should run snapshot test 1`] = `"export const addition = (a, b) => a + b;"`; exports[`testUtils synchronous should run sync defineSnapshotTest 1`] = `"export const addition = (a, b) => a + b;"`; exports[`testUtils synchronous should run sync defineSnapshotTestFromFixture 1`] = `"export const addition = (a, b) => a + b;"`; ================================================ FILE: src/__tests__/argsParser-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /*global jest, describe, it, expect, beforeEach*/ 'use strict'; const argsParser = require('../argsParser'); describe('argsParser', function() { it('prints the help text', function() { const parser = argsParser.options({}); let exception; try { parser.parse(['--help']); } catch(e) { exception = e; } expect(exception.exitCode).toEqual(0); expect(exception.message).toEqual(parser.getHelpText()); }); it('parsers positional arguments', function() { const parser = argsParser.options({}); const {positionalArguments} = parser.parse(['foo', 'bar']); expect(positionalArguments).toEqual(['foo', 'bar']); }); it('parsers mixed options, flags and positional arguments', function() { const parser = argsParser.options({ foo: {}, bar: { flag: true, }, bay: { flag: true, }, baz: { default: 'zab', }, }); expect(parser.parse(['arg1', '--foo=1', 'arg2', '--bar', '--bay=1', 'arg3', 'arg4'])) .toEqual({ options: { foo: '1', bar: true, bay: true, baz: 'zab', }, positionalArguments: ['arg1', 'arg2', 'arg3', 'arg4'], }); }); describe('options', function() { function test(testCases) { for (const testName in testCases) { const testCase = testCases[testName]; const parser = argsParser.options(testCase.options); it(testName + ' (space separated values)', function() { const parse = () => parser.parse( Array.prototype.concat.apply([], testCase.args) ); if (typeof testCase.expected === 'string') { expect(parse).toThrowError(testCase.expected); } else { expect(parse()).toEqual(testCase.expected); } }); it(testName + ' (= separated values)', function() { const parse = () => parser.parse( testCase.args.map(args => args.join('=')) ); if (typeof testCase.expected === 'string') { expect(parse).toThrowError(testCase.expected); } else { expect(parse()).toEqual(testCase.expected); } }); } } test({ 'understands separate arg name and short option names': { options: { foo: { full: 'another-foo', }, bar: { abbr: 'b', }, }, args: [['--another-foo', 'oof'], ['-b', 'rab']], expected: { options: { foo: 'oof', bar: 'rab', }, positionalArguments: [] }, }, 'understands default values': { options: { foo: {}, bar: { default: 'rab', }, baz: { abbr: 'b', default: 'zab', }, }, args: [['--foo', 'oof']], expected: { options: { foo: 'oof', bar: 'rab', baz: 'zab', }, positionalArguments: [] }, }, 'allows preprocessing values': { options: { foo: {}, bar: { default: 456, }, bay: { process: Number, }, baz: { abbr: 'b', process: v => v+v, }, }, args: [['--foo', '123'], ['--bay', '789'], ['-b', 'zab']], expected: { options: { foo: '123', bar: 456, bay: 789, baz: 'zabzab', }, positionalArguments: [] }, }, 'understands lists': { options: { foo: { list: true, }, bar: { list: true, }, baz: {}, }, args: [ ['--foo', 'oof1'], ['--baz', 'zab1'], ['--foo', 'oof2'], ['--baz', 'zab2'], ], expected: { options: { foo: ['oof1', 'oof2'], bar: [], baz: 'zab2', }, positionalArguments: [] }, }, 'errors when an option does not have a value (1)': { options: { foo: {}, bar: { abbr: 'b', }, baz: {}, }, args: [ ['--foo', 'oof'], ['-b'], ['--baz', 'zab'], ], expected: '--bar requires a value', }, 'errors when an option does not have a value (2)': { options: { foo: {}, bar: { abbr: 'b', }, baz: {}, }, args: [ ['--foo', 'oof'], ['--bar'], ['--baz', 'zab'], ], expected: '--bar requires a value', }, 'errors when an option does not have a value (3)': { options: { foo: { default: 'oof', }, }, args: [['--foo']], expected: '--foo requires a value', }, 'understands choices': { options: { foo: { choices: ['oof'], }, }, args: [ ['--foo', 'oof'], ], expected: { options: { foo: 'oof', }, positionalArguments: [] }, }, 'errors if choice does not match': { options: { foo: { choices: ['oof'], }, bar: { choices: ['rab1', 'rab2'], }, }, args: [ ['--foo', 'oof'], ['--bar', 'rab'], ], expected: '--bar must be one of the values rab1,rab2', }, 'accepts unkown options': { options: {}, args: [ ['--foo'], ['--bar'], ['--bay', 'yab'], ['foo'], ['--b', 'zab1'], ['--foo', 'oof'], ['--b', 'zab2'], ['bar'], ], expected: { options: { foo: 'oof', bar: true, bay: 'yab', b: ['zab1', 'zab2'], }, positionalArguments: ['foo', 'bar'], }, }, 'parses unkown options as JSON': { options: {}, args: [ ['--foo', '{"foo": "bar"}'], ], expected: { options: { foo: {foo: 'bar'}, }, positionalArguments: [], }, }, }); }); describe('flags', function() { const parser = argsParser.options({ foo: { abbr: 'f', full: 'foo', flag: true, }, bar: { full: 'another-bar', flag: true, default: false, }, }); it('sets values to true of specified', function() { expect(parser.parse(['--foo', '--another-bar', 'foo', 'bar'])) .toEqual({ options: { foo: true, bar: true, }, positionalArguments: ['foo', 'bar'], }); }); it('understands short options', function() { expect(parser.parse(['-f', '--another-bar', 'f', 'bar'])) .toEqual({ options: { foo: true, bar: true, }, positionalArguments: ['f', 'bar'], }); }); it('sets default value if not specified', function() { expect(parser.parse(['f', 'bar'])) .toEqual({ options: { bar: false, }, positionalArguments: ['f', 'bar'], }); }); it('accepts flag=0 and flag=1 (undocumented)', function() { expect(parser.parse(['--foo=0', '--another-bar=1'])) .toEqual({ options: { foo: false, bar: true, }, positionalArguments: [], }); expect(parser.parse(['-f=0'])) .toEqual({ options: { foo: false, bar: false, }, positionalArguments: [], }); }); it('understands --no-prefixes', function() { expect(parser.parse(['--no-foo', '--no-another-bar'])) .toEqual({ options: { foo: false, bar: false, }, positionalArguments: [], }); }); }); }); ================================================ FILE: src/__tests__/core-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; /*global jest, describe, it, expect*/ const core = require('../core'); const recast = require('recast'); const b = recast.types.builders; const NodePath = recast.types.NodePath; describe('core API', function() { it('returns a Collection from a source string', function() { expect(core('var foo;').constructor.name ).toContain('Collection'); }); it('returns a Collection from an AST node', function() { const node = b.identifier('foo'); expect(core(node).constructor.name).toContain('Collection'); }); it('returns a Collection from an array of AST nodes', function() { const node = b.identifier('foo'); expect(core([node]).constructor.name).toContain('Collection'); }); it('returns a Collection from a path', function() { const path = new NodePath(b.identifier('foo')); expect(core(path).constructor.name).toContain('Collection'); }); it('returns a Collection from an array of paths', function() { const path = new NodePath(b.identifier('foo')); expect(core([path]).constructor.name).toContain('Collection'); }); it('returns a Collection from an empty array', function() { expect(core([]).constructor.name).toContain('Collection'); }); it('throws if it gets an invalid value', function() { expect(() => core(42)).toThrow(); expect(() => core({})).toThrow(); }); it('returns the source as is if nothing was modified', function () { const source = '\nvar foo;\n'; expect(core(source).toSource()).toEqual(source); }); it('plugins are called with core', function (done) { core.use(function (j) { expect(j).toBe(core); done(); }); }); it('plugins are only registered once', function () { let ct = 0; function plugin() { ct++; } core.use(plugin); core.use(plugin); expect(ct).toBe(1); }); }); ================================================ FILE: src/__tests__/matchNode-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /*global jest, describe, it, expect, beforeEach*/ 'use strict'; const matchNode = require('../matchNode'); describe('matchNode', function() { beforeEach(function() { expect.extend({ toMatchNode: function(haystack, needle) { const result = {}; result.pass = matchNode(haystack, needle); return result; } }); }) it('matches null and undefined', function() { expect(null).toMatchNode(null); expect(null).not.toMatchNode(undefined); expect(undefined).toMatchNode(undefined); expect(undefined).not.toMatchNode(null); }); it('matches scalars', function() { expect('foo').toMatchNode('foo'); expect('foo').not.toMatchNode('bar'); expect('123').not.toMatchNode(123); expect(123).toMatchNode(123); expect(123).not.toMatchNode(456); expect(123).not.toMatchNode('123'); expect(true).toMatchNode(true); expect(true).not.toMatchNode(false); expect(true).not.toMatchNode('true'); }); it('matches arrays', function() { expect([1, 2, 3]).toMatchNode([1, 2, 3]); expect([1, 2, 3]).not.toMatchNode([4, 5, 6]); expect([[1, 2, 3], 'foo']).toMatchNode([[1, 2, 3], 'foo']); expect([[1, 2, 3], 'foo']).not.toMatchNode([[456], 'foo']); expect([[1, 2, 3], 'foo']).not.toMatchNode([[1, 2, 3], 'bar']); expect([1, 2, 3, 4]).toMatchNode([1, 2, 3]); expect([1, 2, 3]).not.toMatchNode([1, 2, 3, 4]); }); it('matches objects', function() { expect({}).toMatchNode({}); expect({name: 'foo'}).toMatchNode({name: 'foo'}); expect({name: 'foo'}).not.toMatchNode({name: 'bar'}); expect({name: 'foo', value: {name: 'bar'}}) .toMatchNode({name: 'foo', value: {name: 'bar'}}); expect({name: 'foo', value: {name: 'bar'}}) .not.toMatchNode({name: 'foo', value: {name: 'baz'}}); expect({name: 'foo', value: 'bar'}).toMatchNode({name: 'foo'}); expect({name: 'foo'}).not.toMatchNode({name: 'foo', value: 'bar'}); expect(Object.create({name: 'foo'})).not.toMatchNode({name: 'foo'}); expect({}).toMatchNode(Object.create({name: 'foo'})); }); it('matches with a function', function() { const haystack = {name: 'foo'}; const needle = jest.fn(); needle.mockReturnValue(true); expect(haystack).toMatchNode(needle); expect(needle).toBeCalledWith(haystack); needle.mockReturnValue(false); expect(haystack).not.toMatchNode(needle); }); it('matches nested value with a function', function() { const haystack = {name: 'foo', value: 'bar'}; const needle = { name: jest.fn(), value: jest.fn(), }; needle.name.mockReturnValue(true); needle.value.mockReturnValue(true); expect(haystack).toMatchNode(needle); expect(needle.name).toBeCalledWith(haystack.name); expect(needle.value).toBeCalledWith(haystack.value); needle.name.mockReturnValue(false); needle.value.mockReturnValue(true); expect(haystack).not.toMatchNode(needle); needle.name.mockReturnValue(true); needle.value.mockReturnValue(false); expect(haystack).not.toMatchNode(needle); }); }); ================================================ FILE: src/__tests__/template-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; /*global jest, describe, it, expect, beforeEach*/ describe('Templates', () => { let statements; let statement; let expression; let jscodeshift; beforeEach(() => { jest.resetModules(); jscodeshift = require('../core'); const template = jscodeshift.template; expression = template.expression; statement = template.statement; statements = template.statements; }); it('interpolates expression nodes with source code', () => { let input = `var foo = bar; if(bar) { console.log(42); }`; let expected = `var foo = alert(bar); if(alert(bar)) { console.log(42); }`; expect( jscodeshift(input) .find('Identifier', {name: 'bar'}) .replaceWith(path => expression`alert(${path.node})`) .toSource() ).toEqual(expected); }); it('interpolates statement nodes with source code', () => { let input = `for (var i = 0; i < 10; i++) { console.log(i); console.log(i / 2); }`; let expected = `var i = 0; while (i < 10) { console.log(i); console.log(i / 2); i++; }`; expect( jscodeshift(input) .find('ForStatement') .replaceWith( p => statements` ${p.node.init}; while (${p.node.test}) { ${p.node.body.body} ${p.node.update}; }` ) .toSource() ).toEqual(expected); }); it('can be used with a different parser', () => { const parser = require('../../parser/flow')(); const template = require('../template')(parser); const node = {type: 'Literal', value: 41}; expect( jscodeshift(template.expression`1 + ${node}`, {parser}).toSource() ).toEqual('1 + 41'); }); it('handles out-of-order traversal', () => { const input = 'var x'; const expected = 'class X extends a {f(b) {}}'; const a = jscodeshift.identifier('a'); const b = jscodeshift.identifier('b'); const classDecl = statement` class X extends ${a} {f(${b}) {}} `; expect( jscodeshift(input) .find('VariableDeclaration') .replaceWith(classDecl) .toSource() ) .toEqual(expected); }); it('correctly parses expressions without any interpolation', () => { const expected = 'function() {}'; expect( jscodeshift( expression`function() {}` ) .toSource() ) .toEqual(expected); }); for (const parser of ['babel', 'babylon', 'flow', 'ts', 'tsx']) { it(`asyncExpression correctly parses expressions with await -- ${parser}`, () => { const expected = '{\n bar: await baz\n}' const j = jscodeshift.withParser(parser) expect(j(j.template.asyncExpression`{\n bar: await baz\n}`).toSource()).toEqual(expected) }) } describe('explode arrays', () => { it('explodes arrays in function definitions', () => { let input = 'var foo = [a, b];'; let expected = 'var foo = function foo(a, b, c) {};'; expect( jscodeshift(input) .find('ArrayExpression') .replaceWith( p => expression`function foo(${p.node.elements}, c) {}` ) .toSource() ) .toEqual(expected); expected = 'var foo = function(a, b, c) {};'; expect( jscodeshift(input) .find('ArrayExpression') .replaceWith( p => expression`function(${p.node.elements}, c) {}` ) .toSource() ) .toEqual(expected); expected = 'var foo = (a, b) => {};'; expect( jscodeshift(input) .find('ArrayExpression') .replaceWith( p => expression`${p.node.elements} => {}` ) .toSource() ) .toEqual(expected); expected = 'var foo = (a, b, c) => {};'; expect( jscodeshift(input) .find('ArrayExpression') .replaceWith( p => expression`(${p.node.elements}, c) => {}` ) .toSource() ) .toEqual(expected); }); it('explodes arrays in variable declarations', () => { let input = 'var foo = [a, b];'; let expected = 'var foo, a, b;'; expect( jscodeshift(input) .find('VariableDeclaration') // Need to use a block here because the arrow doesn't seem to be // compiled with a line break after the return statement. Can't repro // outside here though .replaceWith(p => { const node = p.node.declarations[0]; return statement`var ${node.id}, ${node.init.elements};`; }) .toSource() ) .toEqual(expected); }); it('explodes arrays in array expressions', () => { let input = 'var foo = [a, b];'; let expected = 'var foo = [a, b, c];'; expect( jscodeshift(input) .find('ArrayExpression') .replaceWith(p => expression`[${p.node.elements}, c]`) .toSource() ) .toEqual(expected); }); it('explodes arrays in object expressions', () => { let input = 'var foo = {a, b};'; let expected = /var foo = \{\s*a,\s*b,\s*c: 42\s*};/; expect( jscodeshift(input) .find('ObjectExpression') .replaceWith(p => expression`{${p.node.properties}, c: 42}`) .toSource() ) .toMatch(expected); }); it('explodes arrays in call expressions', () => { let input = 'var foo = [a, b];'; let expected = 'var foo = bar(a, b, c);'; expect( jscodeshift(input) .find('ArrayExpression') .replaceWith( p => expression`bar(${p.node.elements}, c)` ) .toSource() ) .toEqual(expected); }); }); }); ================================================ FILE: src/__tests__/testUtils-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const fs = require('fs'); const path = require('path'); const testSyncTransform = require('../__testfixtures__/test-sync-transform'); const testAsyncTransform = require('../__testfixtures__/test-async-transform'); const testUtils = require('../testUtils'); const testInputSource = 'export const sum = (a, b) => a + b;'; const expectedInlineOutput = 'export const addition = (a, b) => a + b;'; const getModuleToTransform = () => { const moduleToTransformPath = path.join(__dirname, '..', '__testfixtures__', 'test-sync-transform.input.js'); const source = fs.readFileSync(moduleToTransformPath, 'utf8'); return { path: moduleToTransformPath, source, } } describe('testUtils', () => { describe('synchronous', () => { it('should apply transformation', () => { const moduleToTransform = getModuleToTransform(); const transformedCode = testUtils.applyTransform(testSyncTransform, null, moduleToTransform); expect(transformedCode).not.toMatch(/sum/); expect(transformedCode).toMatch(/addition/); }); it('should run test', () => { testUtils.runTest( __dirname, path.join('__testfixtures__', 'test-sync-transform'), null, 'test-sync-transform' ); }); it ('should run snapshot test', () => { const moduleToTransform = getModuleToTransform(); testUtils.runSnapshotTest(testSyncTransform, null, moduleToTransform); }); it('should run inline test', () => { const moduleToTransform = getModuleToTransform(); testUtils.runInlineTest(testSyncTransform, null, moduleToTransform, expectedInlineOutput); }); testUtils.defineTest( __dirname, path.join('__testfixtures__', 'test-sync-transform'), null, 'test-sync-transform' ); testUtils.defineInlineTest( testSyncTransform, null, testInputSource, expectedInlineOutput, 'should run sync defineInlineTest' ); testUtils.defineSnapshotTest( testSyncTransform, null, testInputSource, 'should run sync defineSnapshotTest' ); testUtils.defineSnapshotTestFromFixture( __dirname, testSyncTransform, null, 'test-sync-transform', 'should run sync defineSnapshotTestFromFixture' ); }); describe('async', () => { it('should apply transformation', async () => { const moduleToTransform = getModuleToTransform(); const transformedCode = await testUtils.applyTransform(testAsyncTransform, null, moduleToTransform); expect(transformedCode).not.toMatch(/sum/); expect(transformedCode).toMatch(/addition/); }); it('should run test', () => { return testUtils.runTest(__dirname, path.join('__testfixtures__', 'test-async-transform'), null, 'test-async-transform'); }); it ('should run snapshot test', () => { const moduleToTransform = getModuleToTransform(); return testUtils.runSnapshotTest(testAsyncTransform, null, moduleToTransform); }); it('should run inline test', () => { const moduleToTransform = getModuleToTransform(); return testUtils.runInlineTest(testAsyncTransform, null, moduleToTransform, expectedInlineOutput); }); testUtils.defineTest( __dirname, path.join('__testfixtures__', 'test-async-transform'), null, 'test-async-transform' ); testUtils.defineInlineTest( testAsyncTransform, null, testInputSource, expectedInlineOutput, 'should run async defineInlineTest' ); testUtils.defineSnapshotTest( testAsyncTransform, null, testInputSource, 'should run async defineSnapshotTest' ); testUtils.defineSnapshotTestFromFixture( __dirname, testSyncTransform, null, 'test-async-transform', 'should run async defineSnapshotTestFromFixture' ); }); }); ================================================ FILE: src/__tests__/ts-decorator-auto-accessor-test.js ================================================ 'use strict'; function transformer(file, api) { const j = api.jscodeshift; return j(file.source).toSource(); } transformer.parser = 'ts'; jest.autoMockOff(); const defineInlineTest = require('../../src/testUtils').defineInlineTest; describe('should be parse typescript decoratorAutoAccessors correctly', function () { defineInlineTest( transformer, {}, 'export class Test {\n' + ' public accessor myValue = 10;\n' + '}\n', 'export class Test {\n' + ' public accessor myValue = 10;\n' + '}', 'ts-decorator-auto-accessor', ); }); ================================================ FILE: src/argsParser.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ function throwError(exitCode, message, helpText) { const error = new Error( helpText ? `${message}\n\n---\n\n${helpText}` : message ); error.exitCode = exitCode; throw error; } function formatOption(option) { let text = ' '; text += option.abbr ? `-${option.abbr}, ` : ' '; text += `--${option.flag ? '(no-)' : ''}${option.full}`; if (option.choices) { text += `=${option.choices.join('|')}`; } else if (option.metavar) { text += `=${option.metavar}`; } if (option.list) { text += ' ...'; } if (option.defaultHelp || option.default !== undefined || option.help) { text += ' '; if (text.length < 32) { text += ' '.repeat(32 - text.length); } const textLength = text.length; if (option.help) { text += option.help; } if (option.defaultHelp || option.default !== undefined) { if (option.help) { text += '\n'; } text += `${' '.repeat(textLength)}(default: ${option.defaultHelp || option.default})`; } } return text; } function getHelpText(options) { const opts = Object.keys(options) .map(k => options[k]) .sort((a,b) => a.display_index - b.display_index); const text = ` Usage: jscodeshift [OPTION]... PATH... or: jscodeshift [OPTION]... -t TRANSFORM_PATH PATH... or: jscodeshift [OPTION]... -t URL PATH... or: jscodeshift [OPTION]... --stdin < file_list.txt Apply transform logic in TRANSFORM_PATH (recursively) to every PATH. If --stdin is set, each line of the standard input is used as a path. Options: "..." behind an option means that it can be supplied multiple times. All options are also passed to the transformer, which means you can supply custom options that are not listed here. ${opts.map(formatOption).join('\n')} `; return text.trimLeft(); } function validateOptions(parsedOptions, options) { const errors = []; for (const optionName in options) { const option = options[optionName]; if (option.choices && !option.choices.includes(parsedOptions[optionName])) { errors.push( `Error: --${option.full} must be one of the values ${option.choices.join(',')}` ); } } if (errors.length > 0) { throwError( 1, errors.join('\n'), getHelpText(options) ); } } function prepareOptions(options) { options.help = { display_index: 5, abbr: 'h', help: 'print this help and exit', callback() { return getHelpText(options); }, }; const preparedOptions = {}; for (const optionName of Object.keys(options)) { const option = options[optionName]; if (!option.full) { option.full = optionName; } option.key = optionName; preparedOptions['--'+option.full] = option; if (option.abbr) { preparedOptions['-'+option.abbr] = option; } if (option.flag) { preparedOptions['--no-'+option.full] = option; } } return preparedOptions; } function isOption(value) { return /^--?/.test(value); } function parse(options, args=process.argv.slice(2)) { const missingValue = Symbol(); const preparedOptions = prepareOptions(options); const parsedOptions = {}; const positionalArguments = []; for (const optionName in options) { const option = options[optionName]; if (option.default !== undefined) { parsedOptions[optionName] = option.default; } else if (option.list) { parsedOptions[optionName] = []; } } for (let i = 0; i < args.length; i++) { const arg = args[i]; if (isOption(arg)) { let optionName = arg; let value = null; let option = null; if (optionName.includes('=')) { const index = arg.indexOf('='); optionName = arg.slice(0, index); value = arg.slice(index+1); } if (preparedOptions.hasOwnProperty(optionName)) { option = preparedOptions[optionName]; } else { // Unknown options are just "passed along". // The logic is as follows: // - If an option is encountered without a value, it's treated // as a flag // - If the option has a value, it's initialized with that value // - If the option has been seen before, it's converted to a list // If the previous value was true (i.e. a flag), that value is // discarded. const realOptionName = optionName.replace(/^--?(no-)?/, ''); const isList = parsedOptions.hasOwnProperty(realOptionName) && parsedOptions[realOptionName] !== true; option = { key: realOptionName, full: realOptionName, flag: !parsedOptions.hasOwnProperty(realOptionName) && value === null && isOption(args[i+1]), list: isList, process(value) { // Try to parse values as JSON to be compatible with nomnom try { return JSON.parse(value); } catch(_e) {} return value; }, }; if (isList) { const currentValue = parsedOptions[realOptionName]; if (!Array.isArray(currentValue)) { parsedOptions[realOptionName] = currentValue === true ? [] : [currentValue]; } } } if (option.callback) { throwError(0, option.callback()); } else if (option.flag) { if (optionName.startsWith('--no-')) { value = false; } else if (value !== null) { value = value === '1'; } else { value = true; } parsedOptions[option.key] = value; } else { if (value === null && i < args.length - 1 && !isOption(args[i+1])) { // consume next value value = args[i+1]; i += 1; } if (value !== null) { if (option.process) { value = option.process(value); } if (option.list) { parsedOptions[option.key].push(value); } else { parsedOptions[option.key] = value; } } else { parsedOptions[option.key] = missingValue; } } } else { positionalArguments.push(/^\d+$/.test(arg) ? Number(arg) : arg); } } for (const optionName in parsedOptions) { if (parsedOptions[optionName] === missingValue) { throwError( 1, `Missing value: --${options[optionName].full} requires a value`, getHelpText(options) ); } } const result = { positionalArguments, options: parsedOptions, }; validateOptions(parsedOptions, options); return result; } module.exports = { /** * `options` is an object of objects. Each option can have the following * properties: * * - full: The name of the option to be used in the command line (if * different than the property name. * - abbr: The short version of the option, a single character * - flag: Whether the option takes an argument or not. * - default: The default value to use if option is not supplied * - choices: Restrict possible values to these values * - help: The help text to print * - metavar: Value placeholder to use in the help * - callback: If option is supplied, call this function and exit * - process: Pre-process value before returning it */ options(options) { return { parse(args) { return parse(options, args); }, getHelpText() { return getHelpText(options); }, }; }, }; ================================================ FILE: src/collections/ImportDeclaration.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ "use strict"; const Collection = require("../Collection"); const NodeCollection = require("./Node"); const assert = require("assert"); const once = require("../utils/once"); const recast = require("recast"); const types = recast.types.namedTypes; const globalMethods = { /** * Inserts an ImportDeclaration at the top of the AST * * @param {string} sourcePath * @param {Array} specifiers */ insertImportDeclaration: function (sourcePath, specifiers) { assert.ok( sourcePath && typeof sourcePath === "string", "insertImportDeclaration(...) needs a source path" ); assert.ok( specifiers && Array.isArray(specifiers), "insertImportDeclaration(...) needs an array of specifiers" ); if (this.hasImportDeclaration(sourcePath)) { return this; } const importDeclaration = recast.types.builders.importDeclaration( specifiers, recast.types.builders.stringLiteral(sourcePath) ); return this.forEach((path) => { if (path.value.type === "Program") { path.value.body.unshift(importDeclaration); } }); }, /** * Finds all ImportDeclarations optionally filtered by name * * @param {string} sourcePath * @return {Collection} */ findImportDeclarations: function (sourcePath) { assert.ok( sourcePath && typeof sourcePath === "string", "findImportDeclarations(...) needs a source path" ); return this.find(types.ImportDeclaration, { source: { value: sourcePath }, }); }, /** * Determines if the collection has an ImportDeclaration with the given sourcePath * * @param {string} sourcePath * @returns {boolean} */ hasImportDeclaration: function (sourcePath) { assert.ok( sourcePath && typeof sourcePath === "string", "findImportDeclarations(...) needs a source path" ); return this.findImportDeclarations(sourcePath).length > 0; }, /** * Renames all ImportDeclarations with the given name * * @param {string} sourcePath * @param {string} newSourcePath * @return {Collection} */ renameImportDeclaration: function (sourcePath, newSourcePath) { assert.ok( sourcePath && typeof sourcePath === "string", "renameImportDeclaration(...) needs a name to look for" ); assert.ok( newSourcePath && typeof newSourcePath === "string", "renameImportDeclaration(...) needs a new name to rename to" ); return this.findImportDeclarations(sourcePath).forEach((path) => { path.value.source.value = newSourcePath; }); }, }; function register() { NodeCollection.register(); Collection.registerMethods(globalMethods, types.Node); } exports.register = once(register); ================================================ FILE: src/collections/JSXElement.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const Collection = require('../Collection'); const NodeCollection = require('./Node'); const assert = require('assert'); const once = require('../utils/once'); const recast = require('recast'); const requiresModule = require('./VariableDeclarator').filters.requiresModule; const types = recast.types.namedTypes; const JSXElement = types.JSXElement; const JSXAttribute = types.JSXAttribute; const Literal = types.Literal; /** * Contains filter methods and mutation methods for processing JSXElements. * @mixin */ const globalMethods = { /** * Finds all JSXElements optionally filtered by name * * @param {string} name * @return {Collection} */ findJSXElements: function(name) { const nameFilter = name && {openingElement: {name: {name: name}}}; return this.find(JSXElement, nameFilter); }, /** * Finds all JSXElements by module name. Given * * var Bar = require('Foo'); * * * findJSXElementsByModuleName('Foo') will find , without having to * know the variable name. */ findJSXElementsByModuleName: function(moduleName) { assert.ok( moduleName && typeof moduleName === 'string', 'findJSXElementsByModuleName(...) needs a name to look for' ); return this.find(types.VariableDeclarator) .filter(requiresModule(moduleName)) .map(function(path) { const id = path.value.id.name; if (id) { return Collection.fromPaths([path]) .closestScope() .findJSXElements(id) .paths(); } }); } }; const filterMethods = { /** * Filter method for attributes. * * @param {Object} attributeFilter * @return {function} */ hasAttributes: function(attributeFilter) { const attributeNames = Object.keys(attributeFilter); return function filter(path) { if (!JSXElement.check(path.value)) { return false; } const elementAttributes = Object.create(null); path.value.openingElement.attributes.forEach(function(attr) { if (!JSXAttribute.check(attr) || !(attr.name.name in attributeFilter)) { return; } elementAttributes[attr.name.name] = attr; }); return attributeNames.every(function(name) { if (!(name in elementAttributes) ){ return false; } const value = elementAttributes[name].value; const expected = attributeFilter[name]; // Only when value is truthy access it's properties const actual = !value ? value : Literal.check(value) ? value.value : value.expression; if (typeof expected === 'function') { return expected(actual); } // Literal attribute values are always strings return String(expected) === actual; }); }; }, /** * Filter elements which contain a specific child type * * @param {string} name * @return {function} */ hasChildren: function(name) { return function filter(path) { return JSXElement.check(path.value) && path.value.children.some( child => JSXElement.check(child) && child.openingElement.name.name === name ); }; } }; /** * @mixin */ const traversalMethods = { /** * Returns all child nodes, including literals and expressions. * * @return {Collection} */ childNodes: function() { const paths = []; this.forEach(function(path) { const children = path.get('children'); const l = children.value.length; for (let i = 0; i < l; i++) { paths.push(children.get(i)); } }); return Collection.fromPaths(paths, this); }, /** * Returns all children that are JSXElements. * * @return {JSXElementCollection} */ childElements: function() { const paths = []; this.forEach(function(path) { const children = path.get('children'); const l = children.value.length; for (let i = 0; i < l; i++) { if (types.JSXElement.check(children.value[i])) { paths.push(children.get(i)); } } }); return Collection.fromPaths(paths, this, JSXElement); }, /** * Returns all children that are of jsxElementType. * * @return {Collection} */ childNodesOfType: function(jsxChildElementType) { const paths = []; this.forEach(function(path) { const children = path.get('children'); const l = children.value.length; for (let i = 0; i < l; i++) { if (jsxChildElementType.check(children.value[i])) { paths.push(children.get(i)); } } }); return Collection.fromPaths(paths, this, jsxChildElementType); }, }; const mappingMethods = { /** * Given a JSXElement, returns its "root" name. E.g. it would return "Foo" for * both and . * * @param {NodePath} path * @return {string} */ getRootName: function(path) { let name = path.value.openingElement.name; while (types.JSXMemberExpression.check(name)) { name = name.object; } return name && name.name || null; } }; function register() { NodeCollection.register(); Collection.registerMethods(globalMethods, types.Node); Collection.registerMethods(traversalMethods, JSXElement); } exports.register = once(register); exports.filters = filterMethods; exports.mappings = mappingMethods; ================================================ FILE: src/collections/Node.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const Collection = require('../Collection'); const matchNode = require('../matchNode'); const once = require('../utils/once'); const recast = require('recast'); const Node = recast.types.namedTypes.Node; var types = recast.types.namedTypes; /** * @mixin */ const traversalMethods = { /** * Find nodes of a specific type within the nodes of this collection. * * @param {type} * @param {filter} * @return {Collection} */ find: function(type, filter) { const paths = []; const visitorMethodName = 'visit' + type; const visitor = {}; function visit(path) { /*jshint validthis:true */ if (!filter || matchNode(path.value, filter)) { paths.push(path); } this.traverse(path); } this.__paths.forEach(function(p, i) { const self = this; visitor[visitorMethodName] = function(path) { if (self.__paths[i] === path) { this.traverse(path); } else { return visit.call(this, path); } }; recast.visit(p, visitor); }, this); return Collection.fromPaths(paths, this, type); }, /** * Returns a collection containing the paths that create the scope of the * currently selected paths. Dedupes the paths. * * @return {Collection} */ closestScope: function() { return this.map(path => path.scope && path.scope.path); }, /** * Traverse the AST up and finds the closest node of the provided type. * * @param {Collection} * @param {filter} * @return {Collection} */ closest: function(type, filter) { return this.map(function(path) { let parent = path.parent; while ( parent && !( type.check(parent.value) && (!filter || matchNode(parent.value, filter)) ) ) { parent = parent.parent; } return parent || null; }); }, /** * Finds the declaration for each selected path. Useful for member expressions * or JSXElements. Expects a callback function that maps each path to the name * to look for. * * If the callback returns a falsey value, the element is skipped. * * @param {function} nameGetter * * @return {Collection} */ getVariableDeclarators: function(nameGetter) { return this.map(function(path) { /*jshint curly:false*/ let scope = path.scope; if (!scope) return; const name = nameGetter.apply(path, arguments); if (!name) return; scope = scope.lookup(name); if (!scope) return; const bindings = scope.getBindings()[name]; if (!bindings) return; const decl = Collection.fromPaths(bindings) .closest(types.VariableDeclarator); if (decl.length === 1) { return decl.paths()[0]; } }, types.VariableDeclarator); }, }; function toArray(value) { return Array.isArray(value) ? value : [value]; } /** * @mixin */ const mutationMethods = { /** * Simply replaces the selected nodes with the provided node. If a function * is provided it is executed for every node and the node is replaced with the * functions return value. * * @param {Node|Array|function} nodes * @return {Collection} */ replaceWith: function(nodes) { return this.forEach(function(path, i) { const newNodes = (typeof nodes === 'function') ? nodes.call(path, path, i) : nodes; path.replace.apply(path, toArray(newNodes)); }); }, /** * Inserts a new node before the current one. * * @param {Node|Array|function} insert * @return {Collection} */ insertBefore: function(insert) { return this.forEach(function(path, i) { const newNodes = (typeof insert === 'function') ? insert.call(path, path, i) : insert; path.insertBefore.apply(path, toArray(newNodes)); }); }, /** * Inserts a new node after the current one. * * @param {Node|Array|function} insert * @return {Collection} */ insertAfter: function(insert) { return this.forEach(function(path, i) { const newNodes = (typeof insert === 'function') ? insert.call(path, path, i) : insert; path.insertAfter.apply(path, toArray(newNodes)); }); }, remove: function() { return this.forEach(path => path.prune()); } }; function register() { Collection.registerMethods(traversalMethods, Node); Collection.registerMethods(mutationMethods, Node); Collection.setDefaultCollectionType(Node); } exports.register = once(register); ================================================ FILE: src/collections/VariableDeclarator.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const Collection = require('../Collection'); const NodeCollection = require('./Node'); const once = require('../utils/once'); const recast = require('recast'); const astNodesAreEquivalent = recast.types.astNodesAreEquivalent; const b = recast.types.builders; var types = recast.types.namedTypes; const VariableDeclarator = recast.types.namedTypes.VariableDeclarator; /** * @mixin */ const globalMethods = { /** * Finds all variable declarators, optionally filtered by name. * * @param {string} name * @return {Collection} */ findVariableDeclarators: function(name) { const filter = name ? {id: {name: name}} : null; return this.find(VariableDeclarator, filter); } }; const filterMethods = { /** * Returns a function that returns true if the provided path is a variable * declarator and requires one of the specified module names. * * @param {string|Array} names A module name or an array of module names * @return {Function} */ requiresModule: function(names) { if (names && !Array.isArray(names)) { names = [names]; } const requireIdentifier = b.identifier('require'); return function(path) { const node = path.value; if (!VariableDeclarator.check(node) || !types.CallExpression.check(node.init) || !astNodesAreEquivalent(node.init.callee, requireIdentifier)) { return false; } return !names || names.some( n => astNodesAreEquivalent(node.init.arguments[0], b.literal(n)) ); }; } }; /** * @mixin */ const transformMethods = { /** * Renames a variable and all its occurrences. * * @param {string} newName * @return {Collection} */ renameTo: function(newName) { // TODO: Include JSXElements return this.forEach(function(path) { const node = path.value; const oldName = node.id.name; const rootScope = path.scope; const rootPath = rootScope.path; Collection.fromPaths([rootPath]) .find(types.Identifier, {name: oldName}) .filter(function(path) { // ignore non-variables const parent = path.parent.node; if ( types.MemberExpression.check(parent) && parent.property === path.node && !parent.computed ) { // obj.oldName return false; } if ( types.Property.check(parent) && parent.key === path.node && !parent.computed ) { // { oldName: 3 } return false; } if ( types.ObjectProperty.check(parent) && parent.key === path.node && !parent.computed ) { // { oldName: 3 } return false; } if ( types.ObjectMethod.check(parent) && parent.key === path.node && !parent.computed ) { // { oldName() {} } return false; } if ( types.MethodDefinition.check(parent) && parent.key === path.node && !parent.computed ) { // class A { oldName() {} } return false; } if ( types.ClassMethod.check(parent) && parent.key === path.node && !parent.computed ) { // class A { oldName() {} } return false; } if ( types.ClassProperty.check(parent) && parent.key === path.node && !parent.computed ) { // class A { oldName = 3 } return false; } if ( types.JSXAttribute.check(parent) && parent.name === path.node && !parent.computed ) { // return false; } if ( (types.JSXOpeningElement.check(parent) || types.JSXClosingElement.check(parent)) && parent.name === path.node && /^[a-z]/.test(path.node.name) ) { // return false; } return true; }) .forEach(function(path) { let scope = path.scope; while (scope && scope !== rootScope) { if (scope.declares(oldName)) { return; } scope = scope.parent; } if (scope) { // identifier must refer to declared variable // It may look like we filtered out properties, // but the filter only ignored property "keys", not "value"s // In shorthand properties, "key" and "value" both have an // Identifier with the same structure. const parent = path.parent.node; if ( types.Property.check(parent) && parent.shorthand && !parent.method ) { path.parent.get('shorthand').replace(false); } path.get('name').replace(newName); } }); }); } }; function register() { NodeCollection.register(); Collection.registerMethods(globalMethods); Collection.registerMethods(transformMethods, VariableDeclarator); } exports.register = once(register); exports.filters = filterMethods; ================================================ FILE: src/collections/__tests__/.eslintrc ================================================ { "globals": { "jest": true }, "env": { "jasmine": true } } ================================================ FILE: src/collections/__tests__/.jshintrc ================================================ { "predef": ["jest", "describe", "beforeEach" , "xit", "it", "expect"], "node": true, "esnext": true } ================================================ FILE: src/collections/__tests__/ImportDeclaration-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ "use strict"; const getParser = require("./../../getParser"); describe("ImportDeclaration API", function () { let nodes; let Collection; let ImportDeclarationCollection; let recast; let types; let b; beforeEach(function () { jest.resetModules(); Collection = require("../../Collection"); ImportDeclarationCollection = require("../ImportDeclaration"); recast = require("recast"); types = recast.types.namedTypes; b = recast.types.builders; ImportDeclarationCollection.register(); nodes = [ recast.parse( [ 'import FooBar from "XYZ";', 'import Foo, { Bar, Baz } from "@meta/foo";', 'import { Bar as Burger } from "@meta/bar";', ].join("\n"), { parser: getParser() } ).program, ]; }); describe("Traversal", function () { describe("hasImportDeclaration", function () { it("returns true if an ImportDeclaration exists", function () { const hasImport = Collection.fromNodes(nodes).hasImportDeclaration("XYZ"); expect(hasImport).toBe(true); }); it("returns false if an ImportDeclaration does not exist", function () { const hasImport = Collection.fromNodes(nodes).hasImportDeclaration("ABC"); expect(hasImport).toBe(false); }); }); describe("findImportDeclarations", function () { it("lets us find ImportDeclarations by source path conveniently", function () { const imports = Collection.fromNodes(nodes).findImportDeclarations("XYZ"); expect(imports.length).toBe(1); }); it("returns an empty ImportDeclarationCollection if no ImportDeclarations are found", function () { const imports = Collection.fromNodes(nodes).findImportDeclarations("Foo"); expect(imports.length).toBe(0); }); }); describe("renameImportDeclaration", function () { it("renames an ImportDeclaration with the given sourcePath", function () { Collection.fromNodes(nodes).renameImportDeclaration("XYZ", "ABC"); { const imports = Collection.fromNodes(nodes).findImportDeclarations("ABC"); expect(imports.length).toBe(1); } { const imports = Collection.fromNodes(nodes).findImportDeclarations("XYZ"); expect(imports.length).toBe(0); } }); it("throws if sourcePath is not provided", function () { expect(function () { Collection.fromNodes(nodes).renameImportDeclaration(); }).toThrow(); }); it("throws if newSourcePath is not provided", function () { expect(function () { Collection.fromNodes(nodes).renameImportDeclaration("XYZ"); }).toThrow(); }); }); describe("insertImportDeclaration", function () { it("inserts an ImportDeclaration into the AST", function () { Collection.fromNodes(nodes).insertImportDeclaration("@foo/bar", [ b.importDefaultSpecifier(b.identifier("Foo")), b.importSpecifier(b.identifier("ABC")), b.importSpecifier(b.identifier("123")), ]); const imports = Collection.fromNodes(nodes).findImportDeclarations("@foo/bar"); expect(imports.length).toBe(1); const importSpecifiers = imports.paths()[0].value.specifiers; expect(importSpecifiers.length).toBe(3); }); it("does not insert duplicate ImportDeclarations", function () { Collection.fromNodes(nodes).insertImportDeclaration("@foo/baz", [ b.importDefaultSpecifier(b.identifier("Foo")), b.importSpecifier(b.identifier("ABC")), ]); Collection.fromNodes(nodes).insertImportDeclaration("@foo/baz", [ b.importDefaultSpecifier(b.identifier("Foo")), b.importSpecifier(b.identifier("ABC")), ]); const imports = Collection.fromNodes(nodes).findImportDeclarations("@foo/baz"); expect(imports.length).toBe(1); }); it("throws if importDeclaration is not provided", function () { expect(function () { Collection.fromNodes(nodes).insertImportDeclaration(); }).toThrow(); }); }); }); }); ================================================ FILE: src/collections/__tests__/JSXElement-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const getParser = require('./../../getParser'); describe('JSXCollection API', function() { let nodes; let Collection; let JSXElementCollection; let recast; let types; let b; beforeEach(function() { jest.resetModules(); Collection = require('../../Collection'); JSXElementCollection = require('../JSXElement'); recast = require('recast'); types = recast.types.namedTypes; b = recast.types.builders; JSXElementCollection.register(); nodes = [recast.parse([ 'var FooBar = require("XYZ");', '', ' ', ' ', ' ', ' ', ' ', ' {"foo"}', '' ].join('\n'), {parser: getParser()}).program]; }); describe('Traversal', function() { it('returns a non empty JSXCollection', function() { const jsx = Collection.fromNodes(nodes).find(types.JSXElement); expect(jsx.getTypes()).toContain('JSXElement'); expect(jsx.length).toBeGreaterThan(0); }); it('lets us find JSXElements by name conveniently', function() { const jsx = Collection.fromNodes(nodes).findJSXElements('Child'); expect(jsx.length).toBe(3); }); it('finds JSXElements by module name', function() { const jsx = Collection.fromNodes(nodes).findJSXElementsByModuleName('XYZ'); expect(jsx.length).toBe(1); }); it('returns the child nodes of an JSXElement', function() { const children = Collection.fromNodes(nodes) .findJSXElements('FooBar') .childNodes(); expect(children.length).toBe(7); expect(children.getTypes()).toContain('Expression'); }); it('returns the child JSXElements of an JSXElement', function() { const children = Collection.fromNodes(nodes) .findJSXElements('FooBar') .childElements(); expect(children.length).toBe(2); expect(children.getTypes()).toContain('JSXElement'); }); it('returns the child element types of an JSXElement', function() { const children = Collection.fromNodes(nodes) .findJSXElements('FooBar') .childNodesOfType(types.JSXExpressionContainer); expect(children.length).toBe(1); expect(children.getTypes()).toContain('JSXExpressionContainer'); }); it('returns a properly typed collection even if empty', function() { const children = Collection.fromNodes([]) .findJSXElements('Foo') .childElements(); expect(children.length).toBe(0); expect(children.getTypes()).toContain('JSXElement'); }); }); describe('Filtering', function() { it('filters elements by attributes', function() { const jsx = Collection.fromNodes(nodes) .findJSXElements() .filter(JSXElementCollection.filters.hasAttributes({foo: 'bar'})); expect(jsx.length).toBe(2); }); it('accepts callback functions as attribute filters', function() { const jsx = Collection.fromNodes(nodes) .findJSXElements() .filter(JSXElementCollection.filters.hasAttributes( { foo: v => ['bar', 'baz'].indexOf(v) > -1, baz: v => v === null } )); expect(jsx.length).toBe(2); }); it('filters elements by children', function() { const jsx = Collection.fromNodes(nodes) .findJSXElements() .filter(JSXElementCollection.filters.hasChildren('Child')); expect(jsx.length).toBe(2); }); }); describe('Mappings', function() { it('gets the root names of JSXElements', function() { const names = Collection.fromNodes(nodes) .findJSXElements() .paths().map(JSXElementCollection.mappings.getRootName); expect(names.indexOf('FooBar') > -1).toBe(true); expect(names.indexOf('Child') > -1).toBe(true); expect(names.indexOf('Baz') > -1).toBe(true); }); }); describe('Mutation', function() { it('handles insertions before children correctly', function() { const childElement = b.jsxElement( b.jsxOpeningElement(b.jsxIdentifier('Bar'), [], true) ); const newChildElement = b.jsxElement( b.jsxOpeningElement(b.jsxIdentifier('Baz'), [], true) ); const literal = b.literal('\n '); const ast = b.jsxElement( b.jsxOpeningElement(b.jsxIdentifier('Foo')), b.jsxClosingElement(b.jsxIdentifier('Foo')), [literal, childElement, literal, childElement, b.literal('\n')] ); Collection.fromNodes([ast]) .childElements().at(1).insertBefore(newChildElement); expect(ast.children.length).toBe(6); expect(ast.children[3]).toBe(newChildElement); }); }); }); ================================================ FILE: src/collections/__tests__/Node-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; describe('Collection API', function() { let ast; let Collection; let NodeCollection; let recast; let types; let b; beforeEach(function() { jest.resetModules(); Collection = require('../../Collection'); NodeCollection = require('../Node'); recast = require('recast'); types = recast.types.namedTypes; b = recast.types.builders; NodeCollection.register(); ast = b.program([ b.variableDeclaration( 'var', [b.variableDeclarator(b.identifier('foo'), null)] ), b.functionDeclaration( b.identifier('f'), [], b.blockStatement([ b.variableDeclaration( 'var', [b.variableDeclarator(b.identifier('bar'), null)] ), b.variableDeclaration( 'var', [b.variableDeclarator(b.identifier('baz'), null)] ) ]) ), ]); }); describe('Traversal', function() { describe('find', function() { it('finds nodes by type', function() { const ast = b.sequenceExpression([ // eslint-disable-line no-shadow b.identifier('foo'), b.literal('asd'), b.identifier('bar'), ]); const vars = Collection.fromNodes([ast]).find(types.Identifier); expect(vars.length).toBe(2); }); it('doesn\'t find the nodes in the collection itself', function() { const nodes = [ b.identifier('foo'), b.literal('asd'), b.identifier('bar'), ]; const vars = Collection.fromNodes(nodes).find(types.Identifier); expect(vars.length).toBe(0); }); it('finds nodes by type and properties', function() { const ast = b.sequenceExpression([ // eslint-disable-line no-shadow b.identifier('foo'), b.literal('asd'), b.identifier('bar'), ]); const vars = Collection.fromNodes([ast]) .find(types.Identifier, {name: 'bar'}); expect(vars.length).toBe(1); expect(vars.nodes()[0]).toBe(ast.expressions[2]); }); it('handles chained find calls properly', function() { const vars = Collection.fromNodes([ast]) .find(types.FunctionDeclaration) .find(types.VariableDeclarator, {id: {name: 'bar'}}); expect(vars.length).toBe(1); expect(vars.nodes()[0]).toBe( ast.body[1].body.body[0].declarations[0] ); }); it('handles multi chain find calls properly', function() { const functionBody = ast.body[1].body.body; const functionDeclarations = Collection.fromNodes([ast]) .find(types.FunctionDeclaration); const bar = functionDeclarations .find(types.VariableDeclarator, {id: {name: 'bar'}}); const baz = functionDeclarations .find(types.VariableDeclarator, {id: {name: 'baz'}}); expect(bar.length).toBe(1); expect(bar.nodes()[0]).toBe(functionBody[0].declarations[0]); expect(baz.length).toBe(1); expect(baz.nodes()[0]).toBe(functionBody[1].declarations[0]); }); }); describe('closestScope', function() { it('gets the closest scope', function() { const functionDeclaration = ast.body[1]; const scopes = Collection.fromNodes([ast]) .find(types.Identifier) .closestScope(); expect(scopes.nodes()[0]).toBe(ast); expect(scopes.nodes()[1]).toBe(functionDeclaration); }); }); describe('closest', function() { let decl; beforeEach(()=> { decl = b.functionDeclaration( b.identifier('foo'), [], b.blockStatement([ b.functionDeclaration( b.identifier('bar'), [], b.blockStatement([ b.returnStatement( b.literal(3) ) ]) ), ]) ); }); it('finds closest node (up the tree) of the given type', function() { const functionDeclaration = ast.body[1]; decl = Collection.fromNodes([ast]) .find(types.Identifier) .closest(types.FunctionDeclaration); expect(decl.length).toBe(1); expect(decl.nodes()[0]).toBe(functionDeclaration); }); it('allows to filter nodes by pattern', function() { const literals = Collection.fromNodes([decl]) .find(types.Literal); expect(literals.get(0).node.value).toBe(3); const closest = literals.closest( types.FunctionDeclaration, {id: {name: 'foo'}} ); expect(closest.get(0).node.id.name).toBe('foo'); }); it('allows to filter nodes with a filter function', function() { const literals = Collection.fromNodes([decl]) .find(types.Literal); expect(literals.get(0).node.value).toBe(3); const closest = literals.closest( types.FunctionDeclaration, (node) => node.id && node.id.name === 'foo' ); expect(closest.get(0).node.id.name).toBe('foo'); }); it('fails when filter evaluates as false', function() { const literals = Collection.fromNodes([decl]) .find(types.Literal); expect(literals.get(0).node.value).toBe(3); const closest = literals.closest( types.FunctionDeclaration, (node) => node.id && node.id.name === 'blue' ); expect(closest.nodes().length).toBe(0); }); }); describe('getVariableDeclarators', function() { it('gets the variable declarators for each selected path', function() { const variableDeclarator = b.variableDeclarator(b.identifier('foo'), null); const program = b.program([ b.variableDeclaration('var', [variableDeclarator]), b.expressionStatement(b.identifier('foo')), b.expressionStatement(b.identifier('bar')) ]); const decl = Collection.fromNodes([program]) .find(types.Identifier) .getVariableDeclarators(p => p.value.name); expect(decl.length).toBe(1); expect(decl.nodes()[0]).toBe(variableDeclarator); }); }); }); describe('Mutation', function() { describe('replaceWith', function() { it('handles simple AST node replacement', function() { const ast = b.sequenceExpression([ // eslint-disable-line no-shadow b.identifier('foo'), b.literal('asd'), b.identifier('bar'), ]); const newNode = b.identifier('xyz'); const S = Collection.fromNodes([ast]); S.find(types.Identifier, {name: 'bar'}) .replaceWith(newNode); expect(S.nodes()[0].expressions[2]).toBe(newNode); }); it('accepts an array as replacement', function() { const ast = b.sequenceExpression([ // eslint-disable-line no-shadow b.identifier('foo'), b.literal('asd'), b.identifier('bar'), ]); const newNode1 = b.identifier('xyz'); const newNode2 = b.identifier('jkl'); const S = Collection.fromNodes([ast]); S.find(types.Identifier, {name: 'bar'}) .replaceWith([newNode1, newNode2]); expect(S.nodes()[0].expressions[2]).toBe(newNode1); expect(S.nodes()[0].expressions[3]).toBe(newNode2); }); it('accepts a function as replacement ', function() { const ast = b.sequenceExpression([ // eslint-disable-line no-shadow b.identifier('foo'), b.literal('asd'), b.identifier('bar'), ]); const expectedArgs = [b.identifier('foo'), b.identifier('bar')]; const receivedArgs = []; const replaceFunction = jest.fn(function(path, i) { // We have to keep a reference to the argument before it gets // replaced receivedArgs.push(path.value); return b.identifier(path.value.name + i); }); const S = Collection.fromNodes([ast]); S.find(types.Identifier) .replaceWith(replaceFunction); expect(replaceFunction.mock.calls.length).toBe(2); expect(receivedArgs).toEqual(expectedArgs); // baz is properly replaced expect(S.nodes()[0].expressions[0]).toEqual(b.identifier('foo0')); // baz1 is properly replaced expect(S.nodes()[0].expressions[2]).toEqual(b.identifier('bar1')); }); }); describe('insertBefore', function() { it('inserts a new node before the current one', function() { const ast = b.variableDeclaration( // eslint-disable-line no-shadow 'var', [b.variableDeclarator(b.identifier('foo'), null)] ); const one = b.variableDeclarator(b.identifier('one'), null); Collection.fromNodes([ast]) .find(types.VariableDeclarator) .insertBefore(one); expect(ast.declarations.length).toBe(2); expect(ast.declarations[0]).toBe(one); }); it('accepts an array of nodes', function() { const ast = b.variableDeclaration( // eslint-disable-line no-shadow 'var', [b.variableDeclarator(b.identifier('foo'), null)] ); const one = b.variableDeclarator(b.identifier('one'), null); const two = b.variableDeclarator(b.identifier('two'), null); Collection.fromNodes([ast]) .find(types.VariableDeclarator) .insertBefore([one, two]); expect(ast.declarations.length).toBe(3); expect(ast.declarations[0]).toBe(one); expect(ast.declarations[1]).toBe(two); }); it('accepts a function', function() { const x = b.identifier('x'); const foo = b.identifier('foo'); const bar = b.identifier('bar'); const ast = b.sequenceExpression([foo, bar]); // eslint-disable-line no-shadow Collection.fromNodes([ast]) .find(types.Identifier) .insertBefore(function() { return x; }); expect(ast.expressions.length).toBe(4); expect(ast.expressions).toEqual([x, foo, x, bar]); expect(ast.expressions[0]).toBe(x); expect(ast.expressions[2]).toBe(x); }); }); describe('insertAfter', function() { it('inserts a new node after the current one', function() { const ast = b.variableDeclaration( // eslint-disable-line no-shadow 'var', [b.variableDeclarator(b.identifier('foo'), null)] ); const one = b.variableDeclarator(b.identifier('one'), null); Collection.fromNodes([ast]) .find(types.VariableDeclarator) .insertAfter(one); expect(ast.declarations.length).toBe(2); expect(ast.declarations[1]).toBe(one); }); it('accepts an array of nodes', function() { const ast = b.variableDeclaration( // eslint-disable-line no-shadow 'var', [b.variableDeclarator(b.identifier('foo'), null)] ); const one = b.variableDeclarator(b.identifier('one'), null); const two = b.variableDeclarator(b.identifier('two'), null); Collection.fromNodes([ast]) .find(types.VariableDeclarator) .insertAfter([one, two]); expect(ast.declarations.length).toBe(3); expect(ast.declarations[1]).toBe(one); expect(ast.declarations[2]).toBe(two); }); it('accepts a function', function() { const x = b.identifier('x'); const foo = b.identifier('foo'); const bar = b.identifier('bar'); const ast = b.sequenceExpression([foo, bar]); // eslint-disable-line no-shadow Collection.fromNodes([ast]) .find(types.Identifier) .insertAfter(function() { return x; }); expect(ast.expressions.length).toBe(4); expect(ast.expressions).toEqual([foo, x, bar, x]); expect(ast.expressions[1]).toBe(x); expect(ast.expressions[3]).toBe(x); }); }); describe('removes', function() { it('removes a node if it is part of the body of a statement', function() { const x = b.expressionStatement(b.identifier('x')); const y = b.expressionStatement(b.identifier('y')); const ast = b.program([x, y]); // eslint-disable-line no-shadow Collection.fromNodes([ast]) .find(types.Identifier, {name: 'x'}) .remove(); expect(ast.body.length).toBe(1); expect(ast.body[0]).toBe(y); }); it('removes a node if it is a function param', function() { const x = b.identifier('x'); const y = b.identifier('y'); const ast = b.arrowFunctionExpression( // eslint-disable-line no-shadow [x, b.identifier('z')], y ); Collection.fromNodes([ast]) .find(types.Identifier, {name: 'z'}) .remove(); expect(ast.params.length).toBe(1); expect(ast.params[0]).toBe(x); expect(ast.body).toBe(y); }); }); }); }); ================================================ FILE: src/collections/__tests__/VariableDeclarator-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const getParser = require('./../../getParser'); const recast = require('recast'); const types = recast.types.namedTypes; describe('VariableDeclarators', function() { let nodes; let Collection; let VariableDeclaratorCollection; beforeEach(function() { jest.resetModules(); Collection = require('../../Collection'); VariableDeclaratorCollection = require('../VariableDeclarator'); VariableDeclaratorCollection.register(); nodes = [recast.parse([ 'var foo = 42;', 'var bar = require("module");', 'var baz = require("module2");', 'function first() {', ' var x = bar;', ' bar.someMethod();', ' func1(bar);', '}', 'function func1(bar) {', ' var bar = 21;', '}', 'foo.bar();', 'foo[bar]();', 'bar.foo();', 'function second() {', ' var blah;', ' var obj = {', ' blah: 4,', ' blah() {},', ' };', ' obj.blah = 3;', ' class A {', ' blah = 10', ' blah() {}', ' }', '}', 'class Foo { @decorator\n*stuff() {} }', '', ].join('\n'), {parser: getParser()}).program]; }); describe('Traversal', function() { it('adds a root method to find variable declarators', function() { expect(Collection.fromNodes([]).findVariableDeclarators).toBeDefined(); }); it('finds all variable declarators', function() { const declarators = Collection.fromNodes(nodes).findVariableDeclarators(); expect(declarators.getTypes()).toContain('VariableDeclarator'); expect(declarators.length).toBe(7); }); it('finds variable declarators by name', function() { const declarators = Collection.fromNodes(nodes) .findVariableDeclarators('bar'); expect(declarators.length).toBe(2); }); }); describe('Filters', function() { it('finds module imports (require)', function() { const declarators = Collection.fromNodes(nodes) .findVariableDeclarators() .filter(VariableDeclaratorCollection.filters.requiresModule()); expect(declarators.length).toBe(2); }); it('finds module imports (require) by module name', function() { const declarators = Collection.fromNodes(nodes) .findVariableDeclarators() .filter(VariableDeclaratorCollection.filters.requiresModule('module')); expect(declarators.length).toBe(1); }); it('accepts multiple module names', function() { const declarators = Collection.fromNodes(nodes) .findVariableDeclarators() .filter(VariableDeclaratorCollection.filters.requiresModule( ['module', 'module2'] )); expect(declarators.length).toBe(2); }); }); describe('Transform', function() { it('renames variable declarations considering scope', function() { Collection.fromNodes(nodes) .findVariableDeclarators() .filter(VariableDeclaratorCollection.filters.requiresModule('module')) .renameTo('xyz'); const identifiers = Collection.fromNodes(nodes) .find(types.Identifier, {name: 'xyz'}); expect(identifiers.length).toBe(6); }); it('does not rename things that are not variables', function() { Collection.fromNodes(nodes) .findVariableDeclarators('blah') .renameTo('blarg'); const identifiers = Collection.fromNodes(nodes) .find(types.Identifier, {name: 'blarg'}); expect(identifiers.length).toBe(1); }); it('properly renames a shorthand property that was using the old variable name', function() { nodes = [recast.parse([ 'var foo = 42;', 'var obj2 = {', ' foo,', '};', ].join('\n'), {parser: getParser()}).program]; // Outputs: // var newFoo = 42; // var obj2 = { // foo: newFoo, // }; Collection.fromNodes(nodes) .findVariableDeclarators('foo').renameTo('newFoo'); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'newFoo' }).length ).toBe(2); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'foo' }).length ).toBe(1); expect( Collection.fromNodes(nodes).find(types.Property).filter(prop => !prop.value.shorthand).length ).toBe(1); expect( Collection.fromNodes(nodes).find(types.Property).filter(prop => prop.value.shorthand).length ).toBe(0); }); it('does not rename React component prop name', function () { Collection.fromNodes(nodes) .findVariableDeclarators('foo') .renameTo('xyz'); const identifiers = Collection.fromNodes(nodes) .find(types.JSXIdentifier, { name: 'foo' }); expect(identifiers.length).toBe(1); }); it('does not rename JSX open and closing tags that start with a lowercase letter', function () { nodes = [recast.parse([ 'var span = useRef(null);', 'var element = ;', ].join('\n'), {parser: getParser()}).program]; Collection.fromNodes(nodes) .findVariableDeclarators('span') .renameTo('spanRef'); const identifiers = Collection.fromNodes(nodes) .find(types.JSXIdentifier, { name: 'spanRef' }); expect(identifiers.length).toBe(0); }); it('does rename JSX open and closing tags that are capitalized', function () { nodes = [recast.parse([ 'var Span = require("./Span");', 'var span = useRef(null);', 'var element = ;', ].join('\n'), {parser: getParser()}).program]; Collection.fromNodes(nodes) .findVariableDeclarators('Span') .renameTo('SpanComponent'); const identifiers = Collection.fromNodes(nodes) .find(types.JSXIdentifier, { name: 'SpanComponent' }); expect(identifiers.length).toBe(2); }); describe('parsing with bablylon', function() { it('does not rename object property', function () { nodes = [ recast.parse('var foo = 42; var obj = { foo: null };', { parser: getParser('babylon'), }).program ]; Collection .fromNodes(nodes) .findVariableDeclarators('foo').renameTo('newFoo'); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'newFoo' }).length ).toBe(1); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'foo' }).length ).toBe(1); }) it('does not rename object method', function () { nodes = [ recast.parse('var foo = 42; var obj = { foo() {} };', { parser: getParser('babylon'), }).program ]; Collection .fromNodes(nodes) .findVariableDeclarators('foo').renameTo('newFoo'); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'newFoo' }).length ).toBe(1); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'foo' }).length ).toBe(1); }) it('does not rename class method', function () { nodes = [ recast.parse('var foo = 42; class A { foo() {} }', { parser: getParser('babylon'), }).program ]; Collection .fromNodes(nodes) .findVariableDeclarators('foo').renameTo('newFoo'); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'newFoo' }).length ).toBe(1); expect( Collection.fromNodes(nodes).find(types.Identifier, { name: 'foo' }).length ).toBe(1); }) }); }); }); ================================================ FILE: src/collections/index.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ module.exports = { Node: require('./Node'), JSXElement: require('./JSXElement'), VariableDeclarator: require('./VariableDeclarator'), ImportDeclaration: require('./ImportDeclaration'), }; ================================================ FILE: src/core.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const Collection = require('./Collection'); const collections = require('./collections'); const getParser = require('./getParser'); const matchNode = require('./matchNode'); const recast = require('recast'); const template = require('./template'); const Node = recast.types.namedTypes.Node; const NodePath = recast.types.NodePath; // Register all built-in collections for (var name in collections) { collections[name].register(); } /** * Main entry point to the tool. The function accepts multiple different kinds * of arguments as a convenience. In particular the function accepts either * * - a string containing source code * The string is parsed with Recast * - a single AST node * - a single node path * - an array of nodes * - an array of node paths * * @exports jscodeshift * @param {Node|NodePath|Array|string} source * @param {Object} options Options to pass to Recast when passing source code * @return {Collection} */ function core(source, options) { return typeof source === 'string' ? fromSource(source, options) : fromAST(source); } /** * Returns a collection from a node, node path, array of nodes or array of node * paths. * * @ignore * @param {Node|NodePath|Array} source * @return {Collection} */ function fromAST(ast) { if (Array.isArray(ast)) { if (ast[0] instanceof NodePath || ast.length === 0) { return Collection.fromPaths(ast); } else if (Node.check(ast[0])) { return Collection.fromNodes(ast); } } else { if (ast instanceof NodePath) { return Collection.fromPaths([ast]); } else if (Node.check(ast)) { return Collection.fromNodes([ast]); } } throw new TypeError( 'Received an unexpected value ' + Object.prototype.toString.call(ast) ); } function fromSource(source, options) { if (!options) { options = {}; } if (!options.parser) { options.parser = getParser(); } return fromAST(recast.parse(source, options)); } /** * Utility function to match a node against a pattern. * @augments core * @static * @param {Node|NodePath|Object} path * @parma {Object} filter * @return boolean */ function match(path, filter) { if (!(path instanceof NodePath)) { if (typeof path.get === 'function') { path = path.get(); } else { path = {value: path}; } } return matchNode(path.value, filter); } const plugins = []; /** * Utility function for registering plugins. * * Plugins are simple functions that are passed the core jscodeshift instance. * They should extend jscodeshift by calling `registerMethods`, etc. * This method guards against repeated registrations (the plugin callback will only be called once). * * @augments core * @static * @param {Function} plugin */ function use(plugin) { if (plugins.indexOf(plugin) === -1) { plugins.push(plugin); plugin(core); } } /** * Returns a version of the core jscodeshift function "bound" to a specific * parser. * * @augments core * @static */ function withParser(parser) { if (typeof parser === 'string') { parser = getParser(parser); } const newCore = function(source, options) { if (options && !options.parser) { options.parser = parser; } else { options = {parser}; } return core(source, options); }; return enrichCore(newCore, parser); } /** * The ast-types library * @external astTypes * @see {@link https://github.com/benjamn/ast-types} */ function enrichCore(core, parser) { // add builders and types to the function for simple access Object.assign(core, recast.types.namedTypes); Object.assign(core, recast.types.builders); core.registerMethods = Collection.registerMethods; /** * @augments core * @type external:astTypes */ core.types = recast.types; core.match = match; core.template = template(parser); // add mappings and filters to function core.filters = {}; core.mappings = {}; for (const name in collections) { if (collections[name].filters) { core.filters[name] = collections[name].filters; } if (collections[name].mappings) { core.mappings[name] = collections[name].mappings; } } core.use = use; core.withParser = withParser; return core; } module.exports = enrichCore(core, getParser()); ================================================ FILE: src/getParser.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; module.exports = function getParser(parserName, options) { switch (parserName) { case 'babylon': return require('../parser/babylon')(options); case 'flow': return require('../parser/flow')(options); case 'ts': return require('../parser/ts')(options); case 'tsx': return require('../parser/tsx')(options); case 'babel': default: return require('../parser/babel5Compat')(options); } }; ================================================ FILE: src/ignoreFiles.js ================================================ 'use strict'; const fs = require('fs'); const mm = require('micromatch'); const matchers = []; /** * Add glob patterns to ignore matched files and folders. * Creates glob patterns to approximate gitignore patterns. * @param {String} val - the glob or gitignore-style pattern to ignore * @see {@linkplain https://git-scm.com/docs/gitignore#_pattern_format} */ function addIgnorePattern(val) { if (val && typeof val === 'string' && val[0] !== '#') { let pattern = val; if (pattern.indexOf('/') === -1) { matchers.push('**/' + pattern); } else if (pattern[pattern.length-1] === '/') { matchers.push('**/' + pattern + '**'); matchers.push(pattern + '**'); } matchers.push(pattern); } } /** * Adds ignore patterns directly from function input * @param {String|Array} input - the ignore patterns */ function addIgnoreFromInput(input) { let patterns = []; if (input) { patterns = patterns.concat(input); } patterns.forEach(addIgnorePattern); } /** * Adds ignore patterns by reading files * @param {String|Array} input - the paths to the ignore config files */ function addIgnoreFromFile(input) { let lines = []; let files = []; if (input) { files = files.concat(input); } files.forEach(function(config) { const stats = fs.statSync(config); if (stats.isFile()) { const content = fs.readFileSync(config, 'utf8'); lines = lines.concat(content.split(/\r?\n/)); } }); lines.forEach(addIgnorePattern); } function shouldIgnore(path) { const matched = matchers.length ? mm.isMatch(path, matchers, { dot:true }) : false; return matched; } exports.add = addIgnoreFromInput; exports.addFromFile = addIgnoreFromFile; exports.shouldIgnore = shouldIgnore; ================================================ FILE: src/matchNode.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const hasOwn = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty); /** * Checks whether needle is a strict subset of haystack. * * @param {*} haystack Value to test. * @param {*} needle Test function or value to look for in `haystack`. * @return {bool} */ function matchNode(haystack, needle) { if (typeof needle === 'function') { return needle(haystack); } if (isNode(needle) && isNode(haystack)) { return Object.keys(needle).every(function(property) { return ( hasOwn(haystack, property) && matchNode(haystack[property], needle[property]) ); }); } return haystack === needle; } function isNode(value) { return typeof value === 'object' && value; } module.exports = matchNode; ================================================ FILE: src/template.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const recast = require('recast'); const builders = recast.types.builders; const types = recast.types.namedTypes; function splice(arr, element, replacement) { arr.splice.apply(arr, [arr.indexOf(element), 1].concat(replacement)); } function cleanLocation(node) { delete node.start; delete node.end; delete node.loc; return node; } function ensureStatement(node) { return types.Statement.check(node) ? // Removing the location information seems to ensure that the node is // correctly reprinted with a trailing semicolon cleanLocation(node) : builders.expressionStatement(node); } function getVistor(varNames, nodes) { return { visitIdentifier: function(path) { this.traverse(path); const node = path.node; const parent = path.parent.node; // If this identifier is not one of our generated ones, do nothing const varIndex = varNames.indexOf(node.name); if (varIndex === -1) { return; } let replacement = nodes[varIndex]; nodes[varIndex] = null; // If the replacement is an array, we need to explode the nodes in context if (Array.isArray(replacement)) { if (types.Function.check(parent) && parent.params.indexOf(node) > -1) { // Function parameters: function foo(${bar}) {} splice(parent.params, node, replacement); } else if (types.VariableDeclarator.check(parent)) { // Variable declarations: var foo = ${bar}, baz = 42; splice( path.parent.parent.node.declarations, parent, replacement ); } else if (types.ArrayExpression.check(parent)) { // Arrays: var foo = [${bar}, baz]; splice(parent.elements, node, replacement); } else if (types.Property.check(parent) && parent.shorthand) { // Objects: var foo = {${bar}, baz: 42}; splice( path.parent.parent.node.properties, parent, replacement ); } else if (types.CallExpression.check(parent) && parent.arguments.indexOf(node) > -1) { // Function call arguments: foo(${bar}, baz) splice(parent.arguments, node, replacement); } else if (types.ExpressionStatement.check(parent)) { // Generic sequence of statements: { ${foo}; bar; } path.parent.replace.apply( path.parent, replacement.map(ensureStatement) ); } else { // Every else, let recast take care of it path.replace.apply(path, replacement); } } else if (types.ExpressionStatement.check(parent)) { path.parent.replace(ensureStatement(replacement)); } else { path.replace(replacement); } } }; } function replaceNodes(src, varNames, nodes, parser) { const ast = recast.parse(src, {parser}); recast.visit(ast, getVistor(varNames, nodes)); return ast; } let varNameCounter = 0; function getUniqueVarName() { return `$jscodeshift${varNameCounter++}$`; } module.exports = function withParser(parser) { function statements(template/*, ...nodes*/) { template = Array.from(template); const nodes = Array.from(arguments).slice(1); const varNames = nodes.map(() => getUniqueVarName()); const src = template.reduce( (result, elem, i) => result + varNames[i - 1] + elem ); return replaceNodes( src, varNames, nodes, parser ).program.body; } function statement(/*template, ...nodes*/) { return statements.apply(null, arguments)[0]; } function expression(template/*, ...nodes*/) { // wrap code in `(...)` to force evaluation as expression template = Array.from(template); if (template.length > 0) { template[0] = '(' + template[0]; template[template.length - 1] += ')'; } const expression = statement.apply( null, [template].concat(Array.from(arguments).slice(1)) ).expression; // Remove added parens if (expression.extra) { expression.extra.parenthesized = false; } return expression; } function asyncExpression(template/*, ...nodes*/) { template = Array.from(template); if (template.length > 0) { template[0] = 'async () => (' + template[0]; template[template.length - 1] += ')'; } const expression = statement.apply( null, [template].concat(Array.from(arguments).slice(1)) ).expression.body; // Remove added parens if (expression.extra) { expression.extra.parenthesized = false; } return expression; } return {statements, statement, expression, asyncExpression}; } ================================================ FILE: src/testUtils.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* global expect, describe, it */ 'use strict'; const fs = require('fs'); const path = require('path'); function applyTransform(module, options, input, testOptions = {}) { // Handle ES6 modules using default export for the transform const transform = module.default ? module.default : module; // Jest resets the module registry after each test, so we need to always get // a fresh copy of jscodeshift on every test run. let jscodeshift = require('./core'); if (testOptions.parser || module.parser) { jscodeshift = jscodeshift.withParser(testOptions.parser || module.parser); } const output = transform( input, { jscodeshift, j: jscodeshift, stats: () => {}, }, options || {} ); // Support async transforms if (output instanceof Promise) { return output.then(output => (output || '').trim()); } return (output || '').trim(); } exports.applyTransform = applyTransform; function runSnapshotTest(module, options, input) { const output = applyTransform(module, options, input); if (output instanceof Promise) { return output.then(output => { expect(output).toMatchSnapshot(); return output; }); } expect(output).toMatchSnapshot(); return output; } exports.runSnapshotTest = runSnapshotTest; function runInlineTest(module, options, input, expectedOutput, testOptions) { const output = applyTransform(module, options, input, testOptions); const expectation = (output => expect(output).toEqual(expectedOutput.trim())) if (output instanceof Promise) { return output.then(output => { expectation(output); return output; }); } expectation(output); return output; } exports.runInlineTest = runInlineTest; function extensionForParser(parser) { switch (parser) { case 'ts': case 'tsx': return parser; default: return 'js' } } /** * Utility function to run a jscodeshift script within a unit test. This makes * several assumptions about the environment: * * - `dirName` contains the name of the directory the test is located in. This * should normally be passed via __dirname. * - The test should be located in a subdirectory next to the transform itself. * Commonly tests are located in a directory called __tests__. * - `transformName` contains the filename of the transform being tested, * excluding the .js extension. * - `testFilePrefix` optionally contains the name of the file with the test * data. If not specified, it defaults to the same value as `transformName`. * This will be suffixed with ".input.js" for the input file and ".output.js" * for the expected output. For example, if set to "foo", we will read the * "foo.input.js" file, pass this to the transform, and expect its output to * be equal to the contents of "foo.output.js". * - Test data should be located in a directory called __testfixtures__ * alongside the transform and __tests__ directory. */ function runTest(dirName, transformName, options, testFilePrefix, testOptions = {}) { if (!testFilePrefix) { testFilePrefix = transformName; } // Assumes transform is one level up from __tests__ directory const module = require(path.join(dirName, '..', transformName)); const extension = extensionForParser(testOptions.parser || module.parser) const fixtureDir = path.join(dirName, '..', '__testfixtures__'); const inputPath = path.join(fixtureDir, testFilePrefix + `.input.${extension}`); const source = fs.readFileSync(inputPath, 'utf8'); const expectedOutput = fs.readFileSync( path.join(fixtureDir, testFilePrefix + `.output.${extension}`), 'utf8' ); const testResult = runInlineTest(module, options, { path: inputPath, source }, expectedOutput, testOptions); return testResult instanceof Promise ? testResult : undefined; } exports.runTest = runTest; /** * Handles some boilerplate around defining a simple jest/Jasmine test for a * jscodeshift transform. */ function defineTest(dirName, transformName, options, testFilePrefix, testOptions) { const testName = testFilePrefix ? `transforms correctly using "${testFilePrefix}" data` : 'transforms correctly'; describe(transformName, () => { it(testName, () => { const testResult = runTest(dirName, transformName, options, testFilePrefix, testOptions); return testResult instanceof Promise ? testResult : undefined; }); }); } exports.defineTest = defineTest; function defineInlineTest(module, options, input, expectedOutput, testName) { it(testName || 'transforms correctly', () => { const testResult = runInlineTest(module, options, { source: input }, expectedOutput); return testResult instanceof Promise ? testResult : undefined; }); } exports.defineInlineTest = defineInlineTest; function defineSnapshotTest(module, options, input, testName) { it(testName || 'transforms correctly', () => { const testResult = runSnapshotTest(module, options, { source: input }); return testResult instanceof Promise ? testResult : undefined; }); } exports.defineSnapshotTest = defineSnapshotTest; /** * Handles file-loading boilerplates, using same defaults as defineTest */ function defineSnapshotTestFromFixture(dirName, module, options, testFilePrefix, testName, testOptions = {}) { const extension = extensionForParser(testOptions.parser || module.parser) const fixtureDir = path.join(dirName, '..', '__testfixtures__'); const inputPath = path.join(fixtureDir, testFilePrefix + `.input.${extension}`); const source = fs.readFileSync(inputPath, 'utf8'); const testResult = defineSnapshotTest(module, options, source, testName) return testResult instanceof Promise ? testResult : undefined; } exports.defineSnapshotTestFromFixture = defineSnapshotTestFromFixture; ================================================ FILE: src/utils/__tests__/intersection-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const intersection = require('../intersection'); function test(testCases) { for (const testName in testCases) { const testCase = testCases[testName]; it(testName, function() { expect(intersection(testCase.input)).toEqual(testCase.output); }); } } describe('intersection', function() { test({ 'intersects string values': { input: [['foo', 'bar', 'baz'], ['foo', 'bar'], ['bar', 'baz']], output: ['bar'], }, 'returns empty list if no intersection': { input: [['foo', 'bar', 'baz'], ['foo'], ['bar']], output: [], }, }); }); ================================================ FILE: src/utils/__tests__/once-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const once = require('../once'); describe('once', function() { it('executes the function only once', function() { const mock = jest.fn().mockImplementation(foo => foo); const wrapped = once(mock); wrapped('foo'); const result = wrapped('bar'); expect(result).toEqual('foo'); expect(mock).toHaveBeenCalledTimes(1); }); }); ================================================ FILE: src/utils/__tests__/union-test.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const union = require('../union'); function test(testCases) { for (const testName in testCases) { const testCase = testCases[testName]; it(testName, function() { expect(union(testCase.input)).toEqual(testCase.output); }); } } describe('union', function() { test({ 'unions string values': { input: [['foo', 'bar', 'baz'], ['foo', 'bar'], ['bar', 'baz']], output: ['foo', 'bar', 'baz'], }, 'understands empty input arrays': { input: [[], ['foo'], ['bar']], output: ['foo', 'bar'], }, }); }); ================================================ FILE: src/utils/intersection.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ module.exports = function(arrays) { const result = new Set(arrays[0]); let resultSize = result.length; let i, value, valuesToCheck; for (i = 1; i < arrays.length; i++) { valuesToCheck = new Set(arrays[i]); for (value of result) { if (!valuesToCheck.has(value)) { result.delete(value); resultSize -= 1; } if (resultSize === 0) { return []; } } } return Array.from(result); }; ================================================ FILE: src/utils/once.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * This replicates lodash's once functionality for our purposes. */ module.exports = function(func) { let called = false; let result; return function(...args) { if (called) { return result; } called = true; return result = func.apply(this, args); }; }; ================================================ FILE: src/utils/union.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ module.exports = function(arrays) { const result = new Set(arrays[0]); let i,j, array; for (i = 1; i < arrays.length; i++) { array = arrays[i]; for (j = 0; j < array.length; j++) { result.add(array[j]); } } return Array.from(result); }; ================================================ FILE: utils/requirePackage.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const path = require('path'); module.exports = function requirePackage(name) { const entry = require.resolve(name); let dir = path.dirname(entry); while (dir !== '/') { try { const pkg = require(path.join(dir, 'package.json')); return pkg.name === name ? pkg : {}; } catch(error) {} // eslint-disable-line no-empty dir = path.dirname(dir); } return {}; } ================================================ FILE: utils/testUtils.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const fs = require('fs'); const path = require('path'); const tmp = require('tmp'); function renameFileTo(oldPath, newFilename, extension = '') { const projectPath = path.dirname(oldPath); const newPath = path.join(projectPath, newFilename + extension); fs.mkdirSync(path.dirname(newPath), { recursive: true }); fs.renameSync(oldPath, newPath); return newPath; } function createTempFileWith(content, filename, extension) { const info = tmp.fileSync({ postfix: extension }); let filePath = info.name; fs.writeSync(info.fd, content); fs.closeSync(info.fd); if (filename) { filePath = renameFileTo(filePath, filename, extension); } return filePath; } exports.createTempFileWith = createTempFileWith; // Test transform files need a js extension to work with @babel/register // .ts or .tsx work as well function createTransformWith(content, ext='.js') { return createTempFileWith( 'module.exports = function(fileInfo, api, options) { ' + content + ' }', undefined, ext ); } exports.createTransformWith = createTransformWith; function getFileContent(filePath) { return fs.readFileSync(filePath).toString(); } exports.getFileContent = getFileContent; ================================================ FILE: website/.astro/settings.json ================================================ { "_variables": { "lastUpdateCheck": 1719853949463 } } ================================================ FILE: website/.astro/types.d.ts ================================================ declare module 'astro:content' { interface Render { '.mdx': Promise<{ Content: import('astro').MarkdownInstance<{}>['Content']; headings: import('astro').MarkdownHeading[]; remarkPluginFrontmatter: Record; }>; } } declare module 'astro:content' { interface Render { '.md': Promise<{ Content: import('astro').MarkdownInstance<{}>['Content']; headings: import('astro').MarkdownHeading[]; remarkPluginFrontmatter: Record; }>; } } declare module 'astro:content' { type Flatten = T extends { [K: string]: infer U } ? U : never; export type CollectionKey = keyof AnyEntryMap; export type CollectionEntry = Flatten; export type ContentCollectionKey = keyof ContentEntryMap; export type DataCollectionKey = keyof DataEntryMap; type AllValuesOf = T extends any ? T[keyof T] : never; type ValidContentEntrySlug = AllValuesOf< ContentEntryMap[C] >['slug']; export function getEntryBySlug< C extends keyof ContentEntryMap, E extends ValidContentEntrySlug | (string & {}), >( collection: C, // Note that this has to accept a regular string too, for SSR entrySlug: E ): E extends ValidContentEntrySlug ? Promise> : Promise | undefined>; export function getDataEntryById( collection: C, entryId: E ): Promise>; export function getCollection>( collection: C, filter?: (entry: CollectionEntry) => entry is E ): Promise; export function getCollection( collection: C, filter?: (entry: CollectionEntry) => unknown ): Promise[]>; export function getEntry< C extends keyof ContentEntryMap, E extends ValidContentEntrySlug | (string & {}), >(entry: { collection: C; slug: E; }): E extends ValidContentEntrySlug ? Promise> : Promise | undefined>; export function getEntry< C extends keyof DataEntryMap, E extends keyof DataEntryMap[C] | (string & {}), >(entry: { collection: C; id: E; }): E extends keyof DataEntryMap[C] ? Promise : Promise | undefined>; export function getEntry< C extends keyof ContentEntryMap, E extends ValidContentEntrySlug | (string & {}), >( collection: C, slug: E ): E extends ValidContentEntrySlug ? Promise> : Promise | undefined>; export function getEntry< C extends keyof DataEntryMap, E extends keyof DataEntryMap[C] | (string & {}), >( collection: C, id: E ): E extends keyof DataEntryMap[C] ? Promise : Promise | undefined>; /** Resolve an array of entry references from the same collection */ export function getEntries( entries: { collection: C; slug: ValidContentEntrySlug; }[] ): Promise[]>; export function getEntries( entries: { collection: C; id: keyof DataEntryMap[C]; }[] ): Promise[]>; export function reference( collection: C ): import('astro/zod').ZodEffects< import('astro/zod').ZodString, C extends keyof ContentEntryMap ? { collection: C; slug: ValidContentEntrySlug; } : { collection: C; id: keyof DataEntryMap[C]; } >; // Allow generic `string` to avoid excessive type errors in the config // if `dev` is not running to update as you edit. // Invalid collection names will be caught at build time. export function reference( collection: C ): import('astro/zod').ZodEffects; type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; type InferEntrySchema = import('astro/zod').infer< ReturnTypeOrOriginal['schema']> >; type ContentEntryMap = { "docs": { "build/api-reference.mdx": { id: "build/api-reference.mdx"; slug: "build/api-reference"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; "build/ast-grammar.mdx": { id: "build/ast-grammar.mdx"; slug: "build/ast-grammar"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; "index.mdx": { id: "index.mdx"; slug: "index"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; "overview/introduction.mdx": { id: "overview/introduction.mdx"; slug: "overview/introduction"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; "run/cli.mdx": { id: "run/cli.mdx"; slug: "run/cli"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; }; }; type DataEntryMap = {}; type AnyEntryMap = ContentEntryMap & DataEntryMap; export type ContentConfig = typeof import("../src/content/config.js"); } ================================================ FILE: website/README.md ================================================ # jscodeshift docs [![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) ## 🚀 Project Structure ``` . ├── public/ ├── src/ │ ├── assets/ │ ├── content/ │ │ ├── docs/ │ │ └── config.ts │ └── env.d.ts ├── astro.config.mjs ├── package.json └── tsconfig.json ``` Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name. Images can be added to `src/assets/` and embedded in Markdown with a relative link. Static assets, like favicons, can be placed in the `public/` directory. ## 🧞 Commands All commands are run from the root of the project, from a terminal: | Command | Action | |:-----------------------|:-------------------------------------------------| | `yarn install` | Installs dependencies | | `yarn dev` | Starts local dev server at `localhost:4321` | | `yarn build` | Build your production site to `./dist/` | | `yarn preview` | Preview your build locally, before deploying | | `yarn astro ...` | Run CLI commands like `astro add`, `astro check` | | `yarn astro -- --help` | Get help using the Astro CLI | ## 👀 Want to learn more? Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat). ================================================ FILE: website/astro.config.mjs ================================================ import {defineConfig} from 'astro/config'; import starlight from '@astrojs/starlight'; // https://astro.build/config export default defineConfig({ integrations: [ starlight({ title: 'jscodeshift', social: { github: 'https://github.com/facebook/jscodeshift', }, sidebar: [ { label: 'Overview', items: [ // Each item here is one entry in the navigation menu. {label: 'Introduction', link: '/overview/introduction/'}, ], }, { label: 'Building', items: [ {label: 'API Reference', link: '/build/api-reference/'}, {label: 'AST Grammar', link: '/build/ast-grammar/'}, ], }, { label: 'Running', items: [ {label: 'CLI', link: '/run/cli/'}, ], }, ], }), ], }); ================================================ FILE: website/package.json ================================================ { "name": "docs", "type": "module", "version": "0.0.1", "scripts": { "dev": "astro dev", "start": "astro dev", "build": "astro build", "preview": "astro preview", "astro": "astro" }, "dependencies": { "@astrojs/starlight": "^0.24.4", "astro": "^4.16.18", "sharp": "^0.32.5" } } ================================================ FILE: website/src/content/config.ts ================================================ import {defineCollection} from 'astro:content'; import {docsSchema} from '@astrojs/starlight/schema'; export const collections = { docs: defineCollection({schema: docsSchema()}), }; ================================================ FILE: website/src/content/docs/build/api-reference.mdx ================================================ --- title: API Reference --- import {Steps, LinkCard, Card, CardGrid} from '@astrojs/starlight/components'; `jscodeshift` has around 25 APIs to help developers easily detect and transform any JS/TS code. Generally, creating a codemod involves two main tasks: **detection** and **transformation**. 1. **Detection** Detecting a specific pattern in a large codebase can be expensive, so this task is often divided into two passes. 1. **First pass** In the first pass, we perform an initial scope reduction of the AST nodes to significantly reduce the search space and produce a collection of AST nodes to process. 2. **Second pass** In the second pass, we process and filter the nodes collection to pinpoint the specific AST nodes that need transformation. 2. **Transformation** Once we detect the desired nodes, we transform the AST and produce the modified code. For jscodeshift, we have a set of APIs for each part of the codemod process (initial traversal, filtering, transformation), as detailed below. jscodeshift accepts `—parser` as argument. We can select from the list of parser that are currently supported, all those parsers should be compatible with `estree` spec and have same AST grammar. It's important to know the AST grammar for describing the nodes in the codemod. Refer to the [`jscodeshift` node types](/build/ast-grammar/). ## Building jscodeshift codemods ## Core API ### **`jscodeshift`** The main function that returns the jscodeshift instance. **Parameters**: `source` (String): The source code to be transformed. **Example**: ```jsx const jscodeshift = require('jscodeshift'); const sourceCode = `const a = 1;`; const j = jscodeshift(sourceCode); ``` ## Node Traversal APIs Below are APIs that often used in the **initial scope reduction** phase ([source](https://github.com/facebook/jscodeshift/blob/4851fc8a01036868efb4cf9676f3e97836097376/src/collections/Node.js#L139)). The input is usually the whole file, and the output is a collection of nodes. ### **`find`** Finds nodes that match the provided type. **Parameters**: `type` (String or Function): The type of nodes to find. **Example**: ```jsx const variableDeclarations = j.find(j.VariableDeclaration); ``` ### **`findImportDeclarations`** Finds all ImportDeclarations optionally filtered by name. **Parameters**: `sourcePath` (String). **Example**: ```jsx const routerImports = j.findImportDeclarations('react-router-dom'); ``` ### **`closestScope`** Finds the closest enclosing scope of a node. Useful for determining the scope context of variables and functions. **Example**: ```jsx const closestScopes = j.find(j.Identifier).closestScope(); ``` ### **`closest`** Finds the nearest parent node that matches the specified type. The child node must be obtained from a previous function call, such as find. **Parameters**:`type` (String or Function): The type of ancestor to find. ```jsx const closestFunction = j.find(j.Identifier).closest(j.FunctionDeclaration); ``` ### **`getVariableDeclarators`** Retrieves variable declarators from the current collection. If the callback function returns a falsy value, the element is not included in the result. **Parameters**:`callback` (Function): A function that returns the name of the variable to find. **Example**: ```jsx const variableDeclarators = j.find(j.Identifier).getVariableDeclarators(path => path.value.name); ``` ### **`findVariableDeclarators` ([source](https://github.com/facebook/jscodeshift/blob/main/src/collections/VariableDeclarator.js))** Finds variable declarators by name. **Parameters**: `name` (String): The name of the variable to find. **Example**: ```jsx const variableDeclarators = j.findVariableDeclarators('a'); ``` Below are the APIs that are often used in the second phase, which is the **detailed node filtering** ([source](https://github.com/facebook/jscodeshift/blob/4851fc8a01036868efb4cf9676f3e97836097376/src/Collection.js)). The input of this phase is usually a collection of nodes, and the output, is specific nodes to transform. ### **`filter`** Filters nodes based on a predicate function. **Parameters**: `predicate` (Function): A function to test each element. **Example**: ```jsx const constDeclarations = j.find(j.VariableDeclaration) .filter(path => path.node.kind === 'const'); ``` ### **`forEach`** Iterates over each node in the collection. **Parameters**: `callback` (Function): A function to call for each node. **Example**: ```jsx j.find(j.VariableDeclaration).forEach(path => { console.log(path.node); }); ``` ### `some` `some` checks if at least one element in the collection passes the test implemented by the provided function. **Parameters:** `callback`: A function that tests each element. The callback function takes three arguments: - `path`: The current element being processed. - `index`: The index of the current element. - `array`: The array `some` was called upon. **Example:** ```jsx const j = require('jscodeshift'); const root = j(`const a = 1; const b = 2; const c = 3;`); const hasVariableA = root.find(j.VariableDeclarator).some(path => path.node.id.name === 'a'); console.log(hasVariableA); // true ``` ### `every` `every` checks if all elements in the collection pass the test implemented by the provided function. **Parameters:** `callback`: A function that tests each element. The callback function takes three arguments: - `path`: The current element being processed. - `index`: The index of the current element. - `array`: The array `every` was called upon. **Example:** ```jsx const j = require('jscodeshift'); const root = j(`const a = 1; const b = 2; const c = 3;`); const allAreConst = root.find(j.VariableDeclaration).every(path => path.node.kind === 'const'); console.log(allAreConst); // true ``` ### **`map`** Maps each node in the collection to a new value. **Parameters**:`callback` (Function): A function to call for each node. **Example**: ```jsx const variableNames = j.find(j.VariableDeclaration) .map(path => path.node.declarations.map(decl => decl.id.name)); ``` ### **`size`** Returns the number of nodes in the collection. **Example**: ```jsx const numberOfNodes = j.find(j.VariableDeclaration).size(); ``` ### `length` `length` returns the number of elements in the collection. **Example:** ```jsx const j = require('jscodeshift'); const root = j(`const a = 1; const b = 2; const c = 3;`); const varCount = root.find(j.VariableDeclarator).length; console.log(varCount); // 3 ``` ### **`nodes`** Returns the AST nodes in the collection. **Example**: ```jsx const nodes = j.find(j.VariableDeclaration).nodes(); ``` ### **`paths`** Returns the paths of the found nodes. **Example**: ```jsx const paths = j.find(j.VariableDeclaration).paths(); ``` ### `getAST` `getAST` returns the root AST node of the collection. **Example:** ```jsx const j = require('jscodeshift'); const root = j(`const a = 1;`); const ast = root.getAST(); console.log(ast.type); // File ``` ### **`get`** Gets the first node in the collection. **Example**: ```jsx const firstVariableDeclaration = j.find(j.VariableDeclaration).get(); ``` ### **`at`** Navigates to a specific path in the AST. **Parameters**: `index` (Number): The index of the path to navigate to. **Example**: ```jsx const secondVariableDeclaration = j.find(j.VariableDeclaration).at(1); ``` ### `getTypes` `getTypes` returns the set of node types present in the collection. **Example:** ```jsx const j = require('jscodeshift'); const root = j(`const a = 1; const b = 2;`); const types = root.find(j.VariableDeclarator).getTypes(); console.log(types); // Set { 'VariableDeclarator' } ``` ### `isOfType` `isOfType` checks if the node in the collection is of a specific type. **Parameters:** `type`: The type to check against. **Example:** ```jsx const j = require('jscodeshift'); const root = j(`const a = 1;`); const isVariableDeclarator = root.find(j.VariableDeclarator).at(0).isOfType('VariableDeclarator'); console.log(isVariableDeclarator); // true ``` ## Node Transformation APIs Below are the APIs used in node transformations. ([source](https://github.com/facebook/jscodeshift/blob/4851fc8a01036868efb4cf9676f3e97836097376/src/collections/Node.js#L139)) ### **`replaceWith`** Replaces the current node(s) with a new node. **Parameters**: `newNode` (Node or Function): The new node or a function that returns a new node. **Example**: ```jsx j.find(j.Identifier) .replaceWith(path => j.identifier(path.node.name.toUpperCase())); ``` ### **`insertBefore`** Inserts a node before the current node. **Parameters**: `newNode` (Node): The node to insert. **Example**: ```jsx j.find(j.FunctionDeclaration) .insertBefore(j.expressionStatement(j.stringLiteral('Inserted before'))); ``` ### **`insertAfter`** Inserts a node after the current node. **Parameters**: `newNode` (Node): The node to insert. **Example**: ```jsx j.find(j.FunctionDeclaration) .insertAfter(j.expressionStatement(j.stringLiteral('Inserted after'))); ``` ### **`remove`** Removes the current node(s). **Example**: ```jsx j.find(j.VariableDeclaration).remove(); ``` ### `renameTo` ([source](https://github.com/facebook/jscodeshift/blob/main/src/collections/VariableDeclarator.js)) `renameTo` renames the nodes in the collection to a new name. **Parameters:** `newName`: The new name to rename to. **Example:** ```jsx const j = require('jscodeshift'); const root = j(`const a = 1; const b = 2;`); root.find(j.Identifier, { name: 'a' }).renameTo('x'); console.log(root.toSource()); // const x = 1; const b = 2; ``` These descriptions and examples should give you a clear understanding of how to use each of these jscodeshift APIs. ### **`toSource`** Converts the transformed AST back to source code. **Parameters**: `options` (Object): Optional formatting options. **Example**: ```jsx const transformedSource = j.toSource({ quote: 'single' }); ``` ================================================ FILE: website/src/content/docs/build/ast-grammar.mdx ================================================ --- title: AST Grammar --- jscodeshift provides 278 node types which are mapped to their corresponding node type in `ast-types`. This is a comprehensive list of each node type used in `jscodeshift`. For an easier approach to identifying the AST node type in a piece of code, please refer to [AST Explorer](https://astexplorer.net/). ### AnyTypeAnnotation A type annotation representing any type. ```typescript export interface AnyTypeAnnotation extends Omit { type: "AnyTypeAnnotation"; } ``` ### ArrayExpression Represents an array literal. ```typescript export interface ArrayExpression extends Omit { type: "ArrayExpression"; elements: (K.ExpressionKind | K.SpreadElementKind | K.RestElementKind | null)[]; } ``` ### ArrayPattern A pattern that matches an array from a destructuring assignment. ```typescript export interface ArrayPattern extends Omit { type: "ArrayPattern"; elements: (K.PatternKind | K.SpreadElementKind | null)[]; } ``` ### ArrayTypeAnnotation A type annotation for arrays. ```typescript export interface ArrayTypeAnnotation extends Omit { type: "ArrayTypeAnnotation"; elementType: K.FlowTypeKind; } ``` ### ArrowFunctionExpression An arrow function expression. ```typescript export interface ArrowFunctionExpression extends Omit, Omit { type: "ArrowFunctionExpression"; id?: null; body: K.BlockStatementKind | K.ExpressionKind; generator?: false; } ``` ### AssignmentExpression Represents an assignment expression. ```typescript export interface AssignmentExpression extends Omit { type: "AssignmentExpression"; operator: "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "<<=" | ">>=" | ">>>=" | "|=" | "^=" | "&=" | "**=" | "||=" | "&&=" | "??="; left: K.PatternKind | K.MemberExpressionKind; right: K.ExpressionKind; } ``` ### AssignmentPattern A pattern that matches an assignment from a destructuring assignment. ```typescript export interface AssignmentPattern extends Omit { type: "AssignmentPattern"; left: K.PatternKind; right: K.ExpressionKind; } ``` ### AwaitExpression Represents an await expression. ```typescript export interface AwaitExpression extends Omit { type: "AwaitExpression"; argument: K.ExpressionKind | null; all?: boolean; } ``` ### BigIntLiteral A literal representing a big integer. ```typescript export interface BigIntLiteral extends Omit { type: "BigIntLiteral"; value: string | number; extra?: { rawValue: string; raw: string; }; } ``` ### BigIntLiteralTypeAnnotation A type annotation for big integer literals. ```typescript export interface BigIntLiteralTypeAnnotation extends Omit { type: "BigIntLiteralTypeAnnotation"; value: null; raw: string; } ``` ### BigIntTypeAnnotation A type annotation for big integers. ```typescript export interface BigIntTypeAnnotation extends Omit { type: "BigIntTypeAnnotation"; } ``` ### BinaryExpression Represents a binary expression. ```typescript export interface BinaryExpression extends Omit { type: "BinaryExpression"; operator: "==" | "!=" | "===" | "!==" | "<" | "<=" | ">" | ">=" | "<<" | ">>" | ">>>" | "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "in" | "instanceof" | "**"; left: K.ExpressionKind; right: K.ExpressionKind; } ``` ### BindExpression Represents a bind expression. ```typescript export interface BindExpression extends Omit { type: "BindExpression"; object: K.ExpressionKind | null; callee: K.ExpressionKind; } ``` ### Block A comment block. ```typescript export interface Block extends Comment { type: "Block"; } ``` ### BlockStatement Represents a block statement. ```typescript export interface BlockStatement extends Omit { type: "BlockStatement"; body: K.StatementKind[]; directives?: K.DirectiveKind[]; } ``` ### BooleanLiteral A literal representing a boolean value. ```typescript export interface BooleanLiteral extends Omit { type: "BooleanLiteral"; value: boolean; } ``` ### BooleanLiteralTypeAnnotation A type annotation for boolean literals. ```typescript export interface BooleanLiteralTypeAnnotation extends Omit { type: "BooleanLiteralTypeAnnotation"; value: boolean; raw: string; } ``` ### BooleanTypeAnnotation A type annotation for boolean values. ```typescript export interface BooleanTypeAnnotation extends Omit { type: "BooleanTypeAnnotation"; } ``` ### BreakStatement Represents a break statement. ```typescript export interface BreakStatement extends Omit { type: "BreakStatement"; label?: K.IdentifierKind | null; } ``` ### CallExpression Represents a call expression. ```typescript export interface CallExpression extends Omit, Omit { type: "CallExpression"; callee: K.ExpressionKind; arguments: (K.ExpressionKind | K.SpreadElementKind)[]; typeArguments?: null | K.TypeParameterInstantiationKind; } ``` ### CatchClause Represents a catch clause in a try statement. ```typescript export interface CatchClause extends Omit { type: "CatchClause"; param?: K.PatternKind | null; guard?: K.ExpressionKind | null; body: K.BlockStatementKind; } ``` ### ChainElement An element of a chain expression. ```typescript export interface ChainElement extends Node { optional?: boolean; } ``` ### ChainExpression Represents a chain expression. ```typescript export interface ChainExpression extends Omit { type: "ChainExpression"; expression: K.ChainElementKind; } ``` ### ClassBody Represents the body of a class, which contains method definitions. ```typescript export interface ClassBody extends Omit { type: "ClassBody"; body: (K.MethodDefinitionKind | K.VariableDeclaratorKind | K.ClassPropertyDefinitionKind | K.ClassPropertyKind | K.ClassPrivatePropertyKind | K.ClassAccessorPropertyKind | K.ClassMethodKind | K.ClassPrivateMethodKind | K.StaticBlockKind | K.TSDeclareMethodKind | K.TSCallSignatureDeclarationKind | K.TSConstructSignatureDeclarationKind | K.TSIndexSignatureKind | K.TSMethodSignatureKind | K.TSPropertySignatureKind)[]; } ``` ### ClassDeclaration Represents a class declaration. ```typescript export interface ClassDeclaration extends Omit { type: "ClassDeclaration"; id: K.IdentifierKind | null; body: K.ClassBodyKind; superClass?: K.ExpressionKind | null; typeParameters?: K.TypeParameterDeclarationKind | K.TSTypeParameterDeclarationKind | null; superTypeParameters?: K.TypeParameterInstantiationKind | K.TSTypeParameterInstantiationKind | null; implements?: K.ClassImplementsKind[] | K.TSExpressionWithTypeArgumentsKind[]; } ``` ### ClassExpression Represents a class expression. ```typescript export interface ClassExpression extends Omit { type: "ClassExpression"; id?: K.IdentifierKind | null; body: K.ClassBodyKind; superClass?: K.ExpressionKind | null; typeParameters?: K.TypeParameterDeclarationKind | K.TSTypeParameterDeclarationKind | null; superTypeParameters?: K.TypeParameterInstantiationKind | K.TSTypeParameterInstantiationKind | null; implements?: K.ClassImplementsKind[] | K.TSExpressionWithTypeArgumentsKind[]; } ``` ### ClassImplements Represents an implementation of a class. ```typescript export interface ClassImplements extends Omit { type: "ClassImplements"; id: K.IdentifierKind; superClass?: K.ExpressionKind | null; typeParameters?: K.TypeParameterInstantiationKind | null; } ``` ### ClassMethod Represents a method of a class. ```typescript export interface ClassMethod extends Omit, Omit { type: "ClassMethod"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; kind?: "get" | "set" | "method" | "constructor"; body: K.BlockStatementKind; access?: "public" | "private" | "protected" | null; computed?: boolean; static?: boolean; abstract?: boolean; accessibility?: "public" | "private" | "protected" | null; decorators?: K.DecoratorKind[] | null; definite?: boolean; optional?: boolean; override?: boolean; readonly?: boolean; } ``` ### ClassPrivateMethod Represents a private method of a class. ```typescript export interface ClassPrivateMethod extends Omit, Omit { type: "ClassPrivateMethod"; key: K.PrivateNameKind; body: K.BlockStatementKind; access?: "public" | "private" | "protected" | null; computed?: boolean; static?: boolean; decorators?: K.DecoratorKind[] | null; } ``` ### ClassPrivateProperty Represents a private property of a class. ```typescript export interface ClassPrivateProperty extends Omit { type: "ClassPrivateProperty"; key: K.PrivateNameKind; value?: K.ExpressionKind | null; access?: "public" | "private" | "protected" | null; computed?: boolean; static?: boolean; decorators?: K.DecoratorKind[] | null; optional?: boolean; override?: boolean; readonly?: boolean; variance?: K.VarianceKind | "plus" | "minus" | null; definite?: boolean; } ``` ### ClassProperty Represents a property of a class. ```typescript export interface ClassProperty extends Omit { type: "ClassProperty"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; value?: K.ExpressionKind | null; access?: "public" | "private" | "protected" | null; computed?: boolean; static?: boolean; decorators?: K.DecoratorKind[] | null; optional?: boolean; override?: boolean; readonly?: boolean; variance?: K.VarianceKind | "plus" | "minus" | null; definite?: boolean; } ``` ### ClassPropertyDefinition Represents a property definition in a class. ```typescript export interface ClassPropertyDefinition extends Omit { type: "ClassPropertyDefinition"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; value?: K.ExpressionKind | null; access?: "public" | "private" | "protected" | null; computed?: boolean; static?: boolean; decorators?: K.DecoratorKind[] | null; optional?: boolean; override?: boolean; readonly?: boolean; variance?: K.VarianceKind | "plus" | "minus" | null; definite?: boolean; } ``` ### Comment Represents a comment in the code. ```typescript export interface Comment extends Printable { type: "Comment"; value: string; } ``` ### CommentBlock Represents a block comment. ```typescript export interface CommentBlock extends Comment { type: "Block"; } ``` ### CommentLine Represents a line comment. ```typescript export interface CommentLine extends Comment { type: "Line"; } ``` ### ComprehensionBlock Represents a comprehension block. ```typescript export interface ComprehensionBlock extends Omit { type: "ComprehensionBlock"; left: K.PatternKind; right: K.ExpressionKind; each: boolean; } ``` ### ComprehensionExpression Represents a comprehension expression. ```typescript export interface ComprehensionExpression extends Omit { type: "ComprehensionExpression"; body: K.ExpressionKind; blocks: K.ComprehensionBlockKind[]; filter?: K.ExpressionKind | null; } ``` ### ConditionalExpression Represents a conditional expression (ternary). ```typescript export interface ConditionalExpression extends Omit { type: "ConditionalExpression"; test: K.ExpressionKind; consequent: K.ExpressionKind; alternate: K.ExpressionKind; } ``` ### ContinueStatement Represents a continue statement. ```typescript export interface ContinueStatement extends Omit { type: "ContinueStatement"; label?: K.IdentifierKind | null; } ``` ### DebuggerStatement Represents a debugger statement. ```typescript export interface DebuggerStatement extends Omit { type: "DebuggerStatement"; } ``` ### Declaration Represents a declaration in the code. ```typescript export interface Declaration extends Statement { type: "Declaration"; } ``` ### DeclareClass Represents a Flow type declaration for a class. ```typescript export interface DeclareClass extends Omit { type: "DeclareClass"; id: K.IdentifierKind; typeParameters?: K.TypeParameterDeclarationKind | null; extends: K.InterfaceExtendsKind[]; body: K.ObjectTypeAnnotationKind; mixins?: K.InterfaceExtendsKind[] | null; implements?: K.ClassImplementsKind[] | K.TSExpressionWithTypeArgumentsKind[]; } ``` ### DeclaredPredicate Represents a declared predicate in Flow. ```typescript export interface DeclaredPredicate extends Omit { type: "DeclaredPredicate"; value: K.ExpressionKind; } ``` ### DeclareExportAllDeclaration Represents a Flow type declaration for exporting everything. ```typescript export interface DeclareExportAllDeclaration extends Omit { type: "DeclareExportAllDeclaration"; source?: K.LiteralKind | null; } ``` ### DeclareExportDeclaration Represents a Flow type declaration for exporting. ```typescript export interface DeclareExportDeclaration extends Omit { type: "DeclareExportDeclaration"; default: boolean; declaration?: K.DeclarationKind | K.ExpressionKind | null; specifiers?: K.ExportSpecifierKind[] | null; source?: K.LiteralKind | null; } ``` ### DeclareFunction Represents a Flow type declaration for a function. ```typescript export interface DeclareFunction extends Omit { type: "DeclareFunction"; id: K.IdentifierKind; } ``` ### DeclareInterface Represents a Flow type declaration for an interface. ```typescript export interface DeclareInterface extends Omit { type: "DeclareInterface"; id: K.IdentifierKind; typeParameters?: K.TypeParameterDeclarationKind | null; extends: K.InterfaceExtendsKind[]; body: K.ObjectTypeAnnotationKind; } ``` ### DeclareModule Represents a Flow type declaration for a module. ```typescript export interface DeclareModule extends Omit { type: "DeclareModule"; id: K.StringLiteralKind | K.IdentifierKind; body: K.BlockStatementKind; kind?: "commonjs" | "es" | null; } ``` ### DeclareModuleExports Represents a Flow type declaration for module exports. ```typescript export interface DeclareModuleExports extends Omit { type: "DeclareModuleExports"; typeAnnotation: K.TypeAnnotationKind; } ``` ### DeclareOpaqueType Represents a Flow type declaration for an opaque type. ```typescript export interface DeclareOpaqueType extends Omit { type: "DeclareOpaqueType"; id: K.IdentifierKind; typeParameters?: K.TypeParameterDeclarationKind | null; impltype: K.FlowTypeKind; supertype?: K.FlowTypeKind | null; } ``` ### DeclareTypeAlias Represents a Flow type declaration for a type alias. ```typescript export interface DeclareTypeAlias extends Omit { type: "DeclareTypeAlias"; id: K.IdentifierKind; typeParameters?: K.TypeParameterDeclarationKind | null; right: K.FlowTypeKind; } ``` ### DeclareVariable Represents a Flow type declaration for a variable. ```typescript export interface DeclareVariable extends Omit { type: "DeclareVariable"; id: K.IdentifierKind; } ``` ### Decorator Represents a decorator. ```typescript export interface Decorator extends Omit { type: "Decorator"; expression: K.ExpressionKind; } ``` ### Directive Represents a directive in a function or a script. ```typescript export interface Directive extends Node { type: "Directive"; value: K.DirectiveLiteralKind; } ``` ### DirectiveLiteral Represents the value of a directive. ```typescript export interface DirectiveLiteral extends Omit { type: "DirectiveLiteral"; value: string; } ``` ### DoExpression Represents a do expression. ```typescript export interface DoExpression extends Omit { type: "DoExpression"; body: K.BlockStatementKind; } ``` ### DoWhileStatement Represents a do...while statement. ```typescript export interface DoWhileStatement extends Omit { type: "DoWhileStatement"; test: K.ExpressionKind; body: K.StatementKind; } ``` ### EmptyStatement Represents an empty statement. ```typescript export interface EmptyStatement extends Omit { type: "EmptyStatement"; } ``` ### EmptyTypeAnnotation A type annotation for an empty type. ```typescript export interface EmptyTypeAnnotation extends Omit { type: "EmptyTypeAnnotation"; } ``` ### EnumBooleanBody Represents the body of a boolean enum. ```typescript export interface EnumBooleanBody extends Omit { type: "EnumBooleanBody"; members: K.EnumBooleanMemberKind[]; explicitType?: boolean; hasUnknownMembers?: boolean ; } ``` ### EnumBooleanMember Represents a member of a boolean enum. ```typescript export interface EnumBooleanMember extends Omit { type: "EnumBooleanMember"; id: K.IdentifierKind; init: K.BooleanLiteralKind; } ``` ### EnumDeclaration Represents an enum declaration. ```typescript export interface EnumDeclaration extends Omit { type: "EnumDeclaration"; id: K.IdentifierKind; body: K.EnumBooleanBodyKind | K.EnumNumberBodyKind | K.EnumStringBodyKind | K.EnumSymbolBodyKind; } ``` ### EnumDefaultedMember Represents a defaulted member of an enum. ```typescript export interface EnumDefaultedMember extends Omit { type: "EnumDefaultedMember"; id: K.IdentifierKind; } ``` ### EnumNumberBody Represents the body of a number enum. ```typescript export interface EnumNumberBody extends Omit { type: "EnumNumberBody"; members: K.EnumNumberMemberKind[]; explicitType?: boolean; hasUnknownMembers?: boolean; } ``` ### EnumNumberMember Represents a member of a number enum. ```typescript export interface EnumNumberMember extends Omit { type: "EnumNumberMember"; id: K.IdentifierKind; init: K.NumericLiteralKind; } ``` ### EnumStringBody Represents the body of a string enum. ```typescript export interface EnumStringBody extends Omit { type: "EnumStringBody"; members: K.EnumStringMemberKind[]; explicitType?: boolean; hasUnknownMembers?: boolean; } ``` ### EnumStringMember Represents a member of a string enum. ```typescript export interface EnumStringMember extends Omit { type: "EnumStringMember"; id: K.IdentifierKind; init?: K.StringLiteralKind; } ``` ### EnumSymbolBody Represents the body of a symbol enum. ```typescript export interface EnumSymbolBody extends Omit { type: "EnumSymbolBody"; members: K.EnumDefaultedMemberKind[]; hasUnknownMembers?: boolean; } ``` ### ExistentialTypeParam Represents an existential type parameter in Flow. ```typescript export interface ExistentialTypeParam extends Omit { type: "ExistentialTypeParam"; } ``` ### ExistsTypeAnnotation A type annotation for an existential type. ```typescript export interface ExistsTypeAnnotation extends Omit { type: "ExistsTypeAnnotation"; } ``` ### ExportAllDeclaration Represents an export all declaration. ```typescript export interface ExportAllDeclaration extends Omit { type: "ExportAllDeclaration"; source: K.LiteralKind; exportKind?: "type" | "value" | null; } ``` ### ExportBatchSpecifier Represents a batch export specifier. ```typescript export interface ExportBatchSpecifier extends Omit { type: "ExportBatchSpecifier"; } ``` ### ExportDeclaration Represents an export declaration. ```typescript export interface ExportDeclaration extends Omit { type: "ExportDeclaration"; default: boolean; declaration?: K.DeclarationKind | K.ExpressionKind | null; specifiers?: K.ExportSpecifierKind[] | null; source?: K.LiteralKind | null; } ``` ### ExportDefaultDeclaration Represents an export default declaration. ```typescript export interface ExportDefaultDeclaration extends Omit { type: "ExportDefaultDeclaration"; declaration: K.DeclarationKind | K.ExpressionKind; } ``` ### ExportDefaultSpecifier Represents an export default specifier. ```typescript export interface ExportDefaultSpecifier extends Omit { type: "ExportDefaultSpecifier"; exported: K.IdentifierKind; } ``` ### ExportNamedDeclaration Represents a named export declaration. ```typescript export interface ExportNamedDeclaration extends Omit { type: "ExportNamedDeclaration"; declaration?: K.DeclarationKind | null; specifiers: K.ExportSpecifierKind[]; source?: K.LiteralKind | null; exportKind?: "type" | "value" | null; } ``` ### ExportNamespaceSpecifier Represents an export namespace specifier. ```typescript export interface ExportNamespaceSpecifier extends Omit { type: "ExportNamespaceSpecifier"; exported: K.IdentifierKind; } ``` ### ExportSpecifier Represents an export specifier. ```typescript export interface ExportSpecifier extends Omit { type: "ExportSpecifier"; exported: K.IdentifierKind; local: K.IdentifierKind; } ``` ### Expression Represents an expression in the code. ```typescript export interface Expression extends Node { type: "Expression"; } ``` ### ExpressionStatement Represents an expression statement. ```typescript export interface ExpressionStatement extends Omit { type: "ExpressionStatement"; expression: K.ExpressionKind; directive?: string; } ``` ### File Represents a file in the AST. ```typescript export interface File extends Omit { type: "File"; program: K.ProgramKind; comments?: K.CommentKind[] | null; tokens?: any[] | null; } ``` ### Flow Represents a Flow type. ```typescript export interface Flow extends Node { type: "Flow"; } ``` ### FlowPredicate Represents a Flow predicate. ```typescript export interface FlowPredicate extends Omit { type: "FlowPredicate"; } ``` ### FlowType Represents a Flow type. ```typescript export interface FlowType extends Flow { type: "FlowType"; } ``` ### ForAwaitStatement Represents a for-await statement. ```typescript export interface ForAwaitStatement extends Omit { type: "ForAwaitStatement"; left: K.VariableDeclarationKind | K.ExpressionKind; right: K.ExpressionKind; body: K.StatementKind; } ``` ### ForInStatement Represents a for-in statement. ```typescript export interface ForInStatement extends Omit { type: "ForInStatement"; left: K.VariableDeclarationKind | K.ExpressionKind; right: K.ExpressionKind; body: K.StatementKind; } ``` ### ForOfStatement Represents a for-of statement. ```typescript export interface ForOfStatement extends Omit { type: "ForOfStatement"; left: K.VariableDeclarationKind | K.ExpressionKind; right: K.ExpressionKind; body: K.StatementKind; } ``` ### ForStatement Represents a for statement. ```typescript export interface ForStatement extends Omit { type: "ForStatement"; init?: K.VariableDeclarationKind | K.ExpressionKind | null; test?: K.ExpressionKind | null; update?: K.ExpressionKind | null; body: K.StatementKind; } ``` ### Function Represents a function in the code. ```typescript export interface Function extends Node { type: "Function"; id?: K.IdentifierKind | null; params: (K.PatternKind | K.TSParameterPropertyKind)[]; body: K.BlockStatementKind; generator?: boolean; async?: boolean; expression?: boolean; returnType?: K.TypeAnnotationKind | K.TSTypeAnnotationKind | K.NoopKind | null; typeParameters?: K.TypeParameterDeclarationKind | K.TSTypeParameterDeclarationKind | null; } ``` ### FunctionDeclaration Represents a function declaration. ```typescript export interface FunctionDeclaration extends Omit, Omit { type: "FunctionDeclaration"; body: K.BlockStatementKind; declare?: boolean; } ``` ### FunctionExpression Represents a function expression. ```typescript export interface FunctionExpression extends Omit, Omit { type: "FunctionExpression"; } ``` ### FunctionTypeAnnotation A type annotation for a function. ```typescript export interface FunctionTypeAnnotation extends Omit { type: "FunctionTypeAnnotation"; params: K.FunctionTypeParamKind[]; returnType: K.FlowTypeKind; rest?: K.FunctionTypeParamKind | null; typeParameters?: K.TypeParameterDeclarationKind | null; } ``` ### FunctionTypeParam Represents a parameter in a function type annotation. ```typescript export interface FunctionTypeParam extends Omit { type: "FunctionTypeParam"; name: K.IdentifierKind | null; typeAnnotation: K.FlowTypeKind; optional?: boolean; } ``` ### GeneratorExpression Represents a generator expression. ```typescript export interface GeneratorExpression extends Omit { type: "GeneratorExpression"; } ``` ### GenericTypeAnnotation A type annotation for a generic type. ```typescript export interface GenericTypeAnnotation extends Omit { type: "GenericTypeAnnotation"; id: K.IdentifierKind | K.QualifiedTypeIdentifierKind; typeParameters?: K.TypeParameterInstantiationKind | null; } ``` ### Identifier Represents an identifier. ```typescript export interface Identifier extends Omit, Omit { type: "Identifier"; name: string; optional?: boolean; typeAnnotation?: K.TypeAnnotationKind | K.TSTypeAnnotationKind | null; decorators?: K.DecoratorKind[] | null; } ``` ### IfStatement Represents an if statement. ```typescript export interface IfStatement extends Omit { type: "IfStatement"; test: K.ExpressionKind; consequent: K.StatementKind; alternate?: K.StatementKind | null; } ``` ### Import Represents an import expression. ```typescript export interface Import extends Omit { type: "Import"; } ``` ### ImportDeclaration Represents an import declaration. ```typescript export interface ImportDeclaration extends Omit { type: "ImportDeclaration"; specifiers: (K.ImportSpecifierKind | K.ImportNamespaceSpecifierKind | K.ImportDefaultSpecifierKind)[]; source: K.LiteralKind; importKind?: "type" | "typeof" | "value" | null; } ``` ### ImportDefaultSpecifier Represents a default import specifier. ```typescript export interface ImportDefaultSpecifier extends Omit { type: "ImportDefaultSpecifier"; local: K.IdentifierKind; } ``` ### ImportExpression Represents an import expression. ```typescript export interface ImportExpression extends Omit { type: "ImportExpression"; source: K.LiteralKind; } ``` ### ImportNamespaceSpecifier Represents a namespace import specifier. ```typescript export interface ImportNamespaceSpecifier extends Omit { type: "ImportNamespaceSpecifier"; local: K.IdentifierKind; } ``` ### ImportSpecifier Represents an import specifier. ```typescript export interface ImportSpecifier extends Omit { type: "ImportSpecifier"; local: K.IdentifierKind; imported: K.IdentifierKind; importKind?: "type" | "typeof" | "value" | null; } ``` ### InferredPredicate Represents an inferred predicate in Flow. ```typescript export interface InferredPredicate extends Omit { type: "InferredPredicate"; } ``` ### InterfaceDeclaration Represents an interface declaration. ```typescript export interface InterfaceDeclaration extends Omit { type: "InterfaceDeclaration"; id: K.IdentifierKind; typeParameters?: K.TypeParameterDeclarationKind | null; extends: K.InterfaceExtendsKind[]; body: K.ObjectTypeAnnotationKind; } ``` ### InterfaceExtends Represents an extension of an interface. ```typescript export interface InterfaceExtends extends Omit { type: "InterfaceExtends"; id: K.IdentifierKind | K.QualifiedTypeIdentifierKind; typeParameters?: K.TypeParameterInstantiationKind | null; } ``` ### InterfaceTypeAnnotation A type annotation for an interface. ```typescript export interface InterfaceTypeAnnotation extends Omit { type: "InterfaceTypeAnnotation"; body: K.ObjectTypeAnnotationKind; extends?: K.InterfaceExtendsKind[] | null; } ``` ### InterpreterDirective Represents an interpreter directive at the top of a script. ```typescript export interface InterpreterDirective extends Omit { type: "InterpreterDirective"; value: string; } ``` ### IntersectionTypeAnnotation A type annotation for an intersection type. ```typescript export interface IntersectionTypeAnnotation extends Omit { type: "IntersectionTypeAnnotation"; types: K.FlowTypeKind[]; } ``` ### JSXAttribute Represents an attribute in a JSX element. ```typescript export interface JSXAttribute extends Omit { type: "JSXAttribute"; name: K.JSXIdentifierKind | K.JSXNamespacedNameKind; value?: K.LiteralKind | K.JSXExpressionContainerKind | null; } ``` ### JSXClosingElement Represents a closing element in JSX. ```typescript export interface JSXClosingElement extends Omit { type: "JSXClosingElement"; name: K.JSXIdentifierKind | K.JSXMemberExpressionKind | K.JSXNamespacedNameKind; } ``` ### JSXClosingFragment Represents a closing fragment in JSX. ```typescript export interface JSXClosingFragment extends Omit { type: "JSXClosingFragment"; } ``` ### JSXElement Represents a JSX element. ```typescript export interface JSXElement extends Omit { type: "JSXElement"; openingElement: K.JSXOpeningElementKind; closingElement?: K.JSXClosingElementKind | null; children: K.JSXElementKind[]; selfClosing?: boolean; } ``` ### JSXEmptyExpression Represents an empty expression in JSX. ```typescript export interface JSXEmptyExpression extends Omit { type: "JSXEmptyExpression"; } ``` ### JSXExpressionContainer Represents an expression container in JSX. ```typescript export interface JSXExpressionContainer extends Omit { type: "JSXExpressionContainer"; expression: K.ExpressionKind | K.JSXEmptyExpressionKind; } ``` ### JSXFragment Represents a JSX fragment. ```typescript export interface JSXFragment extends Omit { type: "JSXFragment"; openingFragment: K.JSXOpeningFragmentKind; closingFragment: K.JSXClosingFragmentKind; children: K.JSXElementKind[]; } ``` ### JSXIdentifier Represents an identifier in JSX. ```typescript export interface JSXIdentifier extends Omit { type: "JSXIdentifier"; name: string; } ``` ### JSXMemberExpression Represents a member expression in JSX. ```typescript export interface JSXMemberExpression extends Omit { type: "JSXMemberExpression"; object: K.JSXIdentifierKind | K.JSXMemberExpressionKind; property: K.JSXIdentifierKind; } ``` ### JSXNamespacedName Represents a namespaced name in JSX. ```typescript export interface JSXNamespacedName extends Omit { type: "JSXNamespacedName"; namespace: K.JSXIdentifierKind; name: K.JSXIdentifierKind; } ``` ### JSXOpeningElement Represents an opening element in JSX. ```typescript export interface JSXOpeningElement extends Omit { type: "JSXOpeningElement"; name: K.JSXIdentifierKind | K.JSXMemberExpressionKind | K.JSXNamespacedNameKind; attributes: (K.JSXAttributeKind | K.JSXSpreadAttributeKind)[]; selfClosing: boolean; } ``` ### JSXOpeningFragment Represents an opening fragment in JSX. ```typescript export interface JSXOpeningFragment extends Omit { type: "JSXOpeningFragment"; } ``` ### JSXSpreadAttribute Represents a spread attribute in JSX. ```typescript export interface JSXSpreadAttribute extends Omit { type: "JSXSpreadAttribute"; argument: K.ExpressionKind; } ``` ### JSXSpreadChild Represents a spread child in JSX. ```typescript export interface JSXSpreadChild extends Omit { type: "JSXSpreadChild"; expression: K.ExpressionKind; } ``` ### JSXText Represents text in JSX. ```typescript export interface JSXText extends Omit { type: "JSXText"; value: string; raw: string; } ``` ### LabeledStatement Represents a labeled statement. ```typescript export interface LabeledStatement extends Omit { type: "LabeledStatement"; label: K.IdentifierKind; body: K.StatementKind; } ``` ### Line Represents a line comment. ```typescript export interface Line extends Comment { type: "Line"; } ``` ### Literal Represents a literal value. ```typescript export interface Literal extends Expression, Pattern { type: "Literal"; value: boolean | number | string | RegExp | null; regex?: { pattern: string; flags: string }; raw?: string; bigint?: string; } ``` ### LogicalExpression Represents a logical expression. ```typescript export interface LogicalExpression extends Omit { type: "LogicalExpression"; operator: "||" | "&&" | "??"; left: K.ExpressionKind; right: K.ExpressionKind; } ``` ### MemberExpression Represents a member expression. ```typescript export interface MemberExpression extends Omit, Omit { type: "MemberExpression"; object: K.ExpressionKind | K.SuperKind; property: K.IdentifierKind | K.ExpressionKind; computed: boolean; } ``` ### MemberTypeAnnotation A type annotation for a member type. ```typescript export interface MemberTypeAnnotation extends Omit { type: "MemberTypeAnnotation"; object: K.IdentifierKind; property: K.IdentifierKind; } ``` ### MetaProperty Represents a meta property. ```typescript export interface MetaProperty extends Omit { type: "MetaProperty"; meta: K.IdentifierKind; property: K.IdentifierKind; } ``` ### MethodDefinition Represents a method definition. ```typescript export interface MethodDefinition extends Omit { type: "MethodDefinition"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; value: K.FunctionExpressionKind; kind?: "get" | "set" | "method" | "constructor"; access?: "public" | "private" | "protected" | null; computed?: boolean; static?: boolean; decorators?: K.DecoratorKind[] | null; } ``` ### MixedTypeAnnotation A type annotation for a mixed type. ```typescript export interface MixedTypeAnnotation extends Omit { type: "MixedTypeAnnotation"; } ``` ### ModuleSpecifier Represents a module specifier. ```typescript export interface ModuleSpecifier extends Node { type: "ModuleSpecifier"; } ``` ### NewExpression Represents a new expression. ```typescript export interface NewExpression extends Omit { type: "NewExpression"; callee: K.ExpressionKind; arguments: (K.ExpressionKind | K.SpreadElementKind)[]; typeArguments?: null | K.TypeParameterInstantiationKind; } ``` ### Node Represents a generic AST node. ```typescript export interface Node { type: string; loc?: K.SourceLocationKind | null; comments?: (K.CommentBlockKind | K.CommentLineKind)[] | null; } ``` ### Noop Represents a no-op (no operation) statement. ```typescript export interface Noop extends Omit { type: "Noop"; } ``` ### NullableTypeAnnotation A type annotation for a nullable type. ```typescript export interface NullableTypeAnnotation extends Omit { type: "NullableTypeAnnotation"; typeAnnotation: K.FlowTypeKind; } ``` ### NullLiteral Represents a null literal. ```typescript export interface NullLiteral extends Omit { type: "NullLiteral"; value: null; } ``` ### NullLiteralTypeAnnotation A type annotation for null literals. ```typescript export interface NullLiteralTypeAnnotation extends Omit { type: "NullLiteralTypeAnnotation"; value: null; raw: string; } ``` ### NullTypeAnnotation A type annotation for null types. ```typescript export interface NullTypeAnnotation extends Omit { type: "NullTypeAnnotation"; } ``` ### NumberLiteralTypeAnnotation A type annotation for number literals. ```typescript export interface NumberLiteralTypeAnnotation extends Omit { type: "NumberLiteralTypeAnnotation"; value: number; raw: string; } ``` ### NumberTypeAnnotation A type annotation for number types. ```typescript export interface NumberTypeAnnotation extends Omit { type: "NumberTypeAnnotation"; } ``` ### NumericLiteral Represents a numeric literal. ```typescript export interface NumericLiteral extends Omit { type: "NumericLiteral"; value: number; raw?: string; } ``` ### NumericLiteralTypeAnnotation A type annotation for numeric literals. ```typescript export interface NumericLiteralTypeAnnotation extends Omit { type: "NumericLiteralTypeAnnotation"; value: number; raw: string; } ``` ### ObjectExpression Represents an object expression. ```typescript export interface ObjectExpression extends Omit { type: "ObjectExpression"; properties: (K.PropertyKind | K.ObjectMethodKind | K.SpreadElementKind | K.RestElementKind)[]; } ``` ### ObjectMethod Represents a method in an object. ```typescript export interface ObjectMethod extends Omit, Omit { type: "ObjectMethod"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; kind?: "method" | "get" | "set"; body: K.BlockStatementKind; computed?: boolean; static?: boolean; shorthand?: boolean; decorators?: K.DecoratorKind[] | null; access?: "public" | "private" | "protected" | null; optional?: boolean; readonly?: boolean; definite?: boolean; } ``` ### ObjectPattern Represents an object pattern for destructuring. ```typescript export interface ObjectPattern extends Omit { type: "ObjectPattern"; properties: (K.PropertyKind | K.RestElementKind)[]; decorators?: K.DecoratorKind[] | null; } ``` ### ObjectProperty Represents a property in an object. ```typescript export interface ObjectProperty extends Omit { type: "ObjectProperty"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; value: K.ExpressionKind; computed?: boolean; shorthand?: boolean; decorators?: K.DecoratorKind[] | null; access?: "public" | "private" | "protected" | null; optional?: boolean; readonly?: boolean; definite?: boolean; } ``` ### ObjectTypeAnnotation A type annotation for object types. ```typescript export interface ObjectTypeAnnotation extends Omit { type: "ObjectTypeAnnotation"; properties: K.ObjectTypePropertyKind[]; indexers?: K.ObjectTypeIndexerKind[] | null; callProperties?: K.ObjectTypeCallPropertyKind[] | null; internalSlots?: K.ObjectTypeInternalSlotKind[] | null; exact?: boolean; inexact?: boolean; } ``` ### ObjectTypeCallProperty Represents a call property in an object type annotation. ```typescript export interface ObjectTypeCallProperty extends Omit { type: "ObjectTypeCallProperty"; value: K.FunctionTypeAnnotationKind; static?: boolean; } ``` ### ObjectTypeIndexer Represents an indexer in an object type annotation. ```typescript export interface ObjectTypeIndexer extends Omit { type: "ObjectTypeIndexer"; id?: K.IdentifierKind | null; key: K.FlowTypeKind; value: K.FlowTypeKind; variance?: K.VarianceKind | "plus" | "minus" | null; static?: boolean; } ``` ### ObjectTypeInternalSlot Represents an internal slot in an object type annotation. ```typescript export interface ObjectTypeInternalSlot extends Omit { type: "ObjectTypeInternalSlot"; id: K.IdentifierKind; value: K.FlowTypeKind; optional?: boolean; static?: boolean; method?: boolean; } ``` ### ObjectTypeProperty Represents a property in an object type annotation. ```typescript export interface ObjectTypeProperty extends Omit { type: "ObjectTypeProperty"; key: K.LiteralKind | K.IdentifierKind; value: K.FlowTypeKind; optional?: boolean; variance?: K.VarianceKind | "plus" | "minus" | null; static?: boolean; } ``` ### ObjectTypeSpreadProperty Represents a spread property in an object type annotation. ```typescript export interface ObjectTypeSpreadProperty extends Omit { type: "ObjectTypeSpreadProperty"; argument: K.FlowTypeKind; } ``` ### OpaqueType Represents an opaque type. ```typescript export interface OpaqueType extends Omit { type: "OpaqueType"; id: K.IdentifierKind; typeParameters?: K.TypeParameterDeclarationKind | null; impltype: K.FlowTypeKind; supertype?: K.FlowTypeKind | null; } ``` ### OptionalCallExpression Represents an optional call expression. ```typescript export interface OptionalCallExpression extends Omit, Omit { type: "OptionalCallExpression"; callee: K.ExpressionKind; arguments: (K.ExpressionKind | K.SpreadElementKind)[]; optional?: boolean; typeArguments?: null | K.TypeParameterInstantiationKind; } ``` ### OptionalMemberExpression Represents an optional member expression. ```typescript export interface OptionalMemberExpression extends Omit, Omit { type: "OptionalMemberExpression"; object: K.ExpressionKind | K.SuperKind; property: K.IdentifierKind | K.ExpressionKind; computed?: boolean; optional?: boolean; } ``` ### ParenthesizedExpression Represents a parenthesized expression. ```typescript export interface ParenthesizedExpression extends Omit { type: "ParenthesizedExpression"; expression: K.ExpressionKind; } ``` ### Pattern Represents a pattern in the code. ```typescript export interface Pattern extends Node { type: "Pattern"; } ``` ### Position Represents a position in the source code. ```typescript export interface Position extends Omit { type: "Position"; line: number; column: number; } ``` ### Printable Represents a printable node. ```typescript export interface Printable extends Node { type: "Printable"; } ``` ### PrivateName Represents a private name. ```typescript export interface PrivateName extends Omit { type: "PrivateName"; id: K.IdentifierKind; } ``` ### Program Represents the entire program. ```typescript export interface Program extends Omit { type: "Program"; body: (K.StatementKind | K.ModuleDeclarationKind)[]; sourceType: "script" | "module"; directives?: K.DirectiveKind[] | null; } ``` ### Property Represents a property in an object. ```typescript export interface Property extends Omit { type: "Property"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; value: K.ExpressionKind; kind?: "init" | "get" | "set"; computed?: boolean; method?: boolean; shorthand?: boolean; decorators?: K.DecoratorKind[] | null; } ``` ### PropertyPattern Represents a pattern property in an object. ```typescript export interface PropertyPattern extends Omit { type: "PropertyPattern"; key: K.LiteralKind | K.IdentifierKind | K.ExpressionKind; pattern: K.PatternKind; } ``` ### QualifiedTypeIdentifier Represents a qualified type identifier in Flow. ```typescript export interface QualifiedTypeIdentifier extends Omit { type: "QualifiedTypeIdentifier"; qualification: K.IdentifierKind | K.QualifiedTypeIdentifierKind; id: K.IdentifierKind; } ``` ### RegExpLiteral Represents a regular expression literal. ```typescript export interface RegExpLiteral extends Omit { type: "RegExpLiteral"; value: RegExp; regex: { pattern: string; flags: string }; } ``` ### RestElement Represents a rest element in a destructuring assignment. ```typescript export interface RestElement extends Omit { type: "RestElement"; argument: K.PatternKind; decorators?: K.DecoratorKind[] | null; } ``` ### RestProperty Represents a rest property in an object pattern. ```typescript export interface RestProperty extends Omit { type: "RestProperty"; argument: K.PatternKind; } ``` ### ReturnStatement Represents a return statement. ```typescript export interface ReturnStatement extends Omit { type: "ReturnStatement"; argument?: K.ExpressionKind | null; } ``` ### SequenceExpression Represents a sequence expression. ```typescript export interface SequenceExpression extends Omit { type: "SequenceExpression"; expressions: K.ExpressionKind[]; } ``` ### SourceLocation Represents the source location of a node. ```typescript export interface SourceLocation extends Omit { type: "SourceLocation"; start: K.PositionKind; end: K.PositionKind; } ``` ### Specifier Represents a specifier in an import or export declaration. ```typescript export interface Specifier extends Node { type: "Specifier"; } ``` ### SpreadElement Represents a spread element in an array or function call. ```typescript export interface SpreadElement extends Omit { type: "SpreadElement"; argument: K.ExpressionKind; } ``` ### SpreadElementPattern Represents a spread element pattern in an array. ```typescript export interface SpreadElementPattern extends Omit { type: "SpreadElementPattern"; argument: K.PatternKind; } ``` ### SpreadProperty Represents a spread property in an object. ```typescript export interface SpreadProperty extends Omit { type: "SpreadProperty"; argument: K.PatternKind; } ``` ### SpreadPropertyPattern Represents a spread property pattern in an object. ```typescript export interface SpreadPropertyPattern extends Omit { type: "SpreadPropertyPattern"; argument: K.PatternKind; } ``` ### Statement Represents a statement in the code. ```typescript export interface Statement extends Node { type: "Statement"; } ``` ### StringLiteral Represents a string literal. ```typescript export interface StringLiteral extends Omit { type: "StringLiteral"; value: string; raw?: string; } ``` ### StringLiteralTypeAnnotation A type annotation for string literals. ```typescript export interface StringLiteralTypeAnnotation extends Omit { type: "StringLiteralTypeAnnotation"; value: string; raw: string; } ``` ### StringTypeAnnotation A type annotation for string types. ```typescript export interface StringTypeAnnotation extends Omit { type: "StringTypeAnnotation"; } ``` ### Super Represents the `super` keyword. ```typescript export interface Super extends Omit { type: "Super"; } ``` ### SwitchCase Represents a case in a switch statement. ```typescript export interface SwitchCase extends Omit { type: "SwitchCase"; test?: K.ExpressionKind | null; consequent: K.StatementKind[]; } ``` ### SwitchStatement Represents a switch statement. ```typescript export interface SwitchStatement extends Omit { type: "SwitchStatement"; discriminant: K.ExpressionKind; cases: K.SwitchCaseKind[]; } ``` ### SymbolTypeAnnotation A type annotation for symbol types. ```typescript export interface SymbolTypeAnnotation extends Omit { type: "SymbolTypeAnnotation"; } ``` ### TaggedTemplateExpression Represents a tagged template expression. ```typescript export interface TaggedTemplateExpression extends Omit { type: "TaggedTemplateExpression"; tag: K.ExpressionKind; quasi: K.TemplateLiteralKind; } ``` ### TemplateElement Represents an element in a template literal. ```typescript export interface TemplateElement extends Omit { type: "TemplateElement"; tail: boolean; value: { cooked: string; raw: string }; } ``` ### TemplateLiteral Represents a template literal. ```typescript export interface TemplateLiteral extends Omit { type: "TemplateLiteral"; quasis: K.TemplateElementKind[]; expressions: K.ExpressionKind[]; } ``` ### ThisExpression Represents the `this` expression. ```typescript export interface ThisExpression extends Omit { type: "ThisExpression"; } ``` ### ThisTypeAnnotation A type annotation for the `this` type. ```typescript export interface ThisTypeAnnotation extends Omit { type: "ThisTypeAnnotation"; } ``` ### ThrowStatement Represents a throw statement. ```typescript export interface ThrowStatement extends Omit { type: "ThrowStatement"; argument: K.ExpressionKind; } ``` ### TryStatement Represents a try statement. ```typescript export interface TryStatement extends Omit { type: "TryStatement"; block: K.BlockStatementKind; handler?: K.CatchClauseKind | null; finalizer?: K.BlockStatementKind | null; } ``` ### TSAnyKeyword Represents the TypeScript `any` keyword. ```typescript export interface TSAnyKeyword extends Omit { type: "TSAnyKeyword"; } ``` ### TSArrayType Represents a TypeScript array type. ```typescript export interface TSArrayType extends Omit { type: "TSArrayType"; elementType: K.TSTypeKind; } ``` ### TSAsExpression Represents a TypeScript as-expression. ```typescript export interface TSAsExpression extends Omit { type: "TSAsExpression"; expression: K.ExpressionKind; typeAnnotation: K.TSTypeKind; } ``` ### TSBigIntKeyword Represents the TypeScript `bigint` keyword. ```typescript export interface TSBigIntKeyword extends Omit { type: "TSBigIntKeyword"; } ``` ### TSBooleanKeyword Represents the TypeScript `boolean` keyword. ```typescript export interface TSBooleanKeyword extends Omit { type: "TSBooleanKeyword"; } ``` ### TSCallSignatureDeclaration Represents a TypeScript call signature declaration. ```typescript export interface TSCallSignatureDeclaration extends Omit { type: "TSCallSignatureDeclaration"; parameters: (K.IdentifierKind | K.RestElementKind)[]; typeAnnotation?: K.TSTypeAnnotationKind | null; typeParameters?: K.TSTypeParameterDeclarationKind | null; } ``` ### TSConditionalType Represents a TypeScript conditional type. ```typescript export interface TSConditionalType extends Omit { type: "TSConditionalType"; checkType: K.TSTypeKind; extendsType: K.TSTypeKind; trueType: K.TSTypeKind; falseType: K.TSTypeKind; } ``` ### TSConstructorType Represents a TypeScript constructor type. ``` typescript export interface TSConstructorType extends Omit { type: "TSConstructorType"; parameters: (K.IdentifierKind | K.RestElementKind)[]; typeAnnotation: K.TSTypeAnnotationKind; typeParameters?: K.TSTypeParameterDeclarationKind | null; } ``` ### TSConstructSignatureDeclaration Represents a TypeScript construct signature declaration. ```typescript export interface TSConstructSignatureDeclaration extends Omit { type: "TSConstructSignatureDeclaration"; parameters: (K.IdentifierKind | K.RestElementKind)[]; typeAnnotation?: K.TSTypeAnnotationKind | null; typeParameters?: K.TSTypeParameterDeclarationKind | null; } ``` ### TSDeclareFunction Represents a TypeScript function declaration. ```typescript export interface TSDeclareFunction extends Omit { type: "TSDeclareFunction"; } ``` ### TSDeclareMethod Represents a TypeScript method declaration. ```typescript export interface TSDeclareMethod extends Omit { type: "TSDeclareMethod"; } ``` ### TSEnumDeclaration Represents a TypeScript enum declaration. ```typescript export interface TSEnumDeclaration extends Omit { type: "TSEnumDeclaration"; id: K.IdentifierKind; members: K.TSEnumMemberKind[]; const?: boolean; declare?: boolean; modifiers?: K.ModifierKind[] | null; } ``` ### TSEnumMember Represents a member of a TypeScript enum. ```typescript export interface TSEnumMember extends Omit { type: "TSEnumMember"; id: K.IdentifierKind | K.StringLiteralKind; initializer?: K.ExpressionKind | null; } ``` ### TSExportAssignment Represents a TypeScript export assignment. ```typescript export interface TSExportAssignment extends Omit { type: "TSExportAssignment"; expression: K.ExpressionKind; } ``` ### TSExpressionWithTypeArguments Represents a TypeScript expression with type arguments. ```typescript export interface TSExpressionWithTypeArguments extends Omit { type: "TSExpressionWithTypeArguments"; expression: K.IdentifierKind | K.TSQualifiedNameKind; typeParameters?: K.TSTypeParameterInstantiationKind | null; } ``` ### TSExternalModuleReference Represents a TypeScript external module reference. ```typescript export interface TSExternalModuleReference extends Omit { type: "TSExternalModuleReference"; expression: K.StringLiteralKind; } ``` ### TSFunctionType Represents a TypeScript function type. ```typescript export interface TSFunctionType extends Omit { type: "TSFunctionType"; parameters: (K.IdentifierKind | K.RestElementKind)[]; typeAnnotation: K.TSTypeAnnotationKind; typeParameters?: K.TSTypeParameterDeclarationKind | null; } ``` ### TSHasOptionalTypeAnnotation Represents an optional type annotation in TypeScript. ```typescript export interface TSHasOptionalTypeAnnotation extends Omit { type: "TSHasOptionalTypeAnnotation"; typeAnnotation?: K.TSTypeAnnotationKind | null; } ``` ### TSHasOptionalTypeParameterInstantiation Represents an optional type parameter instantiation in TypeScript. ```typescript export interface TSHasOptionalTypeParameterInstantiation extends Omit { type: "TSHasOptionalTypeParameterInstantiation"; typeParameters?: K.TSTypeParameterInstantiationKind | null; } ``` ### TSHasOptionalTypeParameters Represents optional type parameters in TypeScript. ```typescript export interface TSHasOptionalTypeParameters extends Omit { type: "TSHasOptionalTypeParameters"; typeParameters?: K.TSTypeParameterDeclarationKind | null; } ``` ### TSImportEqualsDeclaration Represents a TypeScript import equals declaration. ```typescript export interface TSImportEqualsDeclaration extends Omit { type: "TSImportEqualsDeclaration"; id: K.IdentifierKind; moduleReference: K.IdentifierKind | K.TSQualifiedNameKind | K.TSExternalModuleReferenceKind; isExport?: boolean; } ``` ### TSImportType Represents a TypeScript import type. ```typescript export interface TSImportType extends Omit { type: "TSImportType"; argument: K.StringLiteralKind; qualifier?: K.IdentifierKind | K.TSQualifiedNameKind | null; typeParameters?: K.TSTypeParameterInstantiationKind | null; } ``` ### TSIndexedAccessType Represents a TypeScript indexed access type. ```typescript export interface TSIndexedAccessType extends Omit { type: "TSIndexedAccessType"; objectType: K.TSTypeKind; indexType: K.TSTypeKind; } ``` ### TSIndexSignature Represents a TypeScript index signature. ```typescript export interface TSIndexSignature extends Omit { type: "TSIndexSignature"; parameters: (K.IdentifierKind | K.RestElementKind)[]; typeAnnotation?: K.TSTypeAnnotationKind | null; readonly?: boolean; static?: boolean; declare?: boolean; optional?: boolean; accessibility?: "public" | "private" | "protected" | null; } ``` ### TSInferType Represents a TypeScript infer type. ```typescript export interface TSInferType extends Omit { type: "TSInferType"; typeParameter: K.TSTypeParameterKind; } ``` ### TSInterfaceBody Represents the body of a TypeScript interface. ```typescript export interface TSInterfaceBody extends Omit { type: "TSInterfaceBody"; body: K.TSTypeElementKind[]; } ``` ### TSInterfaceDeclaration Represents a TypeScript interface declaration. ```typescript export interface TSInterfaceDeclaration extends Omit { type: "TSInterfaceDeclaration"; id: K.IdentifierKind; body: K.TSInterfaceBodyKind; typeParameters?: K.TSTypeParameterDeclarationKind | null; extends?: K.TSExpressionWithTypeArgumentsKind[] | null; declare?: boolean; } ``` ### TSIntersectionType Represents a TypeScript intersection type. ```typescript export interface TSIntersectionType extends Omit { type: "TSIntersectionType"; types: K.TSTypeKind[]; } ``` ### TSLiteralType Represents a TypeScript literal type. ```typescript export interface TSLiteralType extends Omit { type: "TSLiteralType"; literal: K.NumericLiteralKind | K.StringLiteralKind | K.BooleanLiteralKind; } ``` ### TSMappedType Represents a TypeScript mapped type. ```typescript export interface TSMappedType extends Omit { type: "TSMappedType"; typeParameter: K.TSTypeParameterKind; nameType?: K.TSTypeKind | null; optional?: boolean | "+" | "-"; readonly?: boolean | "+" | "-"; typeAnnotation?: K.TSTypeKind | null; } ``` ### TSMethodSignature Represents a TypeScript method signature. ```typescript export interface TSMethodSignature extends Omit { type: "TSMethodSignature"; key: K.ExpressionKind; parameters: (K.IdentifierKind | K.RestElementKind)[]; typeAnnotation?: K.TSTypeAnnotationKind | null; typeParameters?: K.TSTypeParameterDeclarationKind | null; computed?: boolean; optional?: boolean; } ``` ### TSModuleBlock Represents a TypeScript module block. ```typescript export interface TSModuleBlock extends Omit { type: "TSModuleBlock"; body: K.StatementKind[]; } ``` ### TSModuleDeclaration Represents a TypeScript module declaration. ```typescript export interface TSModuleDeclaration extends Omit { type: "TSModuleDeclaration"; id: K.IdentifierKind | K.StringLiteralKind; body: K.TSModuleBlockKind | K.TSModuleDeclarationKind; declare?: boolean; global?: boolean; modifiers?: K.ModifierKind[] | null; } ``` ### TSNamedTupleMember Represents a named tuple member in TypeScript. ```typescript export interface TSNamedTupleMember extends Omit { type: "TSNamedTupleMember"; elementType: K.TSTypeKind; label: K.IdentifierKind; optional?: boolean; } ``` ### TSNamespaceExportDeclaration Represents a TypeScript namespace export declaration. ```typescript export interface TSNamespaceExportDeclaration extends Omit { type: "TSNamespaceExportDeclaration"; id: K.IdentifierKind; } ``` ### TSNeverKeyword Represents the TypeScript `never` keyword. ```typescript export interface TSNeverKeyword extends Omit { type: "TSNeverKeyword"; } ``` ### TSNonNullExpression Represents a non-null assertion in TypeScript. ```typescript export interface TSNonNullExpression extends Omit { type: "TSNonNullExpression"; expression: K.ExpressionKind; } ``` ### TSNullKeyword Represents the TypeScript `null` keyword. ```typescript export interface TSNullKeyword extends Omit { type: "TSNullKeyword"; } ``` ### TSNumberKeyword Represents the TypeScript `number` keyword. ```typescript export interface TSNumberKeyword extends Omit { type: "TSNumberKeyword"; } ``` ### TSObjectKeyword Represents the TypeScript `object` keyword. ```typescript export interface TSObjectKeyword extends Omit { type: "TSObjectKeyword"; } ``` ### TSOptionalType Represents an optional type in TypeScript. ```typescript export interface TSOptionalType extends Omit { type: "TSOptionalType"; typeAnnotation: K.TSTypeKind; } ``` ### TSParameterProperty Represents a parameter property in TypeScript. ```typescript export interface TSParameterProperty extends Omit { type: "TSParameterProperty"; parameter: K.IdentifierKind | K.AssignmentPatternKind; accessibility?: "public" | "private" | "protected" | null; readonly?: boolean; } ``` ### TSParenthesizedType Represents a parenthesized type in TypeScript. ```typescript export interface TSParenthesizedType extends Omit { type: "TSParenthesizedType"; typeAnnotation: K.TSTypeKind; } ``` ### TSPropertySignature Represents a property signature in TypeScript. ```typescript export interface TSPropertySignature extends Omit { type: "TSPropertySignature"; key: K.ExpressionKind; typeAnnotation?: K.TSTypeAnnotationKind | null; initializer?: K.ExpressionKind | null; computed?: boolean; optional?: boolean; readonly?: boolean; } ``` ### TSQualifiedName Represents a qualified name in TypeScript. ```typescript export interface TSQualifiedName extends Omit { type: "TSQualifiedName"; left: K.IdentifierKind | K.TSQualifiedNameKind; right: K.IdentifierKind; } ``` ### TSRestType Represents a rest type in TypeScript. ```typescript export interface TSRestType extends Omit { type: "TSRestType"; typeAnnotation: K.TSTypeKind; } ``` ### TSStringKeyword Represents the TypeScript `string` keyword. ```typescript export interface TSStringKeyword extends Omit { type: "TSStringKeyword"; } ``` ### TSSymbolKeyword Represents the TypeScript `symbol` keyword. ```typescript export interface TSSymbolKeyword extends Omit { type: "TSSymbolKeyword"; } ``` ### TSThisType Represents the TypeScript `this` type. ```typescript export interface TSThisType extends Omit { type: "TSThisType"; } ``` ### TSTupleType Represents a tuple type in TypeScript. ```typescript export interface TSTupleType extends Omit { type: "TSTupleType"; elementTypes: K.TSTypeKind[]; } ``` ### TSType Represents a TypeScript type. ```typescript export interface TSType extends Node { type: "TSType"; } ``` ### TSTypeAliasDeclaration Represents a TypeScript type alias declaration. ```typescript export interface TSTypeAliasDeclaration extends Omit { type: "TSTypeAliasDeclaration"; id: K.IdentifierKind; typeAnnotation: K.TSTypeKind; typeParameters?: K.TSTypeParameterDeclarationKind | null; declare?: boolean; } ``` ### TSTypeAnnotation Represents a TypeScript type annotation. ```typescript export interface TSTypeAnnotation extends Omit { type: "TSTypeAnnotation"; typeAnnotation: K.TSTypeKind; } ``` ### TSTypeAssertion Represents a TypeScript type assertion. ```typescript export interface TSTypeAssertion extends Omit { type: "TSTypeAssertion"; expression: K.ExpressionKind; typeAnnotation: K.TSTypeKind; } ``` ### TSTypeLiteral Represents a TypeScript type literal. ```typescript export interface TSTypeLiteral extends Omit { type: "TSTypeLiteral"; members: K.TSTypeElementKind[]; } ``` ### TSTypeOperator Represents a TypeScript type operator. ```typescript export interface TSTypeOperator extends Omit { type: "TSTypeOperator"; operator: "keyof" | "unique" | "readonly"; typeAnnotation: K.TSTypeKind; } ``` ### TSTypeParameter Represents a type parameter in TypeScript. ```typescript export interface TSTypeParameter extends Omit { type: "TSTypeParameter"; name: string; constraint?: K.TSTypeKind | null; default?: K.TSTypeKind | null; } ``` ### TSTypeParameterDeclaration Represents a type parameter declaration in TypeScript. ```typescript export interface TSTypeParameterDeclaration extends Omit { type: "TSTypeParameterDeclaration"; params: K.TSTypeParameterKind[]; } ``` ### TSTypeParameterInstantiation Represents a type parameter instantiation in TypeScript. ```typescript export interface TSTypeParameterInstantiation extends Omit { type: "TSTypeParameterInstantiation"; params: K.TSTypeKind[]; } ``` ### TSTypePredicate Represents a type predicate in TypeScript. ```typescript export interface TSTypePredicate extends Omit { type: "TSTypePredicate"; asserts: boolean; parameterName: K.IdentifierKind | K.TSThisTypeKind; typeAnnotation?: K.TSTypeAnnotationKind | null; } ``` ### TSTypeQuery Represents a type query in TypeScript. ```typescript export interface TSTypeQuery extends Omit { type: "TSTypeQuery"; exprName: K.IdentifierKind | K.TSQualifiedNameKind; } ``` ### TSTypeReference Represents a type reference in TypeScript. ```typescript export interface TSTypeReference extends Omit { type: "TSTypeReference"; typeName: K.IdentifierKind | K.TSQualifiedNameKind; typeParameters?: K.TSTypeParameterInstantiationKind | null; } ``` ### TSUndefinedKeyword Represents the TypeScript `undefined` keyword. ```typescript export interface TSUndefinedKeyword extends Omit { type: "TSUndefinedKeyword"; } ``` ### TSUnionType Represents a union type in TypeScript. ```typescript export interface TSUnionType extends Omit { type: "TSUnionType"; types: K.TSTypeKind[]; } ``` ### TSUnknownKeyword Represents the TypeScript `unknown` keyword. ```typescript export interface TSUnknownKeyword extends Omit { type: "TSUnknownKeyword"; } ``` ### TSVoidKeyword Represents the TypeScript `void` keyword. ```typescript export interface TSVoidKeyword extends Omit { type: "TSVoidKeyword"; } ``` ### TupleTypeAnnotation A type annotation for a tuple type. ```typescript export interface TupleTypeAnnotation extends Omit { type: "TupleTypeAnnotation"; types: K.FlowTypeKind[]; } ``` ### TypeAlias Represents a type alias in Flow. ```typescript export interface TypeAlias extends Omit { type: "TypeAlias"; id: K.IdentifierKind; typeParameters?: K.TypeParameterDeclarationKind | null; right: K.FlowTypeKind; } ``` ### TypeAnnotation Represents a type annotation. ```typescript export interface TypeAnnotation extends Omit { type: "TypeAnnotation"; typeAnnotation: K.FlowTypeKind; } ``` ### TypeCastExpression Represents a type cast expression. ```typescript export interface TypeCastExpression extends Omit { type: "TypeCastExpression"; expression: K.ExpressionKind; typeAnnotation: K.TypeAnnotationKind; } ``` ### TypeofTypeAnnotation A type annotation for a `typeof` type. ```typescript export interface TypeofTypeAnnotation extends Omit { type: "TypeofTypeAnnotation"; argument: K.FlowTypeKind; } ``` ### TypeParameter Represents a type parameter. ```typescript export interface TypeParameter extends Omit { type: "TypeParameter"; name: string; variance?: K.VarianceKind | "plus" | "minus" | null; bound?: K.TypeAnnotationKind | null; default?: K.FlowTypeKind | null; } ``` ### TypeParameterDeclaration Represents a type parameter declaration. ```typescript export interface TypeParameterDeclaration extends Omit { type: "TypeParameterDeclaration"; params: K.TypeParameterKind[]; } ``` ### TypeParameterInstantiation Represents a type parameter instantiation. ```typescript export interface TypeParameterInstantiation extends Omit { type: "TypeParameterInstantiation"; params: K.FlowType Kind[]; } ``` ### UnaryExpression Represents a unary expression. ```typescript export interface UnaryExpression extends Omit { type: "UnaryExpression"; operator: "-" | "+" | "!" | "~" | "typeof" | "void" | "delete"; argument: K.ExpressionKind; prefix: boolean; } ``` ### UnionTypeAnnotation A type annotation for a union type. ```typescript export interface UnionTypeAnnotation extends Omit { type: "UnionTypeAnnotation"; types: K.FlowTypeKind[]; } ``` ### UpdateExpression Represents an update expression. ```typescript export interface UpdateExpression extends Omit { type: "UpdateExpression"; operator: "++" | "--"; argument: K.ExpressionKind; prefix: boolean; } ``` ### VariableDeclaration Represents a variable declaration. ```typescript export interface VariableDeclaration extends Omit { type: "VariableDeclaration"; declarations: K.VariableDeclaratorKind[]; kind: "var" | "let" | "const"; } ``` ### VariableDeclarator Represents a variable declarator. ```typescript export interface VariableDeclarator extends Omit { type: "VariableDeclarator"; id: K.PatternKind; init?: K.ExpressionKind | null; definite?: boolean; } ``` ### Variance Represents a variance in Flow types. ```typescript export interface Variance extends Omit { type: "Variance"; kind: "plus" | "minus"; } ``` ### VoidTypeAnnotation A type annotation for void types. ```typescript export interface VoidTypeAnnotation extends Omit { type: "VoidTypeAnnotation"; } ``` ### WhileStatement Represents a while statement. ```typescript export interface WhileStatement extends Omit { type: "WhileStatement"; test: K.ExpressionKind; body: K.StatementKind; } ``` ### WithStatement Represents a with statement. ```typescript export interface WithStatement extends Omit { type: "WithStatement"; object: K.ExpressionKind; body: K.StatementKind; } ``` ### YieldExpression Represents a yield expression. ```typescript export interface YieldExpression extends Omit { type: "YieldExpression"; argument?: K.ExpressionKind | null; delegate: boolean; } ``` ================================================ FILE: website/src/content/docs/index.mdx ================================================ --- title: Docs description: Get started with building and running jscodeshift codemods. template: doc hero: tagline: Get started with building and running jscodeshift codemods. actions: - text: Get started link: /overview/introduction/ icon: right-arrow variant: primary - text: GitHub link: https://github.com/facebook/jscodeshift icon: external --- ================================================ FILE: website/src/content/docs/overview/introduction.mdx ================================================ --- title: Introduction --- import { Steps, LinkCard, Card, CardGrid } from '@astrojs/starlight/components'; `jscodeshift` is a toolkit for building and running codemods over multiple JavaScript or TypeScript files. It provides: - A runner, which executes the provided transform for each file passed to it. It also outputs a summary of how many files have **not** been transformed. - A wrapper around [recast](https://github.com/benjamn/recast), providing a different API. Recast is an AST-to-AST transform tool and also tries to preserve the style of original code as much as possible. ## How does jscodeshift work? 1. **Parsing Code into AST** First, jscodeshift takes your JavaScript code and converts it into an Abstract Syntax Tree (AST). An AST is a tree representation of your code where each node represents a different part of the code, like variables, functions, and expressions. 2. **Transforming the AST** Using jscodeshift, you can navigate through the AST and apply transformations. For example, you can find all instances of a certain function and rename it or change its parameters. 3. **Generating Code from AST** After transforming the AST, jscodeshift converts it back into JavaScript code. The result is your original code with the specified changes applied. ## Installation Get jscodeshift from [npm](https://www.npmjs.com/package/jscodeshift): ``` $ npm install -g jscodeshift ``` This will install the runner as `jscodeshift`. ## Getting started ================================================ FILE: website/src/content/docs/run/cli.mdx ================================================ --- title: CLI Reference --- import { Aside } from '@astrojs/starlight/components'; The `jscodeshift` command-line interface (CLI) allows you to run jscodeshift codemods using a terminal or through an automated system. ## Usage You can use the `jscodeshift` command to run codemods in 4 different ways **Option 1: Run from `transform.js` file in current working directory:** ```bash jscodeshift [OPTION]... PATH... ``` This automatically looks for a `transform.js` file in the current working directory and attempts to run the transform over the specified `PATH`. **Option 2: Run from specific transform file:** ```bash jscodeshift [OPTION]... -t TRANSFORM_PATH PATH... ``` This allows you to specify a path to a transform file and run the transform over the specified `PATH`. **Option 3: Run from specific transform URL:** ```bash jscodeshift [OPTION]... -t URL PATH... ``` This allows you to specify a URL to a transform file and run the transform over the specified `PATH`. **Option 4: Apply transform to standard input:** ```bash jscodeshift [OPTION]... --stdin < file_list.txt ``` Using `--stdin` allows you to run the transform over every line of standard input provided in `file_list.txt`. ## Options ### `--(no-)babel` `default: true` This allows you to apply babeljs to the transform file. ### `--cpus=N` `aliases: -c` `default: max(all - 1, 1)` This allows you to start at most N child processes to process source files. ### `--(no-)dry` `aliases: -d` `default: false` This allows you to dry run the codemod. Dry running is useful when you need to see how a codemod will affect your project without making changes to your files. ### `--extensions=[EXT]` `default: js` Allows you to specify the extension of the files to be transformed. ### `--help` `aliases: -h` Allows you to print the help menu. ### `--ignore-config=[FILE] ...` Allows you to ignore files if they match patterns sourced from a configuration file (e.g. a .gitignore). ### `--ignore-pattern=[GLOB] ...` Allows you to ignore files that match a provided glob expression. ### `--(no-)gitignore` `default: false` Allows you to add entries to the current directory's .gitignore file. ### `--parser=[PARSER]` `default: babel` `available options: babel|babylon|flow|ts|tsx` Allows you to specify the parser to use for parsing the source files. ### `--parser-config=[FILE]` Allows you to specify path to a JSON file containing a custom parser configuration for flow or babylon. ### `--(no-)print` `aliases: -p` `default: false` Allows you to print transformed files to stdout, useful for development. ### `--(no-)run-in-band` `default: false` Allows `jscodeshift` to run serially in the current process. ### `--(no-)silent` `aliases: -s` `default: false` Allows you to run silently (prevent writing to stdout or stderr). ### `--(no-)stdin` `default: false` Allows you to run the transform over every line of standard input. ### `--transform=[FILE]` `aliases: -t` `default: ./transform.js` Allows you to specify a path to the transform file. Can be either a local path or url. ### `--verbose=[MODE]` `aliases: -v` `available options: 0|1|2` `default: 0` Allows you to show more information about the transform process. ### `--version` Allows you to print your `jscodeshift` version. ### `--fail-on-error` Allows you to return a 1 exit code when errors were found during execution of codemods. ================================================ FILE: website/src/env.d.ts ================================================ /// /// ================================================ FILE: website/tsconfig.json ================================================ { "extends": "astro/tsconfigs/strict" }