Perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away.
(c) Antoine de Saint Exupéry
🐊Putout is a JavaScript Linter, pluggable and configurable code transformer, drop-in ESLint replacement with built-in code printer and ability to fix syntax errors. It has a lot of transformations that keeps your codebase in a clean state, removing any code smell and making code readable according to best practices.
The main target is JavaScript, but:
- ✅ JSX;
- ✅ TypeScript;
- ✅ Flow;
- ✅ Yaml;
- ✅ Markdown;
- ✅ JSON;
- ✅ Ignore;
are also supported. Here is how it looks like:
- 🤷♂️ In doubt about using 🐊Putout?
- 🙏 Whom should I thank for this project exist?
- 🤷♂️ Why does this project exist?
- 🪬Core Concepts
- 🚚 Installation
- 🎙 Usage
- 🦕 Usage with Deno
- 📐 What is Ruler?
- ✂️ How Ruler can help me?
- 🚁 Convert
CommonJS
toESM
- 🏛 Architecture
- 🌲 The Tree of Syntax
- 🌴 Laws of the Jungle
- 🏗 API
- 🏨 Built-in transformations
- 🛠️ Syntax errors
- 🏟 Plugins
- 🦚 Formatters
- 🦉 Configuration
- 🧬 Plugins API
- 🛴 Codemods
- ⏣ Integration with ESLint
- ☄️ Integration with Babel
- 🐈 Integration with Yarn OnP
- ⛓ Using Putout as Loader
- 🚪 Exit Codes
- 🦔 Real-world uses
- 📻 Versioning policy
- 🚀 I want contribute
- 🍄 License
Check out couple variants of plugins that does the same: linting debugger statement:
- ❌ SWCLint no-debugger: 49 lines;
- ❌ RSLint no-debugger: 48 lines;
- ❌ ESLint no-debugger: 43 lines;
- ❌ Rome no-debugger: 28 lines;
- 🐊 Putout remove-debugger: 7 lines:
'use strict';
module.exports.report = () => `Unexpected 'debugger' statement`;
module.exports.replace = () => ({
debugger: '',
});
Choose wisely, competitors cannot even fix… 🤫
🐊Putout in addition to own format .putout.json
supports both eslint.config.js
and .eslintrc.json
, it has ability to autodect format you use.
Also it works good with monorepository, since it uses eslint.config.js
that is closer to linting file, instead of cwd
of ESLint run.
If I have seen further, it is by standing upon the shoulders of giants.
(c) Isaac Newton
- 💪ESLint for stable releases and future proof API.
- 💪Babel for amazing API documented in Handbook and responsiveness of a team.
- 💪Prettier for minimalistic options and uniform codestyle.
- 💪JSCodeshift for making codemods simple and popular.
- ❌ESLint avoids fixes that could change the runtime behavior.
- ❌Babel produces throw-away code.
- ❌Prettier is a formatter.
- ❌JSCodeshift has no config and plugins support.
🐊Putout on the other hand can make more drastic code transformations that directly affects your codebase making it a better place to code 💻:
To install 🐊Putout as a development dependency, run:
npm i putout -D
Make sure that you are running a relatively recent (≥16) version of Node.
Grown-ups never understand anything by themselves, and it is tiresome for children to be always and forever explaining things to them.
(c) Antoine de Saint-Exupéry
🐊Putout tries to be clear and likes a lot to explain things. So when you write putout --help
most likely you will hear gladly purr :
Usage: putout [options] [path]
Options:
-h, --help display this help and exit
-v, --version output version information and exit
-f, --format [formatter] use a specific output format, the default is: 'progress-bar' locally and 'dump' on CI
-s, --staged add staged files when in git repository
-i, --interactive set lint options using interactive menu
--fix apply fixes of errors to code
--fix-count [count = 10] count of fixes rounds
--rulesdir use additional rules from directory
--transform [replacer] apply Replacer, for example 'var __a = __b -> const __a = __b', read about Replacer https://git.io/JqcMn
--plugins [plugins] a comma-separated list of plugins to use
--enable [rule] enable the rule and save it to '.putout.json' walking up parent directories
--disable [rule] disable the rule and save it to '.putout.json' walking up parent directories
--enable-all enable all found rules and save them to '.putout.json' walking up parent directories
--disable-all disable all found rules (set baseline) and save them to '.putout.json' walking up parent directories
--match [pattern] read '.putout.json' and convert 'rules' to 'match' according to 'pattern'
--flow enable flow
--fresh generate a fresh cache
--no-config avoid reading '.putout.json'
--no-ci disable the CI detection
--no-cache disable the cache
--no-worker disable worker thread
To skip prefix node_modules/.bin/
, update your $PATH
variable in with .bashrc
:
echo 'PATH="$PATH:node_modules/.bin"' >> ~/.bashrc
source ~/.bashrc
To find possible transform places in a folder named lib
, run:
putout lib
To find possible transform places in multiple folders, such as folders named lib
and test
, run:
putout lib test
To apply the transforms, use --fix
:
putout lib test --fix
Developers, myself included, usually prefer to make all code changes manually, so that nothing happens to our code without reviewing it first. That is until we trust a tool to make those changes safely for us. An example is WebStorm, which we trust when renaming a class
or a method
. Since 🐊Putout may still feel like a new tool, not all of us will be able to trust it immediately.
A good way to gain trust is two run without --fix
option, and observe error messages. Another way is to use traditional version control tactics. Before running 🐊Putout you should do a git commit
. Then after running 🐊Putout, you’ll be able to inspect the changes it made using git diff
and git status
. You still have the chance to run git checkout -- .
at any time to revert all the changes that 🐊Putout has made. If you need more fine-grained control, you can also use git add -p
or git add -i
to interactively stage only the changes you want to keep.
🐊Putout supports the following environment variables:
PUTOUT_CONFIG_FILE
- path to configuration file;PUTOUT_FILES
- files that should be processed split by comma (,
);
Example:
PUTOUT_FILES=lib,test putout --fix
When you need to run 🐊Putout in Deno, use @putout/bundle
:
import putout from 'https://esm.sh/@putout/bundle';
import removeDebugger from 'https://esm.sh/@putout/plugin-remove-debugger?alias=putout:@putout/bundle';
import declare from 'https://esm.sh/@putout/plugin-declare?alias=putout:@putout/bundle';
putout('isFn(fn); debugger', {
plugins: [
['remove-debugger', removeDebugger],
['declare', declare],
],
});
// returns
({
code: `const isFn = a => typeof a === 'function';\nisFn(fn);`,
places: [],
});
When you need to change configuration file use Ruler instead of editing the file manually.
Ruler can:
- ✅
putout --enable [rule]
; - ✅
putout --disable [rule]
; - ✅
putout --enable-all
; - ✅
putout --disable-all
;
☝️Remember, Ruler should never be used with --fix
, because unclear things makes 🐊 Putout angry and you can find him barking at you:
🐊 '--fix' cannot be used with ruler toggler ('--enable', '--disable')
You may want to convert your CommonJS
to ESM
since node v12 supports it without a flag.
Well, if you have no type
field or type=commonjs
your package will be
converted to CommonJS
automatically. To convert to ESM
just set type=module
.
They will be converted automatically to CommonJS
and ESM
accordingly.
Let's suppose you have a file called index.js
:
const unused = 5;
module.exports = function() {
return promise();
};
async function promise(a) {
return Promise.reject(Error('x'));
}
You call putout --fix index.js
and see that file is changed:
'use strict';
module.exports = async function() {
return await promise();
};
async function promise() {
throw Error('x');
}
But for some reason you don't want so many changes.
☝️ Remember, safe mode of eslint-plugin-putout has the most dangerous rules disabled, so it can be used as auto fix on each save in your IDE.
So, if you want to convert it to ESM
keeping everything else untouched use Ruler: it can easily disable all rules 🐊Putout finds.
putout index.js --disable-all
will find next errors:
1:4 error 'unused' is defined but never used remove-unused-variables
7:23 error 'a' is defined but never used remove-unused-variables
3:0 error Use arrow function convert-to-arrow-function
1:0 error Add missing 'use strict' directive on top of CommonJS mode/add-missing
8:4 error Reject is useless in async functions, use throw instead promises/convert-reject-to-throw
4:11 error Async functions should be called using 'await' promises/add-missing-await
7:0 error Avoid useless async promises/remove-useless-async
It will create config file .putout.json
:
{
"rules": {
"remove-unused-variables": "off",
"convert-to-arrow-function": "off",
"nodejs/strict-mode-add-missing": "off",
"promises/convert-reject-to-throw": "off",
"promises/add-missing-await": "off",
"promises/remove-useless-async": "off"
}
}
Then running putout index.js --enable nodejs/convert-commonjs-to-esm
will update config with:
{
"rules": {
"remove-unused-variables": "off",
"convert-to-arrow-function": "off",
"nodejs/strict-mode-add-missing": "off",
"promises/convert-reject-to-throw": "off",
"promises/add-missing-await": "off",
- "promises/remove-useless-async": "off"
+ "promises/remove-useless-async": "off",
+ "nodejs/convert-commonjs-to-esm": "on"
}
}
Then putout --fix index.js
will do the thing and update index.js
with:
const unused = 5;
export default function() {
return promise();
}
async function promise(a) {
return Promise.reject(Error('x'));
}
So in case of src
directory, it will look like:
putout src --disable-all && putout src --enable nodejs/convert-commonjs-to-esm && putout src --fix
This command will disable all rules that 🐊Putout can find right now and enable a single rule. All built-in rules made for good and highly suggested to be used, all of them are enabled in all my repositories, since they have auto fix.
☝️You can always disable what you don't need, so give it a try. You won't regret 🐊.
Happy coding 🎈!
🐊Putout consists of a couple simple parts, here is a workflow representation:
And here is a CLI scheme:
The wise speak of the perennial Ashvattha tree, which has roots above and branches below. The leaves protecting it are the Vedas. One who knows this, truly knows. The tender sprouts of this mighty tree are the senses nourished by the gunas. The branches extend both above and below. The secondary roots going downward represent actions that bind the individual soul to earthly existence.
(c) “Bhagavatgita”, chapter 15
On the bottom level of 🐊Putout layes down Syntax Tree. This is data structure that makes it possible to do crazy transformations in a simplest possible way. It is used mostly in compilers development.
You can read about it in Babel Plugin Handbook. To understand how things work from the inside take a look at Super Tiny Compiler.
Preoccupied with a single leaf, you won't see the tree. Preoccupied with a single tree, you'll miss the entire forest. When you look at a tree, see it for its leaves, its branches, its trunk and the roots, then and only then will you see the tree.
(c) Takuan Soho, "The Unfettered Mind: Writings of the Zen Master to the Sword Master"
Consider next piece of code:
hello = 'world';
It looks this way in ESTree JavaScript syntax format:
{
"type": "AssignmentExpression",
"operator": "=",
"left": {
"type": "Identifier",
"name": "hello"
},
"right": {
"type": "StringLiteral",
"value": "world"
}
}
When one is not capable of true intelligence, it is good to consult with someone of good sense. An advisor will fulfill the Way when he makes a decision by selfless and frank intelligence because he is not personally involved. This way of doing things will certainly be seen by others as being strongly rooted. It is, for example, like a large tree with many roots.
(c) Yamamoto Tsunetomo "Hagakure"
🐊Putout based on Babel AST. It has a couple of differences from ESTree which are perfectly handled by estree-to-babel
.
☝️ You can get more information about AST in The Book of AST.
- 🐅
engines
chilling withengines
, and chasingplugins
,processors
,operators
; - 🦌
plugins
chilling withplugins
andoperators
viarequire('putout').operator
; - 🦒
processors
chilling withprocessors
; - 🐃
operators
chilling withoperators
;
Engines is the heart of 🐊Putout: Parser, Loader and Runner are running for every processed file. Processor runs all the processors.
Package | Version |
---|---|
@putout/engine-parser |
|
@putout/engine-loader |
|
@putout/engine-runner |
|
@putout/engine-processor |
|
@putout/engine-reporter |
With help of processors 🐊Putout can be extended to read any file format and parse JavaScript from there.
Here is a list of built-int processors:
Package | Version |
---|---|
@putout/processor-javascript |
|
@putout/processor-json |
|
@putout/processor-markdown |
|
@putout/processor-ignore |
|
@putout/processor-yaml |
|
@putout/processor-css |
|
@putout/processor-filesystem |
You can disable any of them with:
{
"processors": [
["markdown", "off"]
]
}
Not bundled processors:
Package | Version |
---|---|
@putout/processor-typescript |
|
@putout/processor-html |
|
@putout/processor-wasm |
External processors:
Package | Version |
---|---|
putout-processor-typos |
To enable, install and use:
{
"processors": [
["typescript", "on"]
]
}
Processors can be tested using @putout/test/processors.
In one’s life there are levels in the pursuit of study. In the lowest level, a person studies but nothing comes of it, and he feels that both he and others are unskillful. At this point he is worthless. In the middle level he is still useless but is aware of his own insufficiencies and can also see the insufficiencies of others. At a higher level, he has pride concerning his own ability, rejoices in praise from others, and laments the lack of ability in his fellows. This man has worth. At the highest level a man has the look of knowing nothing.
(c) Yamamoto Tsunetomo "Hagakure"
In the similar way works 🐊Putout API: it has no plugins defined, tabula rasa.
First things first, require
putout:
const putout = require('putout');
Let's consider the next source
with two VariableDeclarations
and one CallExpression
:
const hello = 'world';
const hi = 'there';
console.log(hello);
We can declare it as source
:
const source = `
const hello = 'world';
const hi = 'there';
console.log(hello);
`;
🐊Putout supports dynamic loading of plugins from node_modules
. Let's consider example of using the remove-unused-variables plugin:
putout(source, {
plugins: [
'remove-unused-variables',
],
});
// returns
({
code: `\n const hello = 'world';\n\n console.log(hello);\n`,
places: [],
});
As you see, places
is empty, but the code is changed: there is no hi
variable.
From the beginning, 🐊Putout developed with ability to split the main process into two concepts: find
(find places that could be fixed) and fix
(apply the fixes to the files).
It is therefore easy to find sections that could be fixed.
In the following example redundant variables are found without making changes to the source file:
putout(source, {
fix: false,
plugins: [
'remove-unused-variables',
],
});
// returns
({
code: '\n' + ` const hello = 'world';\n` + ` const hi = 'there';\n` + ' \n' + ' console.log(hello);\n',
places: [{
rule: 'remove-unused-variables',
message: '"hi" is defined but never used',
position: {
line: 3,
column: 10,
},
}],
});
Source maps are embedded in the generated source using a special comment. These comments may contain the entire source map, using a Data URI, or may reference an external URL or file.
In our case Data URL
used. Here is an example of source map:
{
"version": 3,
"file": "out.js",
"sourceRoot": "",
"sources": [
"foo.js",
"bar.js"
],
"names": [
"src",
"maps",
"are",
"fun"
],
"mappings": "AAgBC,SAAQ,CAAEA"
}
To generate source map you need to pass:
- ✅
sourceFileName
; - ✅
sourceMapName
;
putout(source, {
fix: false,
sourceFileName: 'hello.js',
sourceMapName: 'world.js',
plugins: [
'remove-unused-variables',
],
});
// returns
({
code: `
const hello = 'world';
const hi = 'there';
console.log(hello);
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJ...
`,
places: [{
rule: 'remove-unused-variables',
message: '"hi" is defined but never used',
position: {
line: 3,
column: 10,
},
}],
});
remove unused variables
function show() {
- const message = 'hello';
console.log('hello world');
}
remove unused for...of
variables
-for (const {a, b} of c) {
+for (const {a} of c) {
console.log(a);
}
remove unreferenced variables
-let a;
- a = 1;
let b;
b = 2;
console.log(b);
remove duplicate keys
const a = {
- x: 'hello',
- ...y,
x: 'world',
...y,
}
remove duplicate case
switch (x) {
case 5:
console.log('hello');
break;
- case 5:
- console.log('zz');
- break;
}
remove unused private fields
class Hello {
#a = 5;
- #b = 3;
get() {
return this.#a;
};
}
remove unused expressions
function show(error) {
- showError;
}
remove useless variables
- function hi(a) {
- const b = a;
};
+ function hi(b) {
};
remove useless push
function notUsed() {
- const paths = [];
for (const [key, name] of tuples) {
- paths.push([key, full]);
}
}
remove useless Object.assign()
-const load = stub().rejects(assign(Error('LOAD USED')));
+const load = stub().rejects(Error('LOAD USED'));
remove useless replace()
-const a = 'hello'.replace(world, world);
+const a = 'hello';
remove useless new
(why)
-new Error('something when wrong');
+Error('something when wrong');
add missing new
-const map = Map();
+const map = new Map();
remove useless constructor
(why)
class A extends B() {
- constructor(...args) {
- super(...args);
- }
}
remove useless map
-const [str] = lines.map((line) => `hello ${line}`);
+const [line] = lines;
+const str = `hello ${line}`;
remove useless continue
-for (sign = decpt, i = 0; (sign /= 10) != 0; i++)
- continue;
+for (sign = decpt, i = 0; (sign /= 10) != 0; i++);
remove useless operand
-a = a + b;
+a += b;
remove useless return
-module.exports.traverse = ({push}) => {
- return {
- ObjectExpression(path) {
- }
- }
-};
+module.exports.traverse = ({push}) => ({
+ ObjectExpression(path) {
+ }
+});
remove useless array
-A[[B]];
+A[B];
remove useless array constructor
-const a = Array(1, 2, 3);
+const a = [1, 2, 3];
remove useless conditions
-if (zone?.tooltipCallback) {
- zone.tooltipCallback(e);
-}
+zone?.tooltipCallback(e);
remove useless type conversion
-const a = Boolean(b.includes(c));
+const a = b.includes(c);
--if (!!a)
++if (a)
console.log('hi');
remove useless functions
-const f = (...a) => fn(...a);
-array.filter((a) => a);
+const f = fn;
+array.filter(Boolean);
remove useless typeof
- typeof typeof 'hello';
+ typeof 'hello';
declare before reference
-const {compare} = operator;
import {operator} from 'putout';
+const {compare} = operator
declare imports
first
-const [arg] = process.argv;
import esbuild from 'esbuild';
+const [arg] = process.argv;
declare variables
+const fs = import 'fs/promises';
+const {stub} = import 'supertape';
+const {assign} = Object;
const readFile = stub();
assign(fs, {
readFile,
});
remove useless arguments
onIfStatement({
push,
- generate,
- abc,
})
function onIfStatement({push}) {
}
remove useless template expressions
-let y = `${"hello"} + ${"world"}`;
+let y = `hello + world`;
remove useless for...of
-for (const a of ['hello']) {
- console.log(a);
-}
+console.log('hello');
remove useless array.entries()
-for (const [, element] of array.entries()) {
-}
+for (const element of array) {
+}
reuse duplicate init
const putout = require('putout');
-const {operator} = require('putout');
+const {operator} = putout;
convert assignment
to arrow function
-const createRegExp = (a) = RegExp(a, 'g');
+const createRegExp = (a) => RegExp(a, 'g');
convert assignment
to comparison
-if (a = 5) {
+if (a === 5) {
}
convert arrow function
to condition
-if (a => b) {}
+if (a >= b) {}
convert quotes
to backticks
-const a = 'hello \'world\'';
+const a = `hello 'world'`;
convert typeof
to is type
+const isFn = (a) => typeof a === 'function';
+
+if (isFn(fn))
-if (typeof fn === 'function')
fn();
convert bitwise
to logical
-a | !b
+a || !b
convert equal
to strict equal
-if (a == b) {
+if (a === b) {
}
remove useless escape
-const t = 'hello \"world\"';
-const s1 = `hello \"world\"`;
-const s = `hello \'world\'`;
+const t = 'hello "world"';
+const s1 = `hello "world"`;
+const s = `hello 'world'`;
remove useless Array.from()
-for (const x of Array.from(y)) {}
+for (const x of y) {}
remove useless spread
-for (const x of [...y]) {}
+for (const x of y) {}
remove debugger
statement
- debugger;
remove iife
-(function() {
- console.log('hello world');
-}());
+console.log('hello world');
remove boolean
from assertions
-if (a === true)
+if (a)
alert();
remove boolean
from logical expressions
-const t = true && false;
+const t = false;
remove nested blocks
for (const x of Object.keys(a)) {
- {
- console.log(x);
- }
+ console.log(x);
}
remove unreachable code
function hi() {
return 5;
- console.log('hello');
}
split variable declarations
-let a, b;
+let a;
+let b;
split nested destructuring
-const {a: {b}} = c;
+const {a} = c;
+const {b} = a;
simplify assignment
-const {a} = {a: 5};
-const [b] = [5];
+const a = 5;
+const b = 5;
simplify boolean return
function isA(a, b) {
- if (a.length === b.length)
- return true;
-
- return false;
+ return a.length === b.length;
}
simplify logical expressions
-!(options && !options.bidirectional);
+!options || options.bidirectional;
simplify ternary
-module.exports = fs.copyFileSync ? fs.copyFileSync : copyFileSync;
+module.exports = fs.copyFileSync || copyFileSync;
remove console.log
calls
-console.log('hello');
remove empty block statements
-if (x > 0) {
-}
remove empty patterns
-const {} = process;
remove constant conditions
function hi(a) {
- if (2 < 3) {
- console.log('hello');
- console.log('world');
- }
+ console.log('hello');
+ console.log('world');
};
function world(a) {
- if (false) {
- console.log('hello');
- console.log('world');
- }
};
convert replace
to replaceAll
(stage-4)
-'hello'.replace(/hello/g, 'world');
+'hello'.replaceAll('hello', 'world');
apply consistent-blocks
-if (a)
+if (a) {
b();
+} else {
-else {
c();
d();
}
apply destructuring
-const hello = world.hello;
-const a = b[0];
+const {hello} = world;
+const [a] = b;
apply dot notation
-a['hello']['world'] = 5;
+a.hello.world = 5;
apply .startsWith()
const {a = ''} = b;
-!a.indexOf('>');
+a.startsWith('>');
apply overrides
-export const readRules = (dirOpt, rulesDir, {cwd, readdirSync}) => {}
+export const readRules = (dirOpt, rulesDir, overrides) => {
const {cwd, readdirSync} = overrides;
+}
sort imports by specifiers
+import a1 from 'a1';
import {
a,
b,
c,
d,
} from 'd';
-import a1 from 'a1';
apply template literals
-const line = 'hello' + world;
+const line = `hello${world}`
apply flatMap()
-array.map(getId).flat();
+array.flatMap(getId);
apply if condition
-if (2 > 3);
+if (2 > 3)
alert();
apply isArray()
-x instanceof Array;
+Array.isArray(x);
apply Array.at()
-const latest = (a) => a[a.length - 1];
+const latest = (a) => a.at(-1);
apply optional chaining (proposal-optional-chaining)
-const result = hello && hello.world;
+const result = hello?.world;
apply nullish coalescing (proposal-nullish-coalescing, not bundled)
-result = typeof result === 'undefined' ? 'hello': result;
result = result ?? 'hello';
convert throw
statement into expression (proposal-throw-expressions, not bundled)
-const fn = (a) => {throw Error(a);}
+const fn = (a) => throw Error(a);
merge destructuring properties
-const {one} = require('numbers'):
-const {two} = require('numbers');
+ const {
+ one,
+ two
+} = require('numbers');
merge duplicate imports
-import {m as b} from 'y';
-import {z} from 'y';
-import x from 'y';
+import x, {m as b, z} from 'y';
merge duplicate functions
const isFn = (a) => typeof a === 'function';
-const isFn1 = (a) => typeof a === 'function';
isFn(1);
-isFn1(2);
+isFn(2);
merge if
statements
-if (a > b)
- if (b < c)
- console.log('hi');
+if (a > b && b < c)
+ console.log('hi');
convert anonymous
to arrow function
-module.exports = function(a, b) {
+module.exports = (a, b) => {
}
convert for
to for...of
-for (let i = 0; i < items.length; i++) {
+for (const item of items) {
- const item = items[i];
log(item);
}
convert forEach
to for...of
-Object.keys(json).forEach((name) => {
+for (const name of Object.keys(json)) {
manage(name, json[name]);
-});
+}
convert for...in
to for...of
-for (const name in object) {
- if (object.hasOwnProperty(name)) {
+for (const name of Object.keys(object)) {
console.log(a);
- }
}
convert map
to for...of
-names.map((name) => {
+for (const name of names) {
alert(`hello ${name}`);
+}
-});
convert reduce
to for...of
-const result = list.reduce((a, b) => a + b, 1);
+let sum = 1;
+for (const a of list) {
+ sum += a;
+}
convert array copy
to slice
-const places = [
- ...items,
-];
+const places = items.slice();
extract sequence expressions
-module.exports.x = 1,
-module.exports.y = 2;
+module.exports.x = 1;
+module.exports.y = 2;
extract object properties into variable
-const {replace} = putout.operator;
-const {isIdentifier} = putout.types;
+const {operator, types} = putout;
+const {replace} = operator;
+const {isIdentifier} = types;
convert apply
to spread
-console.log.apply(console, arguments);
+console.log(...arguments);
convert concat
to flat
-[].concat(...array);
+array.flat();
convert arguments
to rest
-function hello() {
- console.log(arguments);
+function hello(...args) {
+ console.log(args);
}
convert Object.assign()
to merge spread
function merge(a) {
- return Object.assign({}, a, {
- hello: 'world'
- });
+ return {
+ ...a,
+ hello: 'world'
+ };
};
convert comparison
to boolean
- const a = b === b;
+ const a = true;
apply comparison order
-5 === a;
+a === 5;
convert const
to let
- const a = 5;
+ let a = 5;
a = 3;
convert label
to object
-const a = () => {
- hello: 'world'
-}
+const a = () => ({
+ hello: 'world'
+})
remove unused labels
-hello:
while (true) {
break;
}
remove useless await
- await await Promise.resolve('hello');
+ await Promise.resolve('hello');
remove useless async
-const show = async () => {
+const show = () => {
console.log('hello');
};
add missing await
-runCli();
+await runCli();
async function runCli() {
}
add missing async
-function hello() {
+async function hello() {
await world();
}
add await
to return promise()
statements (because it's faster, produces call stack and more readable)
async run () {
- return promise();
+ return await promise();
}
apply top-level-await (proposal-top-level-await, enabled for ESM)
import fs from 'fs';
-(async () => {
- const data = await fs.promises.readFile('hello.txt');
-})();
+const data = await fs.promises.readFile('hello.txt');
remove useless Promise.resolve()
async () => {
- return Promise.resolve('x');
+ return 'x';
}
convert Promise.reject()
to throw
async () => {
- return Promise.reject('x');
+ throw 'x';
}
apply await import()
-const {readFile} = import('fs/promises');
+const {readFile} = await import('fs/promises');
apply numeric separators(proposal-numeric-separator)
-const a = 100000000;
+const a = 100_000_000;
convert Math.sqrt()
to Math.hypot()
-const a = Math.sqrt(b ** 2 + c ** 2);
+const a = Math.hypot(a, b);
convert Math.imul()
to multiplication
- const a = Math.imul(b, c);
+ const a = b * c;
convert Math.pow
to exponentiation operator
-Math.pow(2, 4);
+2 ** 4;
remove strict mode
directive from esm
-'use strict';
-
import * from fs;
Add strict mode
directive in commonjs
if absent
+'use strict';
+
const fs = require('fs');
Add strict mode
directive in commonjs
if absent
+'use strict';
+
const fs = require('fs');
remove strict mode
directive from esm
-'use strict';
-
import * from fs;
Add strict mode
directive in commonjs
if absent
+'use strict';
+
const fs = require('fs');
remove strict mode
directive from esm
-'use strict';
-
import * from fs;
Add strict mode
directive in commonjs
if absent
+'use strict';
+
const fs = require('fs');
remove strict mode
directive from esm
-'use strict';
-
import * from fs;
Add strict mode
directive in commonjs
if absent
+'use strict';
+
const fs = require('fs');
convert esm
to commonjs
(disabled)
-import hello from 'world';
+const hello = require('world');
convert commonjs
to esm
(disabled)
-const hello = require('world');
+import hello from 'world';
convert fs.promises
to fs/promises
for node.js
-const {readFile} = require('fs').promises;
+const {readFile} = require('fs/promises');
convert top-level return
into process.exit()
(because EcmaScript Modules doesn't support top level return)
- return;
+ process.exit();
remove process.exit
call
-process.exit();
replace test.only
with test
calls
-test.only('some test here', (t) => {
+test('some test here', (t) => {
t.end();
});
replace test.skip
with test
calls
-test.skip('some test here', (t) => {
+test('some test here', (t) => {
t.end();
});
remove duplicates from union
-type x = boolean[] | A | string | A | string[] | boolean[];
+type x = boolean[] | A | string | string[];
convert generic
to shorthand
(why)
interface A {
- x: Array<X>;
+ x: X[];
}
remove useless types
from constants
-const x: any = 5;
+const x = 5;
remove useless mapped types
-type SuperType = {
- [Key in keyof Type]: Type[Key]
-}
+type SuperType = Type;
remove useless mapping modifiers
type SuperType = {
- +readonly[Key in keyof Type]+?: Type[Key];
+ readonly[Key in keyof Type]?: Type[Key];
}
remove useless types
type oldType = number;
-type newType = oldType;
-const x: newType = 5;
+const x: oldType = 5;
remove duplicate interface
keys
interface Hello {
- 'hello': any;
'hello': string;
}
remove unused types
type n = number;
-type s = string;
const x: n = 5;
apply as
type assertion (according to best practices)
-const boundaryElement = <HTMLElement>e.target;
+const boundaryElement1 = e.target as HTMLElement;
apply utility types
-type SuperType = {
- [Key in keyof Type]?: Type[Key];
-}
+type SuperType = Partial<Type>;
The 🐊Putout repo is comprised of many npm packages. It is a Lerna monorepo similar to Babel. It has a lot of plugins divided by groups:
Package | Version |
---|---|
@putout/plugin-sort-imports-by-specifiers |
Package | Version |
---|---|
@putout/plugin-split-assignment-expressions |
|
@putout/plugin-split-variable-declarations |
|
@putout/plugin-split-nested-destructuring |
Package | Version |
---|---|
@putout/plugin-merge-destructuring-properties |
|
@putout/plugin-merge-duplicate-imports |
|
@putout/plugin-merge-duplicate-functions |
Package | Version |
---|---|
@putout/plugin-simplify-assignment |
|
@putout/plugin-simplify-ternary |
|
@putout/plugin-simplify-boolean-return |
Package | Version |
---|---|
@putout/plugin-declare |
|
@putout/plugin-declare-imports-first |
|
@putout/plugin-declare-before-reference |
Package | Version |
---|---|
@putout/plugin-extract-sequence-expressions |
|
@putout/plugin-extract-object-properties |
Package | Version |
---|---|
@putout/plugin-reuse-duplicate-init |
Package | Version |
---|---|
@putout/plugin-group-imports-by-source |
Next packages not bundled with 🐊Putout but can be installed separately.
Package | Version |
---|---|
@putout/plugin-apply-entries |
|
@putout/plugin-eslint-plugin |
|
@putout/plugin-react |
|