├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── extensions └── imports │ ├── README.md │ ├── __tests__ │ ├── addImportCJSBasicConfig-test.js │ └── addImportFBConfig-test.js │ ├── addImport.js │ ├── config │ ├── CJSBasicRequireConfig.js │ └── FBRequireConfig.js │ ├── index.js │ ├── package.json │ ├── utils │ ├── getDeclarationName.js │ └── isValidRequireDeclaration.js │ └── yarn.lock ├── package.json ├── transforms ├── __testfixtures__ │ ├── .eslintrc │ ├── __tests__ │ │ ├── jest-11-update.input.js │ │ ├── jest-11-update.output.js │ │ ├── jest-rm-mock.input.js │ │ ├── jest-rm-mock.output.js │ │ ├── jest-update.input.js │ │ └── jest-update.output.js │ ├── arrow-function-arguments.input.js │ ├── arrow-function-arguments.output.js │ ├── arrow-function-length-40.input.js │ ├── arrow-function-length-40.output.js │ ├── arrow-function.input.js │ ├── arrow-function.output.js │ ├── arrow-function2.input.js │ ├── arrow-function2.output.js │ ├── expect.input.js │ ├── expect.output.js │ ├── flow-bool-to-boolean.input.js │ ├── flow-bool-to-boolean.output.js │ ├── invalid-requires.input.js │ ├── invalid-requires.output.js │ ├── jest-arrow.input.js │ ├── jest-arrow.output.js │ ├── jest-remove-describe.input.js │ ├── jest-remove-describe.output.js │ ├── jest-remove-disable-automock.input.js │ ├── jest-remove-disable-automock.output.js │ ├── no-vars.input.js │ ├── no-vars.output.js │ ├── object-shorthand.input.js │ ├── object-shorthand.output.js │ ├── rm-copyProperties.input.js │ ├── rm-copyProperties.output.js │ ├── rm-copyProperties2.input.js │ ├── rm-copyProperties2.output.js │ ├── rm-merge.input.js │ ├── rm-merge.output.js │ ├── rm-object-assign.input.js │ ├── rm-object-assign.output.js │ ├── rm-requires.input.js │ ├── rm-requires.output.js │ ├── template-literals.input.js │ ├── template-literals.output.js │ ├── touchable.input.js │ ├── touchable.output.js │ ├── trailing-commas.input.js │ ├── trailing-commas.output.js │ ├── unchain-variables.input.js │ ├── unchain-variables.output.js │ ├── underscore-to-lodash-native.input.js │ ├── underscore-to-lodash-native.output.js │ ├── unquote-properties.input.js │ ├── unquote-properties.output.js │ ├── use-strict.input.js │ └── use-strict.output.js ├── __tests__ │ ├── arrow-function-arguments-test.js │ ├── arrow-function-test.js │ ├── expect.js │ ├── flow-bool-to-boolean-test.js │ ├── invalid-requires-test.js │ ├── jest-11-update.js │ ├── jest-arrow.js │ ├── jest-remove-describe-test.js │ ├── jest-remove-disable-automock-test.js │ ├── jest-rm-mock.js │ ├── jest-update-test.js │ ├── no-reassign-params-test.js │ ├── no-vars-test.js │ ├── object-shorthand-test.js │ ├── rm-copyProperties-test.js │ ├── rm-merge-test.js │ ├── rm-object-assign-test.js │ ├── rm-requires-test.js │ ├── template-literals-test.js │ ├── trailing-commas-test.js │ ├── unchain-variables-test.js │ ├── underscore-to-lodash-native-test.js │ ├── unquote-properties-test.js │ └── use-strict-test.js ├── arrow-function-arguments.js ├── arrow-function.js ├── expect.js ├── flow-bool-to-boolean.js ├── invalid-requires.js ├── jest-11-update.js ├── jest-arrow.js ├── jest-remove-describe.js ├── jest-remove-disable-automock.js ├── jest-rm-mock.js ├── jest-update.js ├── no-reassign-params.js ├── no-vars.js ├── object-shorthand.js ├── outline-require.js ├── rm-copyProperties.js ├── rm-merge.js ├── rm-object-assign.js ├── rm-requires.js ├── template-literals.js ├── touchable.js ├── trailing-commas.js ├── unchain-variables.js ├── underscore-to-lodash-native.js ├── unquote-properties.js ├── updated-computed-props.js └── use-strict.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | parser: babel-eslint 3 | 4 | extends: 5 | - ./node_modules/fbjs-scripts/eslint/.eslintrc.js 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | sudo: false 5 | notifications: 6 | email: false 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christoph Pojer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## js-codemod [![Build Status](https://travis-ci.org/cpojer/js-codemod.svg)](https://travis-ci.org/cpojer/js-codemod) 2 | 3 | This repository contains a collection of codemod scripts for use with 4 | [JSCodeshift](https://github.com/facebook/jscodeshift). 5 | 6 | ### Setup & Run 7 | 8 | ```sh 9 | npm install -g jscodeshift 10 | git clone https://github.com/cpojer/js-codemod.git 11 | jscodeshift -t 12 | ``` 13 | 14 | Use the `-d` option for a dry-run and use `-p` to print the output for 15 | comparison. 16 | 17 | ### Included Scripts 18 | 19 | #### `arrow-function-arguments` 20 | 21 | ```sh 22 | jscodeshift -t js-codemod/transforms/arrow-function-arguments.js 23 | ``` 24 | 25 | #### `arrow-function` 26 | 27 | Transforms callbacks only when it can guarantee it won't break `this` context in the function. Also transforms `function() { }.bind(this)` calls to `() => {}`. 28 | 29 | ```sh 30 | jscodeshift -t js-codemod/transforms/arrow-function.js 31 | ``` 32 | 33 | ##### Options: 34 | 35 | `--inline-single-expressions=true`: If you are feeling lucky and you know that returning the value of single-expression functions will not affect the behavior of your application you can specify the option and it will transform `function() { relay(); }` to `() => relay()` instead of `() => { relay(); }`. 36 | 37 | `--max-width=120`: Try the best it can to keep line lengths under the specified length. 38 | 39 | #### `invalid-requires` 40 | 41 | ```sh 42 | jscodeshift -t js-codemod/transforms/invalid-requires.js 43 | ``` 44 | 45 | #### `jest-update` 46 | 47 | ```sh 48 | jscodeshift -t js-codemod/transforms/jest-update.js 49 | ``` 50 | 51 | #### `no-reassign-params` 52 | 53 | Converts functions to not reassign to parameters. This is useful to turn on in conjunction with [Flow's const_params](https://flow.org/en/docs/config/options/#toc-experimental-const-params-boolean) option. 54 | 55 | ```sh 56 | jscodeshift -t js-codemod/transforms/no-reassign-params.js 57 | ``` 58 | 59 | #### `no-vars` 60 | 61 | Conservatively converts `var` to `const` or `let`. 62 | 63 | ```sh 64 | jscodeshift -t js-codemod/transforms/no-vars.js 65 | ``` 66 | 67 | #### `object-shorthand` 68 | 69 | Transforms object literals to use [ES6 shorthand](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) 70 | for properties and methods. 71 | 72 | ```sh 73 | jscodeshift -t js-codemod/transforms/object-shorthand.js 74 | ``` 75 | 76 | #### `outline-require` 77 | 78 | ```sh 79 | jscodeshift -t js-codemod/transforms/outline-require.js 80 | ``` 81 | 82 | #### `rm-copyProperties` 83 | 84 | ```sh 85 | jscodeshift -t js-codemod/transforms/rm-copyProperties.js 86 | ``` 87 | 88 | #### `rm-merge` 89 | 90 | ```sh 91 | jscodeshift -t js-codemod/transforms/rm-merge.js 92 | ``` 93 | 94 | #### `rm-object-assign` 95 | 96 | ```sh 97 | jscodeshift -t js-codemod/transforms/rm-object-assign.js 98 | ``` 99 | 100 | #### `rm-requires` 101 | 102 | Removes any requires where the imported value is not referenced. Additionally 103 | if any module is required more than once the two requires will be merged. 104 | 105 | ```sh 106 | jscodeshift -t js-codemod/transforms/rm-requires.js 107 | ``` 108 | 109 | #### `template-literals` 110 | 111 | Replaces string concatenation with template literals. 112 | 113 | ```sh 114 | jscodeshift -t js-codemod/transforms/template-literals.js 115 | ``` 116 | 117 | Adapted from ["How to write a codemod" by Ramana Venkata](https://vramana.github.io/blog/2015/12/21/codemod-tutorial/). 118 | 119 | Areas of improvement: 120 | 121 | - Comments in the middle of string concatenation are currently added before the 122 | string but after the assignment. Perhaps in these situations, the string 123 | concatenation should be preserved as-is. 124 | 125 | - Nested concatenation inside template literals is not currently simplified. 126 | Currently, a + `b${'c' + d}` becomes `${a}b${'c' + d}` but it would ideally 127 | become ``${a}b${`c${d}`}``. 128 | 129 | - Unnecessary escaping of quotes from the resulting template literals is 130 | currently not removed. This is possibly the domain of a different transform. 131 | 132 | - Unicode escape sequences are converted to unicode characters when the 133 | simplified concatenation results in a string literal instead of a template 134 | literal. It would be nice to perserve the original--whether it be a unicode 135 | escape sequence or a unicode character. 136 | 137 | #### `touchable` 138 | 139 | ```sh 140 | jscodeshift -t js-codemod/transforms/touchable.js 141 | ``` 142 | 143 | #### `trailing-commas` 144 | 145 | Adds trailing commas to array and object literals. 146 | 147 | ```sh 148 | jscodeshift -t js-codemod/transforms/trailing-commas.js 149 | ``` 150 | 151 | #### `unchain-variables` 152 | 153 | Unchains chained variable declarations. 154 | 155 | ```sh 156 | jscodeshift -t js-codemod/transforms/unchain-variables.js 157 | ``` 158 | 159 | #### `underscore-to-lodash-native` 160 | 161 | Replaces underscore (or lodash) to ES6 + lodash, preferring native ES6 array methods. Member imports are used by default to allow tree-shaking, but the `--split-imports=true` option will split each lodash import into its own `lodash/` import. 162 | 163 | ```sh 164 | jscodeshift -t js-codemod/transforms/underscore-to-lodash-native.js 165 | ``` 166 | 167 | #### `unquote-properties` 168 | 169 | Removes quotes from object properties whose keys are strings which are valid 170 | identifiers. 171 | 172 | ```sh 173 | jscodeshift -t js-codemod/transforms/unquote-properties.js 174 | ``` 175 | 176 | #### `updated-computed-props` 177 | 178 | ```sh 179 | jscodeshift -t js-codemod/transforms/updated-computed-props.js 180 | ``` 181 | 182 | #### `use-strict` 183 | 184 | Adds a top-level `'use strict'` statement to JavaScript files 185 | 186 | ```sh 187 | jscodeshift -t js-codemod/transforms/use-strict.js 188 | ``` 189 | 190 | 191 | ### Included extensions 192 | 193 | `jscodeshift-imports` helpers for modifying `import` and `require` statements, 194 | [see docs](extensions/imports/). 195 | 196 | ### Recast Options 197 | 198 | [Options to recast's printer](https://github.com/benjamn/recast/blob/master/lib/options.ts) can be provided 199 | through the `printOptions` command line argument 200 | 201 | ```sh 202 | jscodeshift -t transform.js --printOptions='{"quote":"double"}' 203 | ``` 204 | -------------------------------------------------------------------------------- /extensions/imports/README.md: -------------------------------------------------------------------------------- 1 | ## jscodeshift-imports extension 2 | 3 | A [JSCodeshift](https://github.com/facebook/jscodeshift) extension which 4 | contains helpers for modifying `import` and `require` statements. 5 | 6 | ### Setup 7 | 8 | Install extension: `npm install jscodeshift-imports`. 9 | 10 | Register the extension with jscodeshift: 11 | ```javascript 12 | const imports = require('jscodeshift-imports'); 13 | 14 | module.exports = function(fileInfo, api) { 15 | const {jscodeshift} = api; 16 | 17 | imports.register(jscodeshift, imports.config.CJSBasicRequire); 18 | 19 | // Your transform here. 20 | } 21 | ``` 22 | 23 | Different configs will be needed based on your code style, this extension comes 24 | with two default configs: 25 | - [Basic commonJS style](./config/CJSBasicRequireConfig.js), all requires in one 26 | block. 27 | - [Facebook style](./config/FBRequireConfig.js), requires are split by the case 28 | of the module's name 29 | 30 | You can also provide your own custom config. 31 | 32 | ### API 33 | 34 | #### `addImport` 35 | 36 | This helper allows you to insert require statements into the most appropriate 37 | place within a file, it does this in a non destructive way so your codemod will 38 | change as little in the file as it can. 39 | 40 | Usage: 41 | ```javascript 42 | const imports = require('jscodeshift-imports'); 43 | 44 | module.exports = function(fileInfo, api) { 45 | const {jscodeshift} = api; 46 | const {statement} = jscodeshift.template; 47 | 48 | imports.register(jscodeshift, imports.config.CJSBasicRequire); 49 | 50 | return jscodeshift(file.source) 51 | .addImport(statement` 52 | const MyRequireItem = require('MyRequireItem'); 53 | `) 54 | .toSource(); 55 | } 56 | ``` 57 | 58 | `addImport` accepts any `require` or `import` statement as long as it matches 59 | one of the config types. 60 | 61 | **Note:** This transform cannot be trusted. Whilst this implementation works 62 | in most cases it can give incorrect whitespace due to limitations in the AST. 63 | You will need to manually verify the output after running. 64 | -------------------------------------------------------------------------------- /extensions/imports/__tests__/addImportCJSBasicConfig-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); 4 | 5 | const jscodeshift = require('jscodeshift'); 6 | const imports = require('../index'); 7 | 8 | const statement = jscodeshift.template.statement; 9 | 10 | const DEFAULT_RECAST_CONFIG = { 11 | quote: 'single', 12 | trailingComma: true, 13 | tabWidth: 2, 14 | }; 15 | 16 | function test(inputCollection, output) { 17 | expect( 18 | (inputCollection.toSource(DEFAULT_RECAST_CONFIG) || '').trim() 19 | ).toEqual( 20 | (output || '').trim() 21 | ); 22 | } 23 | 24 | // Setup JSCodeShift 25 | const plugins = imports.createPlugins(imports.config.CJSBasicRequire); 26 | jscodeshift.registerMethods({ 27 | addBasicImport: plugins.addImport, 28 | }); 29 | 30 | describe('addImportCJSBasicConfig', () => { 31 | it('should add to block start', () => { 32 | const jfile = jscodeshift(` 33 | var A1 = require('A1'); 34 | var A3 = require('A3'); 35 | 36 | aaa; 37 | `) 38 | .addBasicImport(statement` 39 | var A0 = require('A0'); 40 | `); 41 | 42 | test(jfile, ` 43 | var A0 = require('A0'); 44 | var A1 = require('A1'); 45 | var A3 = require('A3'); 46 | 47 | aaa; 48 | `); 49 | }); 50 | 51 | it('should add to block middle', () => { 52 | const jfile = jscodeshift(` 53 | var A1 = require('A1'); 54 | var A3 = require('A3'); 55 | 56 | aaa; 57 | `) 58 | .addBasicImport(statement` 59 | var A2 = require('A2'); 60 | `); 61 | 62 | test(jfile, ` 63 | var A1 = require('A1'); 64 | var A2 = require('A2'); 65 | var A3 = require('A3'); 66 | 67 | aaa; 68 | `); 69 | }); 70 | 71 | it('should add to block end', () => { 72 | const jfile = jscodeshift(` 73 | var A1 = require('A1'); 74 | var A3 = require('A3'); 75 | 76 | aaa; 77 | `) 78 | .addBasicImport(statement` 79 | var A4 = require('A4'); 80 | `); 81 | 82 | test(jfile, ` 83 | var A1 = require('A1'); 84 | var A3 = require('A3'); 85 | var A4 = require('A4'); 86 | 87 | aaa; 88 | `); 89 | }); 90 | 91 | // TODO: Comments mess with the whitespace output, fix this. 92 | // it('should add and maintain comments', () => { 93 | // const jfile = jscodeshift(` 94 | // /* @flow */ 95 | // 96 | // var A1 = require('A1'); 97 | // var A3 = require('A3'); 98 | // 99 | // aaa; 100 | // `) 101 | // .addBasicImport(statement` 102 | // var A0 = require('A0'); 103 | // `); 104 | // 105 | // test(jfile, ` 106 | // /* @flow */ 107 | // 108 | // var A0 = require('A0'); 109 | // var A1 = require('A1'); 110 | // var A3 = require('A3'); 111 | // 112 | // aaa; 113 | // `); 114 | // }); 115 | 116 | it('should add to a non first statement', () => { 117 | const jfile = jscodeshift(` 118 | /* @flow */ 119 | 120 | 'use strict'; 121 | 122 | var A1 = require('A1'); 123 | var A3 = require('A3'); 124 | 125 | aaa; 126 | `) 127 | .addBasicImport(statement` 128 | var A0 = require('A0'); 129 | `); 130 | 131 | test(jfile, ` 132 | /* @flow */ 133 | 134 | 'use strict'; 135 | 136 | var A0 = require('A0'); 137 | var A1 = require('A1'); 138 | var A3 = require('A3'); 139 | 140 | aaa; 141 | `); 142 | }); 143 | 144 | // TODO: We should check the `node.loc` to see if there is >1 line between 145 | // each require, we should only consider the first set of requires that have 146 | // no newlines between each other. 147 | // it('should not touch any other blocks', () => { 148 | // const jfile = jscodeshift(` 149 | // /* @flow */ 150 | // 151 | // 'use strict'; 152 | // 153 | // var A1 = require('A1'); 154 | // var A3 = require('A3'); 155 | // 156 | // var A4 = require('A4'); 157 | // 158 | // aaa; 159 | // `) 160 | // .addBasicImport(statement` 161 | // var A5 = require('A5'); 162 | // `); 163 | // 164 | // test(jfile, ` 165 | // /* @flow */ 166 | // 167 | // 'use strict'; 168 | // 169 | // var A1 = require('A1'); 170 | // var A3 = require('A3'); 171 | // var A5 = require('A5'); 172 | // 173 | // var A4 = require('A4'); 174 | // 175 | // aaa; 176 | // `); 177 | // }); 178 | 179 | it('should not touch other any other scopes', () => { 180 | const jfile = jscodeshift(` 181 | /* @flow */ 182 | 183 | 'use strict'; 184 | 185 | var A1 = require('A1'); 186 | var A3 = require('A3'); 187 | if (true) { 188 | var A4 = require('A4'); 189 | } 190 | 191 | aaa; 192 | `) 193 | .addBasicImport(statement` 194 | var A5 = require('A5'); 195 | `); 196 | 197 | test(jfile, ` 198 | /* @flow */ 199 | 200 | 'use strict'; 201 | 202 | var A1 = require('A1'); 203 | var A3 = require('A3'); 204 | var A5 = require('A5'); 205 | if (true) { 206 | var A4 = require('A4'); 207 | } 208 | 209 | aaa; 210 | `); 211 | }); 212 | 213 | it('should create block if it doesnt exist', () => { 214 | const jfile = jscodeshift(` 215 | /* @flow */ 216 | 217 | aaa; 218 | `) 219 | .addBasicImport(statement` 220 | var A1 = require('A1'); 221 | `); 222 | 223 | test(jfile, ` 224 | /* @flow */ 225 | 226 | var A1 = require('A1'); 227 | 228 | aaa; 229 | `); 230 | }); 231 | 232 | xit('should create block if it doesnt exist (use strict)', () => { 233 | const jfile = jscodeshift(` 234 | /* @flow */ 235 | 236 | 'use strict'; 237 | 238 | aaa; 239 | `) 240 | .addBasicImport(statement` 241 | var A1 = require('A1'); 242 | `); 243 | 244 | test(jfile, ` 245 | /* @flow */ 246 | 247 | 'use strict'; 248 | 249 | var A1 = require('A1'); 250 | 251 | aaa; 252 | `); 253 | }); 254 | 255 | it('should add more that one', () => { 256 | const jfile = jscodeshift(` 257 | /* @flow */ 258 | 259 | 'use strict'; 260 | 261 | var A1 = require('A1'); 262 | 263 | aaa; 264 | `) 265 | .addBasicImport(statement` 266 | var A0 = require('A0'); 267 | `) 268 | .addBasicImport(statement` 269 | var A2 = require('A2'); 270 | `) 271 | .addBasicImport(statement` 272 | var A3 = require('A3'); 273 | `); 274 | 275 | test(jfile, ` 276 | /* @flow */ 277 | 278 | 'use strict'; 279 | 280 | var A0 = require('A0'); 281 | var A1 = require('A1'); 282 | var A2 = require('A2'); 283 | var A3 = require('A3'); 284 | 285 | aaa; 286 | `); 287 | }); 288 | }); 289 | -------------------------------------------------------------------------------- /extensions/imports/__tests__/addImportFBConfig-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); 4 | 5 | const jscodeshift = require('jscodeshift'); 6 | const imports = require('../index'); 7 | 8 | const statement = jscodeshift.template.statement; 9 | 10 | const DEFAULT_RECAST_CONFIG = { 11 | quote: 'single', 12 | trailingComma: true, 13 | tabWidth: 2, 14 | }; 15 | 16 | function test(inputCollection, output) { 17 | expect( 18 | (inputCollection.toSource(DEFAULT_RECAST_CONFIG) || '').trim() 19 | ).toEqual( 20 | (output || '').trim() 21 | ); 22 | } 23 | 24 | // Setup JSCodeShift 25 | const plugins = imports.createPlugins(imports.config.FBRequire); 26 | jscodeshift.registerMethods({ 27 | addFBImport: plugins.addImport, 28 | }); 29 | 30 | describe('addImportFBConfig', () => { 31 | it('should add to block start large', () => { 32 | const jfile = jscodeshift(` 33 | var A1 = require('A1'); 34 | var A3 = require('A3'); 35 | 36 | var a1 = require('a1'); 37 | 38 | aaa; 39 | `) 40 | .addFBImport(statement` 41 | var A0 = require('A0'); 42 | `); 43 | 44 | test(jfile, ` 45 | var A0 = require('A0'); 46 | var A1 = require('A1'); 47 | var A3 = require('A3'); 48 | 49 | var a1 = require('a1'); 50 | 51 | aaa; 52 | `); 53 | }); 54 | it('should add to block start small', () => { 55 | const jfile = jscodeshift(` 56 | var A1 = require('A1'); 57 | var A3 = require('A3'); 58 | 59 | var a1 = require('a1'); 60 | 61 | aaa; 62 | `) 63 | .addFBImport(statement` 64 | var a0 = require('a0'); 65 | `); 66 | 67 | test(jfile, ` 68 | var A1 = require('A1'); 69 | var A3 = require('A3'); 70 | 71 | var a0 = require('a0'); 72 | var a1 = require('a1'); 73 | 74 | aaa; 75 | `); 76 | }); 77 | 78 | it('should add to block middle', () => { 79 | const jfile = jscodeshift(` 80 | var A1 = require('A1'); 81 | var A3 = require('A3'); 82 | 83 | var a1 = require('a1'); 84 | var a3 = require('a3'); 85 | 86 | aaa; 87 | `) 88 | .addFBImport(statement` 89 | var a2 = require('a2'); 90 | `); 91 | 92 | test(jfile, ` 93 | var A1 = require('A1'); 94 | var A3 = require('A3'); 95 | 96 | var a1 = require('a1'); 97 | var a2 = require('a2'); 98 | var a3 = require('a3'); 99 | 100 | aaa; 101 | `); 102 | }); 103 | 104 | it('should create small block if it doesnt exist', () => { 105 | const jfile = jscodeshift(` 106 | /* @flow */ 107 | 108 | 'use strict'; 109 | 110 | var A1 = require('A1'); 111 | 112 | aaa; 113 | `) 114 | .addFBImport(statement` 115 | var a2 = require('a2'); 116 | `); 117 | 118 | test(jfile, ` 119 | /* @flow */ 120 | 121 | 'use strict'; 122 | 123 | var A1 = require('A1'); 124 | 125 | var a2 = require('a2'); 126 | 127 | aaa; 128 | `); 129 | }); 130 | 131 | it('should create large block if it doesnt exist', () => { 132 | const jfile = jscodeshift(` 133 | /* @flow */ 134 | 135 | 'use strict'; 136 | 137 | var a2 = require('a2'); 138 | 139 | aaa; 140 | `) 141 | .addFBImport(statement` 142 | var A1 = require('A1'); 143 | `); 144 | 145 | test(jfile, ` 146 | /* @flow */ 147 | 148 | 'use strict'; 149 | 150 | var A1 = require('A1'); 151 | 152 | var a2 = require('a2'); 153 | 154 | aaa; 155 | `); 156 | }); 157 | 158 | // TODO: Get the whitespace to display correctly in this case. 159 | // it('should add more that one', () => { 160 | // const jfile = jscodeshift(` 161 | // /* @flow */ 162 | // 163 | // 'use strict'; 164 | // 165 | // var A1 = require('A1'); 166 | // 167 | // aaa; 168 | // `) 169 | // .addFBImport(statement` 170 | // var A0 = require('A0'); 171 | // `) 172 | // .addFBImport(statement` 173 | // var a2 = require('a2'); 174 | // `) 175 | // .addFBImport(statement` 176 | // var A3 = require('A3'); 177 | // `); 178 | // 179 | // test(jfile, ` 180 | // /* @flow */ 181 | // 182 | // 'use strict'; 183 | // 184 | // var A0 = require('A0'); 185 | // var A1 = require('A1'); 186 | // var A3 = require('A3'); 187 | // 188 | // var a2 = require('a2'); 189 | // 190 | // aaa; 191 | // `); 192 | // }); 193 | }); 194 | -------------------------------------------------------------------------------- /extensions/imports/addImport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const j = require('jscodeshift'); 4 | 5 | const statements = j.template.statements; 6 | const statement = j.template.statement; 7 | 8 | function findViaConfigType(type, nodePath) { 9 | return j(nodePath) 10 | .find(type.searchTerms[0]) 11 | .filter(p => type.filters.every(filter => filter(p))); 12 | } 13 | 14 | function findInsertTypeIndex(config, requireStatement) { 15 | const wrappedRequireStatement = j.program([requireStatement]); 16 | for (let i = 0; i < config.length; i++) { 17 | if (findViaConfigType(config[i], wrappedRequireStatement).size()) { 18 | return i; 19 | } 20 | } 21 | 22 | throw new Error('No valid config found!'); 23 | } 24 | 25 | function findNextInsertIndex(config, root) { 26 | for (let i = config.length - 1; i >= 0; i--) { 27 | if (findViaConfigType(config[i], root).size()) { 28 | return i; 29 | } 30 | } 31 | 32 | return -1; 33 | } 34 | 35 | function reprintComment(node) { 36 | if (node.type === 'Block') { 37 | return j.block(node.value); 38 | } else if (node.type === 'Line') { 39 | return j.line(node.value); 40 | } 41 | return node; 42 | } 43 | 44 | function applyCommentsToStatements(nodes, comments) { 45 | if (comments) { 46 | nodes[0].comments = comments.map(comment => reprintComment(comment)); 47 | } 48 | return nodes; 49 | } 50 | 51 | function reprintNode(node) { 52 | if (j.ExpressionStatement.check(node)) { 53 | return statement`${node.expression}`; 54 | } 55 | 56 | if (j.VariableDeclaration.check(node)) { 57 | const declaration = node.declarations[0]; 58 | return j.variableDeclaration(node.kind, [ 59 | j.variableDeclarator(declaration.id, declaration.init), 60 | ]); 61 | } 62 | 63 | if (j.ImportDeclaration.check(node) && node.importKind === 'type') { 64 | // TODO: Properly remove new lines from the node. 65 | return node; 66 | } 67 | 68 | return node; 69 | } 70 | 71 | function addImport(config, root, requireStatement) { 72 | const insertTypeIndex = findInsertTypeIndex(config, requireStatement); 73 | const currentType = config[insertTypeIndex]; 74 | const nodesForType = findViaConfigType(currentType, root).paths(); 75 | 76 | if (nodesForType.length) { 77 | let insertAt = nodesForType.length; 78 | for (let i = 0; i < nodesForType.length; i++) { 79 | const pos = currentType.comparator( 80 | nodesForType[i].value, 81 | requireStatement 82 | ); 83 | 84 | if (pos >= 0) { 85 | insertAt = i; 86 | break; 87 | } 88 | } 89 | 90 | if (insertAt === 0) { 91 | const nodePath = nodesForType[0]; 92 | j(nodePath) 93 | .replaceWith(applyCommentsToStatements(statements` 94 | ${requireStatement}; 95 | ${reprintNode(nodePath.value)}; 96 | `, nodePath.value.comments)); 97 | } else { 98 | const nodePath = nodesForType[insertAt - 1]; 99 | j(nodePath) 100 | .replaceWith(applyCommentsToStatements(statements` 101 | ${reprintNode(nodePath.value)}; 102 | ${requireStatement}; 103 | `, nodePath.value.comments)); 104 | } 105 | } else { 106 | const nextFoundConfigTypeIndex = findNextInsertIndex( 107 | config, 108 | root 109 | ); 110 | 111 | if (nextFoundConfigTypeIndex > -1) { 112 | const requiresForType = findViaConfigType( 113 | config[nextFoundConfigTypeIndex], 114 | root 115 | ).paths(); 116 | 117 | if (insertTypeIndex < nextFoundConfigTypeIndex) { 118 | j(requiresForType[0]) 119 | .insertBefore(requireStatement); 120 | } else { 121 | j(requiresForType[requiresForType.length - 1]) 122 | .insertAfter(requireStatement); 123 | } 124 | } else { 125 | const firstPath = j(root) 126 | .find(j.Program) 127 | .get('body') 128 | .get(0); 129 | 130 | j(firstPath) 131 | .replaceWith(applyCommentsToStatements(statements` 132 | ${requireStatement}; 133 | ${reprintNode(firstPath.value)}; 134 | `, firstPath.value.comments)); 135 | } 136 | } 137 | } 138 | 139 | module.exports = addImport; 140 | -------------------------------------------------------------------------------- /extensions/imports/config/CJSBasicRequireConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const StringUtils = require('nuclide-format-js/lib/common/utils/StringUtils'); 4 | const getDeclarationName = require('../utils/getDeclarationName'); 5 | const isGlobal = require('nuclide-format-js/lib/common/utils/isGlobal'); 6 | const isValidRequireDeclaration = require('../utils/isValidRequireDeclaration'); 7 | const jscs = require('jscodeshift'); 8 | 9 | module.exports = [ 10 | // Handle general requires, e.g: `require('lowerCase');` 11 | { 12 | searchTerms: [jscs.VariableDeclaration], 13 | filters: [ 14 | isGlobal, 15 | path => isValidRequireDeclaration(path.node), 16 | ], 17 | comparator: (node1, node2) => StringUtils.compareStrings( 18 | getDeclarationName(node1), 19 | getDeclarationName(node2) 20 | ), 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /extensions/imports/config/FBRequireConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const StringUtils = require('nuclide-format-js/lib/common/utils/StringUtils'); 4 | const getDeclarationName = require('../utils/getDeclarationName'); 5 | const isGlobal = require('nuclide-format-js/lib/common/utils/isGlobal'); 6 | const isValidRequireDeclaration = require('../utils/isValidRequireDeclaration'); 7 | const jscs = require('jscodeshift'); 8 | 9 | module.exports = [ 10 | // Handle UpperCase requires, e.g: `require('UpperCase');` 11 | { 12 | searchTerms: [jscs.VariableDeclaration], 13 | filters: [ 14 | isGlobal, 15 | path => isValidRequireDeclaration(path.node), 16 | path => StringUtils.isCapitalized(getDeclarationName(path.node)), 17 | ], 18 | comparator: (node1, node2) => StringUtils.compareStrings( 19 | getDeclarationName(node1), 20 | getDeclarationName(node2) 21 | ), 22 | }, 23 | 24 | // Handle lowerCase requires, e.g: `require('lowerCase');` 25 | { 26 | searchTerms: [jscs.VariableDeclaration], 27 | filters: [ 28 | isGlobal, 29 | path => isValidRequireDeclaration(path.node), 30 | path => !StringUtils.isCapitalized(getDeclarationName(path.node)), 31 | ], 32 | comparator: (node1, node2) => StringUtils.compareStrings( 33 | getDeclarationName(node1), 34 | getDeclarationName(node2) 35 | ), 36 | }, 37 | ]; 38 | -------------------------------------------------------------------------------- /extensions/imports/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const addImport = require('./addImport'); 4 | const CJSBasicRequireConfig = require('./config/CJSBasicRequireConfig'); 5 | const FBRequireConfig = require('./config/FBRequireConfig'); 6 | 7 | module.exports = { 8 | register(jscodeshift, config) { 9 | const plugins = this.createPlugins(config); 10 | jscodeshift.registerMethods(plugins); 11 | }, 12 | 13 | createPlugins(config) { 14 | return { 15 | 16 | /** 17 | * Add a new import statement. 18 | * 19 | * Usage: 20 | * 21 | * jscodeshift(file.source) 22 | * .addImport(statement` 23 | * import MyImportItem from './path/to/MyImportItem'; 24 | * `); 25 | */ 26 | addImport(importStatement) { 27 | return this.forEach(path => { 28 | addImport(config, path, importStatement); 29 | }); 30 | }, 31 | }; 32 | }, 33 | 34 | config: { 35 | CJSBasicRequire: CJSBasicRequireConfig, 36 | FBRequire: FBRequireConfig, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /extensions/imports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jscodeshift-imports", 3 | "author": "Pieter Vanderwerff", 4 | "version": "1.1.0", 5 | "description": "A JSCodeshift extension with helpers for modifying `import` and `require` statements.", 6 | "license": "MIT", 7 | "repository": "https://github.com/cpojer/js-codemod/tree/master/extensions/imports", 8 | "keywords": [ 9 | "codemod", 10 | "recast", 11 | "jscodeshift", 12 | "require", 13 | "import" 14 | ], 15 | "dependencies": { 16 | "nuclide-format-js": "0.0.36" 17 | }, 18 | "peerDependencies": { 19 | "jscodeshift": "^0.3.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /extensions/imports/utils/getDeclarationName.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const jscs = require('jscodeshift'); 4 | 5 | function getDeclarationName(node) { 6 | var declaration = node.declarations[0]; 7 | if (jscs.Identifier.check(declaration.id)) { 8 | return declaration.id.name; 9 | } 10 | // Order by the first property name in the object pattern. 11 | if (jscs.ObjectPattern.check(declaration.id)) { 12 | return declaration.id.properties[0].key.name; 13 | } 14 | // Order by the first element name in the array pattern. 15 | if (jscs.ArrayPattern.check(declaration.id)) { 16 | return declaration.id.elements[0].name; 17 | } 18 | return ''; 19 | } 20 | 21 | module.exports = getDeclarationName; 22 | -------------------------------------------------------------------------------- /extensions/imports/utils/isValidRequireDeclaration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const jscs = require('jscodeshift'); 4 | const hasOneRequireDeclaration = require('nuclide-format-js/lib/common/utils/hasOneRequireDeclaration'); 5 | 6 | function isValidRequireDeclaration(node) { 7 | if (!hasOneRequireDeclaration(node)) { 8 | return false; 9 | } 10 | var declaration = node.declarations[0]; 11 | if (jscs.Identifier.check(declaration.id)) { 12 | return true; 13 | } 14 | if (jscs.ObjectPattern.check(declaration.id)) { 15 | return declaration.id.properties.every( 16 | prop => prop.shorthand && jscs.Identifier.check(prop.key) 17 | ); 18 | } 19 | if (jscs.ArrayPattern.check(declaration.id)) { 20 | return declaration.id.elements.every( 21 | element => jscs.Identifier.check(element) 22 | ); 23 | } 24 | return false; 25 | } 26 | 27 | module.exports = isValidRequireDeclaration; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-codemod", 3 | "author": "Christoph Pojer", 4 | "version": "8.0.0", 5 | "description": "Codemod scripts to transform code to next generation JS", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/cpojer/js-codemod.git" 10 | }, 11 | "scripts": { 12 | "test": "f() { EXIT=0; npm run lint || EXIT=$?; jest $@ || EXIT=$?; exit $EXIT; }; f", 13 | "lint": "eslint ." 14 | }, 15 | "keywords": [ 16 | "codemod", 17 | "recast" 18 | ], 19 | "dependencies": { 20 | "jscodeshift": "^0.3.30", 21 | "nuclide-format-js": "0.0.36" 22 | }, 23 | "devDependencies": { 24 | "babel-eslint": "^7.1.1", 25 | "babel-jest": "^18.0.0", 26 | "babel-plugin-transform-object-rest-spread": "^6.20.2", 27 | "babel-preset-es2015": "^6.18.0", 28 | "eslint": "^3.12.2", 29 | "fbjs-scripts": "^0.7.1", 30 | "jest": "^21.2.1" 31 | }, 32 | "jest": { 33 | "globals": { 34 | "baseDir": "../../../" 35 | }, 36 | "roots": [ 37 | "transforms/__tests__", 38 | "extensions" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | no-undef: 0 4 | no-unused-vars: 0 5 | no-extra-parens: 0 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/__tests__/jest-11-update.input.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('foo').dontMock('bar'); 2 | 3 | jest 4 | .dontMock('foo') 5 | .dontMock('bar') 6 | .dontMock('bar'); 7 | 8 | jest.dontMock('foo').mock('bar').dontMock('bar'); 9 | 10 | jest.autoMockOff(); 11 | jest.autoMockOn(); 12 | 13 | jest.genMockFunction(); 14 | jest.genMockFn(); 15 | 16 | jest.genMockFunction().mockImpl(() => { test; }); 17 | jest.genMockFunction().mockImplementation((banana) => banana); 18 | jest.genMockFn().mockImpl(function() { test(); }); 19 | 20 | jest.genMockFn().mockReturnValueOnce(123); 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/__tests__/jest-11-update.output.js: -------------------------------------------------------------------------------- 1 | jest.unmock('foo').unmock('bar'); 2 | 3 | jest 4 | .unmock('foo') 5 | .unmock('bar') 6 | .unmock('bar'); 7 | 8 | jest.unmock('foo').mock('bar').unmock('bar'); 9 | 10 | jest.disableAutomock(); 11 | jest.enableAutomock(); 12 | 13 | jest.fn(); 14 | jest.fn(); 15 | 16 | jest.fn(() => { test; }); 17 | jest.fn((banana) => banana); 18 | jest.fn(function() { test(); }); 19 | 20 | jest.fn().mockReturnValueOnce(123); 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/__tests__/jest-rm-mock.input.js: -------------------------------------------------------------------------------- 1 | jest.mock('ix').mock('cx'); 2 | 3 | jest.disableAutomock() 4 | .mock('cx'); 5 | 6 | jest.disableAutomock() 7 | .mock('foo') 8 | .mock('bar') 9 | .mock('cx'); 10 | 11 | jest.disableAutomock() 12 | .mock('cx') 13 | .mock('ix') 14 | .mock('foo'); 15 | 16 | jest.disableAutomock() 17 | .mock('ix') 18 | .mock('bar') 19 | .mock('foo'); 20 | 21 | jest 22 | .mock('ix') 23 | .mock('cx') 24 | .mock('foo'); 25 | 26 | jest 27 | .mock('ix') 28 | .mock('foo') 29 | .mock('bar') 30 | .mock('cx'); 31 | 32 | jest 33 | .mock('ix') 34 | .mock('bar1') 35 | .mock('cx') 36 | .mock('bar') 37 | .mock('foo'); 38 | 39 | jest.mock('ix').mock('bar').mock('cx'); 40 | 41 | jest.mock('ix'); 42 | 43 | jest.unmock('module'); 44 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/__tests__/jest-rm-mock.output.js: -------------------------------------------------------------------------------- 1 | jest.disableAutomock(); 2 | 3 | jest.disableAutomock() 4 | .mock('foo') 5 | .mock('bar'); 6 | 7 | jest.disableAutomock() 8 | .mock('foo'); 9 | 10 | jest.disableAutomock() 11 | .mock('bar') 12 | .mock('foo'); 13 | 14 | jest 15 | .mock('foo'); 16 | 17 | jest 18 | .mock('foo') 19 | .mock('bar'); 20 | 21 | jest 22 | .mock('bar1') 23 | .mock('bar') 24 | .mock('foo'); 25 | 26 | jest.mock('bar'); 27 | 28 | jest.unmock('module'); 29 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/__tests__/jest-update.input.js: -------------------------------------------------------------------------------- 1 | // Comment 2 | require('mocks'); 3 | 4 | var mocks = require('mocks'); 5 | 6 | var mockModules = require('mock-modules'); 7 | 8 | require('mock-modules').dontMock('Foo').dontMock('Foo1'); 9 | 10 | require('mock-modules').dumpCache(); 11 | 12 | mockModules.dumpCache(); 13 | 14 | modules.dontMock('Foo'); 15 | 16 | require('mocks').getMockFunction(); 17 | 18 | require('mock-modules').doSomethingCrazy(); 19 | 20 | require('mock-modules').generateMock(); 21 | 22 | require('mock-modules').mock('foo').dontMock('bar'); 23 | 24 | mocks.dontMock('foo'); 25 | 26 | require('mock-modules') 27 | .dontMock() 28 | .dontMock(); 29 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/__tests__/jest-update.output.js: -------------------------------------------------------------------------------- 1 | // Comment 2 | jest.dontMock('Foo').dontMock('Foo1'); 3 | 4 | jest.resetModuleRegistry(); 5 | 6 | jest.resetModuleRegistry(); 7 | 8 | jest.dontMock('Foo'); 9 | 10 | jest.genMockFn(); 11 | 12 | require('mock-modules').doSomethingCrazy(); 13 | 14 | jest.genMockFromModule(); 15 | 16 | jest.mock('foo').dontMock('bar'); 17 | 18 | jest.dontMock('foo'); 19 | 20 | jest 21 | .dontMock() 22 | .dontMock(); 23 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function-arguments.input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fn1 = () => console.log(arguments); 4 | 5 | var fn2 = (a, b, c) => { 6 | console.log(arguments); 7 | 8 | return arguments; 9 | }; 10 | 11 | var fn3 = (a, b, args) => { 12 | console.log(args); 13 | 14 | return arguments; 15 | }; 16 | 17 | var fn4 = function(a, b, c) { 18 | console.log(arguments); 19 | var fn5 = () => arguments; 20 | var fn6 = () => (function() { return arguments; }); 21 | class A { 22 | constructor() { 23 | console.log(arguments); 24 | } 25 | } 26 | }; 27 | 28 | var fn5 = (a, ...b) => arguments; 29 | 30 | var fn7 = (ref) => console.log(ref.arguments); 31 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function-arguments.output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fn1 = (...args) => console.log(args); 4 | 5 | var fn2 = (a, b, c, ...args) => { 6 | console.log([a, b, c, ...args]); 7 | 8 | return [a, b, c, ...args]; 9 | }; 10 | 11 | var fn3 = (a, b, args) => { 12 | console.log(args); 13 | 14 | return arguments; 15 | }; 16 | 17 | var fn4 = function(a, b, c) { 18 | console.log(arguments); 19 | var fn5 = (...args) => args; 20 | var fn6 = () => (function() { return arguments; }); 21 | class A { 22 | constructor() { 23 | console.log(arguments); 24 | } 25 | } 26 | }; 27 | 28 | var fn5 = (a, ...b) => [a, ...b]; 29 | 30 | var fn7 = (ref) => console.log(ref.arguments); 31 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function-length-40.input.js: -------------------------------------------------------------------------------- 1 | fooBar(function() { 2 | return 20000000000; 3 | }); 4 | 5 | fooBarfooBarfooBarf(function() { 6 | return 20000000000; 7 | }); 8 | 9 | fooBarfooBarfooBarfo(function() { 10 | return 20000000000; 11 | }); 12 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function-length-40.output.js: -------------------------------------------------------------------------------- 1 | fooBar(() => 20000000000); 2 | 3 | fooBarfooBarfooBarf(() => 20000000000); 4 | 5 | fooBarfooBarfooBarfo(() => { 6 | return 20000000000; 7 | }); 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-extra-bind */ 2 | 'use strict'; 3 | 4 | var fn1 = (function() { 5 | console.log('Banana!'); 6 | }).bind(this); 7 | 8 | var fn2 = (function() { 9 | console.log('Banana banana!'); 10 | }).bind(this, 1, 2, 3); 11 | 12 | var fn3 = (function(a, b, c) { 13 | console.log('foo!'); 14 | console.log(a, b, c); 15 | }).bind(this); 16 | 17 | var fn4 = (function() { 18 | console.log('foo!'); 19 | }).bind(this); 20 | 21 | var fn5 = (function named() { 22 | console.log("don't transform me!"); 23 | }).bind(this); 24 | 25 | var fn6 = (function() { 26 | return { 27 | a: 1, 28 | }; 29 | }).bind(this); 30 | 31 | var fn7 = /*1*/(/*2*/function/*3*/(/*4*/)/*5*/ {/*6*/ 32 | console.log('Keep'); 33 | console.log('comments'); 34 | }/*7*/)/*8*/./*9*/bind/*10*/(/*11*/this/*12*/)/*13*/; 35 | 36 | [1, 2, 3].map(function(x) { 37 | return x * x; 38 | }.bind(this)); 39 | 40 | [1, 2, 3].map(function(x) { 41 | return x * x; 42 | }); 43 | 44 | compare(1, 2, function(num1, num2) { 45 | return num1 > num2; 46 | }); 47 | 48 | /*1*/compare/*2*/(/*3*/1, /*4*/2, /*5*/function/*6*/(/*7*/num1/*8*/, /*9*/num2/*10*/) /*11*/{ 49 | /*12*/return /*13*/num1 > num2/*14*/;/*15*/ 50 | }/*16*/)/*17*/;/*18*/ 51 | 52 | Promise.resolve() 53 | .then(function() { 54 | console.log('foo'); 55 | }.bind(this, 'a')) 56 | .then(function(a) { 57 | return 4; 58 | }); 59 | 60 | foo(function() /*1*/{ 61 | /*2*/console.log('Keep comments when inlining single expressions'); 62 | /*3*/}/*4*/); 63 | 64 | foo(function(a) { 65 | this.bar(function() { 66 | return a + this.b; 67 | }); 68 | }); 69 | 70 | foo(function(a) { 71 | bar(function() { 72 | return a + this.b; 73 | }); 74 | }); 75 | 76 | foo(function(a) { 77 | bar(function() { 78 | return a + this.b; 79 | }.bind(this)); 80 | }); 81 | 82 | foo(function bar() { 83 | console.log('foo'); 84 | }); 85 | 86 | foo(function baz_prototype() { 87 | console.log('foo'); 88 | }); 89 | 90 | baz_prototype.prototype = {}; 91 | 92 | var generatorFunc = function* () { 93 | console.log('I shall not be transformed!'); 94 | }.bind(this); 95 | 96 | foo(function* () { 97 | console.log('I shall not be transformed!'); 98 | }); 99 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-extra-bind */ 2 | 'use strict'; 3 | 4 | var fn1 = () => console.log('Banana!'); 5 | 6 | var fn2 = (function() { 7 | console.log('Banana banana!'); 8 | }).bind(this, 1, 2, 3); 9 | 10 | var fn3 = (a, b, c) => { 11 | console.log('foo!'); 12 | console.log(a, b, c); 13 | }; 14 | 15 | var fn4 = () => console.log('foo!'); 16 | 17 | var fn5 = (function named() { 18 | console.log("don't transform me!"); 19 | }).bind(this); 20 | 21 | var fn6 = () => ({ 22 | a: 1, 23 | }); 24 | 25 | var fn7 = /*2*//*1*/() => /*3*//*4*//*5*/ {/*6*/ 26 | console.log('Keep'); 27 | console.log('comments'); 28 | }/*7*//*8*//*10*//*9*//*11*//*12*//*13*/; 29 | 30 | [1, 2, 3].map(x => x * x); 31 | 32 | [1, 2, 3].map(x => x * x); 33 | 34 | compare(1, 2, (num1, num2) => num1 > num2); 35 | 36 | /*1*/compare/*2*/(/*3*/1, /*4*/2, /*5*/(/*6*//*7*/num1/*8*/, /*9*/num2/*10*/) => /*13*//*11*//*12*/num1 > num2/*14*//*15*//*16*/)/*17*/;/*18*/ 37 | 38 | Promise.resolve() 39 | .then(function() { 40 | console.log('foo'); 41 | }.bind(this, 'a')) 42 | .then(a => 4); 43 | 44 | foo(() => /*1*//*2*/console.log('Keep comments when inlining single expressions') 45 | /*3*//*4*/); 46 | 47 | foo(function(a) { 48 | this.bar(function() { 49 | return a + this.b; 50 | }); 51 | }); 52 | 53 | foo(function(a) { 54 | bar(function() { 55 | return a + this.b; 56 | }); 57 | }); 58 | 59 | foo(function(a) { 60 | bar(() => a + this.b); 61 | }); 62 | 63 | foo(function bar() { 64 | console.log('foo'); 65 | }); 66 | 67 | foo(function baz_prototype() { 68 | console.log('foo'); 69 | }); 70 | 71 | baz_prototype.prototype = {}; 72 | 73 | var generatorFunc = function* () { 74 | console.log('I shall not be transformed!'); 75 | }.bind(this); 76 | 77 | foo(function* () { 78 | console.log('I shall not be transformed!'); 79 | }); 80 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function2.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-extra-bind */ 2 | 'use strict'; 3 | 4 | var fn1 = (function() { 5 | console.log('Banana!'); 6 | }).bind(this); 7 | 8 | var fn2 = (function() { 9 | console.log('foo!'); 10 | }).bind(this); 11 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/arrow-function2.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-extra-bind */ 2 | 'use strict'; 3 | 4 | var fn1 = () => { 5 | console.log('Banana!'); 6 | }; 7 | 8 | var fn2 = () => { 9 | console.log('foo!'); 10 | }; 11 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/expect.input.js: -------------------------------------------------------------------------------- 1 | expect(1).toExist(); 2 | expect(1).toNotExist(); 3 | expect(1).toNotBe(); 4 | expect(1).toNotEqual(); 5 | expect(1).toThrow(); 6 | expect(1).toNotThrow(); 7 | 8 | expect(1).toBeA(a); 9 | expect(1).toBeAn(a); 10 | expect(1).toBeA('string'); 11 | expect(1).toBeAn('string'); 12 | expect(1).toNotBeA(a); 13 | expect(1).toNotBeAn(a); 14 | expect(1).toNotBeA('string'); 15 | expect(1).toNotBeAn('string'); 16 | 17 | expect(1).toMatch('string'); 18 | expect(1).toMatch({object: true}); 19 | expect(1).toNotMatch('string'); 20 | expect(1).toNotMatch({object: true}); 21 | 22 | expect(1).toBeFewerThan(); 23 | expect(1).toBeLessThanOrEqualTo(); 24 | expect(1).toBeMoreThan(); 25 | expect(1).toBeGreaterThanOrEqualTo(); 26 | 27 | expect(1).toInclude(); 28 | expect(1).toExclude(); 29 | expect(1).toNotContain(); 30 | expect(1).toNotInclude(); 31 | 32 | expect(1).toIncludeKey(); 33 | expect(1).toContainKey(); 34 | expect(1).toExcludeKey(); 35 | expect(1).toNotContainKey(); 36 | expect(1).toNotIncludeKey(); 37 | 38 | expect(1).toIncludeKeys([1]); 39 | expect(1).toContainKeys([1]); 40 | expect(1).toExcludeKeys([1]); 41 | expect(1).toNotContainKeys([1]); 42 | expect(1).toNotIncludeKeys([1]); 43 | 44 | expect(1).toNotHaveBeenCalled(); 45 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/expect.output.js: -------------------------------------------------------------------------------- 1 | expect(1).toBeTruthy(); 2 | expect(1).toBeFalsy(); 3 | expect(1).not.toBe(); 4 | expect(1).not.toEqual(); 5 | expect(1).toThrow(); 6 | expect(1).not.toThrow(); 7 | 8 | expect(1).toBeInstanceOf(a); 9 | expect(1).toBeInstanceOf(a); 10 | expect(typeof 1).toBe('string'); 11 | expect(typeof 1).toBe('string'); 12 | expect(1).not.toBeInstanceOf(a); 13 | expect(1).not.toBeInstanceOf(a); 14 | expect(typeof 1).not.toBe('string'); 15 | expect(typeof 1).not.toBe('string'); 16 | 17 | expect(1).toMatch('string'); 18 | expect(1).toMatchObject({object: true}); 19 | expect(1).not.toMatch('string'); 20 | expect(1).not.toMatchObject({object: true}); 21 | 22 | expect(1).toBeLessThan(); 23 | expect(1).toBeLessThanOrEqual(); 24 | expect(1).toBeGreaterThan(); 25 | expect(1).toBeGreaterThanOrEqual(); 26 | 27 | expect(1).toContain(); 28 | expect(1).not.toContain(); 29 | expect(1).not.toContain(); 30 | expect(1).not.toContain(); 31 | 32 | expect(Object.keys(1)).toContain(); 33 | expect(Object.keys(1)).toContain(); 34 | expect(Object.keys(1)).not.toContain(); 35 | expect(Object.keys(1)).not.toContain(); 36 | expect(Object.keys(1)).not.toContain(); 37 | 38 | e.forEach(e => { 39 | expect(1).toContain(e); 40 | }); 41 | e.forEach(e => { 42 | expect(1).toContain(e); 43 | }); 44 | e.forEach(e => { 45 | expect(1).not.toContain(e); 46 | }); 47 | e.forEach(e => { 48 | expect(1).not.toContain(e); 49 | }); 50 | e.forEach(e => { 51 | expect(1).not.toContain(e); 52 | }); 53 | 54 | expect(1).not.toHaveBeenCalled(); 55 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/flow-bool-to-boolean.input.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | var a: bool = 10; 4 | var b: boolean = 10; 5 | function c(x: bool): bool { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/flow-bool-to-boolean.output.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | var a: boolean = 10; 4 | var b: boolean = 10; 5 | function c(x: boolean): boolean { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/invalid-requires.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable one-var */ 2 | var foo = require('foo'), 3 | // A comment 4 | bar = require('bar'); 5 | const baz = require('baz'), 6 | fiz = require('fiz'); 7 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/invalid-requires.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable one-var */ 2 | var foo = require('foo'); 3 | 4 | // A comment 5 | var bar = require('bar'); 6 | 7 | const baz = require('baz'); 8 | const fiz = require('fiz'); 9 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/jest-arrow.input.js: -------------------------------------------------------------------------------- 1 | describe('describe', function() { 2 | it('should be happy', function() { 3 | console.log('actually forwards body'); 4 | }); 5 | it('should leave existing arrow functions alone', () => { 6 | }); 7 | describe('nested describe', function() { 8 | xit('disabled one still count', function() { 9 | 10 | }); 11 | xdescribe('disabled describe as well', function() { 12 | 13 | }); 14 | }); 15 | 16 | beforeEach(function() {}); 17 | afterEach(function() {}); 18 | }); 19 | 20 | function containsit() { 21 | } 22 | containsit(function() { 23 | }); 24 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/jest-arrow.output.js: -------------------------------------------------------------------------------- 1 | describe('describe', () => { 2 | it('should be happy', () => { 3 | console.log('actually forwards body'); 4 | }); 5 | it('should leave existing arrow functions alone', () => { 6 | }); 7 | describe('nested describe', () => { 8 | xit('disabled one still count', () => { 9 | 10 | }); 11 | xdescribe('disabled describe as well', () => { 12 | 13 | }); 14 | }); 15 | 16 | beforeEach(() => {}); 17 | afterEach(() => {}); 18 | }); 19 | 20 | function containsit() { 21 | } 22 | containsit(function() { 23 | }); 24 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/jest-remove-describe.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | 14 | var React; 15 | var ReactTestUtils; 16 | 17 | var AutoMockedComponent; 18 | var MockedComponent; 19 | 20 | describe('ReactMockedComponent', function() { 21 | 22 | beforeEach(function() { 23 | React = require('React'); 24 | ReactTestUtils = require('ReactTestUtils'); 25 | 26 | AutoMockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 27 | MockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 28 | 29 | ReactTestUtils.mockComponent(MockedComponent); 30 | }); 31 | 32 | it('should allow an implicitly mocked component to be rendered without warnings', () => { 33 | spyOn(console, 'error'); 34 | ReactTestUtils.renderIntoDocument(); 35 | expect(console.error.calls.count()).toBe(0); 36 | }); 37 | 38 | it('should allow an implicitly mocked component to be updated', () => { 39 | class Wrapper extends React.Component { 40 | state = {foo: 1}; 41 | 42 | update = () => { 43 | this.setState({foo: 2}); 44 | }; 45 | 46 | render() { 47 | return
; 48 | } 49 | } 50 | 51 | var instance = ReactTestUtils.renderIntoDocument(); 52 | 53 | var found = ReactTestUtils.findRenderedComponentWithType( 54 | instance, 55 | AutoMockedComponent 56 | ); 57 | expect(typeof found).toBe('object'); 58 | 59 | instance.update(); 60 | }); 61 | 62 | it('has custom methods on the implicitly mocked component', () => { 63 | var instance = ReactTestUtils.renderIntoDocument(); 64 | expect(typeof instance.hasCustomMethod).toBe('function'); 65 | }); 66 | 67 | it('should allow an explicitly mocked component to be rendered', () => { 68 | ReactTestUtils.renderIntoDocument(); 69 | }); 70 | 71 | it('should allow an explicitly mocked component to be updated', () => { 72 | class Wrapper extends React.Component { 73 | state = {foo: 1}; 74 | 75 | update = () => { 76 | this.setState({foo: 2}); 77 | }; 78 | 79 | render() { 80 | return
; 81 | } 82 | } 83 | 84 | var instance = ReactTestUtils.renderIntoDocument(); 85 | 86 | var found = ReactTestUtils.findRenderedComponentWithType( 87 | instance, 88 | MockedComponent 89 | ); 90 | expect(typeof found).toBe('object'); 91 | 92 | instance.update(); 93 | }); 94 | 95 | it('has custom methods on the explicitly mocked component', () => { 96 | var instance = ReactTestUtils.renderIntoDocument(); 97 | expect(typeof instance.hasCustomMethod).toBe('function'); 98 | }); 99 | 100 | }); 101 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/jest-remove-describe.output.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | 14 | var React; 15 | var ReactTestUtils; 16 | 17 | var AutoMockedComponent; 18 | var MockedComponent; 19 | 20 | beforeEach(function() { 21 | React = require('React'); 22 | ReactTestUtils = require('ReactTestUtils'); 23 | 24 | AutoMockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 25 | MockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 26 | 27 | ReactTestUtils.mockComponent(MockedComponent); 28 | }); 29 | 30 | it('should allow an implicitly mocked component to be rendered without warnings', () => { 31 | spyOn(console, 'error'); 32 | ReactTestUtils.renderIntoDocument(); 33 | expect(console.error.calls.count()).toBe(0); 34 | }); 35 | 36 | it('should allow an implicitly mocked component to be updated', () => { 37 | class Wrapper extends React.Component { 38 | state = {foo: 1}; 39 | 40 | update = () => { 41 | this.setState({foo: 2}); 42 | }; 43 | 44 | render() { 45 | return
; 46 | } 47 | } 48 | 49 | var instance = ReactTestUtils.renderIntoDocument(); 50 | 51 | var found = ReactTestUtils.findRenderedComponentWithType( 52 | instance, 53 | AutoMockedComponent 54 | ); 55 | expect(typeof found).toBe('object'); 56 | 57 | instance.update(); 58 | }); 59 | 60 | it('has custom methods on the implicitly mocked component', () => { 61 | var instance = ReactTestUtils.renderIntoDocument(); 62 | expect(typeof instance.hasCustomMethod).toBe('function'); 63 | }); 64 | 65 | it('should allow an explicitly mocked component to be rendered', () => { 66 | ReactTestUtils.renderIntoDocument(); 67 | }); 68 | 69 | it('should allow an explicitly mocked component to be updated', () => { 70 | class Wrapper extends React.Component { 71 | state = {foo: 1}; 72 | 73 | update = () => { 74 | this.setState({foo: 2}); 75 | }; 76 | 77 | render() { 78 | return
; 79 | } 80 | } 81 | 82 | var instance = ReactTestUtils.renderIntoDocument(); 83 | 84 | var found = ReactTestUtils.findRenderedComponentWithType( 85 | instance, 86 | MockedComponent 87 | ); 88 | expect(typeof found).toBe('object'); 89 | 90 | instance.update(); 91 | }); 92 | 93 | it('has custom methods on the explicitly mocked component', () => { 94 | var instance = ReactTestUtils.renderIntoDocument(); 95 | expect(typeof instance.hasCustomMethod).toBe('function'); 96 | }); 97 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/jest-remove-disable-automock.input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | jest.disableAutomock(); 13 | 14 | var React; 15 | var ReactTestUtils; 16 | 17 | var AutoMockedComponent; 18 | var MockedComponent; 19 | 20 | jest.disableAutomock().mock('xyz'); 21 | jest.enableAutomock(); 22 | jest.unmock('abc'); 23 | jest.autoMockOff(); 24 | 25 | beforeEach(function() { 26 | jest.disableAutomock(); 27 | React = require('React'); 28 | ReactTestUtils = require('ReactTestUtils'); 29 | 30 | AutoMockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 31 | MockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 32 | 33 | ReactTestUtils.mockComponent(MockedComponent); 34 | }); 35 | 36 | it('should allow an implicitly mocked component to be rendered without warnings', () => { 37 | spyOn(console, 'error'); 38 | ReactTestUtils.renderIntoDocument(); 39 | expect(console.error.calls.count()).toBe(0); 40 | }); 41 | 42 | it('should allow an implicitly mocked component to be updated', () => { 43 | class Wrapper extends React.Component { 44 | state = {foo: 1}; 45 | 46 | update = () => { 47 | this.setState({foo: 2}); 48 | }; 49 | 50 | render() { 51 | return
; 52 | } 53 | } 54 | 55 | var instance = ReactTestUtils.renderIntoDocument(); 56 | 57 | var found = ReactTestUtils.findRenderedComponentWithType( 58 | instance, 59 | AutoMockedComponent 60 | ); 61 | expect(typeof found).toBe('object'); 62 | 63 | instance.update(); 64 | }); 65 | 66 | it('has custom methods on the implicitly mocked component', () => { 67 | var instance = ReactTestUtils.renderIntoDocument(); 68 | expect(typeof instance.hasCustomMethod).toBe('function'); 69 | }); 70 | 71 | it('should allow an explicitly mocked component to be rendered', () => { 72 | ReactTestUtils.renderIntoDocument(); 73 | }); 74 | 75 | it('should allow an explicitly mocked component to be updated', () => { 76 | class Wrapper extends React.Component { 77 | state = {foo: 1}; 78 | 79 | update = () => { 80 | this.setState({foo: 2}); 81 | }; 82 | 83 | render() { 84 | return
; 85 | } 86 | } 87 | 88 | var instance = ReactTestUtils.renderIntoDocument(); 89 | 90 | var found = ReactTestUtils.findRenderedComponentWithType( 91 | instance, 92 | MockedComponent 93 | ); 94 | expect(typeof found).toBe('object'); 95 | 96 | instance.update(); 97 | }); 98 | 99 | it('has custom methods on the explicitly mocked component', () => { 100 | var instance = ReactTestUtils.renderIntoDocument(); 101 | expect(typeof instance.hasCustomMethod).toBe('function'); 102 | }); 103 | 104 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/jest-remove-disable-automock.output.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | var React; 13 | var ReactTestUtils; 14 | 15 | var AutoMockedComponent; 16 | var MockedComponent; 17 | 18 | jest.mock('xyz'); 19 | jest.enableAutomock(); 20 | jest.unmock('abc'); 21 | 22 | beforeEach(function() { 23 | React = require('React'); 24 | ReactTestUtils = require('ReactTestUtils'); 25 | 26 | AutoMockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 27 | MockedComponent = jest.genMockFromModule('ReactMockedComponentTestComponent'); 28 | 29 | ReactTestUtils.mockComponent(MockedComponent); 30 | }); 31 | 32 | it('should allow an implicitly mocked component to be rendered without warnings', () => { 33 | spyOn(console, 'error'); 34 | ReactTestUtils.renderIntoDocument(); 35 | expect(console.error.calls.count()).toBe(0); 36 | }); 37 | 38 | it('should allow an implicitly mocked component to be updated', () => { 39 | class Wrapper extends React.Component { 40 | state = {foo: 1}; 41 | 42 | update = () => { 43 | this.setState({foo: 2}); 44 | }; 45 | 46 | render() { 47 | return
; 48 | } 49 | } 50 | 51 | var instance = ReactTestUtils.renderIntoDocument(); 52 | 53 | var found = ReactTestUtils.findRenderedComponentWithType( 54 | instance, 55 | AutoMockedComponent 56 | ); 57 | expect(typeof found).toBe('object'); 58 | 59 | instance.update(); 60 | }); 61 | 62 | it('has custom methods on the implicitly mocked component', () => { 63 | var instance = ReactTestUtils.renderIntoDocument(); 64 | expect(typeof instance.hasCustomMethod).toBe('function'); 65 | }); 66 | 67 | it('should allow an explicitly mocked component to be rendered', () => { 68 | ReactTestUtils.renderIntoDocument(); 69 | }); 70 | 71 | it('should allow an explicitly mocked component to be updated', () => { 72 | class Wrapper extends React.Component { 73 | state = {foo: 1}; 74 | 75 | update = () => { 76 | this.setState({foo: 2}); 77 | }; 78 | 79 | render() { 80 | return
; 81 | } 82 | } 83 | 84 | var instance = ReactTestUtils.renderIntoDocument(); 85 | 86 | var found = ReactTestUtils.findRenderedComponentWithType( 87 | instance, 88 | MockedComponent 89 | ); 90 | expect(typeof found).toBe('object'); 91 | 92 | instance.update(); 93 | }); 94 | 95 | it('has custom methods on the explicitly mocked component', () => { 96 | var instance = ReactTestUtils.renderIntoDocument(); 97 | expect(typeof instance.hasCustomMethod).toBe('function'); 98 | }); 99 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/no-vars.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable one-var, prefer-const */ 2 | 3 | var module = require('module'); 4 | 5 | for (var i = 0; i < 10; i++) { 6 | 7 | } 8 | 9 | for (i = 0; i < 10; i++) { 10 | } 11 | 12 | var letItBe; 13 | var shouldBeLet = 0; 14 | var shouldBeConst = 0; 15 | 16 | function mutate() { 17 | shouldBeLet = 1; 18 | } 19 | 20 | var array = ['a', 'b', 'c', 'd']; 21 | 22 | for (var letter in array) { 23 | console.log(letter); 24 | } 25 | 26 | var object = { 27 | 'a': 1, 28 | 'b': 2, 29 | 'c': 3, 30 | 'd': 4, 31 | }; 32 | 33 | for (var [key, value] of object.entries()) { 34 | console.log(key, value); 35 | } 36 | 37 | for (var [keyTwo, valueTwo] of object.entries()) { 38 | keyTwo = 'something'; 39 | console.log(keyTwo, valueTwo); 40 | } 41 | 42 | var whileIterator = 10; 43 | while (whileIterator > 0) { 44 | whileIterator--; 45 | } 46 | 47 | var z = 10; 48 | do { 49 | var a = 0; 50 | var b = 1; 51 | a += b; 52 | console.log(a, b); 53 | } while (z--); 54 | 55 | (() => { 56 | var a = 1; 57 | 58 | return (() => { 59 | return _ = _ => _ => _ => _ => _ => { a = 7; }; 60 | })(); 61 | })(); 62 | 63 | (() => { 64 | var a = 1, b = 2, c = 3; 65 | 66 | a++; 67 | 68 | return b + a; 69 | })(); 70 | 71 | (() => { 72 | for (var i = 0, z = 77; i < 10; i++) { 73 | setTimeout(() => console.log(i)); 74 | } 75 | 76 | for (var j = 0; j < 10; j++) { 77 | _.defer(function() { 78 | console.log(j); 79 | }); 80 | } 81 | 82 | for (var k = 0; k < 10; k++) { 83 | console.log(i); 84 | } 85 | 86 | // I should be left alone 87 | for (let z = 0; z < 10; z++) { 88 | setTimeout(() => console.log(z)); 89 | } 90 | })(); 91 | 92 | (() => { 93 | var {foo, number} = bar; 94 | foo = xy; 95 | number++; 96 | })(); 97 | 98 | (() => { 99 | // should not destroy comments 100 | var querySet = {}; 101 | if (true) { 102 | ({querySet} = someComputation()); 103 | } 104 | })(); 105 | 106 | (() => { 107 | var {...foo} = bar; 108 | bar = foo; 109 | var {...foo2} = bar2; 110 | foo2 = bar2; 111 | })(); 112 | 113 | (() => { 114 | var [first, ...rest] = foo; 115 | bar = foo; 116 | var [first2, ...rest2] = foo2; 117 | rest2 = foo2; 118 | })(); 119 | 120 | var myDoubleLet = 10; 121 | myDoubleLet++; 122 | var myDoubleLet = 20; 123 | myDoubleLet++; 124 | 125 | var myFakeConstant = 10; 126 | var myFakeConstant = 20; 127 | 128 | if (true) { 129 | var blockScopeAbuse = 10; 130 | } 131 | console.log(blockScopeAbuse); 132 | 133 | console.log(usedTooEarly); 134 | var usedTooEarly = 10; 135 | 136 | for (var dangerousLoop = 0; dangerousLoop < 10; dangerousLoop++) { 137 | setTimeout(() => { 138 | console.log(dangerousLoop); 139 | }, 100); 140 | } 141 | 142 | console.log(destructuringAlias); 143 | var {destructuringToBeAliased: destructuringAlias} = whatever(); 144 | 145 | var {destructuringB} = whatever(); 146 | var {destructuringB} = whateverElse(); 147 | 148 | var [, destructuringC, destructuringD] = whatever(); 149 | 150 | setSetByHoistedFunction(); 151 | var setByHoistedFunction; 152 | function setSetByHoistedFunction() { 153 | setByHoistedFunction = 10; 154 | } 155 | console.log(setByHoistedFunction); 156 | 157 | var mutatedInAFunction = 10; 158 | function mutateMutatedInAFunction() { 159 | mutatedInAFunction = 20; 160 | } 161 | if (true) { 162 | var usedInAFunction = 10; 163 | } 164 | function useUsedInAFunction() { 165 | console.log(usedInAFunction); 166 | } 167 | 168 | 169 | function jasklfjasklfjdsakl() { 170 | var { 171 | firstPropertyAsPartOfDeepDestructuring: { 172 | propertyExtractedFromDeepDestructuring, 173 | }, 174 | } = objectToDeepDestructure; 175 | 176 | propertyExtractedFromDeepDestructuring = 10; 177 | } 178 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/no-vars.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable one-var, prefer-const */ 2 | 3 | const module = require('module'); 4 | 5 | for (var i = 0; i < 10; i++) { 6 | 7 | } 8 | 9 | for (i = 0; i < 10; i++) { 10 | } 11 | 12 | let letItBe; 13 | let shouldBeLet = 0; 14 | const shouldBeConst = 0; 15 | 16 | function mutate() { 17 | shouldBeLet = 1; 18 | } 19 | 20 | const array = ['a', 'b', 'c', 'd']; 21 | 22 | for (const letter in array) { 23 | console.log(letter); 24 | } 25 | 26 | const object = { 27 | 'a': 1, 28 | 'b': 2, 29 | 'c': 3, 30 | 'd': 4, 31 | }; 32 | 33 | for (const [key, value] of object.entries()) { 34 | console.log(key, value); 35 | } 36 | 37 | for (let [keyTwo, valueTwo] of object.entries()) { 38 | keyTwo = 'something'; 39 | console.log(keyTwo, valueTwo); 40 | } 41 | 42 | let whileIterator = 10; 43 | while (whileIterator > 0) { 44 | whileIterator--; 45 | } 46 | 47 | let z = 10; 48 | do { 49 | let a = 0; 50 | const b = 1; 51 | a += b; 52 | console.log(a, b); 53 | } while (z--); 54 | 55 | (() => { 56 | let a = 1; 57 | 58 | return (() => { 59 | return _ = _ => _ => _ => _ => _ => { a = 7; }; 60 | })(); 61 | })(); 62 | 63 | (() => { 64 | let a = 1, b = 2, c = 3; 65 | 66 | a++; 67 | 68 | return b + a; 69 | })(); 70 | 71 | (() => { 72 | for (var i = 0, z = 77; i < 10; i++) { 73 | setTimeout(() => console.log(i)); 74 | } 75 | 76 | for (var j = 0; j < 10; j++) { 77 | _.defer(function() { 78 | console.log(j); 79 | }); 80 | } 81 | 82 | for (let k = 0; k < 10; k++) { 83 | console.log(i); 84 | } 85 | 86 | // I should be left alone 87 | for (let z = 0; z < 10; z++) { 88 | setTimeout(() => console.log(z)); 89 | } 90 | })(); 91 | 92 | (() => { 93 | let {foo, number} = bar; 94 | foo = xy; 95 | number++; 96 | })(); 97 | 98 | (() => { 99 | // should not destroy comments 100 | let querySet = {}; 101 | if (true) { 102 | ({querySet} = someComputation()); 103 | } 104 | })(); 105 | 106 | (() => { 107 | const {...foo} = bar; 108 | bar = foo; 109 | let {...foo2} = bar2; 110 | foo2 = bar2; 111 | })(); 112 | 113 | (() => { 114 | const [first, ...rest] = foo; 115 | bar = foo; 116 | let [first2, ...rest2] = foo2; 117 | rest2 = foo2; 118 | })(); 119 | 120 | var myDoubleLet = 10; 121 | myDoubleLet++; 122 | var myDoubleLet = 20; 123 | myDoubleLet++; 124 | 125 | var myFakeConstant = 10; 126 | var myFakeConstant = 20; 127 | 128 | if (true) { 129 | var blockScopeAbuse = 10; 130 | } 131 | console.log(blockScopeAbuse); 132 | 133 | console.log(usedTooEarly); 134 | var usedTooEarly = 10; 135 | 136 | for (var dangerousLoop = 0; dangerousLoop < 10; dangerousLoop++) { 137 | setTimeout(() => { 138 | console.log(dangerousLoop); 139 | }, 100); 140 | } 141 | 142 | console.log(destructuringAlias); 143 | var {destructuringToBeAliased: destructuringAlias} = whatever(); 144 | 145 | var {destructuringB} = whatever(); 146 | var {destructuringB} = whateverElse(); 147 | 148 | const [, destructuringC, destructuringD] = whatever(); 149 | 150 | setSetByHoistedFunction(); 151 | var setByHoistedFunction; 152 | function setSetByHoistedFunction() { 153 | setByHoistedFunction = 10; 154 | } 155 | console.log(setByHoistedFunction); 156 | 157 | let mutatedInAFunction = 10; 158 | function mutateMutatedInAFunction() { 159 | mutatedInAFunction = 20; 160 | } 161 | if (true) { 162 | var usedInAFunction = 10; 163 | } 164 | function useUsedInAFunction() { 165 | console.log(usedInAFunction); 166 | } 167 | 168 | 169 | function jasklfjasklfjdsakl() { 170 | let { 171 | firstPropertyAsPartOfDeepDestructuring: { 172 | propertyExtractedFromDeepDestructuring, 173 | }, 174 | } = objectToDeepDestructure; 175 | 176 | propertyExtractedFromDeepDestructuring = 10; 177 | } 178 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/object-shorthand.input.js: -------------------------------------------------------------------------------- 1 | const one = 1; 2 | const two = 2; 3 | const four = 4; 4 | 5 | const myFunc = function() { }; 6 | 7 | const myObj = { 8 | one: one, 9 | two: two, 10 | three: one, 11 | 'four': four, 12 | myFunc: myFunc, 13 | ['computed' + 'property']: 1, 14 | method: function() { }, 15 | method2: () => { }, 16 | method3: function foo(n) { 17 | if (n === 0) { 18 | return n; 19 | } 20 | return foo(n - 1) + n; 21 | }, 22 | method4: function whatever() { 23 | return one; 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/object-shorthand.output.js: -------------------------------------------------------------------------------- 1 | const one = 1; 2 | const two = 2; 3 | const four = 4; 4 | 5 | const myFunc = function() { }; 6 | 7 | const myObj = { 8 | one, 9 | two, 10 | three: one, 11 | four, 12 | myFunc, 13 | ['computed' + 'property']: 1, 14 | method() { }, 15 | method2: () => { }, 16 | method3: function foo(n) { 17 | if (n === 0) { 18 | return n; 19 | } 20 | return foo(n - 1) + n; 21 | }, 22 | method4() { 23 | return one; 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-copyProperties.input.js: -------------------------------------------------------------------------------- 1 | var copyProperties = require('copyProperties'); 2 | 3 | copyProperties(a, {a: 1}); 4 | 5 | copyProperties({a: 1}, {b: 1}); 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-copyProperties.output.js: -------------------------------------------------------------------------------- 1 | Object.assign(a, {a: 1}); 2 | 3 | ({ 4 | a: 1, 5 | b: 1, 6 | }); 7 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-copyProperties2.input.js: -------------------------------------------------------------------------------- 1 | var copyProperties = require('copyProperties'); 2 | 3 | copyProperties(a, {a: 1}); 4 | 5 | copyProperties(A, B); 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-copyProperties2.output.js: -------------------------------------------------------------------------------- 1 | var copyProperties = require('copyProperties'); 2 | 3 | Object.assign(a, {a: 1}); 4 | 5 | copyProperties(A, B); 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-merge.input.js: -------------------------------------------------------------------------------- 1 | var merge = require('merge'); 2 | 3 | var x = merge(a); 4 | 5 | merge(a, b); 6 | 7 | merge({a: 1}, b); 8 | 9 | merge({a: 1}, c, {b: 2}); 10 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-merge.output.js: -------------------------------------------------------------------------------- 1 | var x = { 2 | ...a, 3 | }; 4 | 5 | ({ 6 | ...a, 7 | ...b, 8 | }); 9 | 10 | ({ 11 | a: 1, 12 | ...b, 13 | }); 14 | 15 | ({ 16 | a: 1, 17 | ...c, 18 | b: 2, 19 | }); 20 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-object-assign.input.js: -------------------------------------------------------------------------------- 1 | let x = Object.assign({}, { a: 1 }, { b: 2 }); 2 | 3 | Object.assign({}, a); 4 | 5 | Object.assign({ a: 1 }, b, { c: 3 }); 6 | 7 | Object.assign(a, b); 8 | 9 | Object.assign({}, ...b); 10 | 11 | Object.assign( 12 | { 13 | // comment 1 14 | a: 1 15 | }, 16 | // comment 2 17 | { b: 2 }, 18 | // comment 3 19 | c, 20 | d /* comment 4 */ 21 | ); 22 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-object-assign.output.js: -------------------------------------------------------------------------------- 1 | let x = { 2 | a: 1, 3 | b: 2, 4 | }; 5 | 6 | ({ 7 | ...a, 8 | }); 9 | 10 | ({ 11 | a: 1, 12 | ...b, 13 | c: 3, 14 | }); 15 | 16 | Object.assign(a, b); 17 | 18 | Object.assign({}, ...b); 19 | 20 | ({ 21 | // comment 1 22 | a: 1, 23 | 24 | // comment 2 25 | b: 2, 26 | 27 | // comment 3 28 | ...c, 29 | 30 | ...d /* comment 4 */, 31 | }); 32 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-requires.input.js: -------------------------------------------------------------------------------- 1 | var merge = require('merge'); 2 | var dupMerge = require('merge'); 3 | var nonUsedModule = require('nonUsedModule'); 4 | 5 | // TODO: Handle removing these vars 6 | var {export1, export2} = require('nonUsedModule2'); 7 | 8 | // Leave side effect modules alone 9 | require('sideEffectModule'); 10 | 11 | var x = merge(a); 12 | var a = dupMerge(x); 13 | 14 | window.nonUsedModule; 15 | 16 | dupMerge; 17 | 18 | function newScope() { 19 | var dupMerge2 = require('merge'); 20 | var nonUsedModule2 = require('nonUsedModule2'); 21 | 22 | dupMerge2; 23 | } 24 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rm-requires.output.js: -------------------------------------------------------------------------------- 1 | var merge = require('merge'); 2 | 3 | // TODO: Handle removing these vars 4 | var {export1, export2} = require('nonUsedModule2'); 5 | 6 | // Leave side effect modules alone 7 | require('sideEffectModule'); 8 | 9 | var x = merge(a); 10 | var a = merge(x); 11 | 12 | window.nonUsedModule; 13 | 14 | merge; 15 | 16 | function newScope() { 17 | var dupMerge2 = require('merge'); 18 | 19 | dupMerge2; 20 | } 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/template-literals.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | var test; 3 | test = 'hi' + true; 4 | test = true + 'hi'; 5 | 6 | test = 'hi' + 1 + 2; 7 | test = 'hi' + 1 + 2 + 3; 8 | test = 1 + 2 + 'hi'; 9 | test = 1 + 2 + 'a' + b; 10 | 11 | test = 'hi' + 1 - 2; 12 | test = 'hi' + 1 - 2 - 3; 13 | test = 1 - 2 + 'hi'; 14 | 15 | test = 'hi' + 1 * 2; 16 | test = 1 * 2 + 'hi'; 17 | 18 | test = 'hi' + 1 / 2; 19 | test = 1 / 2 + 'hi'; 20 | 21 | test = 'hi' + foo; // single 22 | test = "hi" + foo; // double 23 | test = `hi` + foo; // template literal 24 | test = foo`hi` + bar; // tagged template literal 25 | 26 | test = 'a\'b' + c; // escaped quote 27 | test = 'a\"b' + c; // escaped quote 28 | test = "a\'b" + c; // escaped quote 29 | test = "a\"b" + c; // escaped quote 30 | test = 'a\'b' + 'c'; // escaped quote 31 | test = 'a\'b' + "c\"d"; // escaped quotes of different kinds 32 | test = 'a\\"b' + c; // non-escaped quote 33 | 34 | test = 'a\tb' + c; // tab 35 | test = 'a\tb' + 'c'; // tab 36 | test = 'a\u00A9' + b; // unicode escape 37 | 38 | test = 'hi\nhello' + foo; // line break 39 | test = 'hi' + // comment in the middle 40 | foo; 41 | test = 'hi' // comment in the middle 42 | + foo; 43 | test = 'hi' + // comment in the middle 44 | 'foo' + // and in the middle again 45 | foo; // and at the end 46 | 47 | test = `hi` + 'foo'; // template literal and string 48 | test = 'hi' + `foo`; // string and template literal 49 | test = 'hi' + 'foo'; // two strings 50 | test = 'hi' + 'foo' + 'bar'; // three strings 51 | test = `hi` + `foo`; // two template literals 52 | test = `hi` + `foo` + `bar`; // three template literals 53 | 54 | test = `${hi}` + 'foo'; // template literal with expression and string 55 | test = 'foo' + `${hi}`; // string and template literal with expression 56 | test = `hi ${foo} there` + `oh ${bar} hello`; // template literals with expressions 57 | test = foo + `/${bar}`; 58 | 59 | test = '(' + foo + ')'; 60 | test = '(' + foo + ')' + bar; 61 | test = '(' + (foo + bar) + ')'; 62 | test = '(' + (1 + 1) + ')'; 63 | test = (a + 'b'); 64 | 65 | test = `hi${foo}` + bar; 66 | 67 | test = '${hi}' + foo; // escaping a string 68 | test = '${hi}${hello}' + foo; // escaping a string 69 | test = '${hi}' + '${hello}'; // escaping a string 70 | 71 | test = foo + 'hi'; 72 | test = foo + 'hi' + bar; 73 | test = foo + 'hi' + bar + baz; 74 | 75 | test = { a: 'hi' + foo }; // in an object 76 | test = { ['a' + b]: 'c' + d }; // computed properties 77 | 78 | test = ['hi' + foo]; // in an array 79 | test = [ 80 | foo + 'bar', // comment 81 | foo + 'bar', /* comment */ 82 | foo + 'bar', /* comment */ // comment 83 | ]; 84 | 85 | test = +1 + 100; 86 | test = +1 + '100'; 87 | test = +'1' + 100; 88 | test = +'1' + +100; 89 | test = +'1' + '100'; 90 | test = 1 + +100; 91 | test = 1 + +'100'; 92 | test = '1' + -100; // this could probably be better 93 | test = 1.2 + 'a' + b; // floats 94 | test = 1.2 + 'a'; // floats 95 | 96 | test = 1 + 1; 97 | test = 1 - 1; 98 | test = foo + 1; 99 | 100 | test = 'hi' + foo + 1; 101 | test = 'hi' + foo - 1; 102 | test = 'hi' + (foo + 1); 103 | 104 | test = 'hi' + foo.join(','); // function 105 | 106 | test = 1 + a.toString() + 'b'; 107 | test = 1 + String(a) + 'b'; 108 | test = 1 + new String(a) + 'b'; 109 | 110 | test = 1 + a.b() + 'c'; 111 | test = 1 + Foo(a) + 'b'; 112 | test = 1 + new Foo(a) + 'b'; 113 | 114 | test = 'hi' + foo.bar; // object member 115 | test = foo.bar + 'hi'; 116 | test = '(' + foo.bar + ')'; 117 | test = 'hi' + foo['bar']; 118 | 119 | test = foo + bar + 'hi'; // foo and bar could be numeric 120 | test = 'hi' + foo + bar; 121 | 122 | test = 'foo' + (bar ? 'bar' : ''); 123 | 124 | foo('hi' + foo); 125 | foo(foo + 'hi'); 126 | foo(a + '\\?.*' + b); 127 | 128 | function a(b = 'c' + d) { 129 | return b + 'e'; 130 | } 131 | 132 | (b = 'c' + d) => { 133 | return b + 'e'; 134 | }; 135 | 136 | (b = 'c' + d) => b + 'e'; 137 | (b = 'c' + d) => (b + 'e'); 138 | 139 | test = a + 'b' + `c${'d' + e}`; // nested concatenation in template literals 140 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/template-literals.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | var test; 3 | test = 'hitrue'; 4 | test = 'truehi'; 5 | 6 | test = 'hi12'; 7 | test = 'hi123'; 8 | test = `${1 + 2}hi`; 9 | test = `${1 + 2}a${b}`; 10 | 11 | test = 'hi1' - 2; 12 | test = 'hi1' - 2 - 3; 13 | test = `${1 - 2}hi`; 14 | 15 | test = `hi${1 * 2}`; 16 | test = `${1 * 2}hi`; 17 | 18 | test = `hi${1 / 2}`; 19 | test = `${1 / 2}hi`; 20 | 21 | test = `hi${foo}`; // single 22 | test = `hi${foo}`; // double 23 | test = `hi${foo}`; // template literal 24 | test = foo`hi` + bar; // tagged template literal 25 | 26 | test = `a\'b${c}`; // escaped quote 27 | test = `a\"b${c}`; // escaped quote 28 | test = `a\'b${c}`; // escaped quote 29 | test = `a\"b${c}`; // escaped quote 30 | test = 'a\'bc'; // escaped quote 31 | test = 'a\'bc"d'; // escaped quotes of different kinds 32 | test = `a\\"b${c}`; // non-escaped quote 33 | 34 | test = `a\tb${c}`; // tab 35 | test = 'a\tbc'; // tab 36 | test = `a\u00A9${b}`; // unicode escape 37 | 38 | test = `hi\nhello${foo}`; // line break 39 | test = // comment in the middle 40 | `hi${foo}`; 41 | test = // comment in the middle 42 | `hi${foo}`; 43 | test = // comment in the middle 44 | // and in the middle again 45 | `hifoo${foo}`; // and at the end 46 | 47 | test = 'hifoo'; // template literal and string 48 | test = 'hifoo'; // string and template literal 49 | test = 'hifoo'; // two strings 50 | test = 'hifoobar'; // three strings 51 | test = 'hifoo'; // two template literals 52 | test = 'hifoobar'; // three template literals 53 | 54 | test = `${hi}foo`; // template literal with expression and string 55 | test = `foo${hi}`; // string and template literal with expression 56 | test = `hi ${foo} thereoh ${bar} hello`; // template literals with expressions 57 | test = `${foo}/${bar}`; 58 | 59 | test = `(${foo})`; 60 | test = `(${foo})${bar}`; 61 | test = `(${foo + bar})`; 62 | test = `(${1 + 1})`; 63 | test = (`${a}b`); 64 | 65 | test = `hi${foo}${bar}`; 66 | 67 | test = `\${hi}${foo}`; // escaping a string 68 | test = `\${hi}\${hello}${foo}`; // escaping a string 69 | test = '${hi}${hello}'; // escaping a string 70 | 71 | test = `${foo}hi`; 72 | test = `${foo}hi${bar}`; 73 | test = `${foo}hi${bar}${baz}`; 74 | 75 | test = { a: `hi${foo}` }; // in an object 76 | test = { [`a${b}`]: `c${d}` }; // computed properties 77 | 78 | test = [`hi${foo}`]; // in an array 79 | test = [ 80 | `${foo}bar`, // comment 81 | `${foo}bar`, /* comment */ 82 | `${foo}bar`, /* comment */ // comment 83 | ]; 84 | 85 | test = +1 + 100; 86 | test = `${+1}100`; 87 | test = +'1' + 100; 88 | test = +'1' + +100; 89 | test = `${+'1'}100`; 90 | test = 1 + +100; 91 | test = 1 + +'100'; 92 | test = `1${-100}`; // this could probably be better 93 | test = `1.2a${b}`; // floats 94 | test = '1.2a'; // floats 95 | 96 | test = 1 + 1; 97 | test = 1 - 1; 98 | test = foo + 1; 99 | 100 | test = `hi${foo}1`; 101 | test = `hi${foo}` - 1; 102 | test = `hi${foo + 1}`; 103 | 104 | test = `hi${foo.join(',')}`; // function 105 | 106 | test = `1${a.toString()}b`; 107 | test = `1${String(a)}b`; 108 | test = `1${new String(a)}b`; 109 | 110 | test = `${1 + a.b()}c`; 111 | test = `${1 + Foo(a)}b`; 112 | test = `${1 + new Foo(a)}b`; 113 | 114 | test = `hi${foo.bar}`; // object member 115 | test = `${foo.bar}hi`; 116 | test = `(${foo.bar})`; 117 | test = `hi${foo['bar']}`; 118 | 119 | test = `${foo + bar}hi`; // foo and bar could be numeric 120 | test = `hi${foo}${bar}`; 121 | 122 | test = `foo${bar ? 'bar' : ''}`; 123 | 124 | foo(`hi${foo}`); 125 | foo(`${foo}hi`); 126 | foo(`${a}\\?.*${b}`); 127 | 128 | function a(b = `c${d}`) { 129 | return `${b}e`; 130 | } 131 | 132 | (b = `c${d}`) => { 133 | return `${b}e`; 134 | }; 135 | 136 | (b = `c${d}`) => `${b}e`; 137 | (b = `c${d}`) => (`${b}e`); 138 | 139 | test = `${a}bc${'d' + e}`; // nested concatenation in template literals 140 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/touchable.input.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | ; 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ; 13 | 14 | 15 | 16 | 17 | 18 | ; 19 | 20 | 21 | 1 22 | 2 23 | ; 24 | 25 | 26 | 27 | ; 28 | 29 | 30 | 31 | ; 32 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/touchable.output.js: -------------------------------------------------------------------------------- 1 | ; 2 | 3 | 4 | 5 | 6 | 7 | 8 | ; 9 | 10 | 11 | 12 | 13 | 14 | ; 15 | 16 | 17 | 1 18 | 2 19 | ; 20 | 21 | ; 22 | 23 | 24 | 25 | ; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/trailing-commas.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-dangle */ 2 | const object = { 3 | hello: 'hello', 4 | allo: 'allo', 5 | hola: 'hola' 6 | }; 7 | 8 | const object2 = {hello: 'hello', allo: 'allo', hola: 'hola'}; 9 | 10 | const array = [ 11 | 'hello', 12 | 'allo', 13 | 'hola' 14 | ]; 15 | 16 | const array2 = ['hello', 'allo', 'hola']; 17 | 18 | test({ 19 | test: 'test' 20 | }); 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/trailing-commas.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-dangle */ 2 | const object = { 3 | hello: 'hello', 4 | allo: 'allo', 5 | hola: 'hola', 6 | }; 7 | 8 | const object2 = {hello: 'hello', allo: 'allo', hola: 'hola'}; 9 | 10 | const array = [ 11 | 'hello', 12 | 'allo', 13 | 'hola', 14 | ]; 15 | 16 | const array2 = ['hello', 'allo', 'hola']; 17 | 18 | test({ 19 | test: 'test', 20 | }); 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/unchain-variables.input.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable one-var, prefer-const */ 2 | var foo = true, 3 | // A comment 4 | bar = false; 5 | const baz = 1, 6 | fiz = '2'; 7 | let buzz = 3.3, 8 | biz = {}; 9 | var hello, ohai = function ohai() {}, hi; 10 | var Neil, deGrasse, Tyson; 11 | for (var i = 0, j = 10; i < j; i++, j--) { 12 | console.log(i, j); 13 | } 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/unchain-variables.output.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable one-var, prefer-const */ 2 | var foo = true; 3 | 4 | // A comment 5 | var bar = false; 6 | 7 | const baz = 1; 8 | const fiz = '2'; 9 | let buzz = 3.3; 10 | let biz = {}; 11 | var hello; 12 | var ohai = function ohai() {}; 13 | var hi; 14 | var Neil; 15 | var deGrasse; 16 | var Tyson; 17 | for (var i = 0, j = 10; i < j; i++, j--) { 18 | console.log(i, j); 19 | } 20 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/underscore-to-lodash-native.input.js: -------------------------------------------------------------------------------- 1 | // Test comment 2 | const _ = require('underscore'); 3 | _.forEach([1, 2], num => num); 4 | const test = [{a: 1}, {a: 2}, {a: 3}]; 5 | const result = _.pluck(test, 'a'); 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/underscore-to-lodash-native.output.js: -------------------------------------------------------------------------------- 1 | // Test comment 2 | import { pluck } from 'lodash'; 3 | [1, 2].forEach(num => num); 4 | const test = [{a: 1}, {a: 2}, {a: 3}]; 5 | const result = pluck(test, 'a'); 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/unquote-properties.input.js: -------------------------------------------------------------------------------- 1 | var x = { 2 | 'quotedProp': 1, 3 | unquotedProp: 2, 4 | 'quoted-prop': 3, 5 | method() { return 4; }, 6 | 'quotedMethod'() { return 4; }, 7 | '_quotedProp': 5, 8 | '$quotedProp': 6, 9 | 'ĦĔĽĻŎ': 7, 10 | 'quoted prop': 8, 11 | 'class': 9, 12 | 1: 10, 13 | '2': 11, 14 | [Math.random()]() { return 'oh no'; }, 15 | [Math.random()]: 13, 16 | ['quoted computed prop']: 14, 17 | }; 18 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/unquote-properties.output.js: -------------------------------------------------------------------------------- 1 | var x = { 2 | quotedProp: 1, 3 | unquotedProp: 2, 4 | 'quoted-prop': 3, 5 | method() { return 4; }, 6 | 'quotedMethod'() { return 4; }, 7 | _quotedProp: 5, 8 | $quotedProp: 6, 9 | ĦĔĽĻŎ: 7, 10 | 'quoted prop': 8, 11 | class: 9, 12 | 1: 10, 13 | 2: 11, 14 | [Math.random()]() { return 'oh no'; }, 15 | [Math.random()]: 13, 16 | ['quoted computed prop']: 14, 17 | }; 18 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/use-strict.input.js: -------------------------------------------------------------------------------- 1 | function x() { 2 | console.log('Banana'); 3 | } 4 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/use-strict.output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function x() { 3 | console.log('Banana'); 4 | } 5 | -------------------------------------------------------------------------------- /transforms/__tests__/arrow-function-arguments-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'arrow-function-arguments'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/arrow-function-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | const printOptions = { 6 | trailingComma: true, 7 | }; 8 | defineTest(__dirname, 'arrow-function', { 9 | 'inline-single-expressions': true, 10 | printOptions, 11 | }); 12 | defineTest(__dirname, 'arrow-function', {}, 'arrow-function2'); 13 | 14 | defineTest(__dirname, 'arrow-function', { 15 | 'max-width': 40, 16 | }, 'arrow-function-length-40'); 17 | -------------------------------------------------------------------------------- /transforms/__tests__/expect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'expect'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/flow-bool-to-boolean-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'flow-bool-to-boolean', null, 'flow-bool-to-boolean'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/invalid-requires-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'invalid-requires'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/jest-11-update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'jest-11-update', null, '__tests__/jest-11-update'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/jest-arrow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'jest-arrow'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/jest-remove-describe-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | defineTest(__dirname, 'jest-remove-describe'); 6 | -------------------------------------------------------------------------------- /transforms/__tests__/jest-remove-disable-automock-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | defineTest(__dirname, 'jest-remove-disable-automock'); 6 | -------------------------------------------------------------------------------- /transforms/__tests__/jest-rm-mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest( 5 | __dirname, 6 | 'jest-rm-mock', 7 | { 8 | moduleNames: ['cx', 'ix'], 9 | }, 10 | '__tests__/jest-rm-mock' 11 | ); 12 | -------------------------------------------------------------------------------- /transforms/__tests__/jest-update-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'jest-update', null, '__tests__/jest-update'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/no-reassign-params-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest; 4 | const transform = require('../no-reassign-params'); 5 | 6 | describe('no-reassign-params', () => { 7 | // Ignore generated files 8 | defineInlineTest( 9 | transform, 10 | undefined, 11 | ` 12 | // @` + 13 | `generated 14 | function foo(boo) { 15 | boo++ 16 | }`, 17 | '', 18 | ); 19 | 20 | // Function declaration 21 | defineInlineTest( 22 | transform, 23 | undefined, 24 | ` 25 | function foo(boo, val) { 26 | val = 4; 27 | 28 | function bar(val) { 29 | return val + 1; 30 | } 31 | 32 | return boo++ + val; 33 | }`, 34 | ` 35 | function foo(boo, val) { 36 | let localBoo = boo; 37 | let localVal = val; 38 | localVal = 4; 39 | 40 | function bar(val) { 41 | return val + 1; 42 | } 43 | 44 | return localBoo++ + localVal; 45 | }`, 46 | ); 47 | 48 | // arrow function 49 | defineInlineTest( 50 | transform, 51 | undefined, 52 | ` 53 | (boo, val) => { 54 | val = 4; 55 | 56 | function bar(val) { 57 | return val + 1; 58 | } 59 | 60 | return boo++ + val; 61 | }`, 62 | ` 63 | (boo, val) => { 64 | let localBoo = boo; 65 | let localVal = val; 66 | localVal = 4; 67 | 68 | function bar(val) { 69 | return val + 1; 70 | } 71 | 72 | return localBoo++ + localVal; 73 | }`, 74 | ); 75 | 76 | // function assignment 77 | defineInlineTest( 78 | transform, 79 | undefined, 80 | ` 81 | const foo3 = function(boo, val) { 82 | val = 4; 83 | 84 | function bar(val) { 85 | return val + 1; 86 | } 87 | 88 | return boo++ + val; 89 | } 90 | `, 91 | ` 92 | const foo3 = function(boo, val) { 93 | let localBoo = boo; 94 | let localVal = val; 95 | localVal = 4; 96 | 97 | function bar(val) { 98 | return val + 1; 99 | } 100 | 101 | return localBoo++ + localVal; 102 | }`, 103 | ); 104 | 105 | // inner function 106 | defineInlineTest( 107 | transform, 108 | undefined, 109 | ` 110 | function foo(bar) { 111 | function boo(bar) { 112 | bar = 2; 113 | return bar; 114 | } 115 | }`, 116 | ` 117 | function foo(bar) { 118 | function boo(bar) { 119 | let localBar = bar; 120 | localBar = 2; 121 | return localBar; 122 | } 123 | }`, 124 | ); 125 | 126 | // defined in outer scope. Should leave unchanged. 127 | // Can support this later if necessary 128 | defineInlineTest( 129 | transform, 130 | undefined, 131 | ` 132 | let localFoo = 2; 133 | function foo(foo) { 134 | foo = 4; 135 | return foo; 136 | }`, 137 | '', 138 | ); 139 | 140 | defineInlineTest( 141 | transform, 142 | undefined, 143 | ` 144 | function foo({x}) { 145 | x = 4; 146 | return x; 147 | }`, 148 | ` 149 | function foo({x}) { 150 | let localX = x; 151 | localX = 4; 152 | return localX; 153 | }`, 154 | ); 155 | 156 | defineInlineTest( 157 | transform, 158 | undefined, 159 | ` 160 | function foo(keys) { 161 | keys++; 162 | [].push({ 163 | keys: keys 164 | }); 165 | }`, 166 | ` 167 | function foo(keys) { 168 | let localKeys = keys; 169 | localKeys++; 170 | [].push({ 171 | keys: localKeys 172 | }); 173 | }`, 174 | ); 175 | 176 | defineInlineTest( 177 | transform, 178 | undefined, 179 | ` 180 | function foo(x, y, z) { 181 | ({y, z} = x); 182 | 183 | return y + z; 184 | }`, 185 | ` 186 | function foo(x, y, z) { 187 | let localY = y; 188 | let localZ = z; 189 | ({y: localY, z: localZ} = x); 190 | 191 | return localY + localZ; 192 | }`, 193 | ); 194 | 195 | defineInlineTest( 196 | transform, 197 | undefined, 198 | ` 199 | function foo(x, y, z) { 200 | ({y: y, z: z} = x); 201 | 202 | return y + z; 203 | }`, 204 | ` 205 | function foo(x, y, z) { 206 | let localY = y; 207 | let localZ = z; 208 | ({y: localY, z: localZ} = x); 209 | 210 | return localY + localZ; 211 | }`, 212 | ); 213 | 214 | defineInlineTest( 215 | transform, 216 | undefined, 217 | ` 218 | function foo(...args) { 219 | args = args.slice(0, 1); 220 | }`, 221 | ` 222 | function foo(...args) { 223 | let localArgs = args; 224 | localArgs = localArgs.slice(0, 1); 225 | }`, 226 | ); 227 | 228 | defineInlineTest( 229 | transform, 230 | undefined, 231 | ` 232 | function foo(arg) { 233 | arg = { 234 | arg: arg 235 | }; 236 | }`, 237 | ` 238 | function foo(arg) { 239 | let localArg = arg; 240 | localArg = { 241 | arg: localArg 242 | }; 243 | }`, 244 | ); 245 | 246 | defineInlineTest( 247 | transform, 248 | undefined, 249 | ` 250 | const toImage = ({focus, image, ...rest}) => ({ 251 | ...rest, 252 | ...image, 253 | ...(focus ? {focusX: focus.x, focusY: focus.y} : null), 254 | });`, 255 | '', 256 | ); 257 | 258 | defineInlineTest( 259 | transform, 260 | undefined, 261 | ` 262 | const GKSwitch = ({label, value, onChange, ...props}) => ( 263 | 264 | {label} 265 | 266 | 267 | );`, 268 | '', 269 | ); 270 | 271 | defineInlineTest( 272 | transform, 273 | undefined, 274 | ` 275 | function foo([{value: Component}, props]) { 276 | Component = 4; 277 | }`, 278 | ` 279 | function foo([{value: Component}, props]) { 280 | let LocalComponent = Component; 281 | LocalComponent = 4; 282 | } 283 | `, 284 | ); 285 | 286 | defineInlineTest( 287 | transform, 288 | undefined, 289 | ` 290 | function foo([, props]) { 291 | props = 4; 292 | }`, 293 | ` 294 | function foo([, props]) { 295 | let localProps = props; 296 | localProps = 4; 297 | }`, 298 | ); 299 | 300 | defineInlineTest( 301 | transform, 302 | undefined, 303 | ` 304 | function foo(num) { 305 | num++; 306 | 307 | return
; 308 | } 309 | `, 310 | ` 311 | function foo(num) { 312 | let localNum = num; 313 | localNum++; 314 | 315 | return
; 316 | } 317 | `, 318 | ); 319 | 320 | defineInlineTest( 321 | transform, 322 | undefined, 323 | ` 324 | function func(i) { 325 | var range = foo(i), i = -1; 326 | 327 | i++; 328 | } 329 | `, 330 | '', 331 | ); 332 | 333 | defineInlineTest( 334 | transform, 335 | undefined, 336 | ` 337 | function foo(Component) { 338 | Component =
; 339 | } 340 | `, 341 | ` 342 | function foo(Component) { 343 | let LocalComponent = Component; 344 | LocalComponent =
; 345 | } 346 | `, 347 | ); 348 | 349 | // Function declaration 350 | defineInlineTest( 351 | transform, 352 | undefined, 353 | ` 354 | // @format 355 | function foo(boo) {boo++;}`, 356 | ` 357 | // @format 358 | function foo(boo) { 359 | let localBoo = boo; 360 | localBoo++; 361 | }`, 362 | ); 363 | 364 | // Shadowing function argument 365 | // This doesn't work yet and is the major blocker to us being able to 366 | // use this at Facebook. 367 | // defineInlineTest( 368 | // transform, 369 | // undefined, 370 | // ` 371 | // function foo(x) { 372 | // x = 2; 373 | // var x = 4; 374 | // return x; 375 | // }`, 376 | // ` 377 | // function foo({x}) { 378 | // let localX = x; 379 | // localX = 2; 380 | 381 | // var localX2 = 4; 382 | // return localX2; 383 | // }`, 384 | // ); 385 | }); 386 | -------------------------------------------------------------------------------- /transforms/__tests__/no-vars-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | const printOptions = { 6 | trailingComma: true, 7 | }; 8 | 9 | defineTest(__dirname, 'no-vars', {printOptions}); 10 | -------------------------------------------------------------------------------- /transforms/__tests__/object-shorthand-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'object-shorthand'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/rm-copyProperties-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | const printOptions = { 6 | trailingComma: true, 7 | }; 8 | 9 | defineTest(__dirname, 'rm-copyProperties', {printOptions}); 10 | defineTest(__dirname, 'rm-copyProperties', {printOptions}, 'rm-copyProperties2'); 11 | -------------------------------------------------------------------------------- /transforms/__tests__/rm-merge-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | const printOptions = { 6 | trailingComma: true, 7 | }; 8 | defineTest(__dirname, 'rm-merge', {printOptions}); 9 | -------------------------------------------------------------------------------- /transforms/__tests__/rm-object-assign-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | const printOptions = { trailingComma: true }; 5 | defineTest(__dirname, 'rm-object-assign', { printOptions }); 6 | -------------------------------------------------------------------------------- /transforms/__tests__/rm-requires-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'rm-requires'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/template-literals-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | const printOptions = { 6 | quote: 'single', 7 | }; 8 | defineTest(__dirname, 'template-literals', {printOptions}); 9 | -------------------------------------------------------------------------------- /transforms/__tests__/trailing-commas-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'trailing-commas'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/unchain-variables-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'unchain-variables'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/underscore-to-lodash-native-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | const printOptions = { 6 | quote: 'single', 7 | }; 8 | defineTest(__dirname, 'underscore-to-lodash-native', {printOptions}); 9 | -------------------------------------------------------------------------------- /transforms/__tests__/unquote-properties-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | defineTest(__dirname, 'unquote-properties'); 5 | -------------------------------------------------------------------------------- /transforms/__tests__/use-strict-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 4 | 5 | const printOptions = { 6 | quote: 'single', 7 | }; 8 | defineTest(__dirname, 'use-strict', {printOptions}); 9 | -------------------------------------------------------------------------------- /transforms/arrow-function-arguments.js: -------------------------------------------------------------------------------- 1 | module.exports = (file, api, options) => { 2 | const j = api.jscodeshift; 3 | 4 | const printOptions = options.printOptions || {quote: 'single'}; 5 | const root = j(file.source); 6 | 7 | const ARGUMENTS = 'arguments'; 8 | const ARGS = 'args'; 9 | 10 | const createArrowFunctionExpression = (fn, args) => 11 | j.arrowFunctionExpression( 12 | (fn.params || []).concat(j.restElement(args)), 13 | fn.body, 14 | fn.generator 15 | ); 16 | 17 | const filterMemberExpressions = path => path.parent.value.type !== "MemberExpression" 18 | 19 | const filterArrowFunctions = path => { 20 | while (path.parent) { 21 | switch (path.value.type) { 22 | case 'ArrowFunctionExpression': 23 | if (j(path).find(j.Identifier, {name: ARGS}).size()) { 24 | console.error( 25 | file.path + ': arrow function uses "' + ARGS + '" already. ' + 26 | 'Please rename this identifier first.' 27 | ); 28 | return false; 29 | } 30 | return true; 31 | case 'FunctionExpression': 32 | case 'MethodDeclaration': 33 | case 'Function': 34 | case 'FunctionDeclaration': 35 | return false; 36 | default: 37 | break; 38 | } 39 | path = path.parent; 40 | } 41 | return false; 42 | }; 43 | 44 | const updateArgumentsCalls = path => { 45 | var afPath = path; 46 | while (afPath.parent) { 47 | if (afPath.value.type == 'ArrowFunctionExpression') { 48 | break; 49 | } 50 | afPath = afPath.parent; 51 | } 52 | 53 | const {value: fn} = afPath; 54 | const {params} = fn; 55 | const param = params[params.length - 1]; 56 | var args; 57 | if (param && param.type == 'RestElement') { 58 | params.pop(); 59 | args = param.argument; 60 | } else { 61 | args = j.identifier(ARGS); 62 | } 63 | j(afPath).replaceWith(createArrowFunctionExpression(fn, args)); 64 | 65 | if (params.length) { 66 | j(path).replaceWith( 67 | j.arrayExpression(params.concat(j.spreadElement(args))) 68 | ); 69 | } else { 70 | j(path).replaceWith(args); 71 | } 72 | }; 73 | 74 | const didTransform = root 75 | .find(j.Identifier, {name: ARGUMENTS}) 76 | .filter(filterMemberExpressions) 77 | .filter(filterArrowFunctions) 78 | .forEach(updateArgumentsCalls) 79 | .size() > 0; 80 | 81 | return didTransform ? root.toSource(printOptions) : null; 82 | }; 83 | -------------------------------------------------------------------------------- /transforms/arrow-function.js: -------------------------------------------------------------------------------- 1 | module.exports = (file, api, options) => { 2 | const j = api.jscodeshift; 3 | 4 | const printOptions = options.printOptions || {quote: 'single'}; 5 | const root = j(file.source); 6 | 7 | const getBodyStatement = fn => { 8 | // 79 characters fit on a line of length 80 9 | const maxWidth = options['max-width'] ? options['max-width'] - 1 : undefined; 10 | 11 | if ( 12 | fn.body.type == 'BlockStatement' && 13 | fn.body.body.length == 1 14 | ) { 15 | const inner = fn.body.body[0]; 16 | const comments = (fn.body.comments || []).concat(inner.comments || []); 17 | 18 | if ( 19 | options['inline-single-expressions'] && 20 | inner.type == 'ExpressionStatement' 21 | ) { 22 | inner.expression.comments = (inner.expression.comments || []).concat(comments); 23 | return inner.expression; 24 | } else if (inner.type == 'ReturnStatement') { 25 | if (inner.argument === null) { 26 | // The rare case of a function with a lone return statement. 27 | fn.body.body = []; 28 | return fn.body; 29 | } 30 | const lineStart = fn.loc.start.line; 31 | const originalLineLength = fn.loc.lines.getLineLength(lineStart); 32 | const approachDifference = 'function(a, b) {'.length - '(a, b) => );'.length; 33 | const argumentLength = inner.argument.end - inner.argument.start; 34 | 35 | const newLength = originalLineLength + argumentLength - approachDifference; 36 | const tooLong = maxWidth && newLength > maxWidth; 37 | 38 | if (!tooLong) { 39 | inner.argument.comments = (inner.argument.comments || []).concat(comments); 40 | return inner.argument; 41 | } 42 | } 43 | } 44 | return fn.body; 45 | }; 46 | 47 | const createArrowFunctionExpression = fn => { 48 | const arrowFunction = j.arrowFunctionExpression( 49 | fn.params, 50 | getBodyStatement(fn), 51 | false 52 | ); 53 | arrowFunction.comments = fn.comments; 54 | arrowFunction.async = fn.async; 55 | return arrowFunction; 56 | }; 57 | 58 | const replacedBoundFunctions = root 59 | .find(j.CallExpression, { 60 | callee: { 61 | type: 'MemberExpression', 62 | object: { 63 | type: 'FunctionExpression', 64 | generator: false, 65 | }, 66 | property: { 67 | type: 'Identifier', 68 | name: 'bind', 69 | }, 70 | }, 71 | }) 72 | .filter(path => ( 73 | !path.value.callee.object.id && 74 | path.value.arguments && 75 | path.value.arguments.length == 1 && 76 | path.value.arguments[0].type == 'ThisExpression' 77 | )) 78 | .forEach(path => { 79 | const comments = path.value.comments || []; 80 | for (const node of [path.value.callee, path.value.callee.property, path.value.arguments[0]]) { 81 | for (const comment of node.comments || []) { 82 | comment.leading = false; 83 | comment.trailing = true; 84 | comments.push(comment); 85 | } 86 | } 87 | const arrowFunction = createArrowFunctionExpression(path.value.callee.object); 88 | arrowFunction.comments = (arrowFunction.comments || []).concat(comments); 89 | j(path).replaceWith(arrowFunction); 90 | }) 91 | .size() > 0; 92 | 93 | const replacedCallbacks = root 94 | .find(j.FunctionExpression, { 95 | generator: false, 96 | }) 97 | .filter(path => { 98 | const isArgument = path.parentPath.name === 'arguments' && path.parentPath.value.indexOf(path.value) > -1; 99 | const noThis = j(path).find(j.ThisExpression).size() == 0; 100 | const notNamed = !path.value.id || !path.value.id.name; 101 | const noArgumentsRef = j(path).find(j.Identifier).filter(idPath => idPath.node.name === 'arguments' && idPath.scope.depth === path.get('body').scope.depth).size() === 0; 102 | 103 | return isArgument && noThis && notNamed && noArgumentsRef; 104 | }) 105 | .forEach(path => 106 | j(path).replaceWith( 107 | createArrowFunctionExpression(path.value) 108 | ) 109 | ) 110 | .size() > 0; 111 | 112 | return replacedBoundFunctions || replacedCallbacks ? root.toSource(printOptions) : null; 113 | }; 114 | -------------------------------------------------------------------------------- /transforms/expect.js: -------------------------------------------------------------------------------- 1 | export default function transformer(file, api) { 2 | const j = api.jscodeshift; 3 | 4 | return j(file.source) 5 | .find(j.MemberExpression, { 6 | object: { 7 | type: 'CallExpression', 8 | callee: {type: 'Identifier', name: 'expect'}, 9 | }, 10 | property: {type: 'Identifier'}, 11 | }) 12 | .forEach(path => { 13 | const toBeArgs = path.parentPath.node.arguments; 14 | const expectArgs = path.node.object.arguments; 15 | const name = path.node.property.name; 16 | const isNot = name.indexOf('Not') !== -1 || name.indexOf('Exclude') !== -1; 17 | 18 | const renaming = { 19 | toExist: 'toBeTruthy', 20 | toNotExist: 'toBeFalsy', 21 | toNotBe: 'not.toBe', 22 | toNotEqual: 'not.toEqual', 23 | toNotThrow: 'not.toThrow', 24 | toBeA: 'toBeInstanceOf', 25 | toBeAn: 'toBeInstanceOf', 26 | toNotBeA: 'not.toBeInstanceOf', 27 | toNotBeAn: 'not.toBeInstanceOf', 28 | toNotMatch: 'not.toMatch', 29 | toBeFewerThan: 'toBeLessThan', 30 | toBeLessThanOrEqualTo: 'toBeLessThanOrEqual', 31 | toBeMoreThan: 'toBeGreaterThan', 32 | toBeGreaterThanOrEqualTo: 'toBeGreaterThanOrEqual', 33 | toInclude: 'toContain', 34 | toExclude: 'not.toContain', 35 | toNotContain: 'not.toContain', 36 | toNotInclude: 'not.toContain', 37 | toNotHaveBeenCalled: 'not.toHaveBeenCalled', 38 | }; 39 | if (renaming[name]) { 40 | path.node.property.name = renaming[name]; 41 | } 42 | if (name === 'toBeA' || name === 'toBeAn' || 43 | name === 'toNotBeA' || name === 'toNotBeAn') { 44 | if (toBeArgs[0].type === 'Literal') { 45 | expectArgs[0] = j.unaryExpression('typeof', expectArgs[0]); 46 | path.node.property.name = isNot ? 'not.toBe' : 'toBe'; 47 | } 48 | } 49 | 50 | if (name === 'toIncludeKey' || name === 'toContainKey' || 51 | name === 'toExcludeKey' || name === 'toNotContainKey' || name === 'toNotIncludeKey') { 52 | expectArgs[0] = j.template.expression`Object.keys(${expectArgs[0]})`; 53 | path.node.property.name = isNot ? 'not.toContain' : 'toContain'; 54 | } 55 | if (name === 'toIncludeKeys' || name === 'toContainKeys' || 56 | name === 'toExcludeKeys' || name === 'toNotContainKeys' || name === 'toNotIncludeKeys') { 57 | toBeArgs[0] = j.identifier('e'); 58 | path.node.property.name = isNot ? 'not.toContain' : 'toContain'; 59 | j(path.parentPath).replaceWith(j.template.expression`\ 60 | ${toBeArgs[0]}.forEach(${toBeArgs[0]} => { 61 | ${path.parentPath.node} 62 | })`); 63 | } 64 | if (name === 'toMatch' || name === 'toNotMatch') { 65 | const arg = toBeArgs[0]; 66 | if (arg.type === 'ObjectExpression') { 67 | path.node.property.name = isNot ? 'not.toMatchObject' : 'toMatchObject'; 68 | } 69 | } 70 | }) 71 | .toSource(); 72 | } 73 | -------------------------------------------------------------------------------- /transforms/flow-bool-to-boolean.js: -------------------------------------------------------------------------------- 1 | export default function transformer(file, api) { 2 | const j = api.jscodeshift; 3 | return j(file.source) 4 | .find(j.BooleanTypeAnnotation) 5 | .replaceWith(j.booleanTypeAnnotation()) 6 | .toSource(); 7 | } 8 | -------------------------------------------------------------------------------- /transforms/invalid-requires.js: -------------------------------------------------------------------------------- 1 | module.exports = function(file, api, options) { 2 | const jscodeshift = api.jscodeshift; 3 | const printOptions = options.printOptions || {quote: 'single'}; 4 | const requireStatements = new Set(); 5 | 6 | const root = jscodeshift(file.source) 7 | .find(jscodeshift.CallExpression, {callee: {name: 'require'}}) 8 | .filter(requireStatement => ( 9 | requireStatement.parent.value.type == 'VariableDeclarator' && 10 | requireStatement.parent.parent.value.declarations.length != 1 11 | )) 12 | .forEach(requireStatement => { 13 | requireStatements.add(requireStatement.parent.parent); 14 | }); 15 | 16 | requireStatements.forEach(requireStatement => { 17 | jscodeshift(requireStatement) 18 | .replaceWith(requireStatement.value.declarations.map((declaration, i) => { 19 | const kind = requireStatement.value.kind; // e.g. var or const 20 | const variableDeclaration = 21 | jscodeshift.variableDeclaration(kind, [declaration]); 22 | 23 | if (i == 0) { 24 | variableDeclaration.comments = requireStatement.value.comments; 25 | } else if (declaration.comments) { 26 | variableDeclaration.comments = declaration.comments; 27 | declaration.comments = null; 28 | } 29 | 30 | return variableDeclaration; 31 | } 32 | )); 33 | }); 34 | 35 | return requireStatements.size ? root.toSource(printOptions) : null; 36 | }; 37 | -------------------------------------------------------------------------------- /transforms/jest-11-update.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function(file, api, options = {}) { 4 | if ( 5 | !file.path.includes(path.sep + '__tests__' + path.sep) && 6 | !file.path.includes(path.sep + '__mocks__' + path.sep) 7 | ) { 8 | return null; 9 | } 10 | 11 | const j = api.jscodeshift; 12 | const printOptions = options.printOptions || {quote: 'single'}; 13 | const root = j(file.source); 14 | let mutations = 0; 15 | 16 | const JEST_API = { 17 | dontMock: 'unmock', 18 | autoMockOff: 'disableAutomock', 19 | autoMockOn: 'enableAutomock', 20 | }; 21 | 22 | const isJestCall = 23 | node => node.name == 'jest' || ( 24 | node.type == 'CallExpression' && 25 | node.callee.type == 'MemberExpression' && 26 | isJestCall(node.callee.object) 27 | ); 28 | 29 | const updateAPIs = (apiMethods) => 30 | mutations += root 31 | .find(j.CallExpression, { 32 | callee: { 33 | type: 'MemberExpression', 34 | object: isJestCall, 35 | property: { 36 | name: name => apiMethods[name], 37 | }, 38 | }, 39 | }) 40 | .forEach(p => { 41 | const name = p.value.callee.property.name; 42 | p.value.callee.property.name = apiMethods[name]; 43 | }) 44 | .size(); 45 | 46 | const JEST_MOCK_FNS = { 47 | genMockFn: true, 48 | genMockFunction: true, 49 | }; 50 | 51 | const JEST_MOCK_IMPLEMENTATION = { 52 | mockImpl: true, 53 | mockImplementation: true, 54 | }; 55 | 56 | const updateMockFns = () => { 57 | mutations += root 58 | .find(j.CallExpression, { 59 | callee: { 60 | type: 'MemberExpression', 61 | object: { 62 | name: 'jest', 63 | }, 64 | property: { 65 | name: name => JEST_MOCK_FNS[name], 66 | }, 67 | }, 68 | }) 69 | .forEach(p => { 70 | p.value.callee.property.name = 'fn'; 71 | 72 | const parent = p.parent.node; 73 | const grandParent = p.parent.parent.node; 74 | if ( 75 | parent.type == 'MemberExpression' && 76 | grandParent.type == 'CallExpression' && 77 | JEST_MOCK_IMPLEMENTATION[parent.property.name] 78 | ) { 79 | p.value.arguments = grandParent.arguments; 80 | j(p.parent.parent).replaceWith(p.value); 81 | } 82 | }) 83 | .size(); 84 | }; 85 | 86 | updateAPIs(JEST_API); 87 | updateMockFns(); 88 | 89 | return mutations ? root.toSource(printOptions) : null; 90 | }; 91 | -------------------------------------------------------------------------------- /transforms/jest-arrow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms all the normal functions into arrow functions as callback of 3 | * jest globals such as describe, it... 4 | * 5 | * describe(function() { 6 | * it('should work', function() { 7 | * ... 8 | * }); 9 | * }); 10 | * 11 | * --> 12 | * 13 | * describe(() => { 14 | * it('should work', () => { 15 | * ... 16 | * }); 17 | * }); 18 | */ 19 | 20 | export default function transformer(file, api) { 21 | const j = api.jscodeshift; 22 | 23 | const functionsToTransform = [ 24 | 'describe', 25 | 'beforeEach', 26 | 'afterEach', 27 | 'it', 28 | 'xit', 29 | 'test', 30 | 'xdescribe', 31 | ]; 32 | 33 | return j(file.source) 34 | .find(j.ExpressionStatement) 35 | .filter(path => { 36 | return ( 37 | path.node.expression.type === 'CallExpression' && 38 | path.node.expression.callee.type === 'Identifier' && 39 | functionsToTransform.indexOf(path.node.expression.callee.name) !== -1 40 | ); 41 | }) 42 | .forEach(path => { 43 | var lastArg = path.node.expression.arguments.length - 1; 44 | var fn = path.node.expression.arguments[lastArg]; 45 | 46 | path.node.expression.arguments[lastArg] = 47 | j.arrowFunctionExpression(fn.params, fn.body); 48 | }) 49 | .toSource(); 50 | } 51 | -------------------------------------------------------------------------------- /transforms/jest-remove-describe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This removes the describe block when there's only one. 3 | * 4 | * Convert 5 | * 6 | * describe("Name", function() { 7 | * it(...); 8 | * ... 9 | * }); 10 | * 11 | * into 12 | * 13 | * it(...); 14 | * ... 15 | * 16 | */ 17 | 18 | export default function transformer(file, api) { 19 | const j = api.jscodeshift; 20 | 21 | var describes = j(file.source) 22 | .find(j.ExpressionStatement) 23 | .filter(path => 24 | path.parentPath.node.type === 'Program' && 25 | path.node.expression.type === 'CallExpression' && 26 | path.node.expression.callee.type === 'Identifier' && 27 | path.node.expression.callee.name === 'describe'); 28 | 29 | if (describes.size() !== 1) { 30 | return null; 31 | } 32 | 33 | describes.forEach(path => { 34 | if (path.parentPath.node.type !== 'Program') { 35 | return; 36 | } 37 | var parentBody = path.parentPath.node.body; 38 | parentBody.splice( 39 | parentBody.indexOf(path.node), 40 | 1, 41 | ...path.node.expression.arguments[1].body.body 42 | ); 43 | }); 44 | 45 | return describes.toSource(); 46 | } 47 | -------------------------------------------------------------------------------- /transforms/jest-remove-disable-automock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This removes jest.disableAutomock() calls. It handles cases where 3 | * `disableAutomock` is part of a chain 4 | * (ex. `jest.disableAutomock().mock('xyz')`) 5 | * It also preserves any header comments at the top of the file if 6 | * the the codemod completely deletes the first line. 7 | */ 8 | 9 | module.exports = function(file, api) { 10 | 11 | const j = api.jscodeshift; 12 | const root = j(file.source); 13 | let mutations = 0; 14 | 15 | const DISABLE_AUTOMOCK_CALLS = { 16 | autoMockOff: 'disableAutomock', 17 | disableAutomock: 'disableAutomock', 18 | }; 19 | 20 | const isGeneratedFile = () => { 21 | const rootElement = root.get(0).node.program.body[0]; 22 | const headers = !!rootElement && rootElement.comments; 23 | return ( 24 | headers && 25 | headers.length > 0 && 26 | headers[0].value.indexOf('@generated') > -1 27 | ); 28 | }; 29 | 30 | const isJestCall = (node) => ( 31 | node.name == 'jest' || ( 32 | node.type == 'CallExpression' && 33 | node.callee.type == 'MemberExpression' && 34 | isJestCall(node.callee.object) 35 | ) 36 | ); 37 | 38 | const isRootElement = (path) => ( 39 | path && 40 | path.parentPath && 41 | path.parentPath.value === root.get(0).node.program.body[0] 42 | ); 43 | 44 | const removeCalls = (calls) => { 45 | const jestUnmocks = root.find(j.CallExpression, { 46 | callee: { 47 | type: 'MemberExpression', 48 | object: isJestCall, 49 | property: { 50 | name: name => calls[name], 51 | }, 52 | }, 53 | }); 54 | // do these one at a time, then search for more 55 | // otherwise the list self-clobbers 56 | if (jestUnmocks.size() > 0) { 57 | const onlyThisOne = jestUnmocks.paths()[0]; 58 | onlyThisOne.replace(onlyThisOne.value.callee.object); 59 | return 1 + removeCalls(calls); 60 | } 61 | return 0; 62 | }; 63 | 64 | const removeDanglingJests = () => { 65 | let header; 66 | const danglers = root 67 | .find(j.Identifier, {name: 'jest'}) 68 | .filter(path => ( 69 | path.parentPath.value.type === j.ExpressionStatement.name 70 | )); 71 | if (danglers.size() > 0) { 72 | header = getHeader(danglers.paths()[0]); 73 | } 74 | danglers.forEach(path => { 75 | path.parentPath.replace(null); 76 | }); 77 | restoreHeader(header); 78 | return danglers.size(); 79 | }; 80 | 81 | const getHeader = (path) => { 82 | if (isRootElement(path)) { 83 | return path.parentPath.value.comments; 84 | } 85 | return null; 86 | } 87 | 88 | const restoreHeader = (header) => { 89 | const body = root.get(0).node.program.body.filter(a => !!a); 90 | if (header && body.length > 0) { 91 | if (body[0].comments) { 92 | body[0].comments.splice(0, 0, ...header); 93 | } else { 94 | body[0].comments = header; 95 | } 96 | } 97 | } 98 | 99 | 100 | 101 | if (!isGeneratedFile()) { 102 | mutations += removeCalls(DISABLE_AUTOMOCK_CALLS); 103 | mutations += removeDanglingJests(); 104 | } 105 | 106 | const printOptions = { 107 | quote: 'single', 108 | trailingComma: true, 109 | wrapColumn: 80, 110 | }; 111 | return mutations > 0 ? root.toSource(printOptions) : null; 112 | }; 113 | -------------------------------------------------------------------------------- /transforms/jest-rm-mock.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function(file, api, options = {}) { 4 | if ( 5 | !file.path.includes(path.sep + '__tests__' + path.sep) && 6 | !file.path.includes(path.sep + '__mocks__' + path.sep) 7 | ) { 8 | return null; 9 | } 10 | 11 | const j = api.jscodeshift; 12 | const printOptions = options.printOptions || {quote: 'single'}; 13 | const root = j(file.source); 14 | 15 | const JEST = 'jest'; 16 | const isJestFn = node => ( 17 | ['mock', 'unmock', 'disableAutomock'].includes(node.name) 18 | ); 19 | 20 | const removeCalls = (moduleNames) => { 21 | let mutated = false; 22 | const isJest = node => ( 23 | node.type == 'CallExpression' && 24 | node.callee.type == 'MemberExpression' && 25 | isJestFn(node.callee.property) 26 | ); 27 | const shouldUpdate = node => ( 28 | node.type == 'CallExpression' && 29 | node.callee.type == 'MemberExpression' && 30 | node.callee.property.name == 'mock' && 31 | node.arguments.some(arg => moduleNames.includes(arg.value)) 32 | ); 33 | const descend = (parent, node) => { 34 | if (node && isJest(node)) { 35 | if (shouldUpdate(node)) { 36 | mutated = true; 37 | parent.callee.object = node.callee.object; 38 | node = parent; 39 | } 40 | descend(node, node.callee.object); 41 | } 42 | }; 43 | const update = statement => { 44 | const expression = statement.expression; 45 | if (isJest(expression)) { 46 | descend(expression, expression.callee.object); 47 | } 48 | if (shouldUpdate(expression)) { 49 | mutated = true; 50 | statement.expression = expression.callee.object; 51 | update(statement); 52 | return true; 53 | } 54 | }; 55 | 56 | const program = root.get(0).node.program; 57 | const body = program.body.filter((statement, index) => { 58 | if (statement.type === 'ExpressionStatement' && update(statement)) { 59 | if ( 60 | statement.expression.type == 'Identifier' && 61 | statement.expression.name == JEST 62 | ) { 63 | return false; 64 | } 65 | } 66 | return true; 67 | }); 68 | program.body = body; 69 | return mutated; 70 | }; 71 | 72 | const mutations = removeCalls(options.moduleNames); 73 | return mutations ? root.toSource(printOptions) : null; 74 | }; 75 | -------------------------------------------------------------------------------- /transforms/jest-update.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function(file, api, options = {}) { 4 | if ( 5 | !file.path.includes(path.sep + '__tests__' + path.sep) && 6 | !file.path.includes(path.sep + '__mocks__' + path.sep) 7 | ) { 8 | return null; 9 | } 10 | 11 | if ( 12 | !file.source.includes('mock-modules') && 13 | !file.source.includes('mocks') 14 | ) { 15 | return null; 16 | } 17 | 18 | const j = api.jscodeshift; 19 | const printOptions = options.printOptions || {quote: 'single'}; 20 | const root = j(file.source); 21 | let mutations = 0; 22 | 23 | const getRequireCall = (path, moduleName) => { 24 | const call = path 25 | .findVariableDeclarators() 26 | .filter(j.filters.VariableDeclarator.requiresModule(moduleName)); 27 | return call.size() == 1 ? call.get() : null; 28 | }; 29 | 30 | const MOCK_MODULES_API = { 31 | dontMock: 'dontMock', 32 | setMock: 'setMock', 33 | mock: 'mock', 34 | autoMockOff: 'autoMockOff', 35 | autoMockOn: 'autoMockOn', 36 | dumpCache: 'resetModuleRegistry', 37 | generateMock: 'genMockFromModule', 38 | }; 39 | 40 | const MOCKS_API = { 41 | getMockFunction: 'genMockFn', 42 | getMockFn: 'genMockFn', 43 | }; 44 | 45 | const moduleMatcher = moduleName => node => ( 46 | node.name == 'mockModules' || 47 | node.name == 'MockModules' || 48 | node.name == 'modules' || 49 | node.name == 'mocks' || 50 | ( 51 | node.type == 'CallExpression' && 52 | node.callee.type == 'Identifier' && 53 | node.callee.name == 'require' && 54 | node.arguments.length == 1 && 55 | node.arguments[0].value == moduleName 56 | ) 57 | ); 58 | 59 | const updateAPIs = (matcher, apiMethods) => 60 | mutations += root 61 | .find(j.CallExpression, { 62 | callee: { 63 | type: 'MemberExpression', 64 | object: matcher, 65 | property: { 66 | name: name => apiMethods[name], 67 | }, 68 | }, 69 | }) 70 | .replaceWith(p => { 71 | const name = p.value.callee.property.name; 72 | if (apiMethods[name] == name) { 73 | // short-circuit to keep code style in-tact 74 | p.value.callee.object = j.identifier('jest'); 75 | return p.value; 76 | } 77 | 78 | return j.callExpression( 79 | j.memberExpression( 80 | j.identifier('jest'), 81 | j.identifier(apiMethods[name]), 82 | false 83 | ), 84 | p.value.arguments 85 | ); 86 | }) 87 | .size(); 88 | 89 | const removeRequireCall = name => { 90 | const declarator = getRequireCall(root, name); 91 | if (declarator) { 92 | const hasMockModulesIdentifier = root 93 | .find(j.Identifier, {name: declarator.value.id.name}) 94 | .size() > 1; 95 | if (!hasMockModulesIdentifier) { 96 | j(declarator).remove(); 97 | mutations++; 98 | } 99 | } 100 | 101 | mutations += root 102 | .find(j.CallExpression, { 103 | callee: { 104 | name: 'require', 105 | }, 106 | arguments: [{value: name}], 107 | }) 108 | .filter(p => p.parent.value.type == 'ExpressionStatement') 109 | .remove() 110 | .size(); 111 | }; 112 | 113 | const firstNode = () => root.find(j.Program).get('body', 0); 114 | const comment = firstNode().node.leadingComments; 115 | updateAPIs(moduleMatcher('mock-modules'), MOCK_MODULES_API); 116 | updateAPIs(moduleMatcher('mocks'), MOCKS_API); 117 | removeRequireCall('mock-modules'); 118 | removeRequireCall('mocks'); 119 | firstNode().node.comments = comment; 120 | 121 | return mutations ? root.toSource(printOptions) : null; 122 | }; 123 | -------------------------------------------------------------------------------- /transforms/no-reassign-params.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function transformer(file, api) { 4 | const j = api.jscodeshift; 5 | const statement = j.template.statement; 6 | 7 | const FUNCTION_TYPES = [ 8 | j.FunctionDeclaration, 9 | j.ArrowFunctionExpression, 10 | j.FunctionExpression, 11 | ]; 12 | 13 | let updated = false; 14 | 15 | function getNewName(paramName) { 16 | const firstChar = paramName.charAt(0); 17 | const isUpperCase = paramName.charAt(0).toUpperCase() === firstChar; 18 | 19 | if (isUpperCase) { 20 | return `Local${paramName}`; 21 | } else { 22 | const upperCase = paramName.charAt(0).toUpperCase() + paramName.slice(1); 23 | return `local${upperCase}`; 24 | } 25 | } 26 | 27 | function getLocalVarStatement(paramName, newName) { 28 | const localVar = statement`let ${j.identifier(newName)} = ${paramName};\n`; 29 | return localVar; 30 | } 31 | 32 | function definedInParentScope(identifierName, scope) { 33 | let localScope = scope; 34 | while (localScope) { 35 | if (localScope.declares(identifierName)) { 36 | return true; 37 | } 38 | localScope = localScope.parent; 39 | } 40 | 41 | return false; 42 | } 43 | 44 | function getParamNames(params) { 45 | return [].concat( 46 | ...params.map(param => { 47 | if (param === null) { 48 | return null; 49 | } else if (param.type === 'Identifier') { 50 | return param.name; 51 | } else if (param.type === 'ObjectPattern') { 52 | return param.properties.map(property => { 53 | if (j.Property.check(property)) { 54 | return property.value.name; 55 | } else if ( 56 | j.SpreadProperty.check(property) || 57 | j.RestProperty.check(property) 58 | ) { 59 | return property.argument.name; 60 | } else { 61 | throw new Error( 62 | `Unexpected Property Type ${property.type} ${j( 63 | property, 64 | ).toSource()}`, 65 | ); 66 | } 67 | }); 68 | } else if (param.type === 'RestElement') { 69 | return param.argument.name; 70 | } else if (j.AssignmentPattern.check(param)) { 71 | return param.left.name; 72 | } else if (j.ArrayPattern.check(param)) { 73 | return [].concat(...getParamNames(param.elements)); 74 | } else { 75 | throw new Error( 76 | `Unexpected Param Type ${param.type} ${j(param).toSource()}`, 77 | ); 78 | } 79 | }), 80 | ); 81 | } 82 | 83 | function updateFunction(func) { 84 | const params = func.get('params'); 85 | 86 | const functionScope = func.scope; 87 | 88 | const newBindings = new Set(); 89 | 90 | const paramNames = getParamNames(params.value); 91 | 92 | const reassignedParamNames = paramNames.filter(paramName => { 93 | const numAssignments = j(func) 94 | .find(j.AssignmentExpression) 95 | .filter(assignment => { 96 | const left = assignment.node.left; 97 | 98 | // old = 4; 99 | if (j.Identifier.check(left)) { 100 | return left.name === paramName; 101 | } else if (j.ObjectPattern.check(left)) { 102 | return left.properties.some(property => { 103 | if (j.Property.check(property)) { 104 | return property.key.name === paramName; 105 | } else if (j.RestProperty.check(property)) { 106 | return property.argument.name === paramName; 107 | } else { 108 | throw new Error( 109 | `Unexpected Property Type ${property.type} ${j( 110 | property, 111 | ).toSource()}`, 112 | ); 113 | } 114 | }); 115 | } 116 | 117 | return false; 118 | }).length; 119 | 120 | const numUpdated = j(func).find(j.UpdateExpression, { 121 | argument: { 122 | name: paramName, 123 | }, 124 | }).length; 125 | 126 | return numAssignments > 0 || numUpdated > 0; 127 | }); 128 | 129 | if (reassignedParamNames.length === 0) { 130 | return; 131 | } 132 | 133 | reassignedParamNames.forEach(paramName => { 134 | const oldName = paramName; 135 | const newName = getNewName(paramName); 136 | const localVar = getLocalVarStatement(paramName, newName); 137 | 138 | if (definedInParentScope(newName, func.scope)) { 139 | return; 140 | } 141 | 142 | j(func.get('body')) 143 | .find(j.Identifier, {name: paramName}) 144 | .forEach(identifier => { 145 | const parent = identifier.parent.node; 146 | 147 | if ( 148 | j.MemberExpression.check(parent) && 149 | parent.property === identifier.node && 150 | !parent.computed 151 | ) { 152 | // obj.oldName 153 | return; 154 | } 155 | 156 | if ( 157 | j.Property.check(parent) && 158 | parent.key === identifier.node && 159 | !parent.computed 160 | ) { 161 | // { oldName: 3 } 162 | 163 | const closestAssignment = j(identifier).closest( 164 | j.AssignmentExpression, 165 | ); 166 | const assignmentHasProperty = 167 | closestAssignment.filter(assignment => { 168 | return ( 169 | j.ObjectPattern.check(assignment.node.left) && 170 | assignment.node.left.properties.includes(parent) 171 | ); 172 | }).length > 0; 173 | 174 | if (!assignmentHasProperty) { 175 | // ({oldName} = x); 176 | return; 177 | } 178 | } 179 | 180 | if ( 181 | j.MethodDefinition.check(parent) && 182 | parent.key === identifier.node && 183 | !parent.computed 184 | ) { 185 | // class A { oldName() {} } 186 | return; 187 | } 188 | 189 | if (j.JSXAttribute.check(parent)) { 190 | // 191 | return; 192 | } 193 | 194 | let scope = identifier.scope; 195 | 196 | if (scope === functionScope) { 197 | const bindings = scope.getBindings()[oldName]; 198 | if (bindings) { 199 | const recentBinding = bindings[bindings.length - 1]; 200 | if (recentBinding.name === 'id') { 201 | return; 202 | } 203 | } 204 | } else { 205 | while (scope !== functionScope) { 206 | if (scope.declares(oldName)) { 207 | return; 208 | } 209 | 210 | scope = scope.parent; 211 | } 212 | } 213 | 214 | if (scope) { 215 | newBindings.add(localVar); 216 | 217 | // ObjectPattern 218 | if (identifier.parent && j.Property.check(identifier.parent.node)) { 219 | const property = identifier.parent; 220 | property.shorthand = false; 221 | property.get('shorthand').replace(false); 222 | property 223 | .get('value') 224 | .get('name') 225 | .replace(newName); 226 | } else { 227 | identifier.get('name').replace(newName); 228 | } 229 | } 230 | }); 231 | }); 232 | 233 | const newBindingStatements = Array.from(newBindings).reverse(); 234 | newBindingStatements.forEach(binding => { 235 | updated = true; 236 | functionScope.node.body.body.unshift(binding); 237 | }); 238 | } 239 | 240 | // Facebook has generated files with an annotation. We don't want to modify these. 241 | // Instead, we should modify the code that generates the files. 242 | // eslint-disable-next-line no-useless-concat 243 | if (file.source.includes('@' + 'generated')) { 244 | return null; 245 | } 246 | 247 | const root = j(file.source); 248 | 249 | FUNCTION_TYPES.forEach(type => { 250 | root.find(type).forEach(updateFunction); 251 | }); 252 | 253 | if (updated) { 254 | return root.toSource({quote: 'single'}); 255 | } 256 | 257 | return null; 258 | }; 259 | -------------------------------------------------------------------------------- /transforms/no-vars.js: -------------------------------------------------------------------------------- 1 | module.exports = function(file, api) { 2 | const j = api.jscodeshift; 3 | 4 | const root = j(file.source); 5 | 6 | const TOP_LEVEL_TYPES = [ 7 | 'Function', 8 | 'FunctionDeclaration', 9 | 'FunctionExpression', 10 | 'ArrowFunctionExpression', 11 | 'Program', 12 | ]; 13 | const FOR_STATEMENTS = [ 14 | 'ForStatement', 15 | 'ForOfStatement', 16 | 'ForInStatement', 17 | ]; 18 | const getScopeNode = blockScopeNode => { 19 | let scopeNode = blockScopeNode; 20 | let isInFor = FOR_STATEMENTS.indexOf(blockScopeNode.value.type) !== -1; 21 | while (TOP_LEVEL_TYPES.indexOf(scopeNode.node.type) === -1) { 22 | scopeNode = scopeNode.parentPath; 23 | isInFor = isInFor || FOR_STATEMENTS.indexOf(scopeNode.value.type) !== -1; 24 | } 25 | return {scopeNode, isInFor}; 26 | }; 27 | const findFunctionDeclaration = (node, container) => { 28 | while (node.value.type !== 'FunctionDeclaration' && node !== container) { 29 | node = node.parentPath; 30 | } 31 | return node !== container ? node : null; 32 | }; 33 | const isForLoopDeclarationWithoutInit = declaration => { 34 | const parentType = declaration.parentPath.value.type; 35 | return parentType === 'ForOfStatement' || parentType === 'ForInStatement'; 36 | }; 37 | 38 | const extractNamesFromIdentifierLike = id => { 39 | if (!id) { 40 | return []; 41 | } else if (id.type === 'ObjectPattern') { 42 | return id.properties.map( 43 | d => (d.type === 'SpreadProperty' ? [d.argument.name] : extractNamesFromIdentifierLike(d.value)) 44 | ).reduce((acc, val) => acc.concat(val), []); 45 | } else if (id.type === 'ArrayPattern') { 46 | return id.elements.map(extractNamesFromIdentifierLike).reduce((acc, val) => acc.concat(val), []); 47 | } else if (id.type === 'Identifier') { 48 | return [id.name]; 49 | } else if (id.type === 'RestElement') { 50 | return [id.argument.name]; 51 | } else { 52 | return []; 53 | } 54 | }; 55 | const getDeclaratorNames = (declarator) => { 56 | return extractNamesFromIdentifierLike(declarator.id); 57 | }; 58 | const isIdInDeclarator = (declarator, name) => { 59 | return getDeclaratorNames(declarator).indexOf(name) !== -1; 60 | }; 61 | const getLocalScope = (scope, parentScope) => { 62 | const names = []; 63 | while (scope !== parentScope) { 64 | if (Array.isArray(scope.value.body)) { 65 | scope.value.body.forEach(node => { 66 | if (node.type === 'VariableDeclaration') { 67 | node.declarations.map(getDeclaratorNames).forEach(dNames => { 68 | dNames.forEach(name => { 69 | if (names.indexOf(name) === -1) { 70 | names.push(name); 71 | } 72 | }); 73 | }); 74 | } 75 | }); 76 | } 77 | if (Array.isArray(scope.value.params)) { 78 | scope.value.params.forEach(id => { 79 | extractNamesFromIdentifierLike(id).forEach(name => { 80 | if (names.indexOf(name) === -1) { 81 | names.push(name); 82 | } 83 | }); 84 | }); 85 | } 86 | scope = scope.parentPath; 87 | } 88 | return names; 89 | }; 90 | const hasLocalDeclarationFor = (nodePath, parentScope, name) => { 91 | return ( 92 | getLocalScope(nodePath, parentScope).indexOf(name) !== -1 93 | ); 94 | }; 95 | 96 | const isTruelyVar = (node, declarator) => { 97 | const blockScopeNode = node.parentPath; 98 | const {scopeNode, isInFor} = getScopeNode(blockScopeNode); 99 | 100 | // if we are in a for loop of some kind, and the variable 101 | // is referenced within a closure, revert to `var` 102 | // It would be safe to do the conversion if you can verify 103 | // that the callback is run synchronously 104 | const isUsedInClosure = ( 105 | isInFor && 106 | j(blockScopeNode).find(j.Function).filter( 107 | functionNode => ( 108 | j(functionNode).find(j.Identifier).filter( 109 | id => isIdInDeclarator(declarator, id.value.name) 110 | ).size() !== 0 111 | ) 112 | ).size() !== 0 113 | ); 114 | 115 | // if two attempts are made to declare the same variable, 116 | // revert to `var` 117 | // TODO: if they are in different block scopes, it may be 118 | // safe to convert them anyway 119 | const isDeclaredTwice = j(scopeNode) 120 | .find(j.VariableDeclarator) 121 | .filter(otherDeclarator => { 122 | return ( 123 | otherDeclarator.value !== declarator && 124 | getScopeNode(otherDeclarator).scopeNode === scopeNode && 125 | getDeclaratorNames(otherDeclarator.value).some( 126 | name => isIdInDeclarator(declarator, name) 127 | ) 128 | ); 129 | }).size() !== 0; 130 | 131 | return isUsedInClosure || isDeclaredTwice || j(scopeNode) 132 | .find(j.Identifier) 133 | .filter(n => { 134 | if (!isIdInDeclarator(declarator, n.value.name)) { 135 | return false; 136 | } 137 | // If the variable is used in a function declaration that gets 138 | // hoisted, it could get called early 139 | const functionDeclaration = findFunctionDeclaration(n, scopeNode); 140 | const isCalledInHoistedFunction = ( 141 | functionDeclaration && 142 | j(scopeNode) 143 | .find(j.Identifier) 144 | .filter(n => { 145 | return ( 146 | n.value.name === functionDeclaration.value.id.name && 147 | n.value.start < declarator.start 148 | ); 149 | }).size() !== 0 150 | ); 151 | if (isCalledInHoistedFunction) { 152 | return true; 153 | } 154 | const referenceScope = getScopeNode(n.parent).scopeNode; 155 | if ( 156 | referenceScope === scopeNode || 157 | !hasLocalDeclarationFor(n, scopeNode, n.value.name) 158 | ) { 159 | // if the variable is referenced outside the current block 160 | // scope, revert to using `var` 161 | const isOutsideCurrentScope = ( 162 | j(blockScopeNode).find(j.Identifier).filter( 163 | innerNode => innerNode.node.start === n.node.start 164 | ).size() === 0 165 | ); 166 | 167 | // if a variable is used before it is declared, revert to 168 | // `var` 169 | // TODO: If `isDeclaredTwice` is improved, and there is 170 | // another declaration for this variable, it may be 171 | // safe to convert this anyway 172 | const isUsedBeforeDeclaration = ( 173 | n.value.start < declarator.start 174 | ); 175 | 176 | return ( 177 | isOutsideCurrentScope || 178 | isUsedBeforeDeclaration 179 | ); 180 | } 181 | }).size() > 0; 182 | }; 183 | 184 | /** 185 | * isMutated utility function to determine whether a VariableDeclaration 186 | * contains mutations. Takes an optional VariableDeclarator node argument to 187 | * return only whether that specific Identifier is mutated 188 | * 189 | * @param {ASTPath} node VariableDeclaration path 190 | * @param {ASTNode} [declarator] VariableDeclarator node 191 | * @return {Boolean} 192 | */ 193 | const isMutated = (node, declarator) => { 194 | const scopeNode = node.parent; 195 | 196 | const hasAssignmentMutation = j(scopeNode) 197 | .find(j.AssignmentExpression) 198 | .filter(n => { 199 | return extractNamesFromIdentifierLike(n.value.left).some(name => { 200 | return isIdInDeclarator(declarator, name); 201 | }); 202 | }).size() > 0; 203 | 204 | const hasUpdateMutation = j(scopeNode) 205 | .find(j.UpdateExpression) 206 | .filter(n => { 207 | return isIdInDeclarator(declarator, n.value.argument.name); 208 | }).size() > 0; 209 | 210 | return hasAssignmentMutation || hasUpdateMutation; 211 | }; 212 | 213 | const updatedAnything = root.find(j.VariableDeclaration).filter( 214 | dec => dec.value.kind === 'var' 215 | ).filter(declaration => { 216 | return declaration.value.declarations.every(declarator => { 217 | return !isTruelyVar(declaration, declarator); 218 | }); 219 | }).forEach(declaration => { 220 | const forLoopWithoutInit = isForLoopDeclarationWithoutInit(declaration); 221 | if ( 222 | declaration.value.declarations.some(declarator => { 223 | return (!declarator.init && !forLoopWithoutInit) || isMutated(declaration, declarator); 224 | }) 225 | ) { 226 | declaration.value.kind = 'let'; 227 | } else { 228 | declaration.value.kind = 'const'; 229 | } 230 | }).size() !== 0; 231 | return updatedAnything ? root.toSource() : null; 232 | } 233 | -------------------------------------------------------------------------------- /transforms/object-shorthand.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplifies object properties in object literals to use ES6 shorthand notation. 3 | * 4 | * This handles properties and methods, as well as properties which use literals as keys. 5 | * 6 | * e.g. 7 | * 8 | * var object = { 9 | * identifier: identifier, 10 | * 'identifier2': identifier2, 11 | * method: function() {} 12 | * } 13 | * 14 | * becomes: 15 | * 16 | * var object = { 17 | * identifier, 18 | * identifier2, 19 | * method() {} 20 | * } 21 | */ 22 | module.exports = (file, api, options) => { 23 | const j = api.jscodeshift; 24 | const printOptions = options.printOptions || {quote: 'single'}; 25 | const root = j(file.source); 26 | 27 | const isRecursive = (value) => { 28 | return !!( 29 | value.id && 30 | j(value.body).find(j.Identifier).filter( 31 | i => i.node.name === value.id.name 32 | ).size() !== 0 33 | ); 34 | }; 35 | 36 | const canBeSimplified = (key, value) => { 37 | // Can be simplified if both key and value are the same identifier or if the 38 | // property is a method that is not recursive 39 | if (key.type === 'Identifier') { 40 | return ( 41 | value.type === 'Identifier' && 42 | key.name === value.name 43 | ) || ( 44 | value.type === 'FunctionExpression' && 45 | !isRecursive(value) 46 | ); 47 | } 48 | 49 | // Can be simplified if the key is a string literal which is equal to the 50 | // identifier name of the value. 51 | if (key.type === 'Literal') { 52 | return value.type === 'Identifier' && key.value === value.name; 53 | } 54 | 55 | return false; 56 | }; 57 | 58 | root 59 | .find(j.Property, { 60 | method: false, 61 | shorthand: false, 62 | computed: false, 63 | }) 64 | .filter(p => canBeSimplified(p.value.key, p.value.value)) 65 | .forEach(p => { 66 | if (p.value.key.type === 'Literal') { 67 | p.value.key = p.value.value; 68 | } 69 | 70 | if (p.value.value.type === 'Identifier') { 71 | p.value.shorthand = true; 72 | } else if (p.value.value.type === 'FunctionExpression') { 73 | p.value.method = true; 74 | } 75 | }); 76 | 77 | return root.toSource(printOptions); 78 | }; 79 | -------------------------------------------------------------------------------- /transforms/outline-require.js: -------------------------------------------------------------------------------- 1 | module.exports = function(file, api, options) { 2 | if (file.path.indexOf('/__tests__/') == -1) { 3 | return null; 4 | } 5 | 6 | const printOptions = options.printOptions || {quote: 'single'}; 7 | 8 | const REQUIRE_CALL = { 9 | type: 'CallExpression', 10 | callee: { 11 | name: 'require', 12 | }, 13 | }; 14 | const j = api.jscodeshift; 15 | 16 | const root = j(file.source); 17 | const {program} = root.get().value; 18 | const {body} = program; 19 | 20 | const firstComment = body[0].comments; 21 | 22 | const isJestCall = node => ( 23 | node.type == 'CallExpression' && 24 | node.callee.type == 'MemberExpression' && 25 | node.callee.object.type == 'Identifier' && 26 | node.callee.object.name == 'jest' 27 | ); 28 | 29 | const isMockModule = node => ( 30 | node && ( 31 | ( 32 | node.type == 'CallExpression' && 33 | node.callee.type == 'Identifier' && 34 | node.callee.name == 'require' && 35 | node.arguments.length && 36 | node.arguments[0].type == 'Literal' && 37 | ['mocks', 'mock-modules'].indexOf(node.arguments[0].value) != -1 38 | ) || ( 39 | node.type == 'AssignmentExpression' && 40 | isMockModule(node.right) 41 | ) || ( 42 | node.type == 'VariableDeclaration' && 43 | node.declarations.length && 44 | node.declarations.some( 45 | declaration => isMockModule(declaration.init) 46 | ) 47 | ) 48 | ) 49 | ); 50 | 51 | const isDescribeCall = node => ( 52 | node.type == 'CallExpression' && 53 | node.callee.type == 'Identifier' && 54 | node.callee.name == 'describe' 55 | ); 56 | 57 | const isMockLike = node => ( 58 | node.type == 'CallExpression' && 59 | node.callee.type == 'MemberExpression' && 60 | ( 61 | ( 62 | node.callee.object.type == 'Identifier' && 63 | ( 64 | node.callee.object.name == 'mocks' || 65 | node.callee.object.name == 'mockModules' 66 | ) 67 | ) || 68 | ( 69 | node.callee.object.type == 'CallExpression' && 70 | isMockLike(node.callee.object) 71 | ) 72 | ) 73 | ); 74 | 75 | const isClassDeclaration = node => node.type == 'ClassDeclaration'; 76 | const isFunctionDeclaration = node => node.type == 'FunctionDeclaration'; 77 | 78 | const isFunctionAssignment = node => ( 79 | node.type == 'VariableDeclaration' && 80 | node.declarations.some(declaration => ( 81 | declaration.init && 82 | declaration.init.type == 'FunctionExpression' 83 | )) 84 | ); 85 | 86 | const findInsertionPoint = body => { 87 | var index = 0; 88 | for (let i = 0; i < body.length; i++) { 89 | const item = body[i]; 90 | if ( 91 | isJestCall(item) || 92 | isMockModule(item) || 93 | (item.expression && isMockLike(item.expression)) 94 | ) { 95 | index = i; 96 | } 97 | 98 | // Stop looking at the first describe block, a class or a function 99 | if ( 100 | (item.expression && isDescribeCall(item.expression)) || 101 | isClassDeclaration(item) || 102 | isFunctionDeclaration(item) || 103 | isFunctionAssignment(item) 104 | ) { 105 | // If we don't have an insertion point, put everything 106 | // right before the first describe block. 107 | if (index == 0) { 108 | index = i - 1; 109 | } 110 | break; 111 | } 112 | } 113 | return index + 1; 114 | }; 115 | 116 | const createRequire = (id, requireName) => 117 | j.variableDeclarator( 118 | id, 119 | j.callExpression( 120 | j.identifier('require'), 121 | [j.literal(requireName)] 122 | ) 123 | ); 124 | 125 | const isSideEffectComment = node => ( 126 | node.comments && 127 | node.comments.length == 1 && 128 | /\@side-effect/.test(node.comments[0].value) 129 | ); 130 | 131 | const isFluxStore = (node, path) => ( 132 | /Store/.test(node.arguments[0].value) 133 | ); 134 | 135 | root 136 | .find(j.CallExpression, REQUIRE_CALL) 137 | .filter(p => ( 138 | p.value.arguments.length == 1 && 139 | p.value.arguments[0].type == 'Literal' && 140 | p.parent.value.type == 'AssignmentExpression' && 141 | p.parent.value.left.type == 'Identifier' && 142 | p.parent.parent.value.type == 'ExpressionStatement' && 143 | p.parent.parent.parent.value != program && 144 | !isSideEffectComment(p.parent.parent.value) && 145 | !isFluxStore(p.value, p.parent.parent) 146 | )) 147 | .forEach(p => { 148 | const {parent} = p; 149 | const name = parent.value.left.name; 150 | const require = p.value.arguments[0].value; 151 | const hasVariableDeclarator = root.find(j.VariableDeclarator, {id: {name}}) 152 | .filter(p => ( 153 | !p.value.init || 154 | ( 155 | p.value.init.type == 'Literal' || 156 | p.value.init.vlaue == 'null' 157 | ) 158 | )) 159 | .replaceWith(p => createRequire(j.identifier(name), require)) 160 | .size(); 161 | if (hasVariableDeclarator) { 162 | j(parent).remove(); 163 | } else { 164 | j(parent).replaceWith(createRequire(j.identifier(name), require)); 165 | } 166 | }); 167 | 168 | const requires = []; 169 | root 170 | .find(j.VariableDeclarator, {init: REQUIRE_CALL}) 171 | .filter(p => p.parent.parent && p.parent.parent.value != program) 172 | .forEach(p => requires.unshift( 173 | j.variableDeclaration( 174 | 'var', 175 | [createRequire(p.value.id, p.value.init.arguments[0].value)] 176 | ) 177 | )) 178 | .remove(); 179 | body.splice(findInsertionPoint(body), 0, ...requires.reverse()); 180 | 181 | // Cleanup empty beforeEach calls 182 | root 183 | .find(j.CallExpression, { 184 | callee: { 185 | name: 'beforeEach', 186 | }, 187 | arguments: [{body: []}], 188 | }) 189 | .filter(p => !p.value.arguments[0].body.body.length) 190 | .remove(); 191 | 192 | // Cleanup duplicate requires 193 | const requireNames = new Set(); 194 | root 195 | .find(j.VariableDeclarator, {init: REQUIRE_CALL}) 196 | .filter(p => p.parent.parent && p.parent.parent.value == program) 197 | .filter(p => { 198 | const {name} = p.value.id; 199 | const requireName = p.value.init.arguments[0].value; 200 | const combined = name + '|' + requireName; 201 | const has = requireNames.has(combined); 202 | if (!has) { 203 | requireNames.add(combined); 204 | } 205 | return has; 206 | }) 207 | .remove(); 208 | 209 | // Cleanup unused requires 210 | root 211 | .find(j.VariableDeclarator, {init: REQUIRE_CALL}) 212 | .filter(p => !!p.value.id && p.value.id.name) 213 | .filter(p => root.find(j.Identifier, {name: p.value.id.name}).size() == 1) 214 | .filter(p => ( 215 | p.value.id.name != 'React' || 216 | root.find(j.JSXElement).size() == 0 217 | )).remove(); 218 | 219 | body[0].comments = firstComment; 220 | 221 | return root.toSource(printOptions); 222 | }; 223 | -------------------------------------------------------------------------------- /transforms/rm-copyProperties.js: -------------------------------------------------------------------------------- 1 | module.exports = (file, api, options) => { 2 | if (!options.filters) { 3 | options.filters = []; 4 | } 5 | 6 | const j = api.jscodeshift; 7 | 8 | const getRequireCall = (path, moduleName) => { 9 | const call = path 10 | .findVariableDeclarators() 11 | .filter(j.filters.VariableDeclarator.requiresModule(moduleName)); 12 | return call.size() == 1 ? call.get() : null; 13 | }; 14 | 15 | const printOptions = options.printOptions || {quote: 'single'}; 16 | const root = j(file.source); 17 | 18 | const flatten = a => Array.isArray(a) ? [].concat(...a.map(flatten)) : a; 19 | 20 | const getObject = node => { 21 | if ( 22 | options.optionsOrConfig && 23 | node.type == 'LogicalExpression' && 24 | node.operator == '||' && 25 | isOptionsOrConfig(node) 26 | ) { 27 | return node.left; 28 | } 29 | return node; 30 | }; 31 | 32 | const inlineObjectExpression = path => 33 | j(path).replaceWith(j.objectExpression( 34 | flatten(path.value.arguments.map(getObject).map(p => 35 | p.type == 'ObjectExpression' ? p.properties : j.spreadProperty(p) 36 | )) 37 | )); 38 | 39 | const isOptionsOrConfig = node => { 40 | if (node.type == 'Identifier' && 41 | ( 42 | node.name == 'options' || 43 | node.name == 'config' || 44 | node.name == 'defaults' || 45 | node.name == '_defaults' || 46 | node.name == 'defaultOptions' || 47 | node.name == '_defaultOptions' 48 | ) 49 | ) { 50 | return true; 51 | } 52 | 53 | if (node.type == 'LogicalExpression' && node.operator == '||') { 54 | return ( 55 | isOptionsOrConfig(node.left) && 56 | node.right.type == 'ObjectExpression' && 57 | !node.right.properties.length 58 | ); 59 | } 60 | 61 | return false; 62 | }; 63 | 64 | const isStateOrProps = node => ( 65 | node.type == 'MemberExpression' && 66 | node.object && 67 | node.object.type == 'MemberExpression' && 68 | node.object.object && 69 | node.object.object.type == 'ThisExpression' && 70 | node.object.property && 71 | node.object.property.type == 'Identifier' && 72 | ( 73 | node.object.property.name == 'state' || 74 | node.object.property.name == 'props' 75 | ) 76 | ); 77 | 78 | const checkArguments = path => 79 | path.value.arguments.slice(1).every(argument => 80 | argument.type == 'ObjectExpression' || 81 | ( 82 | options.arbiterMixin && 83 | argument.type == 'Identifier' && 84 | argument.name == 'ArbiterMixin' 85 | ) || 86 | ( 87 | options.optionsOrConfig && 88 | isOptionsOrConfig(argument) 89 | ) || 90 | ( 91 | options.inlineStateAndProps && 92 | isStateOrProps(argument) 93 | ) 94 | ); 95 | 96 | const availableFilters = { 97 | onlyPrototypeAssignments(path) { 98 | var node = path.value.arguments[0]; 99 | return ( 100 | node.type == 'MemberExpression' && 101 | node.object.type == 'Identifier' && 102 | node.property.type == 'Identifier' && 103 | node.property.name == 'prototype' 104 | ); 105 | }, 106 | onlyThisExpressions(path) { 107 | return path.value.arguments[0].type == 'ThisExpression'; 108 | }, 109 | onlyNewExpressions(path) { 110 | return path.value.arguments[0].type == 'NewExpression'; 111 | }, 112 | onlyCapitalizedIdentifiers(path) { 113 | var node = path.value.arguments[0]; 114 | return ( 115 | node.type == 'Identifier' && 116 | node.name.charAt(0) == node.name.charAt(0).toUpperCase() 117 | ); 118 | }, 119 | onlyCallExpressions(path) { 120 | var node = path.parent.value; 121 | return node.type == 'ExpressionStatement'; 122 | }, 123 | onlyObjectExpressions(path) { 124 | return path.value.arguments[0].type == 'ObjectExpression'; 125 | }, 126 | onlyDefaults(path) { 127 | var node = path.value.arguments[0]; 128 | return ( 129 | node.type == 'Identifier' && 130 | node.name == 'defaults' 131 | ); 132 | }, 133 | }; 134 | 135 | const rmCopyPropertyCalls = path => { 136 | if (availableFilters.onlyObjectExpressions(path)) { 137 | inlineObjectExpression(path); 138 | } else { 139 | j(path).replaceWith(j.callExpression( 140 | j.memberExpression( 141 | j.identifier('Object'), 142 | j.identifier('assign'), 143 | false 144 | ), 145 | path.value.arguments.map(getObject) 146 | )); 147 | } 148 | }; 149 | 150 | if (options.inlineStateAndProps) { 151 | options.filters.push('onlyObjectExpressions'); 152 | } 153 | 154 | const filters = (options.omitArgumentsCheck ? [] : [checkArguments]).concat( 155 | options.filters.map(filterName => availableFilters[filterName]) 156 | ); 157 | 158 | const declarator = getRequireCall(root, 'copyProperties'); 159 | if (declarator) { 160 | const variableName = declarator.value.id.name; 161 | const didTransform = root 162 | .find(j.CallExpression, {callee: {name: variableName}}) 163 | .filter(p => filters.every(filter => filter(p))) 164 | .forEach(rmCopyPropertyCalls) 165 | .size() > 0; 166 | if (didTransform) { 167 | if (!root.find(j.CallExpression, {callee: {name: variableName}}).size()) { 168 | j(declarator).remove(); 169 | } 170 | return root.toSource(printOptions); 171 | } 172 | } 173 | return null; 174 | }; 175 | -------------------------------------------------------------------------------- /transforms/rm-merge.js: -------------------------------------------------------------------------------- 1 | module.exports = (file, api, options) => { 2 | const j = api.jscodeshift; 3 | 4 | const getRequireCall = (path, moduleName) => { 5 | const call = path 6 | .findVariableDeclarators() 7 | .filter(j.filters.VariableDeclarator.requiresModule(moduleName)); 8 | return call.size() == 1 ? call.get() : null; 9 | }; 10 | 11 | const printOptions = options.printOptions || {quote: 'single'}; 12 | const root = j(file.source); 13 | 14 | const flatten = a => Array.isArray(a) ? [].concat(...a.map(flatten)) : a; 15 | 16 | const rmMergeCalls = path => 17 | j(path).replaceWith(j.objectExpression( 18 | flatten(path.value.arguments.map(p => 19 | p.type == 'ObjectExpression' ? p.properties : j.spreadProperty(p) 20 | )) 21 | )); 22 | 23 | const declarator = getRequireCall(root, 'merge'); 24 | if (declarator) { 25 | const didTransform = root 26 | .find(j.CallExpression, {callee: {name: declarator.value.id.name}}) 27 | .forEach(rmMergeCalls) 28 | .size() > 0; 29 | if (didTransform) { 30 | j(declarator).remove(); 31 | return root.toSource(printOptions); 32 | } 33 | } 34 | return null; 35 | }; 36 | -------------------------------------------------------------------------------- /transforms/rm-object-assign.js: -------------------------------------------------------------------------------- 1 | module.exports = (file, api, options) => { 2 | const j = api.jscodeshift; 3 | const printOptions = options.printOptions || { quote: 'single' }; 4 | const root = j(file.source); 5 | 6 | const rmObjectAssignCall = path => 7 | j(path).replaceWith( 8 | j.objectExpression( 9 | path.value.arguments.reduce( 10 | (allProperties, { comments, ...argument }) => { 11 | if (argument.type === 'ObjectExpression') { 12 | const { properties } = argument; 13 | // Copy comments. 14 | if (properties.length > 0 && comments && comments.length > 0) { 15 | properties[0].comments = [ 16 | ...(properties[0].comments || []), 17 | ...(comments || []) 18 | ]; 19 | } 20 | return [...allProperties, ...properties]; 21 | } 22 | 23 | return [...allProperties, { ...j.spreadProperty(argument), comments }]; 24 | }, []) 25 | ) 26 | ); 27 | 28 | root 29 | .find(j.CallExpression, { 30 | callee: { object: { name: 'Object' }, property: { name: 'assign' } }, 31 | arguments: [{ type: 'ObjectExpression' }] 32 | }) 33 | .filter(p => !p.value.arguments.some(a => a.type === 'SpreadElement')) 34 | .forEach(rmObjectAssignCall); 35 | 36 | return root.toSource(printOptions); 37 | }; 38 | -------------------------------------------------------------------------------- /transforms/rm-requires.js: -------------------------------------------------------------------------------- 1 | module.exports = (file, api, options) => { 2 | const j = api.jscodeshift; 3 | 4 | const printOptions = options.printOptions || {quote: 'single'}; 5 | const root = j(file.source); 6 | const requires = {}; 7 | 8 | const filterAndTransformRequires = path => { 9 | const varName = path.value.id.name; 10 | const requireName = path.value.init.arguments[0].value; 11 | const scopeNode = path.scope.node; 12 | 13 | // Check if we already have a require for this, if we do just use 14 | // that one. 15 | if ( 16 | requires[requireName] && 17 | requires[requireName].scopeNode === scopeNode 18 | ) { 19 | j(path).renameTo(requires[requireName].varName); 20 | j(path).remove(); 21 | return true; 22 | } 23 | 24 | // We need this to make sure the JSX transform can use `React` 25 | if (requireName === 'React') { 26 | return false; 27 | } 28 | 29 | // Remove required vars that aren't used. 30 | const usages = j(path) 31 | .closestScope() 32 | .find(j.Identifier, {name: varName}) 33 | // Ignore require vars 34 | .filter(identifierPath => identifierPath.value !== path.value.id) 35 | // Ignore properties in MemberExpressions 36 | .filter(identifierPath => { 37 | const parent = identifierPath.parent.value; 38 | return !( 39 | j.MemberExpression.check(parent) && 40 | parent.property === identifierPath.value 41 | ); 42 | }); 43 | if (!usages.size()) { 44 | j(path).remove(); 45 | return true; 46 | } 47 | 48 | requires[requireName] = {scopeNode, varName}; 49 | }; 50 | 51 | const didTransform = root 52 | .find(j.VariableDeclarator, { 53 | id: {type: 'Identifier'}, 54 | init: {callee: {name: 'require'}}, 55 | }) 56 | .filter(filterAndTransformRequires) 57 | .size() > 0; 58 | 59 | return didTransform ? root.toSource(printOptions) : null; 60 | }; 61 | -------------------------------------------------------------------------------- /transforms/template-literals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replaces string concatenation with template literals. 3 | * Adapted from https://vramana.github.io/blog/2015/12/21/codemod-tutorial/ 4 | * 5 | * Areas of improvement: 6 | * 7 | * - Comments in the middle of string concatenation are currently added before 8 | * the string but after the assignment. Perhaps in these situations, the 9 | * string concatenation should be preserved as-is. 10 | * 11 | * - Nested concatenation inside template literals is not currently simplified. 12 | * Currently, a + `b${'c' + d}` becomes `${a}b${'c' + d}` but it would ideally 13 | * become `${a}b${`c${d}`}`. 14 | * 15 | * - Unnecessary escaping of quotes from the resulting template literals is 16 | * currently not removed. This is possibly the domain of a different 17 | * transform. 18 | * 19 | * - Unicode escape sequences are converted to unicode characters when 20 | * the simplified concatenation results in a string literal instead of a 21 | * template literal. It would be nice to preserve the original--whether it be 22 | * a unicode escape sequence or a unicode character. 23 | */ 24 | module.exports = function templateLiterals(file, api, options) { 25 | const j = api.jscodeshift; 26 | const printOptions = options.printOptions || {quote: 'single'}; 27 | 28 | function extractNodes(node, comments, topLevel = false) { 29 | if (comments) { 30 | node.comments = node.comments || []; 31 | node.comments.push(...comments); 32 | } 33 | 34 | if (node.type !== 'BinaryExpression') { 35 | return [node]; 36 | } 37 | 38 | if (node.operator !== '+') { 39 | return [node]; 40 | } 41 | 42 | if (!topLevel && node.parenthesizedExpression) { 43 | return [node]; 44 | } 45 | 46 | // We need to be careful about not having a stringish node on the left to 47 | // prevent things like 1 + 2 + 'foo', which should evaluate to '3foo' from 48 | // becoming '12foo'. 49 | return [ 50 | ...(hasStringish(node.left) ? extractNodes(node.left) : [node.left]), 51 | ...extractNodes(node.right, node.comments), 52 | ]; 53 | } 54 | 55 | function isStringNode(node) { 56 | return node.type === 'Literal' && (typeof node.value === 'string'); 57 | } 58 | 59 | function isCastToStringNode(node) { 60 | // foo.toString() 61 | if ( 62 | node.type === 'CallExpression' && 63 | node.callee.type === 'MemberExpression' && 64 | node.callee.property.type === 'Identifier' && 65 | node.callee.property.name === 'toString' 66 | ) { 67 | return true; 68 | } 69 | 70 | // String(foo) and new String(foo) 71 | if ( 72 | ['CallExpression', 'NewExpression'].indexOf(node.type) !== -1 && 73 | node.callee.type === 'Identifier' && 74 | node.callee.name === 'String' 75 | ) { 76 | return true; 77 | } 78 | 79 | return false; 80 | } 81 | 82 | function isTemplateLiteralNode(node) { 83 | return node.type === 'TemplateLiteral'; 84 | } 85 | 86 | function isBinaryExpressionNode(node) { 87 | return node.type === 'BinaryExpression'; 88 | } 89 | 90 | function isStringishNode(node) { 91 | return ( 92 | isStringNode(node) || 93 | isTemplateLiteralNode(node) || 94 | isCastToStringNode(node) 95 | ); 96 | } 97 | 98 | function hasStringish(node) { 99 | if (isBinaryExpressionNode(node)) { 100 | return hasStringish(node.left) || hasStringish(node.right); 101 | } 102 | 103 | return isStringishNode(node); 104 | } 105 | 106 | function joinQuasis(leftQuasis, rightQuasis) { 107 | const lastQuasi = leftQuasis.pop(); 108 | 109 | if (lastQuasi) { 110 | rightQuasis[0] = j.templateElement({ 111 | cooked: lastQuasi.value.cooked + rightQuasis[0].value.cooked, 112 | raw: lastQuasi.value.raw + rightQuasis[0].value.raw, 113 | }, false); 114 | } 115 | 116 | return leftQuasis.concat(rightQuasis); 117 | } 118 | 119 | function buildTL(nodes, quasis = [], expressions = [], comments = []) { 120 | if (nodes.length === 0) { 121 | return { quasis, expressions, comments }; 122 | } 123 | 124 | const [node, ...rest] = nodes; 125 | 126 | const newComments = comments.concat(node.comments || []); 127 | 128 | if (node.type === 'Literal') { 129 | const cooked = node.value.toString(); 130 | let raw = node.raw.toString(); 131 | if (typeof node.value === 'string') { 132 | // We need to remove the opening and trailing quote from the raw value 133 | // of the string. 134 | raw = raw.slice(1, -1); 135 | 136 | // We need to escape ${ to prevent new interpolation. 137 | raw = raw.replace(/\$\{/g, '\\${'); 138 | } 139 | 140 | const newQuasi = j.templateElement({ cooked, raw }, false); 141 | const newQuasis = joinQuasis(quasis, [newQuasi]); 142 | return buildTL(rest, newQuasis, expressions, newComments); 143 | } 144 | 145 | if (node.type === 'TemplateLiteral') { 146 | const nodeQuasis = node.quasis.map((q) => ( 147 | j.templateElement({ 148 | cooked: q.value.cooked, 149 | raw: q.value.raw, 150 | }, false) 151 | )); 152 | 153 | // We need to join the last quasi and the next quasi to prevent 154 | // expressions from shifting. 155 | const newQuasis = joinQuasis(quasis, nodeQuasis); 156 | const newExpressions = expressions.concat(node.expressions); 157 | return buildTL(rest, newQuasis, newExpressions, newComments); 158 | } 159 | 160 | const newQuasis = joinQuasis(quasis, [ 161 | j.templateElement({ cooked: '', raw: '' }, false), 162 | j.templateElement({ cooked: '', raw: '' }, false), 163 | ]); 164 | const newExpressions = expressions.concat(node); 165 | 166 | return buildTL(rest, newQuasis, newExpressions, newComments); 167 | } 168 | 169 | function convertToTemplateString(p) { 170 | const tempNodes = extractNodes(p.node, null, true); 171 | 172 | if (!tempNodes.some(isStringishNode)) { 173 | return p.node; 174 | } 175 | 176 | const tlOptions = buildTL(tempNodes); 177 | const tl = j.templateLiteral(tlOptions.quasis, tlOptions.expressions); 178 | if (tl.expressions.length > 0) { 179 | tl.comments = tlOptions.comments; 180 | return tl; 181 | } 182 | 183 | // There are no expressions, so let's use a regular string instead of a 184 | // template literal. 185 | const str = tl.quasis.map(q => q.value.cooked).join(''); 186 | const strLiteral = j.literal(str); 187 | strLiteral.comments = tlOptions.comments; 188 | return strLiteral; 189 | } 190 | 191 | return j(file.source) 192 | .find(j.BinaryExpression, { operator: '+' }) 193 | .replaceWith(convertToTemplateString) 194 | .toSource(printOptions); 195 | }; 196 | -------------------------------------------------------------------------------- /transforms/touchable.js: -------------------------------------------------------------------------------- 1 | function getJSXName(value) { 2 | return value.openingElement.name.name; 3 | } 4 | 5 | module.exports = function(file, api) { 6 | if (file.source.indexOf(' { 8 | // Only transform objects that are on multiple lines. 9 | if (node.properties.length === 0 || (node.loc && node.loc.start.line === node.loc.end.line)) { 10 | return false; 11 | } 12 | const lastProp = node.properties[node.properties.length - 1]; 13 | return file.source.charAt(lastProp.end) !== ','; 14 | }; 15 | 16 | const arrayHasNoTrailingComma = ({node}) => { 17 | // Only transform arrays that are on multiple lines. 18 | if (node.elements.length === 0 || node.loc.start.line === node.loc.end.line) { 19 | return false; 20 | } 21 | const lastEle = node.elements[node.elements.length - 1]; 22 | return file.source.charAt(lastEle.end) !== ','; 23 | }; 24 | 25 | const forceReprint = ({node}) => { 26 | node.original = null; 27 | }; 28 | 29 | const root = j(file.source); 30 | root 31 | .find(j.ObjectExpression) 32 | .filter(objectHasNoTrailingComma) 33 | .forEach(forceReprint); 34 | root 35 | .find(j.ArrayExpression) 36 | .filter(arrayHasNoTrailingComma) 37 | .forEach(forceReprint); 38 | 39 | return root.toSource(Object.assign(options.printOptions || {}, { 40 | trailingComma: true, 41 | wrapColumn: 1, // Makes sure we write each values on a separate line. 42 | })); 43 | } 44 | -------------------------------------------------------------------------------- /transforms/unchain-variables.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unchains chained variable declarations. 3 | */ 4 | module.exports = function(file, api, options) { 5 | const jscodeshift = api.jscodeshift; 6 | const printOptions = options.printOptions || {quote: 'single'}; 7 | 8 | const chainedDeclarations = jscodeshift(file.source) 9 | .find(jscodeshift.VariableDeclaration) 10 | .filter(variableDeclaration => ( 11 | variableDeclaration.value.declarations.length > 1 12 | )) 13 | .filter(variableDeclaration => ( 14 | variableDeclaration.parent.value.type !== 'ForStatement' 15 | )); 16 | 17 | chainedDeclarations.forEach(chainedDeclaration => { 18 | const kind = chainedDeclaration.value.kind; // e.g. const, let, or var 19 | 20 | jscodeshift(chainedDeclaration) 21 | .replaceWith(chainedDeclaration.value.declarations.map((declaration, i) => { 22 | const unchainedDeclaration = 23 | jscodeshift.variableDeclaration(kind, [declaration]); 24 | 25 | if (i === 0) { 26 | unchainedDeclaration.comments = chainedDeclaration.value.comments; 27 | } else if (declaration.comments) { 28 | unchainedDeclaration.comments = declaration.comments; 29 | declaration.comments = null; 30 | } 31 | 32 | return unchainedDeclaration; 33 | })); 34 | }); 35 | 36 | return chainedDeclarations.size() 37 | ? chainedDeclarations.toSource(printOptions) 38 | : null; 39 | }; 40 | -------------------------------------------------------------------------------- /transforms/underscore-to-lodash-native.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_OPTIONS = { 2 | 'split-imports': false, 3 | }; 4 | 5 | const NATIVE_METHODS = { 6 | forEach: 'forEach', 7 | each: 'forEach', 8 | map: 'map', 9 | collect: 'map', 10 | filter: 'filter', 11 | select: 'filter', 12 | every: 'every', 13 | some: 'some', 14 | find: 'find', 15 | detect: 'find', 16 | contains: 'includes', 17 | reduce: 'reduce', 18 | inject: 'reduce', 19 | indexOf: 'indexOf', 20 | lastIndexOf: 'lastIndexOf', 21 | first: (j, identifier) => j.memberExpression(identifier, j.literal(0)), 22 | last: (j, identifier) => j.memberExpression(identifier, 23 | j.binaryExpression('-', 24 | j.memberExpression(identifier, j.identifier('length')), 25 | j.literal(1) 26 | ) 27 | ), 28 | }; 29 | 30 | /** 31 | * This codemod does a few different things. 32 | * 1. Convert all underscore imports/requires to lodash imports 33 | * const _ = require('underscore') -> import _ from 'lodash' 34 | * 2. Remove native equivalents 35 | * _.forEach(array, fn) -> array.forEach(fn) 36 | * 3. Remove unused imports after #2 37 | * 4. Use partial imports from lodash to allow tree-shaking 38 | * import _ from 'lodash' -> import {find} from 'lodash' 39 | * 40 | * Issues: 41 | * 1. Does not check for variables with same name in scope 42 | * 2. Knows nothing of types, so objects using _ methods will break 43 | */ 44 | module.exports = function(fileInfo, { jscodeshift: j }, argOptions) { 45 | const options = Object.assign({}, DEFAULT_OPTIONS, argOptions); 46 | const ast = j(fileInfo.source); 47 | // Cache opening comments/position 48 | const { comments, loc } = ast.find(j.Program).get('body', 0).node; 49 | // Cache of underscore methods used 50 | j.__methods = {}; 51 | 52 | ast // Iterate each _.() usage 53 | .find(j.CallExpression, isUnderscoreExpression) 54 | .forEach(transformExpression(j, options)); 55 | 56 | ast // const _ = require('underscore') 57 | .find(j.VariableDeclaration, isUnderscoreRequire) 58 | .forEach(transformRequire(j, options)); 59 | 60 | ast // const _ = require('lodash') 61 | .find(j.VariableDeclaration, isLodashRequire) 62 | .forEach(transformRequire(j, options)); 63 | 64 | ast // import _ from 'underscore' 65 | .find(j.ImportDeclaration, isUnderscoreImport) 66 | .forEach(transformImport(j, options)); 67 | 68 | ast // import _ from 'lodash' 69 | .find(j.ImportDeclaration, isLodashImport) 70 | .forEach(transformImport(j, options)); 71 | 72 | // Restore opening comments/position 73 | Object.assign(ast.find(j.Program).get('body', 0).node, { comments, loc }); 74 | 75 | return ast.toSource({ 76 | arrowParensAlways: true, 77 | quote: 'single', 78 | }); 79 | }; 80 | 81 | function isUnderscoreExpression(node) { 82 | return ( 83 | node.type === 'CallExpression' && 84 | node.callee.type === 'MemberExpression' && 85 | node.callee.object && 86 | node.callee.object.name === '_' 87 | ); 88 | } 89 | 90 | function isRequire(node, required) { 91 | return ( 92 | node.type === 'VariableDeclaration' && 93 | node.declarations.length > 0 && 94 | node.declarations[0].type === 'VariableDeclarator' && 95 | node.declarations[0].init && 96 | node.declarations[0].init.type === 'CallExpression' && 97 | node.declarations[0].init.callee && 98 | node.declarations[0].init.callee.name === 'require' && 99 | node.declarations[0].init.arguments[0].value === required 100 | ); 101 | } 102 | 103 | function isUnderscoreRequire(node) { 104 | return isRequire(node, 'underscore'); 105 | } 106 | 107 | function isLodashRequire(node) { 108 | return isRequire(node, 'lodash'); 109 | } 110 | 111 | 112 | function isImport(node,imported) { 113 | return ( 114 | node.type === 'ImportDeclaration' && 115 | node.source.value === imported 116 | ); 117 | } 118 | 119 | function isUnderscoreImport(node) { 120 | return isImport(node, 'underscore'); 121 | } 122 | 123 | function isLodashImport(node) { 124 | return isImport(node, 'lodash'); 125 | } 126 | 127 | function transformExpression(j, options) { 128 | return (ast) => { 129 | const methodName = ast.node.callee.property.name; 130 | const nativeMapping = NATIVE_METHODS[methodName]; 131 | if (nativeMapping) { 132 | if (typeof nativeMapping === 'function') { 133 | transformNativeSpecial(j, ast); 134 | } else { 135 | transformNativeMethod(j, ast); 136 | } 137 | } else { 138 | transformUnderscoreMethod(j, ast); 139 | } 140 | }; 141 | } 142 | 143 | function transformNativeSpecial(j, ast) { 144 | const methodName = ast.node.callee.property.name; 145 | const nativeMapping = NATIVE_METHODS[methodName]; 146 | j(ast).replaceWith(nativeMapping(j, ast.node.arguments[0])); 147 | } 148 | 149 | function transformNativeMethod(j, ast) { 150 | const methodName = ast.node.callee.property.name; 151 | const nativeMapping = NATIVE_METHODS[methodName]; 152 | j(ast).replaceWith( 153 | j.callExpression( 154 | j.memberExpression( 155 | ast.node.arguments[0], j.identifier(nativeMapping) 156 | ), 157 | ast.node.arguments.slice(1) 158 | ) 159 | ); 160 | } 161 | 162 | function transformUnderscoreMethod(j, ast) { 163 | const methodName = ast.node.callee.property.name; 164 | j.__methods[methodName] = true; 165 | j(ast).replaceWith( 166 | j.callExpression(j.identifier(methodName), ast.node.arguments) 167 | ); 168 | } 169 | 170 | function transformRequire(j, options) { 171 | const imports = Object.keys(j.__methods); 172 | return (ast) => { 173 | if (imports.length === 0) { 174 | j(ast).remove(); 175 | } else if (options['split-imports']) { 176 | j(ast).replaceWith(buildSplitImports(j, imports)); 177 | } else { 178 | j(ast).replaceWith( 179 | j.importDeclaration(getImportSpecifiers(j, imports), j.literal('lodash')) 180 | ); 181 | } 182 | }; 183 | } 184 | 185 | function transformImport(j, options) { 186 | const imports = Object.keys(j.__methods); 187 | return (ast) => { 188 | ast.node.source = j.literal('lodash'); 189 | if (imports.length === 0) { 190 | j(ast).remove(); 191 | } else if (options['split-imports']) { 192 | j(ast).replaceWith(buildSplitImports(j, imports)); 193 | } else { 194 | ast.node.specifiers = getImportSpecifiers(j, imports); 195 | } 196 | }; 197 | } 198 | 199 | function buildSplitImports(j, imports) { 200 | return imports.map(name => { 201 | return j.importDeclaration([j.importDefaultSpecifier(j.identifier(name))], j.literal(`lodash/${name}`)); 202 | }); 203 | } 204 | 205 | function getImportSpecifiers(j, imports) { 206 | return imports.map(name => { 207 | return j.importSpecifier(j.identifier(name)); 208 | }); 209 | } 210 | -------------------------------------------------------------------------------- /transforms/unquote-properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes quotes from object properties whose keys are strings which are valid 3 | * identifiers. Does not handle quoted methods. 4 | */ 5 | module.exports = (file, api, options) => { 6 | const j = api.jscodeshift; 7 | const printOptions = options.printOptions || {quote: 'single'}; 8 | const root = j(file.source); 9 | 10 | const isValidIdentifierNameForProperty = (name) => { 11 | 12 | if (/^0[xbo]?\d+$/.test(name)) { 13 | return false; 14 | } 15 | 16 | try { 17 | new Function(`({${name}: 1})`)(); //eslint-disable-line no-new-func 18 | } catch (e) { 19 | return false; 20 | } 21 | 22 | return true; 23 | }; 24 | 25 | root 26 | .find(j.Property, { 27 | method: false, 28 | computed: false, 29 | }) 30 | .filter(p => ( 31 | p.value.key.type === 'Literal' && 32 | typeof p.value.key.value === 'string' && 33 | isValidIdentifierNameForProperty(p.value.key.value) 34 | )) 35 | .forEach(p => p.value.key = j.identifier(p.value.key.value)); 36 | 37 | return root.toSource(printOptions); 38 | }; 39 | -------------------------------------------------------------------------------- /transforms/updated-computed-props.js: -------------------------------------------------------------------------------- 1 | const keywords = { 2 | 'this': true, 3 | 'function': true, 4 | 'if': true, 5 | 'return': true, 6 | 'var': true, 7 | 'else': true, 8 | 'for': true, 9 | 'new': true, 10 | 'in': true, 11 | 'typeof': true, 12 | 'while': true, 13 | 'case': true, 14 | 'break': true, 15 | 'try': true, 16 | 'catch': true, 17 | 'delete': true, 18 | 'throw': true, 19 | 'switch': true, 20 | 'continue': true, 21 | 'default': true, 22 | 'instanceof': true, 23 | 'do': true, 24 | 'void': true, 25 | 'finally': true, 26 | 'with': true, 27 | 'debugger': true, 28 | 'implements': true, 29 | 'interface': true, 30 | 'package': true, 31 | 'private': true, 32 | 'protected': true, 33 | 'public': true, 34 | 'static': true, 35 | 'class': true, 36 | 'enum': true, 37 | 'export': true, 38 | 'extends': true, 39 | 'import': true, 40 | 'super': true, 41 | 'true': true, 42 | 'false': true, 43 | 'null': true, 44 | 'abstract': true, 45 | 'boolean': true, 46 | 'byte': true, 47 | 'char': true, 48 | 'const': true, 49 | 'double': true, 50 | 'final': true, 51 | 'float': true, 52 | 'goto': true, 53 | 'int': true, 54 | 'long': true, 55 | 'native': true, 56 | 'short': true, 57 | 'synchronized': true, 58 | 'throws': true, 59 | 'transient': true, 60 | 'volatile': true, 61 | }; 62 | 63 | module.exports = function(file, api) { 64 | const j = api.jscodeshift; 65 | const root = j(file.source); 66 | const didTransform = root 67 | .find(j.MemberExpression, {computed: true, property: {type: 'Literal'}}) 68 | .filter(p => !!keywords[p.value.property.value]) 69 | .replaceWith( 70 | p => j.memberExpression( 71 | p.value.object, 72 | j.identifier(p.value.property.value), 73 | false 74 | ) 75 | ) 76 | .size(); 77 | 78 | return didTransform ? root.toSource() : null; 79 | }; 80 | -------------------------------------------------------------------------------- /transforms/use-strict.js: -------------------------------------------------------------------------------- 1 | module.exports = (file, api, options) => { 2 | const j = api.jscodeshift; 3 | 4 | const hasStrictMode = body => 5 | body.some( 6 | statement => j.match(statement, { 7 | type: 'ExpressionStatement', 8 | expression: { 9 | type: 'Literal', 10 | value: 'use strict', 11 | }, 12 | }) 13 | ); 14 | 15 | const withComments = (to, from) => { 16 | to.comments = from.comments; 17 | return to; 18 | }; 19 | 20 | const createUseStrictExpression = () => 21 | j.expressionStatement( 22 | j.literal('use strict') 23 | ); 24 | 25 | const root = j(file.source); 26 | const body = root.get().value.program.body; 27 | if (!body.length || hasStrictMode(body)) { 28 | return null; 29 | } 30 | 31 | body.unshift(withComments(createUseStrictExpression(), body[0])); 32 | body[0].comments = body[1].comments; 33 | delete body[1].comments; 34 | 35 | return root.toSource(options.printOptions || {quote: 'single'}); 36 | }; 37 | --------------------------------------------------------------------------------