diff --git a/packages/putout/.putout.json b/packages/putout/.putout.json index ba654381d..a63894f1c 100644 --- a/packages/putout/.putout.json +++ b/packages/putout/.putout.json @@ -4,5 +4,9 @@ }, "babelPlugins": [ "transform-inline-consecutive-adds" + ], + "jscodeshiftPlugins": [ + ["async-await-codemod/async-await", "async-await should be used instead of Promises"], + ["async-await-codemod/await-promise-chain", "async-await should be used instead of Promise chain"] ] } diff --git a/packages/putout/lib/run-babel-plugins/get-positions.js b/packages/putout/lib/get-positions-by-diff.js similarity index 100% rename from packages/putout/lib/run-babel-plugins/get-positions.js rename to packages/putout/lib/get-positions-by-diff.js diff --git a/packages/putout/lib/putout.js b/packages/putout/lib/putout.js index a8e3e0d84..f3970bb54 100644 --- a/packages/putout/lib/putout.js +++ b/packages/putout/lib/putout.js @@ -13,6 +13,7 @@ const getPlugins = require('./get-plugins'); const customParser = require('./custom-parser'); const runPlugins = require('./run-plugins'); const runBabelPlugins = require('./run-babel-plugins'); +const runJSCodeshiftPlugins = require('./run-jscodeshift-plugins'); const print = require('./print'); const isUndefined = (a) => typeof a === 'undefined'; @@ -101,6 +102,7 @@ function transform(ast, source, opts) { plugins: pluginNames, cache, babelPlugins = [], + jscodeshiftPlugins = [], rules, fix, fixCount, @@ -128,6 +130,11 @@ function transform(ast, source, opts) { plugins, parser, }), + ...runJSCodeshiftPlugins({ + ast, + fix, + jscodeshiftPlugins, + }), ]; return places; diff --git a/packages/putout/lib/run-babel-plugins/index.js b/packages/putout/lib/run-babel-plugins/index.js index 40d6bdb82..f2335efc0 100644 --- a/packages/putout/lib/run-babel-plugins/index.js +++ b/packages/putout/lib/run-babel-plugins/index.js @@ -3,7 +3,7 @@ const {transformFromAstSync} = require('@babel/core'); const print = require('../print'); -const getPositions = require('./get-positions'); +const getPositions = require('../get-positions-by-diff'); const getMessage = (a) => a .replace(/@babel\/plugin-|babel-plugin-/, '') @@ -12,10 +12,10 @@ const getMessage = (a) => a module.exports = ({fix, ast, babelPlugins}) => { const places = []; - if (!babelPlugins) + if (!babelPlugins.length) return places; - const oldCode = print(ast); + let oldCode = print(ast); for (const plugin of babelPlugins) { transform(ast, '', plugin); @@ -28,6 +28,8 @@ module.exports = ({fix, ast, babelPlugins}) => { const rule = `babel/${plugin}`; const message = getMessage(plugin); + oldCode = newCode; + for (const position of positions) places.push({ rule, diff --git a/packages/putout/lib/run-jscodeshift-plugins/index.js b/packages/putout/lib/run-jscodeshift-plugins/index.js new file mode 100644 index 000000000..d81cd4488 --- /dev/null +++ b/packages/putout/lib/run-jscodeshift-plugins/index.js @@ -0,0 +1,74 @@ +'use strict'; + +const once = require('once'); +const print = require('../print'); +const getPositions = require('../get-positions-by-diff'); + +const {isArray} = Array; +const {assign} = Object; + +const getMessage = (a) => a.replace('/', ': '); +const parsePlugin = (a) => { + if (!isArray(a)) + return [ + a, + getMessage(a), + ]; + + const [name, message] = a; + + return [ + name, + getMessage(message), + ]; +}; + +const getJScodeshift = once(() => require('jscodeshift')); + +const wrapCodeShift = () => { + const j = getJScodeshift(); + + const fixedJscodeshift = (a) => { + return assign(j(a), { + toSource() { + return print(this.__paths[0].value); + }, + }); + }; + + return assign(fixedJscodeshift, j); +}; + +module.exports = ({fix, ast, jscodeshiftPlugins}) => { + const places = []; + + if (!jscodeshiftPlugins) + return places; + + const jscodeshift = wrapCodeShift(ast); + + let oldCode = print(ast); + for (const plugin of jscodeshiftPlugins) { + const [pluginName, message] = parsePlugin(plugin); + const fn = require(pluginName); + + const newCode = fn({source: ast}, {jscodeshift}); + + if (!fix && newCode !== oldCode) { + const positions = getPositions(oldCode, newCode); + const rule = `jscodeshift/${pluginName}`; + + oldCode = newCode; + + for (const position of positions) + places.push({ + rule, + message, + position, + }); + } + } + + return places; +}; + diff --git a/packages/putout/package.json b/packages/putout/package.json index 3a2a96636..708f078d5 100644 --- a/packages/putout/package.json +++ b/packages/putout/package.json @@ -93,6 +93,7 @@ "glob": "^7.1.3", "ignore": "^5.0.4", "is-relative": "^1.0.0", + "jscodeshift": "^0.6.4", "once": "^1.4.0", "recast": "https://github.com/coderaiser/recast/archive/v0.18.1-fix.tar.gz", "try-catch": "^2.0.0", @@ -109,6 +110,7 @@ "acorn": "^6.1.1", "acorn-jsx": "^5.0.1", "acorn-stage3": "^2.0.0", + "async-await-codemod": "https://github.com/sgilroy/async-await-codemod.git", "babel-loader": "^8.0.6", "babel-plugin-macros": "^2.6.1", "codegen.macro": "^3.0.0", diff --git a/packages/putout/test/fixture/jscodeshift-fix.js b/packages/putout/test/fixture/jscodeshift-fix.js new file mode 100644 index 000000000..54b6ec866 --- /dev/null +++ b/packages/putout/test/fixture/jscodeshift-fix.js @@ -0,0 +1,4 @@ +async function promise() { + const helloResult = await hello(); + return world(helloResult); +} diff --git a/packages/putout/test/fixture/jscodeshift.js b/packages/putout/test/fixture/jscodeshift.js new file mode 100644 index 000000000..0ff7a4c09 --- /dev/null +++ b/packages/putout/test/fixture/jscodeshift.js @@ -0,0 +1,3 @@ +function promise() { + return hello().then(world); +} diff --git a/packages/putout/test/putout.js b/packages/putout/test/putout.js index c1cbaa765..5f0b2970d 100644 --- a/packages/putout/test/putout.js +++ b/packages/putout/test/putout.js @@ -35,6 +35,8 @@ const fixture = readFixtures([ 'not-jsx', 'babel-plugins', 'babel-plugins-fix', + 'jscodeshift', + 'jscodeshift-fix', ]); test('putout: no vars', (t) => { @@ -614,3 +616,36 @@ test('putout: plugin: find: push', (t) => { t.deepEqual(places, expected, 'should equal'); t.end(); }); + +test('putout: jscodeshift', (t) => { + const {code} = putout(fixture.jscodeshift, { + jscodeshiftPlugins: [ + 'async-await-codemod/async-await', + ], + }); + + t.deepEqual(code, fixture.jscodeshiftFix); + t.end(); +}); + +test('putout: jscodeshift: messsage', (t) => { + const {places} = putout(fixture.jscodeshift, { + fix: false, + jscodeshiftPlugins: [ + ['async-await-codemod/async-await', 'async-await should be used instead of Promises'], + ], + }); + + const expected = [{ + message: 'async-await should be used instead of Promises', + position: { + column: 0, + line: 2, + }, + rule: 'jscodeshift/async-await-codemod/async-await', + }]; + + t.deepEqual(places, expected); + t.end(); +}); +