├── .gitignore ├── bin └── kodemod ├── .prettierignore ├── renovate.json ├── .babelrc ├── .eslintignore ├── docs └── images │ └── kodemod-cli-screenshot.png ├── transforms ├── __tests__ │ ├── block-scope-to-iife.test.js │ ├── lit-element-to-lit-imports.test.js │ ├── rename-tag.test.js │ └── replace-attrs-test.js ├── __testfixtures__ │ ├── lit-element-to-lit-imports.input.js │ ├── lit-element-to-lit-imports.output.js │ ├── block-scope-to-iife.input.js │ ├── block-scope-to-iife.output.js │ ├── replace-attrs.input.js │ ├── replace-attrs.output.js │ ├── rename-tag.input.js │ └── rename-tag.output.js ├── block-scope-to-iife.js ├── rename-tag.js ├── replace-attrs.js └── lit-element-to-lit-imports.js ├── .prettierrc ├── .editorconfig ├── src ├── index.js ├── run-transform.js └── commands.js ├── .travis.yml ├── .eslintrc.js ├── LICENSE ├── package.json ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /bin/kodemod: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('..'); 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | transforms/__testfixtures__/* 3 | coverage/ 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-proposal-object-rest-spread"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # config files 2 | .*.js 3 | 4 | # test fixtures 5 | transforms/__testfixtures__/* 6 | 7 | # markdown 8 | *.md 9 | -------------------------------------------------------------------------------- /docs/images/kodemod-cli-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kcmr/web-components-codemods/HEAD/docs/images/kodemod-cli-screenshot.png -------------------------------------------------------------------------------- /transforms/__tests__/block-scope-to-iife.test.js: -------------------------------------------------------------------------------- 1 | const { defineTest } = require('jscodeshift/dist/testUtils'); 2 | 3 | defineTest(__dirname, 'block-scope-to-iife'); 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "es5", 7 | "arrowParens": "always" 8 | } 9 | -------------------------------------------------------------------------------- /transforms/__tests__/lit-element-to-lit-imports.test.js: -------------------------------------------------------------------------------- 1 | const { defineTest } = require('jscodeshift/dist/testUtils'); 2 | 3 | defineTest(__dirname, 'lit-element-to-lit-imports'); 4 | -------------------------------------------------------------------------------- /transforms/__tests__/rename-tag.test.js: -------------------------------------------------------------------------------- 1 | const { defineTest } = require('jscodeshift/dist/testUtils'); 2 | 3 | defineTest(__dirname, 'rename-tag', { 4 | oldTag: 'some-tag-name', 5 | newTag: 'some-tag-name-renamed', 6 | }); 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/lit-element-to-lit-imports.input.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit-element'; 2 | import { LitElement, html, property as foo, customElement } from 'lit-element'; 3 | import { repeat } from 'lit-html/directives/repeat.js'; 4 | import { ifDefined } from 'lit-html/directives/if-defined'; 5 | -------------------------------------------------------------------------------- /transforms/__tests__/replace-attrs-test.js: -------------------------------------------------------------------------------- 1 | const { defineTest } = require('jscodeshift/dist/testUtils'); 2 | 3 | defineTest(__dirname, 'replace-attrs', { 4 | tag: 'wc-icon', 5 | attrs: { 6 | icon: 'emoji', 7 | 'some-attr': 'new-attr', 8 | '.someProp': '.newProp', 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/lit-element-to-lit-imports.output.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | import { LitElement, html } from 'lit'; 3 | import { property as foo, customElement } from 'lit/decorators.js'; 4 | import { repeat } from 'lit/directives/repeat.js'; 5 | import { ifDefined } from 'lit/directives/if-defined'; 6 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/block-scope-to-iife.input.js: -------------------------------------------------------------------------------- 1 | { 2 | const { Element, html } = window.Polymer; 3 | 4 | class Component extends Element { 5 | static get is() { 6 | return 'tag-name'; 7 | } 8 | } 9 | } 10 | 11 | class Component extends Element { 12 | static get is() { 13 | return 'tag-name'; 14 | } 15 | } 16 | 17 | if (true) { 18 | // code 19 | } 20 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/block-scope-to-iife.output.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const { Element, html } = window.Polymer; 3 | 4 | class Component extends Element { 5 | static get is() { 6 | return 'tag-name'; 7 | } 8 | } 9 | })(); 10 | 11 | class Component extends Element { 12 | static get is() { 13 | return 'tag-name'; 14 | } 15 | } 16 | 17 | if (true) { 18 | // code 19 | } 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { PathPrompt } = require('inquirer-path'); 2 | const { CliHelper } = require('@kuscamara/cli-helper'); 3 | const commands = require('./commands'); 4 | 5 | const cli = new CliHelper({ 6 | description: 'Codemods for Web Components', 7 | defaultCommandMessage: 'Choose the transform to apply', 8 | commands, 9 | }); 10 | 11 | CliHelper.registerPrompt('path', PathPrompt); 12 | cli.run(); 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | dist: trusty 4 | 5 | cache: npm 6 | 7 | before_install: 8 | - npm install -g npm@6 9 | - npm install 10 | - npm install -g codecov 11 | 12 | install: 13 | - npm ci 14 | 15 | script: 16 | - npm t 17 | - codecov 18 | 19 | deploy: 20 | - provider: script 21 | cleanup: true 22 | script: 23 | - npx -p @semantic-release/changelog -p @semantic-release/git -p semantic-release semantic-release 24 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/replace-attrs.input.js: -------------------------------------------------------------------------------- 1 | const expression = 'foo'; 2 | const html = (str) => str; 3 | 4 | class Component extends HTMLElement { 5 | get foo() { 6 | return html` 7 | 8 | `; 9 | } 10 | 11 | render() { 12 | return html` 13 | 18 | `; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/replace-attrs.output.js: -------------------------------------------------------------------------------- 1 | const expression = 'foo'; 2 | const html = (str) => str; 3 | 4 | class Component extends HTMLElement { 5 | get foo() { 6 | return html` 7 | 8 | `; 9 | } 10 | 11 | render() { 12 | return html` 13 | 18 | `; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rename-tag.input.js: -------------------------------------------------------------------------------- 1 | const stringLiteral = ` 2 | 3 | 4 | `; 5 | 6 | const taggedTemplate = html``; 7 | 8 | const stringMatch = document.querySelector('some-tag-name'); 9 | const stringMatch2 = ["\"\\n \\n \""]; 10 | 11 | const containsName = ``; 12 | 13 | customElements.define('some-tag-name', SomeTagName); 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/rename-tag.output.js: -------------------------------------------------------------------------------- 1 | const stringLiteral = ` 2 | 3 | 4 | `; 5 | 6 | const taggedTemplate = html``; 7 | 8 | const stringMatch = document.querySelector('some-tag-name-renamed'); 9 | const stringMatch2 = ["\"\\n \\n \""]; 10 | 11 | const containsName = ``; 12 | 13 | customElements.define('some-tag-name-renamed', SomeTagName); 14 | -------------------------------------------------------------------------------- /transforms/block-scope-to-iife.js: -------------------------------------------------------------------------------- 1 | export default function transform(file, api) { 2 | const j = api.jscodeshift; 3 | const isProgramChild = (path) => 4 | path.parentPath.parentPath.name === 'program'; 5 | const iife = (path) => 6 | j.expressionStatement( 7 | j.callExpression( 8 | j.functionExpression(null, [], j.blockStatement(path.node.body)), 9 | [] 10 | ) 11 | ); 12 | 13 | return j(file.source) 14 | .find(j.BlockStatement) 15 | .filter(isProgramChild) 16 | .replaceWith(iife) 17 | .toSource(); 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | extends: ['airbnb-base', 'plugin:node/recommended', 'prettier'], 5 | plugins: ['jest', 'node', 'prettier'], 6 | env: { 7 | commonjs: true, 8 | es6: true, 9 | node: true, 10 | }, 11 | rules: { 12 | 'no-restricted-syntax': [ 13 | 'off', 14 | { 15 | selector: 'ForOfStatement', 16 | }, 17 | ], 18 | 'prettier/prettier': 'error', 19 | }, 20 | overrides: [ 21 | { 22 | files: ['transforms/*.js'], 23 | parserOptions: { 24 | ecmaVersion: 2020, 25 | sourceType: 'module', 26 | }, 27 | rules: { 28 | 'node/no-unsupported-features/es-syntax': 'off', 29 | }, 30 | }, 31 | { 32 | files: ['transforms/__tests__/*.js'], 33 | env: { 34 | 'jest/globals': true, 35 | }, 36 | }, 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Kus Cámara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /transforms/rename-tag.js: -------------------------------------------------------------------------------- 1 | class TagReplacer { 2 | constructor(parser, oldTag, newTag) { 3 | this.parser = parser; 4 | this.oldTag = oldTag; 5 | this.newTag = newTag; 6 | this.tag = new RegExp(`${oldTag}[^-]`, 'g'); 7 | this.hasTag = this.nodeHasTag.bind(this); 8 | this.renamedTag = this.getNewTag.bind(this); 9 | } 10 | 11 | nodeHasTag(path) { 12 | return this.parser(path.node).toSource().match(this.tag); 13 | } 14 | 15 | getNewTag(path) { 16 | const source = this.parser(path).toSource(); 17 | return this.replaceTag(source); 18 | } 19 | 20 | replaceTag(source) { 21 | return source.replace(this.tag, (m) => m.replace(this.oldTag, this.newTag)); 22 | } 23 | } 24 | 25 | export default function transform(file, api, options) { 26 | const j = api.jscodeshift; 27 | const root = j(file.source); 28 | const { oldTag, newTag, tabWidth = 2, useTabs = false } = options; 29 | const tagReplacer = new TagReplacer(j, oldTag, newTag); 30 | const { hasTag, renamedTag } = tagReplacer; 31 | 32 | root 33 | .find(j.TemplateElement) 34 | .filter(hasTag) 35 | .replaceWith(renamedTag) 36 | .toSource({ tabWidth, useTabs }); 37 | 38 | root.find(j.Literal).filter(hasTag).replaceWith(renamedTag); 39 | 40 | return root.toSource({ tabWidth, useTabs }); 41 | } 42 | -------------------------------------------------------------------------------- /src/run-transform.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const execa = require('execa'); 3 | const dargs = require('dargs'); 4 | const globby = require('globby'); 5 | 6 | // eslint-disable-next-line node/no-unpublished-require 7 | const jscodeshift = require.resolve('.bin/jscodeshift'); 8 | 9 | // Shamelessly stolen from https://github.com/reactjs/react-codemod/blob/master/bin/cli.js 10 | function expandFilePathsIfNeeded(files) { 11 | const shouldExpandFiles = files.some((file) => file.includes('*')); 12 | return shouldExpandFiles ? globby.sync(files) : files; 13 | } 14 | 15 | module.exports = function runTransform({ command, options }) { 16 | const transformScript = path.join( 17 | __dirname, 18 | '../transforms/', 19 | `${command}.js` 20 | ); 21 | const excludes = ['files', 'useTabs']; 22 | const files = expandFilePathsIfNeeded([options.files]); 23 | const args = [ 24 | ...files, 25 | `--transform=${transformScript}`, 26 | '--ignore-pattern=**/node_modules/**', 27 | '--ignore-pattern=**/bower_components/**', 28 | ...dargs(options, { 29 | excludes, 30 | allowCamelCase: true, 31 | }), 32 | ]; 33 | 34 | // always preview in dry-run mode 35 | if (options.dry) { 36 | args.push('--print'); 37 | } 38 | 39 | if (options.useTabs) { 40 | args.push('--useTabs=true'); 41 | } 42 | 43 | const result = execa.sync(jscodeshift, args, { stdio: 'inherit' }); 44 | 45 | if (result.error) { 46 | throw result.error; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /transforms/replace-attrs.js: -------------------------------------------------------------------------------- 1 | class AttrReplacer { 2 | constructor(parser, tag, attrs) { 3 | this.parser = parser; 4 | this.attrs = attrs; 5 | this.tagWithAttrs = new RegExp(`<${tag}([^>]*)>`, 'g'); 6 | this.hasTag = this.nodeHasTag.bind(this); 7 | this.tagWithNewAttrs = this.getTagWithNewAttrs.bind(this); 8 | } 9 | 10 | nodeHasTag(path) { 11 | return this.parser(path.node).toSource().match(this.tagWithAttrs); 12 | } 13 | 14 | getTagWithNewAttrs(path) { 15 | const source = this.parser(path).toSource(); 16 | return this.replaceAttrs(source); 17 | } 18 | 19 | replaceAttrs(source) { 20 | return source.replace(this.tagWithAttrs, (m) => { 21 | for (const [from, to] of Object.entries(this.attrs)) { 22 | const attribute = new RegExp(`${from}( *=)`, 'g'); 23 | // eslint-disable-next-line no-param-reassign 24 | m = m.replace(attribute, (f) => f.replace(from, to)); 25 | } 26 | 27 | return m; 28 | }); 29 | } 30 | } 31 | 32 | export default function transform(file, api, options) { 33 | const j = api.jscodeshift; 34 | const { tag, attrs, tabWidth = 4, useTabs = false } = options; 35 | const tagAttrReplacer = new AttrReplacer(j, tag, attrs); 36 | const { hasTag, tagWithNewAttrs } = tagAttrReplacer; 37 | 38 | return j(file.source) 39 | .find(j.TaggedTemplateExpression, { tag: { name: 'html' } }) 40 | .filter(hasTag) 41 | .replaceWith(tagWithNewAttrs) 42 | .toSource({ tabWidth, useTabs }); 43 | } 44 | -------------------------------------------------------------------------------- /transforms/lit-element-to-lit-imports.js: -------------------------------------------------------------------------------- 1 | export default function transformer(file, api, options) { 2 | const j = api.jscodeshift; 3 | const { quote = 'single' } = options; 4 | 5 | const isLitElementImport = (path) => 6 | path.value.source.value === 'lit-element'; 7 | const isLitHtmlDirectiveImport = (path) => 8 | path.value.source.value.includes('lit-html/directives/'); 9 | const isDecorator = /(customElement|property)/; 10 | 11 | const litImport = j.literal('lit'); 12 | const litDirectiveImport = (path) => { 13 | const fullPath = path.value.source.value; 14 | return j.literal(fullPath.replace('lit-html', 'lit')); 15 | }; 16 | 17 | const updateDecoratorImports = (path) => { 18 | const decorators = []; 19 | let importNode; 20 | 21 | j(path) 22 | .find(j.ImportSpecifier) 23 | .forEach((specifier) => { 24 | importNode = specifier.parentPath.parentPath; 25 | if (isDecorator.test(specifier.value.imported.name)) { 26 | decorators.push( 27 | j.importSpecifier(specifier.value.imported, specifier.value.local) 28 | ); 29 | specifier.replace(); 30 | } 31 | }); 32 | 33 | if (decorators.length) { 34 | importNode.insertAfter( 35 | j.importDeclaration(decorators, j.literal('lit/decorators.js')) 36 | ); 37 | } 38 | }; 39 | 40 | const replaceImportLiteral = (path) => ({ 41 | with: (value) => j(path).find(j.Literal).replaceWith(value), 42 | }); 43 | 44 | return j(file.source) 45 | .find(j.ImportDeclaration) 46 | .forEach((path) => { 47 | if (isLitElementImport(path)) { 48 | replaceImportLiteral(path).with(litImport); 49 | updateDecoratorImports(path); 50 | } 51 | 52 | if (isLitHtmlDirectiveImport(path)) { 53 | replaceImportLiteral(path).with(litDirectiveImport(path)); 54 | } 55 | }) 56 | .toSource({ quote }); 57 | } 58 | -------------------------------------------------------------------------------- /src/commands.js: -------------------------------------------------------------------------------- 1 | const runTransform = require('./run-transform'); 2 | 3 | const commonParams = { 4 | files: { 5 | message: 'Files where the command will be executed', 6 | type: 'path', 7 | validate: () => true, 8 | }, 9 | dry: { 10 | message: "Run in preview mode (don't transform files)", 11 | type: 'confirm', 12 | default: false, 13 | }, 14 | }; 15 | 16 | module.exports = { 17 | 'block-scope-to-iife': { 18 | desc: 'Replaces brackets used as scope in a file by an IIFE', 19 | params: { 20 | ...commonParams, 21 | }, 22 | action: runTransform, 23 | }, 24 | 'replace-attrs': { 25 | desc: 'Replaces attributes in the specified tag', 26 | params: { 27 | ...commonParams, 28 | tag: { 29 | message: 'Tag name (Example: some-tag)', 30 | type: 'input', 31 | }, 32 | attrs: { 33 | message: 'Object with {"old-attr": "new-attr"} pairs to replace', 34 | type: 'input', 35 | }, 36 | useTabs: { 37 | message: 'Use tabs instead of spaces', 38 | type: 'confirm', 39 | default: false, 40 | }, 41 | tabWidth: { 42 | message: 'Number of spaces used per tab', 43 | type: 'number', 44 | default: 4, 45 | when: (input) => input.useTabs, 46 | }, 47 | }, 48 | action: runTransform, 49 | }, 50 | 'rename-tag': { 51 | desc: 'Renames tag names in strings and template literals', 52 | params: { 53 | ...commonParams, 54 | oldTag: { 55 | message: 'Tag to rename (Example: some-tag)', 56 | type: 'input', 57 | }, 58 | newTag: { 59 | message: 'New tag name (Example: new-tag)', 60 | type: 'input', 61 | }, 62 | useTabs: { 63 | message: 'Use tabs instead of spaces', 64 | type: 'confirm', 65 | default: false, 66 | }, 67 | tabWidth: { 68 | message: 'Number of spaces used per tab', 69 | type: 'number', 70 | default: 2, 71 | when: (input) => input.useTabs, 72 | }, 73 | }, 74 | action: runTransform, 75 | }, 76 | 'lit-element-to-lit-imports': { 77 | desc: 'Replaces lit-element imports to lit imports', 78 | params: { 79 | ...commonParams, 80 | quote: { 81 | message: 'Type of quote (single or double)', 82 | type: 'input', 83 | default: 'single', 84 | }, 85 | }, 86 | action: runTransform, 87 | }, 88 | }; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-components-codemods", 3 | "version": "1.2.3", 4 | "description": "Codemods for Web Components", 5 | "main": "src/index.js", 6 | "keywords": [ 7 | "codemods", 8 | "web-components", 9 | "jscodeshift", 10 | "cli" 11 | ], 12 | "author": "Kus Cámara ", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/kcmr/web-components-codemods.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/kcmr/web-components-codemods/issues" 20 | }, 21 | "homepage": "https://github.com/kcmr/web-components-codemods#readme", 22 | "scripts": { 23 | "test": "jest", 24 | "lint:eslint": "eslint \"bin/**\" \"src/**\" \"transforms/**\"", 25 | "format:prettier": "prettier --write \"**/*.{js,md}\"", 26 | "format:eslint": "npm run lint:eslint -- --fix", 27 | "format": "npm run format:prettier && npm run format:eslint", 28 | "semantic-release": "semantic-release", 29 | "commit": "git-cz" 30 | }, 31 | "bin": { 32 | "kodemod": "./bin/kodemod" 33 | }, 34 | "files": [ 35 | "bin/", 36 | "src/", 37 | "transforms/*.js" 38 | ], 39 | "dependencies": { 40 | "@kuscamara/cli-helper": "^1.0.5", 41 | "dargs": "^8.0.0", 42 | "execa": "^5.0.0", 43 | "globby": "^11.0.0", 44 | "inquirer-path": "^1.0.0-beta5", 45 | "jscodeshift": "^0.13.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.10.3", 49 | "@babel/plugin-proposal-object-rest-spread": "^7.10.3", 50 | "@babel/preset-env": "^7.10.3", 51 | "@commitlint/cli": "^11.0.0", 52 | "@commitlint/config-conventional": "^11.0.0", 53 | "@semantic-release/changelog": "^5.0.1", 54 | "@semantic-release/git": "^9.0.0", 55 | "babel-eslint": "^10.1.0", 56 | "babel-jest": "^27.0.0", 57 | "commitizen": "^4.1.2", 58 | "cz-conventional-changelog": "^3.2.0", 59 | "eslint": "^6.8.0", 60 | "eslint-config-airbnb-base": "^14.0.0", 61 | "eslint-config-prettier": "^7.0.0", 62 | "eslint-plugin-import": "^2.19.1", 63 | "eslint-plugin-jest": "^24.0.0", 64 | "eslint-plugin-node": "^11.0.0", 65 | "eslint-plugin-prettier": "^3.1.2", 66 | "husky": "^4.0.4", 67 | "jest": "^27.0.0", 68 | "lint-staged": "^10.0.3", 69 | "prettier": "^2.0.5", 70 | "semantic-release": "^17.0.0" 71 | }, 72 | "husky": { 73 | "hooks": { 74 | "pre-push": "npm t", 75 | "pre-commit": "lint-staged", 76 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 77 | } 78 | }, 79 | "lint-staged": { 80 | "*.{js,md}": [ 81 | "npm run format" 82 | ] 83 | }, 84 | "config": { 85 | "commitizen": { 86 | "path": "cz-conventional-changelog" 87 | } 88 | }, 89 | "commitlint": { 90 | "extends": [ 91 | "@commitlint/config-conventional" 92 | ] 93 | }, 94 | "release": { 95 | "plugins": [ 96 | "@semantic-release/commit-analyzer", 97 | "@semantic-release/release-notes-generator", 98 | "@semantic-release/npm", 99 | "@semantic-release/changelog", 100 | "@semantic-release/git" 101 | ], 102 | "branch": "master", 103 | "tagFormat": "${version}" 104 | }, 105 | "jest": { 106 | "globals": { 107 | "baseDir": "../" 108 | }, 109 | "testEnvironment": "node", 110 | "roots": [ 111 | "transforms" 112 | ], 113 | "collectCoverage": true 114 | }, 115 | "engines": { 116 | "node": ">=10.13.0" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.2.3](https://github.com/kcmr/web-components-codemods/compare/1.2.2...1.2.3) (2021-10-19) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **deps:** update dependency jscodeshift to ^0.13.0 ([5797f30](https://github.com/kcmr/web-components-codemods/commit/5797f30a654c47ce217460a68e8b2d0d77d83299)) 7 | 8 | ## [1.2.2](https://github.com/kcmr/web-components-codemods/compare/1.2.1...1.2.2) (2021-10-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **deps:** update dependency dargs to v8 ([61514b4](https://github.com/kcmr/web-components-codemods/commit/61514b4975f8bb05b4a315faf6ab549192c22d3f)) 14 | 15 | ## [1.2.1](https://github.com/kcmr/web-components-codemods/compare/1.2.0...1.2.1) (2021-05-01) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * **deps:** update dependency jscodeshift to ^0.12.0 ([#59](https://github.com/kcmr/web-components-codemods/issues/59)) ([6029bcc](https://github.com/kcmr/web-components-codemods/commit/6029bcc723f3b7e80d5c6ece3f9196eeb2bc5e8c)) 21 | 22 | # [1.2.0](https://github.com/kcmr/web-components-codemods/compare/1.1.4...1.2.0) (2021-04-23) 23 | 24 | 25 | ### Features 26 | 27 | * **transforms:** add lit-element to lit imports codemod ([#58](https://github.com/kcmr/web-components-codemods/issues/58)) ([5152f7e](https://github.com/kcmr/web-components-codemods/commit/5152f7e687cd30a4de353104c3e10e553604b202)) 28 | 29 | ## [1.1.4](https://github.com/kcmr/web-components-codemods/compare/1.1.3...1.1.4) (2020-12-31) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * **deps:** update dependency execa to v5 ([3cf140d](https://github.com/kcmr/web-components-codemods/commit/3cf140d3debbdcd4431e77d7affc6f7cebcc8b34)) 35 | 36 | ## [1.1.3](https://github.com/kcmr/web-components-codemods/compare/1.1.2...1.1.3) (2020-11-19) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * **deps:** update dependency jscodeshift to ^0.11.0 ([3bfe7bd](https://github.com/kcmr/web-components-codemods/commit/3bfe7bddf362e60b78727bf40fb90e00f87bf4ce)) 42 | 43 | ## [1.1.2](https://github.com/kcmr/web-components-codemods/compare/1.1.1...1.1.2) (2020-07-07) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * **deps:** update dependency globby to v11 ([0e8e0ed](https://github.com/kcmr/web-components-codemods/commit/0e8e0edd9ff92f82255e2b3d53a1e2ca07244038)) 49 | 50 | ## [1.1.1](https://github.com/kcmr/web-components-codemods/compare/1.1.0...1.1.1) (2020-06-20) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * **deps:** update dependency jscodeshift to ^0.10.0 ([11a3f77](https://github.com/kcmr/web-components-codemods/commit/11a3f778c3b39918aa33a935c039c88cbbd8c2b0)) 56 | 57 | # [1.1.0](https://github.com/kcmr/web-components-codemods/compare/1.0.1...1.1.0) (2020-06-20) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * **commands:** typo in rename-tag command ([2b8ac06](https://github.com/kcmr/web-components-codemods/commit/2b8ac062fa631c6230c070c81da19f702e3df1a7)) 63 | * **transforms:** find tag name in any string ([eb1ee27](https://github.com/kcmr/web-components-codemods/commit/eb1ee27fa8d5ad73c94282f62ef26df647b112d9)) 64 | 65 | 66 | ### Features 67 | 68 | * **commands:** add new kodemod ([de74540](https://github.com/kcmr/web-components-codemods/commit/de74540d106c2f2390863bb2a1632b509d00fbb9)) 69 | * **transforms:** add a codemod to rename tags ([5b40bde](https://github.com/kcmr/web-components-codemods/commit/5b40bde86865b01981588b5f0f39f6f1131a89cb)) 70 | * **transforms:** rename tag name strings ([4e1cdf4](https://github.com/kcmr/web-components-codemods/commit/4e1cdf4e00ac86545a5804bf323b3e96d9cdd94e)) 71 | 72 | ## [1.0.1](https://github.com/kcmr/web-components-codemods/compare/1.0.0...1.0.1) (2020-05-03) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * **package:** update jscodeshift to version 0.8.0 ([ccb1296](https://github.com/kcmr/web-components-codemods/commit/ccb12969093cc9b36ae0dde2600f40653379494f)) 78 | 79 | # 1.0.0 (2020-01-05) 80 | 81 | 82 | ### Bug Fixes 83 | 84 | * **package-lock:** remove private registry in some entries ([89694b2](https://github.com/kcmr/web-components-codemods/commit/89694b29b1ee49f77bd04e5b57c1ac5a52087e3f)) 85 | * **package-lock:** remove private registry in some entries ([fed7c24](https://github.com/kcmr/web-components-codemods/commit/fed7c24994ded66549d2aba401f0bfadfb4273a9)) 86 | * **replace-attrs:** do not replace unmodified nodes ([34a9e2f](https://github.com/kcmr/web-components-codemods/commit/34a9e2fdf8067d4e4f5a6c01e9b3e51b0617e137)) 87 | * **replace-attrs:** prevent replacing the tag name or attribute values ([dbeb37e](https://github.com/kcmr/web-components-codemods/commit/dbeb37e7229bf60dc0adb2f47b342cf916fd2ba7)) 88 | * **transforms:** prevent replacing brackets in if statement by iife ([f6bf99f](https://github.com/kcmr/web-components-codemods/commit/f6bf99faf5b5a8db58a0a19e4e20a40b7bb7cf63)) 89 | 90 | 91 | ### Features 92 | 93 | * **cli:** add CLI to run transforms ([e0f2c82](https://github.com/kcmr/web-components-codemods/commit/e0f2c823976c463792253f4747e528e8b899d52c)), closes [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) [#4](https://github.com/kcmr/web-components-codemods/issues/4) 94 | * **replace-attrs:** allow to pass recast options in params ([d90ca05](https://github.com/kcmr/web-components-codemods/commit/d90ca0520a2c30393519018f81a0f21b777a749f)) 95 | * **transforms:** add a codemod to replace block scope by IIFE ([db57c7b](https://github.com/kcmr/web-components-codemods/commit/db57c7b2474b585634ed546c72182af7a709dedc)) 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Components Codemods 2 | 3 | [![Build Status](https://travis-ci.com/kcmr/web-components-codemods.svg?branch=master)](https://travis-ci.com/kcmr/web-components-codemods) 4 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 5 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 6 | [![codecov](https://codecov.io/gh/kcmr/web-components-codemods/branch/master/graph/badge.svg)](https://codecov.io/gh/kcmr/web-components-codemods) 7 | ![Dependency status](https://img.shields.io/david/kcmr/web-components-codemods.svg) 8 | 9 | [![NPM](https://nodei.co/npm/web-components-codemods.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/web-components-codemods/) 10 | 11 | Codemods for Web Components. 12 | Breaking changes? Don't panic :) 13 | 14 | ## Table of contents 15 | 16 | - [Usage](#usage) 17 | - [Using the included CLI](#using-the-included-cli) 18 | - [Using jscodeshift](#using-jscodeshift) 19 | - [Available codemods](#available-codemods) 20 | - [Replace attrs](#replace-attrs) 21 | - [Replace block scope by IIFE](#replace-block-scope-by-iife) 22 | - [Rename tag](#rename-tag) 23 | - [LitElement to Lit imports](#litelement-to-lit-imports) 24 | - [Acknowledgments](#acknowledgments) 25 | 26 | ## Usage 27 | 28 | The available codemods can be run in two ways: by using the included CLI or running the transform scripts directly with jscodeshift. 29 | 30 | ### Using the included CLI 31 | 32 | Install this package globally: 33 | 34 | ```bash 35 | npm i -g web-components-codemods 36 | ``` 37 | 38 | Run the command in the directory you want to run a transform (the directory can be changed later): 39 | 40 | ```bash 41 | kodemod 42 | ``` 43 | 44 | The command will prompt you for the transform to run and all of its options. 45 | ![kodemod CLI screenshot](https://raw.githubusercontent.com/kcmr/web-components-codemods/master/docs/images/kodemod-cli-screenshot.png) 46 | 47 | Alternatively, you can run a specific transform by running `kodemod `. 48 | 49 | Example: 50 | 51 | ```bash 52 | kodemod replace-attrs 53 | ``` 54 | 55 | Available transform commands (same as transform scripts): 56 | 57 | - [replace-attrs](#replace-attrs) 58 | - [block-scope-to-iife](#replace-block-scope-by-iife) 59 | - [rename-tag](#rename-tag) 60 | - [lit-element-to-lit-imports](#litelement-to-lit-imports) 61 | 62 | ### Using jscodeshift 63 | 64 | Install [jscodeshift](https://github.com/facebook/jscodeshift) globally: 65 | 66 | ```bash 67 | npm i -g jscodeshift 68 | ``` 69 | 70 | Clone or download this repository: 71 | 72 | ```bash 73 | npx degit kcmr/web-components-codemods 74 | ``` 75 | 76 | Run `jscodeshift` passing the transform script with the `-t` option: 77 | 78 | ```bash 79 | jscodeshift target-dir/*.js -t web-components-codemods/.js 80 | ``` 81 | 82 | ## Available codemods 83 | 84 | ### Replace attrs 85 | 86 | Replaces attributes in the specified tag inside a template literal tagged `html` (LitElement or lit-html). 87 | 88 | **Script**: `transforms/replace-attrs.js` 89 | 90 | **Options** 91 | 92 | | Name | Default | Type | Description | 93 | | ------------ | ----------- | --------- | -------------------------------------------------------- | 94 | | `--tag` | `undefined` | `String` | Tag name where the attributes will be replaced | 95 | | `--attrs` | `undefined` | `String` | Stringified object with `{'old-attr': 'new-attr'}` pairs | 96 | | `--tabWidth` | `4` | `Number` | Number of spaces used per tab | 97 | | `--useTabs` | `false` | `Boolean` | Use tabs instead of spaces | 98 | 99 | Example input: 100 | 101 | ```js 102 | class MyComponent extends LitElement { 103 | render() { 104 | return html` 105 | 110 | 111 | `; 112 | } 113 | } 114 | ``` 115 | 116 | Command with options: 117 | 118 | ```bash 119 | jscodeshift input.js -t replace-attrs.js --tag=some-component --attrs='{"attr-one": "foo", ".someProp": ".newProp"}' 120 | ``` 121 | 122 | Output: 123 | 124 | ```diff 125 | class MyComponent extends LitElement { 126 | render() { 127 | return html` 128 | 135 | 136 | `; 137 | } 138 | } 139 | ``` 140 | 141 | ### Replace block scope by IIFE 142 | 143 | Replaces brackets used as scope in a file by an IIFE. 144 | 145 | **Script**: `transforms/block-scope-to-iife.js` 146 | 147 | **Options**: no options. 148 | 149 | Example input: 150 | 151 | ```js 152 | { 153 | const { Element } = Polymer; 154 | } 155 | ``` 156 | 157 | Command with options: 158 | 159 | ```bash 160 | jscodeshift input.js -t block-scope-to-iife.js 161 | ``` 162 | 163 | Output: 164 | 165 | ```diff 166 | -{ 167 | +(function() { 168 | const { Element } = Polymer; 169 | +})(); 170 | -} 171 | ``` 172 | 173 | ### Rename tag 174 | 175 | Renames a tag name inside template literals and strings. 176 | 177 | **Script**: `transforms/rename-tag.js` 178 | 179 | **Options** 180 | 181 | | Name | Default | Type | Description | 182 | | ------------ | ----------- | --------- | ----------------------------- | 183 | | `--oldTag` | `undefined` | `String` | Tag name to replace | 184 | | `--newTag` | `undefined` | `String` | New tag name | 185 | | `--tabWidth` | `2` | `Number` | Number of spaces used per tab | 186 | | `--useTabs` | `false` | `Boolean` | Use tabs instead of spaces | 187 | 188 | Example input: 189 | 190 | ```js 191 | const tpl = ` 192 | 193 | 194 | 195 | `; 196 | customElements.define('some-tag', SomeTag); 197 | ``` 198 | 199 | Command with options: 200 | 201 | ```bash 202 | jscodeshift input.js -t rename-tag.js --oldTag=some-tag --newTag=new-tag 203 | ``` 204 | 205 | Output: 206 | 207 | ```diff 208 | const tpl = ` 209 | - 210 | + 211 | 212 | - 213 | + 214 | `; 215 | -customElements.define('some-tag', SomeTag); 216 | +customElements.define('new-tag', SomeTag); 217 | ``` 218 | 219 | ### LitElement to Lit imports 220 | 221 | Updates the imports from `lit-element` to `lit` according to the [upgrade guide](https://lit.dev/docs/releases/upgrade/) of Lit 2.0 222 | 223 | **Script:** `transforms/lit-element-to-lit-imports.js` 224 | 225 | **Options:** 226 | 227 | | Name | Default | Type | Description | 228 | | --------- | -------- | -------- | -------------------------------- | 229 | | `--quote` | `single` | `String` | Type of quote (single or double) | 230 | 231 | Example input: 232 | 233 | ```js 234 | import { css } from 'lit-element'; 235 | import { LitElement, html, property as foo, customElement } from 'lit-element'; 236 | import { repeat } from 'lit-html/directives/repeat.js'; 237 | import { ifDefined } from 'lit-html/directives/if-defined'; 238 | ``` 239 | 240 | Command with options: 241 | 242 | ```bash 243 | jscodeshift input.js -t lit-element-to-lit-imports.js 244 | ``` 245 | 246 | Output: 247 | 248 | ```diff 249 | -import { css } from 'lit-element'; 250 | +import { css } from 'lit'; 251 | -import { LitElement, html, property as foo, customElement } from 'lit-element'; 252 | +import { LitElement, html } from 'lit'; 253 | +import { property as foo, customElement } from 'lit/decorators.js'; 254 | -import { repeat } from 'lit-html/directives/repeat.js'; 255 | +import { repeat } from 'lit/directives/repeat.js'; 256 | -import { ifDefined } from 'lit-html/directives/if-defined'; 257 | +import { ifDefined } from 'lit/directives/if-defined'; 258 | ``` 259 | 260 | ## Acknowledgments 261 | 262 | **Inspiration** 263 | 264 | - [Fearless refactors con AST - Speaker Deck](https://speakerdeck.com/sanguino/fearless-refactors-con-ast) 265 | - [React Codemod](https://github.com/reactjs/react-codemod) 266 | 267 | **Resources** 268 | 269 | - [Write Code to Rewrite Your Code: jscodeshift](https://www.toptal.com/javascript/write-code-to-rewrite-your-code) 270 | - [jscodeshift API and demos](https://hackmd.io/@yQp_d2iwRF25H5YTCeWj0w/Hy8FL6IWZ?type=view#jscodeshift-draft) 271 | 272 | ## License 273 | 274 | This project is licensed under the [MIT License](LICENSE). 275 | --------------------------------------------------------------------------------