├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── jest └── env.js ├── modules ├── history │ ├── deprecate-createPath-createHref-query.js │ └── deprecate-pushState-replaceState.js └── react-router │ ├── deprecate-Link-location-props.js │ ├── deprecate-context-history.js │ └── deprecate-isActive-query.js ├── package.json └── test ├── __tests__ ├── history │ ├── deprecate-createPath-createHref-query.js │ └── deprecate-pushState-replaceState.js └── react-router │ ├── deprecate-Link-location-props.js │ ├── deprecate-context-history.js │ └── deprecate-isActive-query.js ├── deprecate-Link-location-props-test.js ├── deprecate-Link-location-props-test.output.js ├── deprecate-context-history-test.js ├── deprecate-context-history-test.output.js ├── deprecate-createPath-createHref-query-test.js ├── deprecate-createPath-createHref-query-test.output.js ├── deprecate-createPath-createHref-query-test2.js ├── deprecate-createPath-createHref-query-test2.output.js ├── deprecate-isActive-query-test.js ├── deprecate-isActive-query-test.output.js ├── deprecate-pushState-replaceState-test.js ├── deprecate-pushState-replaceState-test.output.js ├── deprecate-pushState-replaceState-test2.js └── deprecate-pushState-replaceState-test2.output.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "loose": "all" 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "rackt" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Transpiled code 30 | /transforms 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - stable 6 | 7 | cache: 8 | directories: 9 | - node_modules 10 | 11 | branches: 12 | only: 13 | - master 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rackt-codemod [![npm][npm-badge]][npm] 2 | [Codemod](https://github.com/facebook/jscodeshift) scripts for Rackt libraries. 3 | 4 | [![Discord][discord-badge]][discord] 5 | 6 | ## Usage 7 | 8 | Install jscodeshift and rackt-codemod from npm: 9 | 10 | $ npm i -g jscodeshift 11 | $ npm i rackt-codemod 12 | 13 | Then run the codemod of your choice on your code: 14 | 15 | $ jscodeshift -t node_modules/rackt-codemod/ 16 | 17 | [npm-badge]: https://img.shields.io/npm/v/rackt-codemod.svg?style=flat-square 18 | [npm]: https://www.npmjs.org/package/rackt-codemod 19 | 20 | [discord-badge]: https://img.shields.io/badge/Discord-join%20chat%20%E2%86%92-738bd7.svg?style=flat-square 21 | [discord]: https://discord.gg/0ZcbPKXt5bXdZskn 22 | -------------------------------------------------------------------------------- /jest/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.autoMockOff(); 4 | 5 | require('babel-polyfill'); 6 | 7 | const fs = require('fs'); 8 | const jscodeshift = require('jscodeshift'); 9 | const p = require('path'); 10 | 11 | const read = fileName => fs.readFileSync( 12 | p.join(__dirname, global.baseDir, 'test', fileName), 13 | 'utf8' 14 | ); 15 | 16 | global.test = (transformName, testFileName, options, fakeOptions) => { 17 | let path = testFileName + '.js'; 18 | const source = read(testFileName + '.js'); 19 | const output = read(testFileName + '.output.js'); 20 | const transform = require( 21 | p.join(global.baseDir, '/modules/', transformName) 22 | ); 23 | 24 | if (fakeOptions) { 25 | if (fakeOptions.path) { 26 | path = fakeOptions.path; 27 | } 28 | } 29 | 30 | expect( 31 | (transform({path, source}, {jscodeshift}, options || {}) || '').trim() 32 | ).toEqual( 33 | output.trim() 34 | ); 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /modules/history/deprecate-createPath-createHref-query.js: -------------------------------------------------------------------------------- 1 | import parsePath from 'history/lib/parsePath' 2 | 3 | function replace(source, j, methodName) { 4 | return j(source) 5 | .find(j.CallExpression, { 6 | callee: { property: { name: methodName } } } 7 | ) 8 | .forEach(p => { 9 | const [ pathArg, queryArg ] = p.value.arguments 10 | if (!queryArg) { 11 | return 12 | } 13 | 14 | const properties = [] 15 | 16 | if (pathArg.type === 'Literal' && pathArg.value) { 17 | const parsedPath = parsePath(pathArg.value) 18 | Object.keys(parsedPath).map(pathKey => { 19 | const pathValue = parsedPath[pathKey] 20 | if (pathValue) { 21 | properties.push( 22 | j.property('init', j.identifier(pathKey), j.literal(pathValue)) 23 | ) 24 | } 25 | }) 26 | } else { 27 | properties.push( 28 | j.property('init', j.identifier('pathname'), pathArg) 29 | ) 30 | } 31 | 32 | properties.push(j.property('init', j.identifier('query'), queryArg)) 33 | 34 | j(p).replaceWith(j.callExpression( 35 | p.value.callee, 36 | [ j.objectExpression(properties) ] 37 | )) 38 | }) 39 | .toSource() 40 | } 41 | 42 | export default (file, { jscodeshift: j }) => { 43 | let { source } = file 44 | source = replace(source, j, 'createPath') 45 | source = replace(source, j, 'createHref') 46 | 47 | return source 48 | } 49 | -------------------------------------------------------------------------------- /modules/history/deprecate-pushState-replaceState.js: -------------------------------------------------------------------------------- 1 | import parsePath from 'history/lib/parsePath' 2 | 3 | function replace(source, j, flavor) { 4 | return j(source) 5 | .find(j.CallExpression, { 6 | callee: { property: { name: `${flavor}State` } } } 7 | ) 8 | .forEach(p => { 9 | const [ stateArg, pathArg, queryArg ] = p.value.arguments 10 | const properties = [] 11 | 12 | if (queryArg) { 13 | properties.push(j.property('init', j.identifier('query'), queryArg)) 14 | } 15 | if (stateArg.type !== 'Literal' || stateArg.value != null) { 16 | properties.push(j.property('init', j.identifier('state'), stateArg)) 17 | } 18 | 19 | let argument 20 | if (properties.length === 0) { 21 | argument = pathArg 22 | } else { 23 | if (pathArg.type === 'Literal' && pathArg.value) { 24 | const parsedPath = parsePath(pathArg.value) 25 | Object.keys(parsedPath).reverse().map(pathKey => { 26 | const pathValue = parsedPath[pathKey] 27 | if (pathValue) { 28 | properties.unshift( 29 | j.property('init', j.identifier(pathKey), j.literal(pathValue)) 30 | ) 31 | } 32 | }) 33 | } else { 34 | properties.unshift( 35 | j.property('init', j.identifier('pathname'), pathArg) 36 | ) 37 | } 38 | argument = j.objectExpression(properties) 39 | } 40 | 41 | j(p).replaceWith(j.callExpression( 42 | j.memberExpression(p.value.callee.object, j.identifier(flavor)), 43 | [ argument ] 44 | )) 45 | }) 46 | .toSource() 47 | } 48 | 49 | export default (file, { jscodeshift: j }) => { 50 | let { source } = file 51 | source = replace(source, j, 'push') 52 | source = replace(source, j, 'replace') 53 | 54 | return source 55 | } 56 | -------------------------------------------------------------------------------- /modules/react-router/deprecate-Link-location-props.js: -------------------------------------------------------------------------------- 1 | function isDeprecatedAttribute(attribute) { 2 | const { name } = attribute.name 3 | return [ 'query', 'hash', 'state' ].includes(name) 4 | } 5 | 6 | function getValueExpression(value) { 7 | if (value.type === 'Literal') { 8 | return value 9 | } 10 | 11 | return value.expression 12 | } 13 | 14 | function replace(source, j, componentName) { 15 | return j(source) 16 | .find(j.JSXOpeningElement, { name: { name: componentName } }) 17 | .forEach(p => { 18 | const { name, attributes, selfClosing } = p.value 19 | 20 | const deprecatedAttributes = attributes.filter(isDeprecatedAttribute) 21 | if (!deprecatedAttributes.length) { 22 | return 23 | } 24 | 25 | const newAttributes = attributes 26 | .filter(attribute => !isDeprecatedAttribute(attribute)) 27 | .map(attribute => { 28 | if (attribute.name.name !== 'to') { 29 | return attribute 30 | } 31 | 32 | const properties = [ 33 | j.property( 34 | 'init', j.identifier('pathname'), 35 | getValueExpression(attribute.value) 36 | ) 37 | ] 38 | 39 | properties.push(...deprecatedAttributes.map(({ name, value }) => ( 40 | j.property( 41 | 'init', j.identifier(name.name), 42 | getValueExpression(value) 43 | ) 44 | ))) 45 | 46 | return j.jsxAttribute( 47 | j.jsxIdentifier('to'), 48 | j.jsxExpressionContainer(j.objectExpression(properties)) 49 | ) 50 | }) 51 | 52 | j(p).replaceWith(j.jsxOpeningElement(name, newAttributes, selfClosing)) 53 | }) 54 | .toSource() 55 | } 56 | 57 | export default (file, { jscodeshift: j }) => { 58 | let { source } = file 59 | source = replace(source, j, 'Link') 60 | source = replace(source, j, 'IndexLink') 61 | 62 | return source 63 | } 64 | -------------------------------------------------------------------------------- /modules/react-router/deprecate-context-history.js: -------------------------------------------------------------------------------- 1 | export default ({ source }, { jscodeshift: j }) => { 2 | const root = j(source) 3 | 4 | const classContextTypesHistory = root 5 | .find(j.ClassProperty, { key: { name: 'contextTypes' } }) 6 | .find(j.Property, { key: { name: 'history' } }) 7 | const constructorContextTypesHistory = root 8 | .find( 9 | j.AssignmentExpression, { left: { property: { name: 'contextTypes' } } } 10 | ) 11 | .find(j.Property, { key: { name: 'history' } }) 12 | const getContextTypesHistory = root 13 | .find(j.Property, { key: { name: 'getContextTypes' } }) 14 | .find(j.Property, { key: { name: 'history' } }) 15 | 16 | if (!( 17 | classContextTypesHistory.size() || 18 | constructorContextTypesHistory.size() || 19 | getContextTypesHistory.size()) 20 | ) { 21 | return source 22 | } 23 | 24 | return root 25 | .find(j.Identifier, { name: 'history' }) 26 | .replaceWith(() => j.identifier('router')) 27 | .toSource() 28 | } 29 | -------------------------------------------------------------------------------- /modules/react-router/deprecate-isActive-query.js: -------------------------------------------------------------------------------- 1 | export default ({ source }, { jscodeshift: j }) => { 2 | return j(source) 3 | .find(j.CallExpression, { callee: { property: { name: 'isActive' } } }) 4 | .forEach(p => { 5 | const [ pathnameArg, queryArg, indexOnlyArg ] = p.value.arguments 6 | 7 | let locationArg 8 | if ( 9 | queryArg && 10 | (queryArg.type !== 'Literal' || queryArg.value != null) 11 | ) { 12 | locationArg = j.objectExpression([ 13 | j.property('init', j.identifier('pathname'), pathnameArg), 14 | j.property('init', j.identifier('query'), queryArg) 15 | ]) 16 | } else { 17 | locationArg = pathnameArg 18 | } 19 | 20 | const newArguments = [ locationArg ] 21 | if (indexOnlyArg) { 22 | newArguments.push(indexOnlyArg) 23 | } 24 | 25 | j(p).replaceWith(j.callExpression(p.value.callee, newArguments)) 26 | }) 27 | .toSource() 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rackt-codemod", 3 | "version": "0.1.2", 4 | "description": "Codemod scripts for Rackt libraries", 5 | "files": [ 6 | "transforms" 7 | ], 8 | "scripts": { 9 | "build": "rimraf transforms && babel modules/ -d transforms", 10 | "lint": "eslint modules", 11 | "prepublish": "npm run build", 12 | "test": "f() { EXIT=0; npm run lint || EXIT=$?; NODE_ENV=test jest $@ || EXIT=$?; exit $EXIT; }; f" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/rackt/rackt-codemod.git" 17 | }, 18 | "keywords": [ 19 | "react", 20 | "codemod", 21 | "rackt" 22 | ], 23 | "author": "Jimmy Jia", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/rackt/rackt-codemod/issues" 27 | }, 28 | "homepage": "https://github.com/rackt/rackt-codemod#readme", 29 | "dependencies": { 30 | "history": "^1.14.0", 31 | "jscodeshift": "^0.3.11" 32 | }, 33 | "devDependencies": { 34 | "babel": "^5.8.34", 35 | "babel-eslint": "^4.1.6", 36 | "babel-jest": "^5.3.0", 37 | "babel-polyfill": "^6.3.14", 38 | "eslint": "^1.10.3", 39 | "eslint-config-rackt": "^1.1.1", 40 | "jest-cli": "^0.8.2", 41 | "rimraf": "^2.4.4" 42 | }, 43 | "jest": { 44 | "scriptPreprocessor": "./node_modules/babel-jest", 45 | "setupEnvScriptFile": "jest/env.js", 46 | "globals": { 47 | "baseDir": "../" 48 | }, 49 | "testPathDirs": [ 50 | "test" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/__tests__/history/deprecate-createPath-createHref-query.js: -------------------------------------------------------------------------------- 1 | describe('Deprecate createPath and createHref query', () => { 2 | it('should transform correctly', () => { 3 | test('history/deprecate-createPath-createHref-query', 'deprecate-createPath-createHref-query-test'); 4 | test('history/deprecate-createPath-createHref-query', 'deprecate-createPath-createHref-query-test2'); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /test/__tests__/history/deprecate-pushState-replaceState.js: -------------------------------------------------------------------------------- 1 | describe('Deprecate pushState and replaceState query', () => { 2 | it('should transform correctly', () => { 3 | test('history/deprecate-pushState-replaceState', 'deprecate-pushState-replaceState-test'); 4 | test('history/deprecate-pushState-replaceState', 'deprecate-pushState-replaceState-test2'); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /test/__tests__/react-router/deprecate-Link-location-props.js: -------------------------------------------------------------------------------- 1 | describe('Deprecate Link Location Props', () => { 2 | it('should transform correctly', () => { 3 | test('react-router/deprecate-Link-location-props', 'deprecate-Link-location-props-test'); 4 | }); 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /test/__tests__/react-router/deprecate-context-history.js: -------------------------------------------------------------------------------- 1 | describe('Deprecate Context History', () => { 2 | it('should transform correctly', () => { 3 | test('react-router/deprecate-context-history', 'deprecate-context-history-test'); 4 | }); 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /test/__tests__/react-router/deprecate-isActive-query.js: -------------------------------------------------------------------------------- 1 | describe('Deprecate isActive Query', () => { 2 | it('should transform correctly', () => { 3 | test('react-router/deprecate-isActive-query', 'deprecate-isActive-query-test'); 4 | }); 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /test/deprecate-Link-location-props-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ACTIVE = { color: 'red' }; 4 | 5 | class App extends React.Component { 6 | render() { 7 | return ( 8 |
9 |

APP!

10 |
    11 |
  • /
  • 12 |
  • / IndexLink
  • 13 |
  • /users
  • 14 |
  • /users IndexLink
  • 15 |
  • /users/ryan
  • 16 |
  • /users/ryan?foo=bar
  • 17 |
  • /about
  • 18 |
19 | 20 | {this.props.children} 21 |
22 | ) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /test/deprecate-Link-location-props-test.output.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ACTIVE = { color: 'red' }; 4 | 5 | class App extends React.Component { 6 | render() { 7 | return ( 8 |
9 |

APP!

10 |
    11 |
  • /
  • 12 |
  • / IndexLink
  • 13 |
  • /users
  • 14 |
  • /users IndexLink
  • 15 |
  • /users/ryan
  • 16 |
  • /users/ryan?foo=bar
  • 22 |
  • /about
  • 23 |
24 | 25 | {this.props.children} 26 |
27 | ) 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /test/deprecate-context-history-test.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react-router' 2 | 3 | class MyComponent extends React.Component { 4 | doStuff() { 5 | this.context.history.pushState(null, '/some/path'); 6 | } 7 | } 8 | 9 | MyComponent.contextTypes = { history: PropTypes.history }; 10 | 11 | -------------------------------------------------------------------------------- /test/deprecate-context-history-test.output.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react-router' 2 | 3 | class MyComponent extends React.Component { 4 | doStuff() { 5 | this.context.router.pushState(null, '/some/path'); 6 | } 7 | } 8 | 9 | MyComponent.contextTypes = { router: PropTypes.router }; 10 | 11 | -------------------------------------------------------------------------------- /test/deprecate-createPath-createHref-query-test.js: -------------------------------------------------------------------------------- 1 | history.createPath('/foo/bar', { foo: 'bar' }); 2 | 3 | -------------------------------------------------------------------------------- /test/deprecate-createPath-createHref-query-test.output.js: -------------------------------------------------------------------------------- 1 | history.createPath({ 2 | pathname: "/foo/bar", 3 | query: { foo: 'bar' } 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /test/deprecate-createPath-createHref-query-test2.js: -------------------------------------------------------------------------------- 1 | history.createHref('/home', { the: 'query' }); 2 | 3 | -------------------------------------------------------------------------------- /test/deprecate-createPath-createHref-query-test2.output.js: -------------------------------------------------------------------------------- 1 | history.createHref({ 2 | pathname: "/home", 3 | query: { the: 'query' } 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /test/deprecate-isActive-query-test.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react-router' 2 | 3 | class Tab extends React.Component { 4 | render() { 5 | const isActive = this.context.history.isActive( 6 | this.props.to, 7 | this.props.query, 8 | this.props.onlyActiveOnIndex 9 | ); 10 | 11 | const className = isActive ? 'active' : ''; 12 | 13 | return
  • ; 14 | } 15 | } 16 | 17 | Tab.contextTypes = { history: PropTypes.history }; 18 | 19 | -------------------------------------------------------------------------------- /test/deprecate-isActive-query-test.output.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react-router' 2 | 3 | class Tab extends React.Component { 4 | render() { 5 | const isActive = this.context.history.isActive({ 6 | pathname: this.props.to, 7 | query: this.props.query 8 | }, this.props.onlyActiveOnIndex); 9 | 10 | const className = isActive ? 'active' : ''; 11 | 12 | return
  • ; 13 | } 14 | } 15 | 16 | Tab.contextTypes = { history: PropTypes.history }; 17 | 18 | -------------------------------------------------------------------------------- /test/deprecate-pushState-replaceState-test.js: -------------------------------------------------------------------------------- 1 | history.pushState({ the: 'state' }, '/home', { the: 'query value' }); 2 | 3 | -------------------------------------------------------------------------------- /test/deprecate-pushState-replaceState-test.output.js: -------------------------------------------------------------------------------- 1 | history.push({ 2 | pathname: "/home", 3 | query: { the: 'query value' }, 4 | state: { the: 'state' } 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /test/deprecate-pushState-replaceState-test2.js: -------------------------------------------------------------------------------- 1 | history.replaceState({ the: 'state' }, '/home', { the: 'query' }); 2 | 3 | -------------------------------------------------------------------------------- /test/deprecate-pushState-replaceState-test2.output.js: -------------------------------------------------------------------------------- 1 | history.replace({ 2 | pathname: "/home", 3 | query: { the: 'query' }, 4 | state: { the: 'state' } 5 | }); 6 | 7 | --------------------------------------------------------------------------------