├── .babelrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── DEPENDENCIES.md ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── test ├── codemods.spec.js ├── fixtures │ └── use-named-exports │ │ ├── default-literal.input.js │ │ └── default-literal.output.js └── setup.js └── transforms ├── add-react-import.js ├── import-from-root.js ├── lib ├── file.js └── helpers.js ├── move-children-prop.js ├── remove-react-default-props.js ├── remove-react-prop-types.js ├── sort-jsx-props.js ├── sort-object-props.js ├── use-named-exports.js ├── use-named-imports.js └── use-string-literal-props.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { "node": "current" } 7 | } 8 | ], 9 | "jest" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: JamieMason 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Explain how to reproduce a Bug 4 | --- 5 | 6 | ## Description 7 | 8 | 13 | 14 | ## Suggested Solution 15 | 16 | 20 | 21 | ## Help Needed 22 | 23 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | ## Description 7 | 8 | 14 | 15 | ## Suggested Solution 16 | 17 | 21 | 22 | ## Help Needed 23 | 24 | 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description (What) 2 | 3 | 9 | 10 | ## Justification (Why) 11 | 12 | 18 | 19 | ## How Can This Be Tested? 20 | 21 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "printWidth": 120, 4 | "proseWrap": "always", 5 | "singleQuote": true, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.16.3](https://github.com/JamieMason/codemods/compare/0.15.2...0.16.3) (2021-12-16) 2 | 3 | ### Bug Fixes 4 | 5 | - **npm:** update jscodeshift 6 | ([51d238f](https://github.com/JamieMason/codemods/commit/51d238f0528997dcda1461a76da68079969306c8)) 7 | 8 | ### Features 9 | 10 | - **react:** convert string expression props to literals 11 | ([fe35134](https://github.com/JamieMason/codemods/commit/fe3513458c660e1e4bb8939b7d504c2b27d62de7)) 12 | 13 | ## [0.15.2](https://github.com/JamieMason/codemods/compare/0.9.1...0.15.2) (2021-12-16) 14 | 15 | ### Bug Fixes 16 | 17 | - **react:** handle when react is imported without default 18 | ([c94ab27](https://github.com/JamieMason/codemods/commit/c94ab27c09ab507093922891bcd0bdf8bf1b58ef)) 19 | 20 | ### Features 21 | 22 | - **parser:** switch from flow to tsx 23 | ([9f80087](https://github.com/JamieMason/codemods/commit/9f80087f5c7b56763edd1a01ca262aa6b0a734d3)) 24 | - **react:** remove propTypes set as properties 25 | ([78d53e9](https://github.com/JamieMason/codemods/commit/78d53e977d292588031553b323dc740491424a33)), 26 | closes [#12](https://github.com/JamieMason/codemods/issues/12) 27 | - **react:** remove propTypes set via \_defineProperty 28 | ([6d3a514](https://github.com/JamieMason/codemods/commit/6d3a514386651e3db7200e2acf40bb2eb40fa7b2)) 29 | - **react:** remove use of react defaultProps 30 | ([e5afbb9](https://github.com/JamieMason/codemods/commit/e5afbb94cf4f5290574f59b297b1470046e9415a)) 31 | - **react:** remove use of react PropTypes 32 | ([970c459](https://github.com/JamieMason/codemods/commit/970c4598f7371efd620c025464841873b8d56822)) 33 | - **typescript:** process typescript files 34 | ([f5db971](https://github.com/JamieMason/codemods/commit/f5db9719111473789f75273b06dc1e1187b6b938)) 35 | 36 | ## [0.9.1](https://github.com/JamieMason/codemods/compare/0.8.1...0.9.1) (2020-03-11) 37 | 38 | ### Features 39 | 40 | - **react:** add missing react imports 41 | ([e5c35c7](https://github.com/JamieMason/codemods/commit/e5c35c7a18669a17cff022521ef9689169547b53)) 42 | 43 | ## [0.8.1](https://github.com/JamieMason/codemods/compare/0.7.1...0.8.1) (2019-08-01) 44 | 45 | ### Features 46 | 47 | - **react:** ignore spread elements when sorting props 48 | ([1ad03c9](https://github.com/JamieMason/codemods/commit/1ad03c92a2c6acde99728f5dd3d4f984b440766c)) 49 | 50 | ## [0.7.1](https://github.com/JamieMason/codemods/compare/01a2944898cea2047996489643ae16a71e040d75...0.7.1) (2019-08-01) 51 | 52 | ### Bug Fixes 53 | 54 | - **npm:** update dependencies 55 | ([c08b795](https://github.com/JamieMason/codemods/commit/c08b795c2a35c32511e1d3458f6057d0317c7520)) 56 | 57 | ### Features 58 | 59 | - **exports:** naively convert default to named 60 | ([b2e8318](https://github.com/JamieMason/codemods/commit/b2e8318fef078e2badf1ec3d3eab6c98311e0d43)) 61 | - **imports:** import packages from root indexes 62 | ([2e6279f](https://github.com/JamieMason/codemods/commit/2e6279f95188974c16e94a7fb9208cc536a52641)) 63 | - **imports:** naively convert default imports to named imports 64 | ([d61759c](https://github.com/JamieMason/codemods/commit/d61759c0b4bb8a03b40de6d8fa309dc4e8727415)) 65 | - **objects:** ignore spread elements when sorting members 66 | ([55f1599](https://github.com/JamieMason/codemods/commit/55f1599937a54d61d317ce9e89fbc19fbf667d1c)), 67 | closes [#2](https://github.com/JamieMason/codemods/issues/2) 68 | - **objects:** sort members in a-z order 69 | ([e0abf87](https://github.com/JamieMason/codemods/commit/e0abf8755e17cb5fc42bcfee1571919c175209b5)) 70 | - **react:** sort JSX props in a-z order 71 | ([01a2944](https://github.com/JamieMason/codemods/commit/01a2944898cea2047996489643ae16a71e040d75)) 72 | - **scripts:** allow transforms to be run as npm scripts 73 | ([b1d6f1c](https://github.com/JamieMason/codemods/commit/b1d6f1c5eec26171a4c9aafc64c9aa331a6c0e69)) 74 | -------------------------------------------------------------------------------- /DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | # codemods 2 | 3 | Transforms for use with JSCodeshift 4 | 5 | ## Tests 6 | 7 | ```sh 8 | npm install 9 | npm test 10 | ``` 11 | 12 | ## Dependencies 13 | 14 | - [jscodeshift](https://ghub.io/jscodeshift): A toolkit for JavaScript codemods 15 | 16 | ## Dev Dependencies 17 | 18 | - [babel-preset-env](https://ghub.io/babel-preset-env): A Babel preset for each 19 | environment. 20 | - [jest](https://ghub.io/jest): Delightful JavaScript Testing. 21 | 22 | ## License 23 | 24 | MIT 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jamie Mason 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codemods 2 | 3 | > A collection of transforms for use with 4 | > [facebook/jscodeshift](https://github.com/facebook/jscodeshift). 5 | 6 | [![Follow JamieMason on GitHub](https://img.shields.io/github/followers/JamieMason.svg?style=social&label=Follow)](https://github.com/JamieMason) 7 | [![Follow fold_left on Twitter](https://img.shields.io/twitter/follow/fold_left.svg?style=social&label=Follow)](https://twitter.com/fold_left) 8 | 9 | * [🌩 Installation](#-installation) 10 | * [🕹 Usage](#-usage) 11 | * [⚙️ Contributing](#️-contributing) 12 | * [📝 API](#-api) 13 | * [use-string-literal-props](#use-string-literal-props) 14 | * [add-react-import](#add-react-import) 15 | * [import-from-root](#import-from-root) 16 | * [move-children-prop](#move-children-prop) 17 | * [remove-react-prop-types](#remove-react-prop-types) 18 | * [remove-react-default-props](#remove-react-default-props) 19 | * [sort-jsx-props](#sort-jsx-props) 20 | * [sort-object-props](#sort-object-props) 21 | * [use-named-exports](#use-named-exports) 22 | * [use-named-imports](#use-named-imports) 23 | * [❓ Quick Intro To Making A Codemod](#-quick-intro-to-making-a-codemod) 24 | * [🙋🏾‍♀️ Getting Help](#️-getting-help) 25 | 26 | ## 🌩 Installation 27 | 28 | ```sh 29 | git clone https://github.com/JamieMason/codemods.git 30 | cd codemods 31 | yarn install 32 | ``` 33 | 34 | ## 🕹 Usage 35 | 36 | ``` 37 | # yarn 38 | yarn name-of-the-transform 39 | 40 | # npm 41 | npm run name-of-the-transform -- 42 | 43 | # jscodeshift 44 | jscodeshift -t ./transforms/name-of-the-transform.js 45 | ``` 46 | 47 | ## ⚙️ Contributing 48 | 49 | Transforms can be created at `./transforms/.js` and tested by 50 | adding example input files at 51 | `./test/fixtures//.input.js` with the 52 | corresponding expected output alongside it at 53 | `./test/fixtures//.output.js`. 54 | 55 | All fixtures are discovered and tested when running `yarn test`. 56 | 57 | ## 📝 API 58 | 59 | ### use-string-literal-props 60 | 61 | Convert JSX props which are expressions for a string literal, into just a string literal. 62 | 63 | ```jsx 64 | /* INPUT */ 65 | const SomeComponent = () => ( 66 | 71 | ); 72 | 73 | /* OUTPUT */ 74 | const SomeComponent = () => ( 75 | 80 | ); 81 | ``` 82 | 83 | ### add-react-import 84 | 85 | Import React if it is missing from a file which uses JSX. 86 | 87 | ```jsx 88 | /* INPUT */ 89 | export const Component = () =>
90 | 91 | /* OUTPUT */ 92 | import React from "react"; 93 | export const Component = () =>
94 | ``` 95 | 96 | ### import-from-root 97 | 98 | Rewrite deep imports to import from a packages' root index. 99 | 100 | > Set the Environment Variable `IMPORT_FROM_ROOT` to apply this transform only 101 | > to packages whose name starts with that string: 102 | > `IMPORT_FROM_ROOT=some-package yarn import-from-root ` 103 | 104 | ```js 105 | /* INPUT */ 106 | import { foo } from "some-package/foo/bar/baz"; 107 | 108 | /* OUTPUT */ 109 | import { foo } from "some-package"; 110 | ``` 111 | 112 | ### move-children-prop 113 | 114 | Use the common syntax for react children 115 | 116 | ```js 117 | /* INPUT */ 118 | 119 | 120 | /* OUTPUT */ 121 | wat 122 | ``` 123 | 124 | ### remove-react-prop-types 125 | 126 | Remove use of React PropTypes. 127 | 128 | ```jsx 129 | /* INPUT */ 130 | import React from 'react' 131 | import PropTypes from 'prop-types' 132 | 133 | export const Greet = ({ name }) => Hi {name} 134 | Greet.propTypes = { name: PropTypes.string } 135 | /* OUTPUT */ 136 | import React from 'react' 137 | 138 | export const Greet = ({ name }) => Hi {name} 139 | ``` 140 | 141 | ### remove-react-default-props 142 | 143 | Remove use of React defaultProps. 144 | 145 | ```jsx 146 | /* INPUT */ 147 | import React from 'react' 148 | 149 | export const Greet = ({ name }) => Hi {name} 150 | Greet.defaultProps = { name: 'Stranger' } 151 | /* OUTPUT */ 152 | import React from 'react' 153 | 154 | export const Greet = ({ name }) => Hi {name} 155 | ``` 156 | 157 | ### sort-jsx-props 158 | 159 | Sort props of JSX Components alphabetically. 160 | 161 | ```jsx 162 | /* INPUT */ 163 | 164 | 165 | /* OUTPUT */ 166 | 167 | ``` 168 | 169 | ### sort-object-props 170 | 171 | Sort members of Object Literals alphabetically. 172 | 173 | ```js 174 | /* INPUT */ 175 | const players = { messi: true, bergkamp: true, ginola: true }; 176 | 177 | /* OUTPUT */ 178 | const players = { bergkamp: true, ginola: true, messi: true }; 179 | ``` 180 | 181 | ### use-named-exports 182 | 183 | Naively convert a default export to a named export using the name of the file, 184 | which may clash with other variable names in the file. This codemod would need 185 | following up on with ESLint and some manual fixes. 186 | 187 | ```js 188 | /* INPUT */ 189 | // ~/Dev/repo/src/apps/health/server.js 190 | export default mount("/health", app); 191 | 192 | /* OUTPUT */ 193 | // ~/Dev/repo/src/apps/health/server.js 194 | export const server = mount("/health", app); 195 | ``` 196 | 197 | ### use-named-imports 198 | 199 | Naively convert a default import to a named import using the original name, 200 | which may not match what the module is actually exporting. This codemod would 201 | need following up on with ESLint and some manual fixes. 202 | 203 | ```js 204 | /* INPUT */ 205 | import masthead from "@sky-uk/koa-masthead"; 206 | 207 | /* OUTPUT */ 208 | import { masthead } from "@sky-uk/koa-masthead"; 209 | ``` 210 | 211 | ## ❓ Quick Intro To Making A Codemod 212 | 213 | 1. Open [ASTExplorer][astexplorer] with the Parser set to `esprima` and 214 | Transform set to `jscodeshift`. 215 | 2. Paste some **Source** in the Top-Left Panel which you want to Transform. 216 | 3. Edit your **Codemod** in the Bottom-Left Panel. 217 | 218 | There will be 4 Panels: 219 | 220 | | Panel | Purpose | 221 | | :----------- | :------------------------------------------------------------- | 222 | | Top-Left | **Source** you want to transform | 223 | | Top-Right | The **AST** of your **Source** | 224 | | Bottom-Left | Your **Codemod** Script | 225 | | Bottom-Right | The **Result** of applying your **Codemod** to your **Source** | 226 | 227 | The [docs for jscodeshift](https://github.com/facebook/jscodeshift) aren't 228 | enough and you will need to refer to ast-type [definitions] to know what is 229 | available. Using [VariableDeclaration][variabledeclaration] as an example, you 230 | can find all variable declarations using the PascalCase `j.VariableDeclaration` 231 | 232 | ```js 233 | j(file.source).find(j.VariableDeclaration); 234 | ``` 235 | 236 | and create new variable declarations using the camelCase `j.variableDeclarator`. 237 | 238 | ```js 239 | j.variableDeclaration("const", [ 240 | j.variableDeclarator(j.identifier(fileName), exportedValue) 241 | ]); 242 | ``` 243 | 244 | The [VariableDeclaration][variabledeclaration] definition shows what `fields` it 245 | takes, which are the arguments the `j.variableDeclarator` function takes, which 246 | are an `Identifier` (a variable with a name but no value), or a 247 | `VariableDeclarator` (a variable with a name as well as a value assigned). 248 | 249 | If we look up the definition of [VariableDeclarator][variabledeclarator] we see 250 | it takes two arguments called `id` and `init`. The `id` is an identifier 251 | `j.identifier('varName')` and `init` is a value to initialise the variable with, 252 | which should be the AST of whatever value you want to assign. For a simple 253 | literal value, that would be `j.literal('hello')`. 254 | 255 | Putting that all together you have a Hello World Codemod of: 256 | 257 | ```js 258 | export default (file, api) => { 259 | const j = api.jscodeshift; 260 | 261 | // Have a look in the console at what APIs are available 262 | console.log({ 263 | jscodeshiftAPI: j, 264 | fileAPI: j(file.source) 265 | }); 266 | 267 | return j(file.source) 268 | .find(j.Program) 269 | .forEach(path => { 270 | // Unwrap the AST node from this wrapper 271 | const emptyFile = path.value; 272 | 273 | // add a comment 274 | const singleLineComment = j.commentLine(" Hello World"); 275 | const varName = j.identifier("hello"); 276 | emptyFile.comments = [singleLineComment]; 277 | 278 | // add a const 279 | const literalString = j.literal("world"); 280 | const nameValuePair = j.variableDeclarator(varName, literalString); 281 | const constVarStatement = j.variableDeclaration("const", [nameValuePair]); 282 | emptyFile.body = [constVarStatement]; 283 | }) 284 | .toSource(); 285 | }; 286 | ``` 287 | 288 | Good luck! 289 | 290 | ## 🙋🏾‍♀️ Getting Help 291 | 292 | - Get help with issues by creating a 293 | [Bug Report](https://github.com/JamieMason/codemods/issues/new?template=bug_report.md). 294 | - Discuss ideas by opening a 295 | [Feature Request](https://github.com/JamieMason/codemods/issues/new?template=feature_request.md). 296 | 297 | 298 | 299 | [astexplorer]: 300 | https://astexplorer.net/#/gist/47f549f753f541aff11c492c89ae82fa/e56b2df09a8e868c86139bc39ea631a0a725cbf6 301 | [definitions]: https://github.com/benjamn/ast-types/tree/master/def 302 | [variabledeclaration]: 303 | https://github.com/benjamn/ast-types/blob/v0.11.7/def/esprima.js#L9-L13 304 | [variabledeclarator]: 305 | https://github.com/benjamn/ast-types/blob/a7eaba5ecc79a58acb469cbbf9fe7603cec9f57e/def/core.js#L190-L194 306 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverage: false, 3 | setupFilesAfterEnv: ['/test/setup.js'], 4 | testMatch: ['/test/codemods.spec.js'] 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codemods", 3 | "description": "Transforms for use with JSCodeshift", 4 | "version": "0.16.3", 5 | "author": "Jamie Mason (https://github.com/JamieMason)", 6 | "bugs": "https://github.com/JamieMason/codemods/issues", 7 | "dependencies": { 8 | "jscodeshift": "17.1.1" 9 | }, 10 | "devDependencies": { 11 | "babel-preset-env": "1.7.0", 12 | "jest": "27.4.5" 13 | }, 14 | "homepage": "https://github.com/JamieMason/codemods#readme", 15 | "license": "MIT", 16 | "manager": "npm", 17 | "private": true, 18 | "repository": "JamieMason/codemods", 19 | "scripts": { 20 | "add-react-import": "npm run transform -- ./transforms/add-react-import.js", 21 | "import-from-root": "npm run transform -- ./transforms/import-from-root.js", 22 | "move-children-prop": "npm run transform -- ./transforms/move-children-prop.js", 23 | "remove-react-default-props": "npm run transform -- ./transforms/remove-react-default-props.js", 24 | "remove-react-prop-types": "npm run transform -- ./transforms/remove-react-prop-types.js", 25 | "sort-jsx-props": "npm run transform -- ./transforms/sort-jsx-props.js", 26 | "sort-object-props": "npm run transform -- ./transforms/sort-object-props.js", 27 | "test": "jest", 28 | "transform": "npm exec jscodeshift -- --parser tsx --ignore-pattern '**/node_modules/**' --extensions js,jsx,ts,tsx -t", 29 | "use-named-exports": "npm run transform -- ./transforms/use-named-exports.js", 30 | "use-named-imports": "npm run transform -- ./transforms/use-named-imports.js", 31 | "use-string-literal-props": "npm run transform -- ./transforms/use-string-literal-props.js" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/codemods.spec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | // To write tests, create fixture files in the locations below and they 5 | // will be discovered and run: 6 | // 7 | // ./test/fixtures//.input.js 8 | // ./test/fixtures//.output.js 9 | 10 | fs.readdirSync(path.resolve(__dirname, '../transforms')) 11 | .filter(filename => filename.endsWith('.js')) 12 | .map(filename => filename.replace('.js', '')) 13 | .forEach(transformName => { 14 | testTransform(transformName); 15 | }); 16 | -------------------------------------------------------------------------------- /test/fixtures/use-named-exports/default-literal.input.js: -------------------------------------------------------------------------------- 1 | export default 1; 2 | -------------------------------------------------------------------------------- /test/fixtures/use-named-exports/default-literal.output.js: -------------------------------------------------------------------------------- 1 | export const someFile = 1; 2 | export default someFile; 3 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const testUtils = require('jscodeshift/src/testUtils'); 4 | 5 | const fakePath = '/Users/you/Dev/my-project/src/some-file.js'; 6 | 7 | global.testTransform = (name, { filePath = fakePath, options = {} } = {}) => { 8 | describe(name, () => { 9 | const root = path.join(__dirname, '..'); 10 | const transformPath = `${root}/transforms/${name}.js`; 11 | const fixturesPath = `${root}/test/fixtures/${name}`; 12 | 13 | if (!fs.existsSync(fixturesPath)) { 14 | it.skip('create tests in ./test/fixtures//.input.js', () => {}); 15 | return; 16 | } 17 | 18 | fs.readdirSync(fixturesPath) 19 | .filter(filename => filename.endsWith('.input.js')) 20 | .map(filename => filename.replace('.input.js', '')) 21 | .forEach(id => { 22 | const inputPath = `${fixturesPath}/${id}.input.js`; 23 | const outputPath = `${fixturesPath}/${id}.output.js`; 24 | const displayPath = path.relative(root, inputPath); 25 | 26 | it(displayPath, () => { 27 | const transform = require(transformPath); 28 | const input = fs.readFileSync(inputPath, 'utf8'); 29 | const expectedOutput = fs.readFileSync(outputPath, 'utf8'); 30 | jest.spyOn(console, 'log').mockImplementation(() => {}); 31 | testUtils.runInlineTest( 32 | transform, 33 | options, 34 | { path: filePath, source: input }, 35 | expectedOutput 36 | ); 37 | jest.restoreAllMocks(); 38 | }); 39 | }); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /transforms/add-react-import.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const root = j(file.source); 4 | 5 | const containsJsx = root.find(j.JSXIdentifier).length > 0; 6 | 7 | const containsReactPackageImport = 8 | root.find(j.ImportDeclaration).filter((importDeclaration) => importDeclaration.node.source.value === 'react') 9 | .length > 0; 10 | 11 | const containsReactDefaultImport = 12 | root 13 | .find(j.ImportDefaultSpecifier) 14 | .filter((importDefaultSpecifier) => importDefaultSpecifier.node.local.name === 'React').length > 0; 15 | 16 | if (containsJsx && !(containsReactPackageImport && containsReactDefaultImport)) { 17 | root 18 | .get() 19 | .node.program.body.unshift( 20 | j.importDeclaration([j.importDefaultSpecifier(j.identifier('React'))], j.literal('react')), 21 | ); 22 | } 23 | 24 | return root.toSource(); 25 | }; 26 | -------------------------------------------------------------------------------- /transforms/import-from-root.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const { IMPORT_FROM_ROOT = '' } = process.env; 4 | 5 | const matchesUserFilter = (importDeclaration) => 6 | IMPORT_FROM_ROOT && importDeclaration.source.value.startsWith(IMPORT_FROM_ROOT); 7 | 8 | return j(file.source) 9 | .find(j.ImportDeclaration) 10 | .filter((path) => matchesUserFilter(path.value)) 11 | .forEach((path) => { 12 | const importDeclaration = path.value; 13 | const paths = importDeclaration.source.value.split('/'); 14 | if (importDeclaration.source.value.startsWith('@')) { 15 | const [scope, packageName] = paths; 16 | importDeclaration.source.value = `${scope}/${packageName}`; 17 | } else { 18 | const [packageName] = paths; 19 | importDeclaration.source.value = `${packageName}`; 20 | } 21 | }) 22 | .toSource(); 23 | }; 24 | -------------------------------------------------------------------------------- /transforms/lib/file.js: -------------------------------------------------------------------------------- 1 | const reFilePath = /.+\/+/; 2 | const reExtensions = /\..+/; 3 | const reNonCamelCasing = /[-_ ]([a-z])/g; 4 | 5 | export const getName = (file) => file.path.replace('/index', '').replace(reFilePath, '').replace(reExtensions, ''); 6 | 7 | export const getNameInCamelCase = (file) => getName(file).replace(reNonCamelCasing, ($1) => $1.charAt(1).toUpperCase()); 8 | 9 | export const getNameInPascalCase = (file) => 10 | getNameInCamelCase(file).charAt(0).toUpperCase() + getNameInCamelCase(file).slice(1); 11 | -------------------------------------------------------------------------------- /transforms/lib/helpers.js: -------------------------------------------------------------------------------- 1 | const once = (fn) => { 2 | const lock = { enabled: false }; 3 | return (j) => { 4 | if (lock.enabled) return; 5 | lock.enabled = true; 6 | fn(j); 7 | }; 8 | }; 9 | 10 | export const extendApi = once((j) => { 11 | const isTopLevel = (path) => path.parent.value.type === 'Program'; 12 | j.registerMethods({ 13 | getExportsByClassName(className) { 14 | return this.find(j.ExportNamedDeclaration, { 15 | declaration: { type: 'ClassDeclaration', id: { type: 'Identifier', name: className } }, 16 | }); 17 | }, 18 | getExportsByFunctionName(className) { 19 | return this.find(j.ExportNamedDeclaration, { 20 | declaration: { type: 'FunctionDeclaration', id: { type: 'Identifier', name: className } }, 21 | }); 22 | }, 23 | getExportsByVarName(varName) { 24 | return this.find(j.ExportNamedDeclaration, { 25 | declaration: { 26 | type: 'VariableDeclaration', 27 | declarations: [{ type: 'VariableDeclarator', id: { type: 'Identifier', name: varName } }], 28 | }, 29 | }); 30 | }, 31 | getImportsByPackageName(packageName) { 32 | return this.find(j.ImportDeclaration, { 33 | source: { 34 | value: packageName, 35 | }, 36 | }); 37 | }, 38 | getNamedExportedClasses() { 39 | return this.find(j.ExportNamedDeclaration, { 40 | declaration: { type: 'ClassDeclaration', id: { type: 'Identifier' } }, 41 | }); 42 | }, 43 | getNamedExportedFunctions() { 44 | return this.find(j.ExportNamedDeclaration, { 45 | declaration: { type: 'FunctionDeclaration', id: { type: 'Identifier' } }, 46 | }); 47 | }, 48 | getNamedExportedVars() { 49 | return this.find(j.ExportNamedDeclaration, { 50 | declaration: { type: 'VariableDeclaration' }, 51 | }); 52 | }, 53 | getTopLevelClasses() { 54 | return this.find(j.ClassDeclaration).filter(isTopLevel); 55 | }, 56 | getTopLevelFunctions() { 57 | return this.find(j.FunctionDeclaration).filter(isTopLevel); 58 | }, 59 | getTopLevelVariables() { 60 | return this.find(j.VariableDeclaration).filter(isTopLevel); 61 | }, 62 | getTopLevelClassByName(className) { 63 | return this.find(j.ClassDeclaration, { 64 | id: { type: 'Identifier', name: className }, 65 | }).filter(isTopLevel); 66 | }, 67 | getTopLevelFunctionByName(className) { 68 | return this.find(j.FunctionDeclaration, { 69 | id: { type: 'Identifier', name: className }, 70 | }).filter(isTopLevel); 71 | }, 72 | getTopLevelVariableByName(varName) { 73 | return this.find(j.VariableDeclaration, { 74 | declarations: [{ type: 'VariableDeclarator', id: { type: 'Identifier', name: varName } }], 75 | }).filter(isTopLevel); 76 | }, 77 | getImportedVarNames() { 78 | const identifiers = []; 79 | this.find(j.ImportDeclaration).forEach((path) => { 80 | const importDeclaration = path.value; 81 | importDeclaration.specifiers.forEach((specifier) => { 82 | identifiers.push(specifier.local.name); 83 | }); 84 | }); 85 | return identifiers; 86 | }, 87 | getNamedExportedClassNames() { 88 | const identifiers = []; 89 | this.getNamedExportedClasses().forEach((path) => { 90 | const exportNamedDeclaration = path.value; 91 | identifiers.push(exportNamedDeclaration.declaration.id.name); 92 | }); 93 | return identifiers; 94 | }, 95 | getNamedExportedFunctionNames() { 96 | const identifiers = []; 97 | this.getNamedExportedFunctions().forEach((path) => { 98 | const exportNamedDeclaration = path.value; 99 | identifiers.push(exportNamedDeclaration.declaration.id.name); 100 | }); 101 | return identifiers; 102 | }, 103 | getNamedExportedVarNames() { 104 | const identifiers = []; 105 | this.getNamedExportedVars().forEach((path) => { 106 | const exportNamedDeclaration = path.value; 107 | exportNamedDeclaration.declaration.declarations.forEach((declaration) => { 108 | identifiers.push(declaration.id.name); 109 | }); 110 | }); 111 | return identifiers; 112 | }, 113 | getTopLevelClassNames() { 114 | const identifiers = []; 115 | this.getTopLevelClasses().forEach((path) => { 116 | const classDeclaration = path.value; 117 | identifiers.push(classDeclaration.id.name); 118 | }); 119 | return identifiers; 120 | }, 121 | getTopLevelFunctionNames() { 122 | const identifiers = []; 123 | this.getTopLevelFunctions().forEach((path) => { 124 | const functionDeclaration = path.value; 125 | identifiers.push(functionDeclaration.id.name); 126 | }); 127 | return identifiers; 128 | }, 129 | getTopLevelVariableNames() { 130 | const identifiers = []; 131 | this.getTopLevelVariables().forEach((path) => { 132 | const variableDeclaration = path.value; 133 | variableDeclaration.declarations.forEach((declaration) => { 134 | identifiers.push(declaration.id.name); 135 | }); 136 | }); 137 | return identifiers; 138 | }, 139 | getTopLevelVarNames() { 140 | return [].concat( 141 | this.getImportedVarNames(), 142 | this.getNamedExportedClassNames(), 143 | this.getNamedExportedFunctionNames(), 144 | this.getNamedExportedVarNames(), 145 | this.getTopLevelClassNames(), 146 | this.getTopLevelFunctionNames(), 147 | this.getTopLevelVariableNames(), 148 | ); 149 | }, 150 | exportClass(path) { 151 | const classDeclaration = path.value; 152 | return j.exportNamedDeclaration( 153 | j.classDeclaration(j.identifier(classDeclaration.id.name), classDeclaration.body, classDeclaration.superClass), 154 | ); 155 | }, 156 | exportDefaultAsNamed(path, name) { 157 | const exportDefaultDeclaration = path.value; 158 | const varName = j.identifier(name); 159 | const varValue = exportDefaultDeclaration.declaration; 160 | return j.exportNamedDeclaration(j.variableDeclaration('const', [j.variableDeclarator(varName, varValue)])); 161 | }, 162 | exportVarNameAsDefault(name) { 163 | return j.exportDefaultDeclaration(j.identifier(name)); 164 | }, 165 | exportFunction(path) { 166 | const functionDeclaration = path.value; 167 | return j.exportNamedDeclaration( 168 | j.functionDeclaration( 169 | j.identifier(functionDeclaration.id.name), 170 | functionDeclaration.params, 171 | functionDeclaration.body, 172 | ), 173 | ); 174 | }, 175 | exportVariable(path) { 176 | const variableDeclaration = path.value; 177 | return j.exportNamedDeclaration(j.variableDeclaration('const', variableDeclaration.declarations)); 178 | }, 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /transforms/move-children-prop.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const root = j(file.source); 4 | 5 | return root 6 | .find(j.JSXElement) 7 | .forEach((path) => { 8 | // Find elements with a children prop 9 | const childrenProp = path.value.openingElement.attributes.find( 10 | (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'children', 11 | ); 12 | 13 | if (!childrenProp) return; 14 | 15 | // Store the value of the children prop 16 | const childrenValue = childrenProp.value; 17 | 18 | // Remove the children prop 19 | path.value.openingElement.attributes = path.value.openingElement.attributes.filter( 20 | (attr) => attr.name && attr.name.name !== 'children', 21 | ); 22 | 23 | // Make sure the element has a closing tag 24 | if (path.value.openingElement.selfClosing) { 25 | path.value.openingElement.selfClosing = false; 26 | path.value.closingElement = j.jsxClosingElement(j.jsxIdentifier(path.value.openingElement.name.name)); 27 | } 28 | 29 | // Add the children expression between the opening and closing tags 30 | if (childrenValue.type === 'JSXExpressionContainer') { 31 | path.value.children = [childrenValue]; 32 | } else if (childrenValue.type === 'StringLiteral') { 33 | path.value.children = [j.jsxText(childrenValue.value)]; 34 | } else { 35 | path.value.children = [j.jsxExpressionContainer(childrenValue)]; 36 | } 37 | }) 38 | .toSource(); 39 | }; 40 | -------------------------------------------------------------------------------- /transforms/remove-react-default-props.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const removePath = (path) => j(path).remove(); 4 | const isAssigningDefaultProps = (e) => 5 | e.node.left && e.node.left.property && e.node.left.property.name === 'defaultProps'; 6 | 7 | const withoutAssignment = j(file.source) 8 | .find(j.AssignmentExpression) 9 | .filter(isAssigningDefaultProps) 10 | .forEach(removePath) 11 | .toSource(); 12 | 13 | return withoutAssignment; 14 | }; 15 | -------------------------------------------------------------------------------- /transforms/remove-react-prop-types.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const removePath = (path) => j(path).remove(); 4 | 5 | // Obj.proptypes = { ... }; 6 | const isAssigningPropTypes = (e) => e.node.left && e.node.left.property && e.node.left.property.name === 'propTypes'; 7 | 8 | // import PropTypes from 'prop-types'; 9 | const isImportingFromPropTypes = (e) => e.node.source && e.node.source.value === 'prop-types'; 10 | 11 | // require('prop-types'); 12 | const isRequiringFromPropTypes = (e) => 13 | e.node.init && 14 | e.node.init.callee && 15 | e.node.init.callee.name === 'require' && 16 | e.node.init.arguments[0].value === 'prop-types'; 17 | 18 | // _defineProperty(obj, 'propTypes', { ... }); 19 | const isDefiningPropType = (e) => 20 | e.node.expression && 21 | e.node.expression.callee && 22 | e.node.expression.callee.name === '_defineProperty' && 23 | e.node.expression.arguments && 24 | e.node.expression.arguments[1] && 25 | e.node.expression.arguments[1].original && 26 | e.node.expression.arguments[1].original.value === 'propTypes'; 27 | 28 | // { propTypes: { foo: PropTypes.string } }; 29 | const isObjectProperty = (e) => 30 | e.node.key && e.node.key.name === 'propTypes' && e.node.value && e.node.value.type === 'ObjectExpression'; 31 | 32 | const withoutAssignment = j(file.source) 33 | .find(j.AssignmentExpression) 34 | .filter(isAssigningPropTypes) 35 | .forEach(removePath) 36 | .toSource(); 37 | 38 | const withoutImport = j(withoutAssignment) 39 | .find(j.ImportDeclaration) 40 | .filter(isImportingFromPropTypes) 41 | .forEach(removePath) 42 | .toSource(); 43 | 44 | const withoutRequire = j(withoutImport) 45 | .find(j.VariableDeclarator) 46 | .filter(isRequiringFromPropTypes) 47 | .forEach(removePath) 48 | .toSource(); 49 | 50 | const withoutDefineProperty = j(withoutRequire) 51 | .find(j.ExpressionStatement) 52 | .filter(isDefiningPropType) 53 | .forEach(removePath) 54 | .toSource(); 55 | 56 | const withoutObjectProperty = j(withoutDefineProperty) 57 | .find(j.Property) 58 | .filter(isObjectProperty) 59 | .forEach(removePath) 60 | .toSource(); 61 | 62 | return withoutObjectProperty; 63 | }; 64 | -------------------------------------------------------------------------------- /transforms/sort-jsx-props.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const isEvenNumber = (number) => number % 2 === 0; 4 | const isSpreadElement = (prop) => prop && prop.type === 'JSXSpreadAttribute'; 5 | const isValue = (prop) => prop && prop.type !== 'JSXSpreadAttribute'; 6 | const getPropName = (jsxAttribute) => (jsxAttribute.name ? jsxAttribute.name.name : '...spread'); 7 | const sortByPropName = (a, b) => { 8 | if (a < b) return -1; 9 | if (a > b) return 1; 10 | return 0; 11 | }; 12 | const sortProps = (props) => { 13 | props.sort((a, b) => sortByPropName(getPropName(a), getPropName(b))); 14 | }; 15 | 16 | return j(file.source) 17 | .find(api.jscodeshift.JSXOpeningElement) 18 | .forEach((path) => { 19 | const chunks = []; 20 | const nextAttributes = []; 21 | const jSXOpeningElement = path.value; 22 | 23 | jSXOpeningElement.attributes.forEach((prop, i, props) => { 24 | if (isValue(prop)) { 25 | const prev = props[i - 1]; 26 | const next = props[i + 1]; 27 | const isChunkStart = !isValue(prev); 28 | const isChunkEnd = !isValue(next); 29 | const isWithinChunk = !isChunkStart && !isChunkEnd; 30 | 31 | isChunkStart && chunks.push([]); 32 | const [chunk] = chunks.slice(-1); 33 | chunk.push(prop); 34 | 35 | if (isChunkEnd) { 36 | sortProps(chunk); 37 | nextAttributes.push(...chunk); 38 | } 39 | } else if (isSpreadElement(prop)) { 40 | nextAttributes.push(prop); 41 | } 42 | }); 43 | 44 | jSXOpeningElement.attributes = nextAttributes; 45 | }) 46 | .toSource(); 47 | }; 48 | -------------------------------------------------------------------------------- /transforms/sort-object-props.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const isEvenNumber = (number) => number % 2 === 0; 4 | const isSpreadElement = (prop) => prop && prop.type === 'SpreadElement'; 5 | const isValue = (prop) => prop && prop.type !== 'SpreadElement'; 6 | const getPropName = (prop) => (prop.key && (prop.key.name || prop.key.value)) || ''; 7 | const sortByPropName = (a, b) => { 8 | if (a < b) return -1; 9 | if (a > b) return 1; 10 | return 0; 11 | }; 12 | const sortProps = (props) => { 13 | props.sort((a, b) => sortByPropName(getPropName(a), getPropName(b))); 14 | }; 15 | 16 | return j(file.source) 17 | .find(api.jscodeshift.ObjectExpression) 18 | .forEach((path) => { 19 | const chunks = []; 20 | const nextProperties = []; 21 | const objectExpression = path.value; 22 | 23 | objectExpression.properties.forEach((prop, i, props) => { 24 | if (isValue(prop)) { 25 | const prev = props[i - 1]; 26 | const next = props[i + 1]; 27 | const isChunkStart = !isValue(prev); 28 | const isChunkEnd = !isValue(next); 29 | const isWithinChunk = !isChunkStart && !isChunkEnd; 30 | 31 | isChunkStart && chunks.push([]); 32 | const [chunk] = chunks.slice(-1); 33 | chunk.push(prop); 34 | 35 | if (isChunkEnd) { 36 | sortProps(chunk); 37 | nextProperties.push(...chunk); 38 | } 39 | } else if (isSpreadElement(prop)) { 40 | nextProperties.push(prop); 41 | } 42 | }); 43 | 44 | objectExpression.properties = nextProperties; 45 | }) 46 | .toSource(); 47 | }; 48 | -------------------------------------------------------------------------------- /transforms/use-named-exports.js: -------------------------------------------------------------------------------- 1 | import { getNameInCamelCase, getNameInPascalCase } from './lib/file'; 2 | import { extendApi } from './lib/helpers'; 3 | 4 | export default (file, api) => { 5 | const j = api.jscodeshift; 6 | const f = j(file.source); 7 | 8 | extendApi(j); 9 | 10 | if (f.find(j.ExportDefaultDeclaration).length === 0) { 11 | console.log(`%s has no default export`, file.path); 12 | return; 13 | } 14 | 15 | const topLevelVarNames = f.getTopLevelVarNames(); 16 | const usesReact = f.getImportsByPackageName('react').length > 0; 17 | const intendedName = usesReact ? getNameInPascalCase(file) : getNameInCamelCase(file); 18 | const caseInsensitiveMatch = (name) => name.toLowerCase() === intendedName.toLowerCase(); 19 | const existingName = topLevelVarNames.find(caseInsensitiveMatch); 20 | const nameIsInUse = Boolean(existingName); 21 | const exportName = existingName || intendedName; 22 | 23 | if (!nameIsInUse) { 24 | return f 25 | .find(j.ExportDefaultDeclaration) 26 | .insertBefore((path) => f.exportDefaultAsNamed(path, exportName)) 27 | .replaceWith(() => f.exportVarNameAsDefault(exportName)) 28 | .toSource(); 29 | } 30 | 31 | const classExportOfName = f.getExportsByClassName(exportName); 32 | const functionExportOfName = f.getExportsByFunctionName(exportName); 33 | const namedExportOfName = f.getExportsByVarName(exportName); 34 | const matchingClass = f.getTopLevelClassByName(exportName); 35 | const matchingFunction = f.getTopLevelFunctionByName(exportName); 36 | const matchingVariable = f.getTopLevelVariableByName(exportName); 37 | 38 | if (classExportOfName.length > 0) { 39 | console.log(`%s already exports a class called %s`, file.path, exportName); 40 | return; 41 | } 42 | 43 | if (functionExportOfName.length > 0) { 44 | console.log(`%s already exports a function called %s`, file.path, exportName); 45 | return; 46 | } 47 | 48 | if (namedExportOfName.length > 0) { 49 | console.log(`%s already exports a const called %s`, file.path, exportName); 50 | return; 51 | } 52 | 53 | if (matchingClass.length > 0) { 54 | console.log(`%s has a class called %s which is not exported`, file.path, exportName); 55 | return matchingClass.replaceWith(() => f.exportClass(matchingClass.get())).toSource(); 56 | } 57 | 58 | if (matchingFunction.length > 0) { 59 | console.log(`%s has a function called %s which is not exported`, file.path, exportName); 60 | return matchingFunction.replaceWith(() => f.exportFunction(matchingFunction.get())).toSource(); 61 | } 62 | 63 | if (matchingVariable.length > 0) { 64 | console.log(`%s has a variable called %s which is not exported`, file.path, exportName); 65 | return matchingVariable.replaceWith(() => f.exportVariable(matchingVariable.get())).toSource(); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /transforms/use-named-imports.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | const isDefaultImport = (specifier) => specifier.type === 'ImportDefaultSpecifier'; 4 | const getDefaultImport = (importDeclaration) => importDeclaration.specifiers.find(isDefaultImport); 5 | const hasDefaultImport = (importDeclaration) => Boolean(getDefaultImport(importDeclaration)); 6 | const isRelativeImport = (importDeclaration) => importDeclaration.source.value.startsWith('.'); 7 | const isScriptImport = (importDeclaration) => 8 | !['.json', '.md', '.css', '.svg'].some((ext) => importDeclaration.source.value.endsWith(ext)); 9 | 10 | return j(file.source) 11 | .find(j.ImportDeclaration) 12 | .filter((path) => hasDefaultImport(path.value)) 13 | .filter((path) => isRelativeImport(path.value)) 14 | .filter((path) => isScriptImport(path.value)) 15 | .forEach((path) => { 16 | const importDeclaration = path.value; 17 | importDeclaration.specifiers = importDeclaration.specifiers.map((specifier) => { 18 | if (isDefaultImport(specifier)) { 19 | const name = specifier.local.name; 20 | const namedImport = j.importSpecifier(j.identifier(name)); 21 | return namedImport; 22 | } 23 | return specifier; 24 | }); 25 | }) 26 | .toSource(); 27 | }; 28 | -------------------------------------------------------------------------------- /transforms/use-string-literal-props.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | 4 | const withoutStringLiterals = j(file.source) 5 | .find(j.JSXAttribute) 6 | .filter((path) => { 7 | return ( 8 | path.value && 9 | path.value.value && 10 | path.value.value.expression && 11 | path.value.value.type === 'JSXExpressionContainer' && 12 | path.value.value.expression.type === 'StringLiteral' 13 | ); 14 | }) 15 | .forEach((path) => { 16 | path.value.value = j.stringLiteral(path.value.value.expression.value); 17 | }) 18 | .toSource(); 19 | 20 | const withoutTemplateLiterals = j(withoutStringLiterals) 21 | .find(j.JSXAttribute) 22 | .filter((path) => { 23 | return ( 24 | path.value && 25 | path.value.value && 26 | path.value.value.expression && 27 | path.value.value.type === 'JSXExpressionContainer' && 28 | path.value.value.expression && 29 | path.value.value.expression.type === 'TemplateLiteral' && 30 | path.value.value.expression.expressions && 31 | path.value.value.expression.expressions.length === 0 && 32 | path.value.value.expression.quasis && 33 | path.value.value.expression.quasis[0] && 34 | path.value.value.expression.quasis[0].value 35 | ); 36 | }) 37 | .forEach((path) => { 38 | path.value.value = j.stringLiteral(path.value.value.expression.quasis[0].value.raw); 39 | }) 40 | .toSource(); 41 | 42 | return withoutTemplateLiterals; 43 | }; 44 | --------------------------------------------------------------------------------