├── .envrc ├── .gitignore ├── src ├── index.js └── jscodeshift-mode.js ├── tests ├── transforms │ ├── preserves-style-spacing │ │ ├── transform.js │ │ └── transform.test.js │ ├── non-top-level-tags │ │ ├── transform.js │ │ └── transform.test.js │ ├── preserves-template-spacing │ │ ├── transform.js │ │ └── transform.test.js │ ├── no-script-block │ │ ├── transform.js │ │ └── transform.test.js │ ├── processing-js-file │ │ ├── transform.js │ │ └── transform.test.js │ ├── script-setup-block │ │ ├── transform.js │ │ └── transform.test.js │ ├── preserves-script-spacing │ │ ├── transform.js │ │ └── transform.test.js │ ├── script-setup-and-script-blocks │ │ ├── transform.js │ │ └── transform.test.js │ ├── two-scripts-only-script-changed │ │ ├── transform.js │ │ └── transform.test.js │ ├── two-scripts-only-script-setup-changed │ │ ├── transform.js │ │ └── transform.test.js │ ├── handles-whitespace-lines │ │ ├── transform.js │ │ └── transform.test.js │ ├── test-helper.js │ └── codemod-example │ │ ├── transform.js │ │ └── transform.test.js ├── make-sfc.js └── jscodeshift-mode.test.js ├── .travis.yml ├── flake.nix ├── LICENSE.mit ├── package.json ├── flake.lock ├── README.md ├── ideas.md └── CHANGELOG.md /.envrc: -------------------------------------------------------------------------------- 1 | use flake . 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # nix 4 | .direnv 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { jscodeshiftMode } from './jscodeshift-mode.js'; 2 | 3 | export default function adapt(transform) { 4 | return jscodeshiftMode(transform); 5 | } 6 | -------------------------------------------------------------------------------- /tests/transforms/preserves-style-spacing/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return ` 5 | export default {}; 6 | `; 7 | }); 8 | -------------------------------------------------------------------------------- /tests/transforms/non-top-level-tags/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return ` 5 | // this is new` + fileInfo.source; 6 | }); 7 | -------------------------------------------------------------------------------- /tests/transforms/preserves-template-spacing/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return ` 5 | export default {}; 6 | `; 7 | }); 8 | -------------------------------------------------------------------------------- /tests/transforms/no-script-block/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | throw new Error('This transform should not have been invoked'); 5 | }); 6 | -------------------------------------------------------------------------------- /tests/transforms/processing-js-file/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return ` 5 | export function go() { 6 | console.log('going'); 7 | } 8 | `; 9 | }); 10 | -------------------------------------------------------------------------------- /tests/transforms/script-setup-block/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return api.jscodeshift(fileInfo.source) 5 | .findVariableDeclarators('foo') 6 | .renameTo('bar') 7 | .toSource(); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/transforms/preserves-script-spacing/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return api.jscodeshift(fileInfo.source) 5 | .findVariableDeclarators('foo') 6 | .renameTo('bar') 7 | .toSource(); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/transforms/script-setup-and-script-blocks/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return api.jscodeshift(fileInfo.source) 5 | .findVariableDeclarators('foo') 6 | .renameTo('bar') 7 | .toSource(); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/transforms/two-scripts-only-script-changed/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return api.jscodeshift(fileInfo.source) 5 | .findVariableDeclarators('foo') 6 | .renameTo('bar') 7 | .toSource(); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/transforms/two-scripts-only-script-setup-changed/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | return api.jscodeshift(fileInfo.source) 5 | .findVariableDeclarators('foo') 6 | .renameTo('bar') 7 | .toSource(); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/transforms/processing-js-file/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | export function stop() { 6 | console.log('stopping'); 7 | } 8 | `; 9 | 10 | const output = ` 11 | export function go() { 12 | console.log('going'); 13 | } 14 | `; 15 | 16 | testTransform(transform, 'util.js', input, output); 17 | -------------------------------------------------------------------------------- /tests/transforms/no-script-block/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 8 | `; 9 | 10 | // jscodeshift test utils represent a no-op transform as empty string 11 | const output = ''; 12 | 13 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/handles-whitespace-lines/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | const j = api.jscodeshift; 5 | 6 | return j(fileInfo.source) 7 | .find(j.Identifier) 8 | .forEach((path) => { 9 | j(path).replaceWith( 10 | j.identifier(path.node.name.split('').reverse().join('')) 11 | ); 12 | }) 13 | .toSource(); 14 | }); 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "20" 4 | - "18" 5 | - "16" 6 | # copied from https://github.com/greenkeeperio/greenkeeper-lockfile 7 | before_install: 8 | # package-lock.json was introduced in npm@5 9 | - '[[ $(node -v) =~ ^v9.*$ ]] || npm install -g npm@latest' # skipped when using node 9 10 | - npm install -g greenkeeper-lockfile 11 | install: npm install 12 | before_script: greenkeeper-lockfile-update 13 | after_script: greenkeeper-lockfile-upload -------------------------------------------------------------------------------- /tests/make-sfc.js: -------------------------------------------------------------------------------- 1 | export default ({ template, script, scriptSetup, style }) => { 2 | const source = [ 3 | template ? `` : '', 4 | script ? `` : '', 5 | scriptSetup ? `` : '', 6 | style ? `` : '', 7 | ] 8 | .filter(content => content) 9 | .join('\n\n'); 10 | 11 | return '\n' + source + '\n'; 12 | }; 13 | -------------------------------------------------------------------------------- /tests/transforms/preserves-style-spacing/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 10 | 11 | 16 | `; 17 | 18 | const output = ` 19 | 22 | 23 | 28 | `; 29 | 30 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/preserves-script-spacing/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 8 | 14 | `; 15 | 16 | const output = ` 17 | 20 | 26 | `; 27 | 28 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/test-helper.js: -------------------------------------------------------------------------------- 1 | import { runInlineTest } from 'jscodeshift/dist/testUtils'; 2 | 3 | /** 4 | * Test a transform function. 5 | * 6 | * @param {Function} transform 7 | * @param {String} path - (fake) file path of source passed to transform 8 | * @param {String} input - Source code 9 | * @param {String} output - Expected transform result 10 | * @param {Object} options - Options passed to transform, optional 11 | */ 12 | export function testTransform(transform, path, input, output, options = {}) { 13 | const fileInfo = { 14 | path, 15 | source: input 16 | }; 17 | 18 | it('transforms correctly', () => { 19 | runInlineTest(transform, options, fileInfo, output); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /tests/transforms/script-setup-block/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 10 | 11 | 14 | 15 | 20 | `; 21 | 22 | const output = ` 23 | 28 | 29 | 32 | 33 | 38 | `; 39 | 40 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/script-setup-and-script-blocks/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 10 | 11 | 14 | 15 | 18 | 19 | 24 | `; 25 | 26 | const output = ` 27 | 32 | 33 | 36 | 37 | 40 | 41 | 46 | `; 47 | 48 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/preserves-template-spacing/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 13 | 14 | 24 | 25 | 30 | `; 31 | 32 | const output = ` 33 | 41 | 42 | 45 | 46 | 51 | `; 52 | 53 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/two-scripts-only-script-changed/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 10 | 11 | 15 | 16 | 20 | 21 | 26 | `; 27 | 28 | const output = ` 29 | 34 | 35 | 39 | 40 | 44 | 45 | 50 | `; 51 | 52 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/two-scripts-only-script-setup-changed/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 10 | 11 | 15 | 16 | 20 | 21 | 26 | `; 27 | 28 | const output = ` 29 | 34 | 35 | 39 | 40 | 44 | 45 | 50 | `; 51 | 52 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | systems.url = "github:nix-systems/default"; 5 | }; 6 | 7 | outputs = { 8 | systems, 9 | nixpkgs, 10 | ... 11 | } @ inputs: let 12 | eachSystem = f: 13 | nixpkgs.lib.genAttrs (import systems) ( 14 | system: 15 | f nixpkgs.legacyPackages.${system} 16 | ); 17 | in { 18 | devShells = eachSystem (pkgs: { 19 | default = pkgs.mkShell { 20 | buildInputs = [ 21 | pkgs.nodejs_20 22 | # You can set the major version of Node.js to a specific one instead 23 | # of the default version 24 | # pkgs.nodejs-19_x 25 | 26 | # You can choose pnpm, yarn, or none (npm). 27 | # pkgs.nodePackages.pnpm 28 | # pkgs.yarn 29 | 30 | # pkgs.nodePackages.typescript 31 | # pkgs.nodePackages.typescript-language-server 32 | ]; 33 | }; 34 | }); 35 | }; 36 | } -------------------------------------------------------------------------------- /tests/transforms/codemod-example/transform.js: -------------------------------------------------------------------------------- 1 | import adapt from '../../../src/index.js'; 2 | 3 | export default adapt((fileInfo, api, options) => { 4 | const j = api.jscodeshift; 5 | const root = j(fileInfo.source); 6 | 7 | root 8 | .find(j.ExportDefaultDeclaration) 9 | .find(j.ObjectExpression) 10 | .at(0) 11 | .find(j.Property, p => { 12 | return p.key.name === 'props' 13 | }) 14 | .find(j.Property) 15 | .filter(p => { 16 | return p.parent.parent.node.key.name === 'props'; 17 | }) 18 | .find(j.ObjectExpression) 19 | .forEach(o => { 20 | const requiredFlag = o.node.properties.find(p => { 21 | return p.key.name === 'required' 22 | }); 23 | 24 | if (requiredFlag) { 25 | if (requiredFlag.value.value === false) { 26 | requiredFlag.value = j.literal(true); 27 | } 28 | } else { 29 | o.node.properties.push(j.property( 30 | 'init', 31 | j.identifier('required'), 32 | j.literal(true) 33 | )); 34 | } 35 | }); 36 | 37 | return root.toSource(); 38 | }); 39 | -------------------------------------------------------------------------------- /LICENSE.mit: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Paul Salaets 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 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /tests/transforms/handles-whitespace-lines/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 11 | 26 | `; 27 | 28 | const output = ` 29 | 35 | 50 | `; 51 | 52 | testTransform(transform, 'Widget.vue', input, output); 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-jscodeshift-adapter", 3 | "version": "3.0.0", 4 | "description": "Run jscodeshift on Vue single file components", 5 | "type": "module", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" 9 | }, 10 | "keywords": [ 11 | "vue", 12 | "sfc", 13 | "component", 14 | "refactor", 15 | "codemod", 16 | "jscodeshift" 17 | ], 18 | "author": "Paul Salaets ", 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/psalaets/vue-jscodeshift-adapter.git" 23 | }, 24 | "homepage": "https://github.com/psalaets/vue-jscodeshift-adapter", 25 | "bugs": { 26 | "url": "https://github.com/psalaets/vue-jscodeshift-adapter/issues" 27 | }, 28 | "devDependencies": { 29 | "jest": "^29.7.0", 30 | "jscodeshift": "^0.15.2", 31 | "jscodeshift-helper": "^1.1.0" 32 | }, 33 | "dependencies": { 34 | "@vue/compiler-sfc": "^3.4.23", 35 | "vue-sfc-descriptor-to-string": "^3.0.1" 36 | }, 37 | "jest": { 38 | "testEnvironment": "node", 39 | "transform": {} 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1713254108, 6 | "narHash": "sha256-0TZIsfDbHG5zibtlw6x0yOp3jkInIGaJ35B7Y4G8Pec=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "2fd19c8be2551a61c1ddc3d9f86d748f4db94f00", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "systems": "systems" 23 | } 24 | }, 25 | "systems": { 26 | "locked": { 27 | "lastModified": 1681028828, 28 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 | "owner": "nix-systems", 30 | "repo": "default", 31 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "nix-systems", 36 | "repo": "default", 37 | "type": "github" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /tests/transforms/non-top-level-tags/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 15 | 16 | 26 | 27 | 32 | `; 33 | 34 | const output = ` 35 | 45 | 46 | 57 | 58 | 63 | `; 64 | 65 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /tests/transforms/codemod-example/transform.test.js: -------------------------------------------------------------------------------- 1 | import { testTransform } from '../test-helper.js'; 2 | import transform from './transform.js'; 3 | 4 | const input = ` 5 | 10 | 11 | 33 | 34 | 39 | `; 40 | 41 | const output = ` 42 | 47 | 48 | 71 | 72 | 77 | `; 78 | 79 | testTransform(transform, 'Widget.vue', input, output); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-jscodeshift-adapter 2 | 3 | [![Build Status](https://travis-ci.org/psalaets/vue-jscodeshift-adapter.svg?branch=master)](https://travis-ci.org/psalaets/vue-jscodeshift-adapter) 4 | 5 | Run [jscodeshift](https://github.com/facebook/jscodeshift) on Vue single file components 6 | 7 | ## Install 8 | 9 | ``` 10 | npm install vue-jscodeshift-adapter -D 11 | ``` 12 | 13 | ## Usage 14 | 15 | The instructions below assume you're familiar with [jscodeshift](https://github.com/facebook/jscodeshift). 16 | 17 | ### Run a codemod on some `.js` and/or `.vue` files 18 | 19 | |When transforming|`fileInfo.source` will be| 20 | |-----------------|-------------------------| 21 | |`.js` | the contents of the file| 22 | |`.vue` | the contents of `