UNPKG

22.2 kBMarkdownView Raw
1# jscodeshift [![Support Ukraine](https://img.shields.io/badge/Support-Ukraine-FFD500?style=flat&labelColor=005BBB)](https://opensource.fb.com/support-ukraine) [![Build Status](https://travis-ci.org/facebook/jscodeshift.svg?branch=master)](https://travis-ci.org/facebook/jscodeshift)
2
3jscodeshift is a toolkit for running codemods over multiple JavaScript or
4TypeScript files.
5It provides:
6
7- A runner, which executes the provided transform for each file passed to it.
8 It also outputs a summary of how many files have (not) been transformed.
9- A wrapper around [recast][], providing a different API. Recast is an
10 AST-to-AST transform tool and also tries to preserve the style of original code
11 as much as possible.
12
13## Install
14
15Get jscodeshift from [npm](https://www.npmjs.com/package/jscodeshift):
16
17```
18$ npm install -g jscodeshift
19```
20
21This will install the runner as `jscodeshift`.
22
23## VSCode Debugger
24
25[Configure VSCode to debug codemods](#vscode-debugging)
26
27## Usage (CLI)
28
29The CLI provides the following options:
30
31```
32$ jscodeshift --help
33
34Usage: jscodeshift [OPTION]... PATH...
35 or: jscodeshift [OPTION]... -t TRANSFORM_PATH PATH...
36 or: jscodeshift [OPTION]... -t URL PATH...
37 or: jscodeshift [OPTION]... --stdin < file_list.txt
38
39Apply transform logic in TRANSFORM_PATH (recursively) to every PATH.
40If --stdin is set, each line of the standard input is used as a path.
41
42Options:
43"..." behind an option means that it can be supplied multiple times.
44All options are also passed to the transformer, which means you can supply custom options that are not listed here.
45
46 --(no-)babel apply babeljs to the transform file
47 (default: true)
48 -c, --cpus=N start at most N child processes to process source files
49 (default: max(all - 1, 1))
50 -d, --(no-)dry dry run (no changes are made to files)
51 (default: false)
52 --extensions=EXT transform files with these file extensions (comma separated list)
53 (default: js)
54 -h, --help print this help and exit
55 --ignore-config=FILE ... ignore files if they match patterns sourced from a configuration file (e.g. a .gitignore)
56 --ignore-pattern=GLOB ... ignore files that match a provided glob expression
57 --parser=babel|babylon|flow|ts|tsx the parser to use for parsing the source files
58 (default: babel)
59 --parser-config=FILE path to a JSON file containing a custom parser configuration for flow or babylon
60 -p, --(no-)print print transformed files to stdout, useful for development
61 (default: false)
62 --(no-)run-in-band run serially in the current process
63 (default: false)
64 -s, --(no-)silent do not write to stdout or stderr
65 (default: false)
66 --(no-)stdin read file/directory list from stdin
67 (default: false)
68 -t, --transform=FILE path to the transform file. Can be either a local path or url
69 (default: ./transform.js)
70 -v, --verbose=0|1|2 show more information about the transform process
71 (default: 0)
72 --version print version and exit
73 --fail-on-error return a 1 exit code when errors were found during execution of codemods
74```
75
76This passes the source of all passed through the transform module specified
77with `-t` or `--transform` (defaults to `transform.js` in the current
78directory). The next section explains the structure of the transform module.
79
80## Usage (JS)
81
82```js
83const {run: jscodeshift} = require('jscodeshift/src/Runner')
84const path = require('node:path');
85
86const transformPath = path.resolve('transform.js')
87const paths = ['foo.js', 'bar']
88const options = {
89 dry: true,
90 print: true,
91 verbose: 1,
92 // ...
93}
94
95const res = await jscodeshift(transformPath, paths, options)
96console.log(res)
97/*
98{
99 stats: {},
100 timeElapsed: '0.001',
101 error: 0,
102 ok: 0,
103 nochange: 0,
104 skip: 0
105}
106*/
107```
108
109## Transform module
110
111The transform is simply a module that exports a function of the form:
112
113```js
114module.exports = function(fileInfo, api, options) {
115 // transform `fileInfo.source` here
116 // ...
117 // return changed source
118 return source;
119};
120```
121
122As of v0.6.1, this module can also be written in TypeScript.
123
124### Arguments
125
126#### `fileInfo`
127
128Holds information about the currently processed file.
129
130Property | Description
131------------|------------
132path | File path
133source | File content
134
135#### `api`
136
137This object exposes the `jscodeshift` library and helper functions from the
138runner.
139
140Property | Description
141------------|------------
142jscodeshift | A reference to the jscodeshift library
143stats | A function to collect statistics during `--dry` runs
144report | Prints the passed string to stdout
145
146`jscodeshift` is a reference to the wrapper around recast and provides a
147jQuery-like API to navigate and transform the AST. Here is a quick example,
148a more detailed description can be found below.
149
150```js
151/**
152 * This replaces every occurrence of variable "foo".
153 */
154module.exports = function(fileInfo, api, options) {
155 return api.jscodeshift(fileInfo.source)
156 .findVariableDeclarators('foo')
157 .renameTo('bar')
158 .toSource();
159}
160```
161
162**Note:** This API is exposed for convenience, but you don't have to use it.
163You can use any tool to modify the source.
164
165`stats` is a function that only works when the `--dry` options is set. It accepts
166a string, and will simply count how often it was called with that value.
167
168At the end, the CLI will report those values. This can be useful while
169developing the transform, e.g. to find out how often a certain construct
170appears in the source(s).
171
172**`report`** allows you to print arbitrary strings to stdout. This can be
173useful when other tools consume the output of jscodeshift. The reason to not
174directly use `process.stdout` in transform code is to avoid mangled output when
175many files are processed.
176
177#### `options`
178
179Contains all options that have been passed to runner. This allows you to pass
180additional options to the transform. For example, if the CLI is called with
181
182```
183$ jscodeshift -t myTransforms fileA fileB --foo=bar
184```
185
186`options` would contain `{foo: 'bar'}`.
187
188### Return value
189
190The return value of the function determines the status of the transformation:
191
192- If a string is returned and it is different from passed source, the
193 transform is considered to be successful.
194- If a string is returned but it's the same as the source, the transform
195 is considered to be unsuccessful.
196- If nothing is returned, the file is not supposed to be transformed (which is
197 ok).
198
199The CLI provides a summary of the transformation at the end. You can get more
200detailed information by setting the `-v` option to `1` or `2`.
201
202You can collect even more stats via the `stats` function as explained above.
203
204### Parser
205
206The transform file can let jscodeshift know with which parser to parse the source files (and features like templates).
207
208To do that, the transform module can export `parser`, which can either be one
209of the strings `"babel"`, `"babylon"`, `"flow"`, `"ts"`, or `"tsx"`,
210or it can be a parser object that is compatible with recast and follows the estree spec.
211
212__Example: specifying parser type string in the transform file__
213
214```js
215
216module.exports = function transformer(file, api, options) {
217 const j = api.jscodeshift;
218 const rootSource = j(file.source);
219
220 // whatever other code...
221
222 return rootSource.toSource();
223}
224
225// use the flow parser
226module.exports.parser = 'flow';
227```
228
229__Example: specifying a custom parser object in the transform file__
230
231```js
232
233module.exports = function transformer(file, api, options) {
234 const j = api.jscodeshift;
235 const rootSource = j(file.source);
236
237 // whatever other code...
238
239 return rootSource.toSource();
240}
241
242module.exports.parser = {
243 parse: function(source) {
244 // return estree compatible AST
245 },
246};
247```
248
249### Example output
250
251```text
252$ jscodeshift -t myTransform.js src
253Processing 10 files...
254Spawning 2 workers with 5 files each...
255All workers done.
256Results: 0 errors 2 unmodified 3 skipped 5 ok
257```
258
259## The jscodeshift API
260
261As already mentioned, jscodeshift also provides a wrapper around [recast][].
262In order to properly use the jscodeshift API, one has to understand the basic
263building blocks of recast (and ASTs) as well.
264
265### Core Concepts
266
267#### AST nodes
268
269An AST node is a plain JavaScript object with a specific set of fields, in
270accordance with the [Mozilla Parser API][]. The primary way to identify nodes
271is via their `type`.
272
273For example, string literals are represented via `Literal` nodes, which
274have the structure
275
276```js
277// "foo"
278{
279 type: 'Literal',
280 value: 'foo',
281 raw: '"foo"'
282}
283```
284
285It's OK to not know the structure of every AST node type.
286The [(esprima) AST explorer][ast-explorer] is an online tool to inspect the AST
287for a given piece of JS code.
288
289#### Path objects
290
291Recast itself relies heavily on [ast-types][] which defines methods to traverse
292the AST, access node fields and build new nodes. ast-types wraps every AST node
293into a *path object*. Paths contain meta-information and helper methods to
294process AST nodes.
295
296For example, the child-parent relationship between two nodes is not explicitly
297defined. Given a plain AST node, it is not possible to traverse the tree *up*.
298Given a path object however, the parent can be traversed to via `path.parent`.
299
300For more information about the path object API, please have a look at
301[ast-types][].
302
303#### Builders
304
305To make creating AST nodes a bit simpler and "safer", ast-types defines a couple
306of *builder methods*, which are also exposed on `jscodeshift`.
307
308For example, the following creates an AST equivalent to `foo(bar)`:
309
310```js
311// inside a module transform
312var j = jscodeshift;
313// foo(bar);
314var ast = j.callExpression(
315 j.identifier('foo'),
316 [j.identifier('bar')]
317);
318```
319
320The signature of each builder function is best learned by having a look at the
321[definition files](https://github.com/benjamn/ast-types/tree/master/src/def)
322or in the babel/types [docs](https://babeljs.io/docs/en/babel-types).
323
324### Collections and Traversal
325
326In order to transform the AST, you have to traverse it and find the nodes that
327need to be changed. jscodeshift is built around the idea of **collections** of
328paths and thus provides a different way of processing an AST than recast or
329ast-types.
330
331A collection has methods to process the nodes inside a collection, often
332resulting in a new collection. This results in a fluent interface, which can
333make the transform more readable.
334
335Collections are "typed" which means that the type of a collection is the
336"lowest" type all AST nodes in the collection have in common. That means you
337cannot call a method for a `FunctionExpression` collection on an `Identifier`
338collection.
339
340Here is an example of how one would find/traverse all `Identifier` nodes with
341jscodeshift and with recast:
342
343```js
344// recast
345var ast = recast.parse(src);
346recast.visit(ast, {
347 visitIdentifier: function(path) {
348 // do something with path
349 return false;
350 }
351});
352
353// jscodeshift
354jscodeshift(src)
355 .find(jscodeshift.Identifier)
356 .forEach(function(path) {
357 // do something with path
358 });
359```
360
361To learn about the provided methods, have a look at the
362[Collection.js](src/Collection.js) and its [extensions](src/collections/).
363
364### Extensibility
365
366jscodeshift provides an API to extend collections. By moving common operators
367into helper functions (which can be stored separately in other modules), a
368transform can be made more readable.
369
370There are two types of extensions: generic extensions and type-specific
371extensions. **Generic extensions** are applicable to all collections. As such,
372they typically don't access specific node data, but rather traverse the AST from
373the nodes in the collection. **Type-specific** extensions work only on specific
374node types and are not callable on differently typed collections.
375
376#### Examples
377
378```js
379// Adding a method to all Identifiers
380jscodeshift.registerMethods({
381 logNames: function() {
382 return this.forEach(function(path) {
383 console.log(path.node.name);
384 });
385 }
386}, jscodeshift.Identifier);
387
388// Adding a method to all collections
389jscodeshift.registerMethods({
390 findIdentifiers: function() {
391 return this.find(jscodeshift.Identifier);
392 }
393});
394
395jscodeshift(ast).findIdentifiers().logNames();
396jscodeshift(ast).logNames(); // error, unless `ast` only consists of Identifier nodes
397```
398
399### Passing options to [recast]
400
401You may want to change some of the output settings (like setting `'` instead of `"`).
402This can be done by passing config options to [recast].
403
404```js
405.toSource({quote: 'single'}); // sets strings to use single quotes in transformed code.
406```
407
408You can also pass options to recast's `parse` method by passing an object to
409jscodeshift as second argument:
410
411```js
412jscodeshift(source, {...})
413```
414
415More on config options [here](https://github.com/benjamn/recast/blob/52a7ec3eaaa37e78436841ed8afc948033a86252/lib/options.js#L61)
416
417### Unit Testing
418
419jscodeshift 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:
420
421 - The test is located in a subdirectory under the directory the transform itself is located in (eg. `__tests__`)
422 - Test fixtures are located in a `__testfixtures__` directory
423
424This results in a directory structure like this:
425
426```
427/MyTransform.js
428/__tests__/MyTransform-test.js
429/__testfixtures__/MyTransform.input.js
430/__testfixtures__/MyTransform.output.js
431```
432
433A simple example of unit tests is bundled in the [sample directory](sample).
434
435The `testUtils` module exposes a number of useful helpers for unit testing.
436
437#### `defineTest`
438
439Defines a Jest/Jasmine test for a jscodeshift transform which depends on fixtures
440
441```js
442jest.autoMockOff();
443const defineTest = require('jscodeshift/dist/testUtils').defineTest;
444defineTest(__dirname, 'MyTransform');
445```
446
447An alternate fixture filename can be provided as the fourth argument to `defineTest`.
448This also means that multiple test fixtures can be provided:
449
450```js
451defineTest(__dirname, 'MyTransform', null, 'FirstFixture');
452defineTest(__dirname, 'MyTransform', null, 'SecondFixture');
453```
454
455This will run two tests:
456- `__testfixtures__/FirstFixture.input.js`
457- `__testfixtures__/SecondFixture.input.js`
458
459#### `defineInlineTest`
460
461Defines a Jest/Jasmine test suite for a jscodeshift transform which accepts inline values
462
463This is a more flexible alternative to `defineTest`, as this allows to also provide options to your transform
464
465```js
466const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
467const transform = require('../myTransform');
468const transformOptions = {};
469defineInlineTest(transform, transformOptions, 'input', 'expected output', 'test name (optional)');
470```
471
472#### `defineSnapshotTest`
473
474Similar to `defineInlineTest` but instead of requiring an output value, it uses Jest's `toMatchSnapshot()`
475
476```js
477const defineSnapshotTest = require('jscodeshift/dist/testUtils').defineSnapshotTest;
478const transform = require('../myTransform');
479const transformOptions = {};
480defineSnapshotTest(transform, transformOptions, 'input', 'test name (optional)');
481```
482
483For more information on snapshots, check out [Jest's docs](https://jestjs.io/docs/en/snapshot-testing)
484
485#### `defineSnapshotTestFromFixture`
486
487Similar to `defineSnapshotTest` but will load the file using same file-directory defaults as `defineTest`
488
489```js
490const defineSnapshotTestDefault = require('jscodeshift/dist/testUtils').defineSnapshotTestDefault;
491const transform = require('../myTransform');
492const transformOptions = {};
493defineSnapshotTestFromFixture(__dirname, transform, transformOptions, 'FirstFixture', 'test name (optional)');
494```
495
496#### `applyTransform`
497
498Executes your transform using the options and the input given and returns the result.
499This function is used internally by the other helpers, but it can prove useful in other cases.
500
501```js
502const applyTransform = require('jscodeshift/dist/testUtils').applyTransform;
503const transform = require('../myTransform');
504const transformOptions = {};
505const output = applyTransform(transform, transformOptions, 'input');
506```
507
508#### ES modules
509
510If you're authoring your transforms and tests using ES modules, make sure to import the transform's parser (if specified) in your tests:
511
512```js
513// MyTransform.js
514export const parser = 'flow'
515export default function MyTransform(fileInfo, api, options) {
516 // ...
517}
518```
519```js
520// __tests__/MyTransform-test.js
521import { defineInlineTest } from 'jscodeshift/dist/testUtils
522import * as transform from '../MyTransform
523
524console.log(transform.parser) // 'flow'
525
526defineInlineTest(transform, /* ... */)
527```
528
529### Example Codemods
530
531- [react-codemod](https://github.com/reactjs/react-codemod) - React codemod scripts to update React APIs.
532- [js-codemod](https://github.com/cpojer/js-codemod/) - Codemod scripts to transform code to next generation JS.
533- [js-transforms](https://github.com/jhgg/js-transforms) - Some documented codemod experiments to help you learn.
534- [fix-js](https://github.com/anshckr/fix-js) - Codemods to fix some ESLint issues
535
536### Local Documentation Server
537
538 To update docs in `/docs`, use `npm run docs`.
539
540 To view these docs locally, use `npx http-server ./docs`
541
542## VSCode Debugging
543
544It'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.
545
546```
547{
548 // Use IntelliSense to learn about possible attributes.
549 // Hover to view descriptions of existing attributes.
550 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
551 "version": "0.2.0",
552 "configurations": [
553 {
554 "type": "pwa-node",
555 "request": "launch",
556 "name": "Debug Transform",
557 "skipFiles": [
558 "<node_internals>/**"
559 ],
560 "program": "${workspaceRoot}/node_modules/.bin/jscodeshift",
561 "stopOnEntry": false,
562 "args": ["--dry", "--print", "-t", "${input:transformFile}", "--parser", "${input:parser}", "--run-in-band", "${file}"],
563 "preLaunchTask": null,
564 "runtimeExecutable": null,
565 "runtimeArgs": [
566 "--nolazy"
567 ],
568 "console": "internalConsole",
569 "sourceMaps": true,
570 "outFiles": []
571 },
572 {
573 "name": "Debug All JSCodeshift Jest Tests",
574 "type": "node",
575 "request": "launch",
576 "runtimeArgs": [
577 "--inspect-brk",
578 "${workspaceRoot}/node_modules/jest/bin/jest.js",
579 "--runInBand",
580 "--testPathPattern=${fileBasenameNoExtension}"
581 ],
582 "console": "integratedTerminal",
583 "internalConsoleOptions": "neverOpen",
584 "port": 9229
585 }
586 ],
587 "inputs": [
588 {
589 "type": "pickString",
590 "id": "parser",
591 "description": "jscodeshift parser",
592 "options": [
593 "babel",
594 "babylon",
595 "flow",
596 "ts",
597 "tsx",
598 ],
599 "default": "babel"
600 },
601 {
602 "type": "promptString",
603 "id": "transformFile",
604 "description": "jscodeshift transform file",
605 "default": "transform.js"
606 }
607 ]
608}
609```
610
611Once this has been added to the configuration
612
6131. 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.
6142. 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.
6153. Select "Debug Transform" from the debugging menu's options menu.
6164. Click the **"Start Debugging"** button on the VSCode debugger.
6175. 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**
6186. Select the parser to use from the presented selection list of parsers. The transform will otherwise default to using the **babel** parser.
6197. The transform will then be run, stopping at any breakpoints that have been set.
6208. 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.
621
622
623### Recipes
624
625- [Retain leading comment(s) in file when replacing/removing first statement](recipes/retain-first-comment.md)
626
627[npm]: https://www.npmjs.com/
628[Mozilla Parser API]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API
629[recast]: https://github.com/benjamn/recast
630[ast-types]: https://github.com/benjamn/ast-types
631[ast-explorer]: http://astexplorer.net/