Putout

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
β JSX;β TypeScript;β Flow;β Yaml;β Markdown;β JSON;β Ignore;
are also supported. Here is how it looks like:
Table of contents
π€·ββοΈ In doubt about usingπ Putout?π Whom should I thank for this project exist?π€·ββοΈ Why does this project exist?π Installationπ Usageπ What is Ruler?βοΈ How Ruler can help me?π ConvertCommonJStoESMπ Architectureπ² The Tree of Syntaxπ΄ Laws of the Jungleπ APIπ¨ Built-in transformationsπ Pluginsπ¦ Formattersπ¦ Configuration𧬠Plugins APIπΏ Using Babel Plugins with Putoutπ΄ Codemodsπ¦ Integration with ESLintβοΈ Integration with Babelπ Integration with Yarn PnPβ Using Putout as Loaderπͺ Exit Codesπ¦ Real-world usesπ» Versioning policyπ I want contributeπ License
π€·ββοΈ In doubt about using π Putout?
Check out couple variants of plugins that does the same: linting debugger statement:
β ESLint no-debugger: 43 lines;β SWCLint no-debugger: 49 lines;β Rome no-debugger: 28 lines;β RSLint no-debugger: 48 linesπ Putout remove-debugger: 7 lines:
Choose wisely, competitors cannot even fixβ¦
'use strict';
module.exports.report = () => `Unexpected 'debugger' statement`;
module.exports.replace = () => ({
debugger: '',
});π Whom should I thank for this project exist?
If I have seen further, it is by standing upon the shoulders of giants.
(c) Isaak 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.
π€·ββοΈ Why does this project exist?
- 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.
π Installation
To install
npm i putout -D
Make sure that you are running a relatively recent (β₯16) version of Node.
π Usage
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 --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' localy and 'dump' on CI
-s, --staged add staged files when in git repository
--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
To find possible transform places in a folder named lib, run:
npx putout lib
To find possible transform places in multiple folders, such as folders named lib and test, run:
npx putout lib test
To apply the transforms, use --fix:
npx putout lib test --fix
Environment variables
PUTOUT_CONFIG_FILE- path to configuration file;PUTOUT_FILES- files that should be processed splitted by ",";
Example:
PUTOUT_FILES=lib,test putout --fix
π What is Ruler?
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;
--fix, because unclear things makes
π '--fix' cannot be used with ruler toggler ('--enable', '--disable')
βοΈ How Ruler can help me?
You may want to convert your CommonJS to ESM since node v12 supports it without a flag.
π Convert CommonJS to ESM
βοΈ I have a package.json
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.
βοΈ I have .cjs or .mjs files
They will be converted automatically to CommonJS and ESM accordingly.
βοΈ I want to run only one rule
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.
So, if you want to convert it to ESM keeping everything else untouched use Ruler: it can easily disable all rules
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 Arrow functions should be used convert-to-arrow-function
1:0 error 'use strict' directive should be on top of commonjs file strict-mode/add
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 Useless async should be avoided promises/remove-useless-asyncIt will create config file .putout.json:
{
"rules": {
"remove-unused-variables": "off",
"convert-to-arrow-function": "off",
"strict-mode/add": "off",
"promises/convert-reject-to-throw": "off",
"promises/add-missing-await": "off",
"promises/remove-useless-async": "off"
}
}
Then running putout index.js --enable convert-commonjs-to-esm will update config with:
{
"rules": {
"remove-unused-variables": "off",
"convert-to-arrow-function": "off",
"strict-mode/add": "off",
"promises/convert-reject-to-throw": "off",
"promises/add-missing-await": "off",
- "promises/remove-useless-async": "off"
+ "promises/remove-useless-async": "off",
+ "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 convert-commonjs-to-esm && putout src --fixThis command will disable all rules that
Happy coding
π Architecture
Putout consists of a couple simple parts, here is a workflow representation:
And here is a CLI scheme:
π² The Tree of Syntax
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
You can read about it in Babel Plugin Handbook. To understand how things works 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, se it for its leafs, 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 peace 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"
estree-to-babel especially when
π΄ Laws of the Jungle
π engineschilling withengines, and chasingplugins,processors,operators;π¦ pluginschilling withpluginsandoperatorsviarequire('putout').operator;π¦ processorschilling withprocessors;π operatorschilling withoperators;
π Engines
Engines is the heart of
| Package | Version |
|---|---|
@putout/engine-parser |
|
@putout/engine-loader |
|
@putout/engine-runner |
|
@putout/engine-processor |
π§ͺ Processors
With help of processors
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 |
You can disable any of them with:
{
"processors": [
["markdown", "off"]
]
}And not bundled processors:
| Package | Version |
|---|---|
@putout/processor-typescript |
|
@putout/processor-html |
To enable it use:
{
"processors": [
["typescript", "on"]
]
}Processors can be tested using @putout/test/processors.
π API
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.
putout(source, options)
First things first, require putout:
const putout = require('putout');Let's consider the next source with two variables and one call expression:
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);
`;Plugins
Putout supports dynamic loading of plugins from node_modules. Let's consider the 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.
No fix
From the beginning, 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 reduntand 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 map
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: '\n' +
` const hello = 'world';\n` +
` const hi = 'there';\n` +
' \n' +
' console.log(hello);\n' +
' //# 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},
}],
});π¨ Built-in transformations
JavaScript
remove unused variables
function show() {
- const message = 'hello';
console.log('hello world');
}remove duplicates from logical expressions
-a && b && a
+a && bremove 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 new(why)
-new Error('something when wrong');
+Error('something when wrong');remove useless constructor(why)
-const s = String('hello');
+const s = 'hello';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 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 undefined 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 duplicateinit
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 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 || !bconvert equal to strict equal
-if (a == b) {
+if (a === b) {
}convert indexOf to includes
-if (~array.indexOf(element)) {
+if (array.includes(element)) {
}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 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 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 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 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 replace to replaceAll (stage-4)
-'hello'.replace(/hello/g, 'world');
+'hello'.replaceAll('hello', 'world');apply destructuring
-const hello = world.hello;
-const a = b[0];
+const {hello} = world;
+const [a] = b;apply await import
-const {readFile} = import('fs/promises');
+const {readFile} = await import('fs/promises');apply if condition
-if (2 > 3);
+if (2 > 3)
alert();apply isArray
-x instanceof Array;
+Array.isArray(x);apply Array.at(not bundled)
-const latest = (a) => a[a.length - 1];
+const latest = (a) => a.at(-1);apply numeric separators(proposal-numeric-separator)
-const a = 100000000;
+const a = 100_000_000;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 if statements
-if (a > b)
- if (b < c)
- console.log('hi');
+if (a > b && b < c)
+ console.log('hi');convert Math.pow to exponentiation operator
-Math.pow(2, 4);
+2 ** 4;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 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;Promises
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 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';
}Node.js
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();Tape
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();
});TypeScript
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>;π Plugins
The
Appliers
Splitters
| Package | Version |
|---|---|
@putout/plugin-split-variable-declarations |
|
@putout/plugin-split-nested-destructuring |
Mergers
| Package | Version |
|---|---|
@putout/plugin-merge-destructuring-properties |
|
@putout/plugin-merge-duplicate-imports |
|
@putout/plugin-merge-if-statements |
Converters
Removers
Simplifiers
| Package | Version |
|---|---|
@putout/plugin-simplify-assignment |
|
@putout/plugin-simplify-logical-expressions |
|
@putout/plugin-simplify-ternary |
Not bundled
Next packages not bundled with
Groups
π¦ Formatters
--format or -f flag on the command line. For example, --format codeframe uses the codeframe formatter.
The built-in formatter options are:
dumpstreamjsonjson-linescodeframeprogressprogress-barframe(codeframe+progress)memory
Custom Formatter
A formatter function executes on every processed file, it should return an output string.
export default function formatter({name, source, places, index, count, filesCount, errorsCount}) {
return '';
}Here is list of options:
name- name of processed filesource- source code of processed fileindex- current indexcount- processing files countfilesCount- count of files with errorserrorsCountcount of errors
You can avoid any of this and use only what you nead. To make your formatter usable with putout, add the prefix putout-formatter- to your npm package,
and add the tags putout, formatter, putout-formatter.
ESLint Formatters
ESLint formatters can be used as well with help of @putout/formatter-eslint this way:
Install:
npm i putout @putout/formatter-eslint eslint-formatter-pretty -D
Run:
ESLINT_FORMATTER=pretty putout -f eslint libπ¦ Configuration
To configure putout to your package.json file or create .putout.json file and override any of default options.
Rules
All rules located in plugins section and built-in rules are enabled by default.
You can disable rules using "off", or enable them (in match section) using "on".
{
"rules": {
"remove-unused-variables": "off"
}
}Or pass options using rules section:
{
"rules": {
"remove-unused-variables": ["on", {
"exclude": "const global = __"
}]
}
}Exclude
With help of exclude you can set type or code pattern to exclude for current rule.
Pass an array when you have a couple templates to exclude:
{
"rules": {
"remove-unused-variables": ["on", {
"exclude": [
"VariableDeclaration"
]
}]
}
}exclude is cross-plugin function supported by core, when develop your plugin, please use other name
to keep users ability to customize all plugins in a way they need to.
Match
When you need to match paths to rules you can use match section for this purpose in .putout.json:
{
"match": {
"server": {
"nodejs/remove-process-exit": "on"
}
}
}Ignore
When you need to ignore some routes no matter what, you can use ignore section in .putout.json:
{
"ignore": [
"test/fixture"
]
}Plugins
There are two types of plugin names supported by
@putout/plugin-for official pluginsputout-plugin-for user plugins
Example
If you need to remove-something create putout plugin with a name putout-plugin-remove-something and add it to .putout.json:
{
"plugins": [
"remove-something"
]
}Add putout as a peerDependency to your packages.json (>= of version you developing for).
Always add keywords putout, putout-plugin when publish putout plugin to npm so others can easily find it.
𧬠Plugins API
Throughout your life advance daily, becoming more skillful than yesterday more skillful than today. This is never-ending
(c) Yamamoto Tsunetomo "Hagakure"
AST and this is for a reason.
And the reason is JavaScript-compatible language AST-template.
Let's dive into plugin types that you can use for you next code transformation.
Replacer
The simplest
report- report error message toputoutcli;replace- replacekeytemplate intovaluetemplate;
module.exports.report = () => 'use optional chaining';
module.exports.replace = () => ({
'__a && __a.__b': '__a?.__b',
});This plugin will find and suggest to replace all occurrences of code: object && object.property into object?.property.
Includer
More powerful plugin type, when you need more control over traversing. It should contain next 2 functions:
report- report error message toputoutcli;fix- fixes paths usingplacesarray received usingfindfunction;
and one or more of this:
filter- filter path, should returntrue, orfalse(don't use withtraverse);include- returns array of templates, or node names to include;exclude- returns array of templates, or node names to exclude;
module.exports.report = () => 'use optional chaining';
module.exports.include = () => [
'debugger',
];
module.exports.fix = (path) => {
path.remove(path);
};More information about supported plugin types you can find at @putout/engine-runner. About the process of plugins loading you can find at @putout/engine-loader.
When you need, you can use @babel/types, template and generate. All of this can be gotten from putout:
const {
types,
template,
generate,
} = require('putout');Operator
When you need to use replaceWith, replaceWithMultiple, or insertAfter, please use operator instead of path-methods.
const {template, operator} = require('putout');
const {replaceWith} = operator;
const ast = template.ast(`
const str = 'hello';
`);
module.exports.fix = (path) => {
// wrong
path.replaceWith(ast);
// correct
replaceWith(path, ast);
};This should be done to preserve loc and comments information, which is different in babel and recast. putout will handle this case for you :),
just use the methods of operator.
Putout Plugin
When you work on a plugin or codemod please add rule putout into .putout.json:
{
"rules": {
"putout": "on"
}
}@putout/plugin-putout will handle plugin-specific cases for you :).
Example
Let's consider simplest possible plugin for removing debugger statements @putout/plugin-remove-debugger:
// this is a message to show in putout cli
module.exports.report = () => 'Unexpected "debugger" statement';
// let's find all "debugger" statements and replace them with ""
module.exports.replace = () => ({
debugger: '',
});Visitor used in traverse function can be code template as well. So when you need to find module.exports = <something>, you
can use:
module.exports.traverse = ({push}) => ({
'module.exports = __'(path) {
push(path);
},
});Where __ is a placeholder for anything.
You can also use include and/or exclude insead of traverse and filter (more sophisticated example):
// should be always used include/or exclude, when traverse not used
module.exports.include = () => [
'debugger',
];
// optional
module.exports.exclude = () => [
'console.log',
];
// optional
module.exports.filter = (path) => {
// do some checks
return true;
};Template
There is predefined placeholders:
__- any code;"__"- any string literal;__- any template string literal;
πΌ Testing
That was the simplest module to remove debugger statements in your code. Let's look how to test it using @putout/test:
const removeDebugger = require('..');
const test = require('@putout/test')(__dirname, {
'remove-debugger': removeDebugger,
});
// this is how we test that messages is correct
test('remove debugger: report', (t) => {
t.reportCode('debugger', 'Unexpected "debugger" statement');
t.end();
});
// stetement should be removed so result is empty
test('remove debugger: transformCode', (t) => {
t.transformCode('debugger', '');
t.end();
});As you see test runner it is little bit extended
π€·ββοΈ What if I don't want to publish a plugin?
If you don't want to publish a plugin you developed, you can pass it to object described earler. Here is how it can look like:
putout('const a = 5', {
plugins: [
['remove-unused-variables', require('@putout/plugin-remove-unused-variables')],
],
});Where plugins is an array that contains [name, implementation] tuples.
πΏ Using Babel Plugins with Putout
You can add Babel to the plugins section of .putout.json with babel/ prefix.
You can disable a rule, or use a match in a similar way.
babel-plugin- or @babel/plugin: putout will set it up for you :)
Example
Let's add babel-plugin-transform-inline-consecutive-adds to .putout.json:
{
"plugins": [
"babel/transform-inline-consecutive-adds"
]
}Then create a file and process it with the help of babel plugin.
coderaiser@cloudcmd:~$ cat > a.js
const t = [];
t.push(1);
t.push(2);
coderaiser@cloudcmd:~$ putout a.js -f codeframe
/home/coderaiser/a.js:4:0
2 | t.push(1);
3 | t.push(2);
> 4 |
| ^ transform inline consecutive adds
β 1 errors in 1 files
fixable with the `--fix` option
coderaiser@cloudcmd:~$ putout --fix a.js
coderaiser@cloudcmd:~$ cat a.js
const t = [1, 2];Using babel plugins you can not only change file content, but also see what exactly will be changed. You can use your already written babel plugins or reuse work in progress plugins made for babel,
plugins gave more accurate information about changing places, and works faster (no need to find information about changes in transformed file).
Babel plugins list
Here you can find babel plugins which feets the most main purpose of putout and advised to use:
transform-inline-consecutive-adds
-const foo = {};
-foo.a = 42;
-foo.b = ["hi"];
-foo.c = bar();
-foo.d = "str";
+const foo = {
+ a: 42,
+ b: ["hi"],
+ c: bar(),
+ d: "str"
+};
-const bar = [];
-bar.push(1);
-bar.push(2);
+const bar = [1, 2];codemod-object-assign-to-object-spread
function merge(a) {
- return Object.assign({}, a, {
- hello: 'world'
- });
+ return {
+ ...a,
+ hello: 'world'
+ };
};codemod-optional-catch-binding
try {
throw 0;
-} catch (err) {
+} catch {
console.log("it failed, but this code executes");
}Please send pull requests with babel plugins which can be used as codemods, or simplify, fix, makes code more readable.
π΄ Codemods
putout supports codemodes in the similar to plugins way, just create a directory ~/.putout and put your plugins there. Here is example: convert-tape-to-supertape and this is example of work.
rulesdir
When you have plugins related to your project and you don't want to publish them (because it cannot be reused right now). Use rulesdir:
putout --rulesdir ./rulesThis way you can keep rules specific for your project and run them on each lint.
not-rule- and node_modules).
π¦ Integration with ESLint
Find and fix problems in your JavaScript code
(c) eslint.org
If you see that
Install eslint-plugin-putout with:
npm i eslint eslint-plugin-putout -D
Then create .eslintrc.json:
{
"extends": [
"plugin:putout/recommended"
],
"plugins": [
"putout"
]
}And use with
putout --fix libTo set custom config file for ESLint use ESLINT_CONFIG_FILE env variable:
ESLINT_CONFIG_FILE=test.eslintrc.json putout --fix libYou can even lint without eslint-plugin-putout:
eslint --fix lib
Applies
ESLint API
ESLint begins his work as a formatter when
import {eslint} from 'putout/eslint';To use it simply write:
const [source, places] = await eslint({
name: 'hello.js',
code: `const t = 'hi'\n`,
fix: false,
});Isn't it looks similar to
βοΈ π Putout returns object withcodeandplacesproperties.βοΈ ESLint has anameproperty that is used to calculate configuration file.*
And you can even override any of ESLint config property:
const [source, places] = await eslint({
name: 'hello.js',
code: `const t = 'hi'\n`,
fix: false,
config: {
extends: [
'plugin:putout/recommended',
],
},
});If you want to apply putout/putout ESLint rule, enable
const [source, places] = await eslint({
name: 'hello.js',
code: `const t = 'hi'\n`,
fix: true,
putout: true,
config: {
extends: [
'plugin:putout/recommended',
],
},
});It is disabled by default, because ESLint always runs after
This API doesn't suppose to came in eslint-plugin-putout to test plugins, so why not :)? Anyways it's signature didn't changed from the beginning.
βοΈ Integration with Babel
.babelrc.json file with configuration you need.
{
"plugins": [
["putout", {
"rules": {
"remove-unused-variables": "off"
}
}]
]
}π Integration with Yarn PnP
Since
plugins;processors;formatters;
It was a nice adventure to have support of such a nice feature of Yarn as Plug'n'Play.
For this purpose new env variable was added to help to load external extensions: PUTOUT_YARN_PNP.
So if you package eslint-config-hardcore you should run ESLint this way:
PUTOUT_YARN_PNP=eslint-config-hardcore eslint .β Using Putout as Loader
node --loader putout your-file.jsYou can also transform input files using Babel. For example if you need to transform jsx with @babel/plugin-transform-react-jsx you can use .putout.json:
{
"plugins": [
"babel/transform-react-jsx"
]
}πͺ Exit Codes
| Code | Name | Description | Example |
|---|---|---|---|
| 0 | OK |
no errors found | <empty> |
| 1 | PLACE |
found places with errors | <violations of rules> |
| 2 | STAGE |
nothing in stage | no output |
| 3 | NO_FILES |
no files found | π No files matching the pattern "hello" were found |
| 4 | NO_PROCESSORS |
no processor found | π No processors found for hello.abc |
| 5 | NO_FORMATTER |
no formatter found | π Cannot find module 'putout-formatter-hello' |
| 6 | WAS_STOP |
was stop | <empty or violations of rules> |
| 7 | INVALID_OPTION |
invalid option | π Invalid option '--hello'. Perhaps you meant '--help' |
| 8 | CANNOT_LOAD_PROCESSOR |
processor has errors | <unhandled exception> |
| 9 | UNHANDLED |
unhandled exception | <unhandled exception> |
| 10 | RULLER_WITH_FIX |
ruller used with --fix |
π '--fix' cannot be used with ruler toggler ('--enable', '--disable') |
| 11 | RULLER_NO_FILES |
ruller used without files | π 'path' is missing for ruler toggler ('--enable-all', '--disable-all') |
| 12 | INVALID_CONFIG |
config has invalid properties | π .putout.json: exclude: must NOT have additional properties |
| 13 | CANNOT_LOAD_FORMATTER |
formatter has errors | π @putout/formatter-dump: Syntax error |
Example of providing invalid option:
coderaiser@localcmd:~/putout$ putout --helo
π Invalid option `--helo`. Perhaps you meant `--help`
coderaiser@localcmd:~/putout$ echo $?
7API
Exit codes enum can be imported as:
import {OK} from 'putout/exit-codes';π¦ Real-world uses
Cloud Commander: orthodox file manager for the web.Eslint Config Hardcore: The most strict (but practical) ESLint config out there.Mock Import: Mocking of Node.js EcmaScript Modules.Madrun: CLI tool to run multiple npm-scripts in a madly comfortable way.Xterm.js: A terminal for the web.Stylelint: A mighty, modern linter that helps you avoid errors and enforce conventions in your styles.ESTrace: Trace functions in EcmaScript Modules.π© ESCover: Coverage for EcmaScript Modules.β¨οΈ Speca: Write tape tests for you.
Do you use putout in your application as well? Please open a Pull Request to include it here. We would love to have it in our list.
π» Versioning Policy
Putout follows semantic versioning (semver) principles, with version numbers being on the format major.minor.patch:
- patch:
bug fix,dependency update(17.0.0 -> 17.0.1). - minor:
new features,new rulesorfixes(17.0.0 -> 17.1.0). - major
breaking changes,removing rules(17.0.0 -> 18.0.0).
π I want contribute
You can contribute by proposing a feature, fixing a bug or a typo in the documentation.
If you wish to play with code
π License
MIT


