Skip to content

Coudnet/putout

Β 
Β 

Repository files navigation

Putout NPM version Build Status Coverage Status

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

🐊Putout is a pluggable and configurable code transformer with built-in eslint, babel plugins and jscodeshift codemods support for js, jsx typescript and flow files. It has a lot of transforms that will keep your codebase in a clean state, transforming any code smell to readable code according to best practices.

putout

Table of contents

πŸ€·β€β™‚ 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?

Putout on the other hand can make more drastic code transformations that directly affect your codebase making it a better place to code πŸ’».

Installation

To install 🐊Putout as a development dependency, run:

npm i putout -D

Make sure that you are running a relatively recent (β‰₯14.8) 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 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' 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 supports the following environment variables:

  • PUTOUT_FILES - files that should be processed by putout, divided by ",";

Example:

PUTOUT_FILES=lib,test putout --fix

πŸ€·β€β™‚οΈ What is Ruler?

When you need to change .putout.json you can do it not only editing the file, but also with help of Ruler.

Ruler can enable one rule with putout --enable convert-commonjs-to-esm or disable all rules Putout able to find using putout --disable-all. Remember, it 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`)

πŸ€·β€β™‚οΈ How ruler can be helpful to me?

You may want to convert your CommonJS module into Ecma Script Modules since node v12 supports it without a flag.

Converting 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 want to convert it to ESM, and everything else keep untouched. You can do this with a Ruler. So you disable all rules that Putout can find right now.

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-async

And 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 --fix

This command will disable all rules that Putout can find right now and enables a single rule. All Putout rules made for good and highly suggested to be used, they all enabled in all my repositories. You can always disable what you don't need, so give it a try you wan't regret 🐊. Happy coding 🎈!

Architecture

Putout consists of a couple simple parts, here is a workflow representation:

putout

And here is a CLI sheme:

putout

🌲 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

Ashvattha

On the bottom level of 🐊Putout layes down Syntax Tree. This is data structure that makes possible to do crazy transformations in a simplest possible way. It used mostly in compilers development.

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"

🐊Putout based on Babel AST. It has a couple differences from ESTree which are perfectly handled by estree-to-babel especially when 🐊Putout running as a plugin for ESLint.

🌴 Laws of the Jungle

  • πŸ… engines chilling with engines, and chasing plugins, processors, operators;
  • 🦌 plugins chilling with plugins and operators via require('putout').operator;
  • πŸ¦’ processors chilling with processors;
  • πŸƒ operators chilling with operators;

Engines

Engines is the heart of putout: loader, runner and parser run for every processed file. Processor runs all the processors.

Package Version
@putout/engine-parser npm
@putout/engine-loader npm
@putout/engine-runner npm
@putout/engine-processor npm

Processors

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 npm
@putout/processor-json npm
@putout/processor-markdown npm
@putout/processor-ignore npm
@putout/processor-yaml npm
@putout/processor-css npm

You can disable any of them with:

{
    "processors": [
        ["markdown", "off"]
    ]
}

And not bundled processors:

Package Version Dependencies
@putout/processor-typescript npm Dependency Status
@putout/processor-html npm Dependency Status

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 start, putout was 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 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},
    }],
});

Built-in transformations

remove unused variables
  function show() {
-     const message = 'hello';
      console.log('hello world');
  }
remove duplicates from union (for typescript)
-type x = boolean[] | A | string | A | string[] | boolean[];
+type x = boolean[] | A | string | string[];
remove duplicates from logical expressions
-a && b && a
+a && b
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 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++);
-for (sign = decpt, i = 0; (sign /= 10) != 0; i++)
-    continue;
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';
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() {
}
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');
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 || !b
convert equal to strict equal
-if (a == b) {
+if (a === b) {
}
convert indexOf to includes
-if (~array.indexOf(element)) {
+if (array.includes(element)) {
}
convert generic to shorthand (for typescript) (why)
interface A {
-    x: Array<X>;
+    x: X[];
}
remove useless types from constants (for typescript)
-const x: any = 5;
+const x = 5;
remove useless mapped types(for typescript)
-type SuperType = {
-   [Key in keyof Type]: Type[Key]
-}
+type SuperType = Type;
remove useless mapping modifiers(for typescript)
type SuperType = {
-   +readonly[Key in keyof Type]+?: Type[Key];
+   readonly[Key in keyof Type]?: Type[Key];
}
convert fs.promises to fs/promises for node.js
-const {readFile} = require('fs').promises;
+const {readFile} = require('fs/promises');
remove useless types (for typescript)
type oldType = number;
-type newType = oldType;
-const x: newType = 5;
+const x: oldType = 5;
remove duplicate interface keys (for typescript)
interface Hello {
-   'hello': any;
    'hello': string;
}
remove unused types (for typescript)
type n = number;
-type s = string;
const x: n = 5;
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 useless Promise.resolve
async () => {
-    return Promise.resolve('x');
+    return 'x';
}
convert Promise.reject to throw
async () => {
-    return Promise.reject('x');
+    throw 'x';
}
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');
}
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 process.exit call
-process.exit();
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 (disabled, 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 if condition
-if (2 > 3);
+if (2 > 3)
    alert();
apply isArray
-x instanceof Array;
+Array.isArray(x);
apply Array.at(disabled)
-const latest = (a) => a[a.length - 1];
+const latest = (a) => a.at(-1);
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');
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 as type assertion (according to best practices, for typescript)
-const boundaryElement = <HTMLElement>e.target;
+const boundaryElement1 = e.target as HTMLElement;
apply nullish coalescing (proposal-nullish-coalescing, not bundled)
-result = typeof result  === 'undefined' ? 'hello': result;
result = result ?? 'hello';
apply utility types (for typescript)
-type SuperType = {
-    [Key in keyof Type]?: Type[Key];
-}
+type SuperType = Partial<Type>;
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;
convert top-level return into process.exit()(because EcmaScript Modules doesn't support top level return)
-   return;
+   process.exit();
add await to return promise() statements (because it's faster, produces call stack and more readable)
async run () {
-   return promise();
+   return await promise();
}

Plugins

The putout repo is comprised of many npm packages. It is a lerna monorepo similar to babel.

Appliers

Package Version
@putout/plugin-apply-is-array npm
@putout/plugin-apply-array-at npm
@putout/plugin-apply-numeric-separators npm
@putout/plugin-apply-destructuring npm
@putout/plugin-apply-optional-chaining npm
@putout/plugin-apply-as-type-assertions npm
@putout/plugin-apply-if-condition npm
@putout/plugin-apply-utility-types npm

Splitters

Package Version
@putout/plugin-split-variable-declarations npm
@putout/plugin-split-nested-destructuring npm

Mergers

Package Version
@putout/plugin-merge-destructuring-properties npm
@putout/plugin-merge-duplicate-imports npm
@putout/plugin-merge-if-statements npm

Converters

Package Version
@putout/plugin-convert-apply-to-spread npm
@putout/plugin-convert-quotes-to-backticks npm
@putout/plugin-convert-bitwise-to-logical npm
@putout/plugin-convert-concat-to-flat npm
@putout/plugin-convert-esm-to-commonjs npm
@putout/plugin-convert-commonjs-to-esm npm
@putout/plugin-convert-array-copy-to-slice npm
@putout/plugin-convert-template-to-string npm
@putout/plugin-convert-equal-to-strict-equal npm
@putout/plugin-convert-index-of-to-includes npm
@putout/plugin-convert-generic-to-shorthand npm
@putout/plugin-convert-math-pow npm
@putout/plugin-convert-to-arrow-function npm
@putout/plugin-convert-for-to-for-of npm
@putout/plugin-convert-for-each-to-for-of npm
@putout/plugin-convert-for-in-to-for-of npm
@putout/plugin-convert-map-to-for-of npm
@putout/plugin-convert-object-assign-to-merge-spread npm
@putout/plugin-convert-comparison-to-boolean npm
@putout/plugin-convert-top-level-return npm
@putout/plugin-convert-typeof-to-is-type npm
@putout/plugin-convert-mock-require-to-mock-import npm
@putout/plugin-convert-assignment-to-arrow-function npm
@putout/plugin-convert-assignment-to-comparison npm

Removers

Package Version
@putout/plugin-remove-unused-variables npm
@putout/plugin-remove-unused-for-of-variables npm
@putout/plugin-remove-unused-types npm
@putout/plugin-remove-unreferenced-variables npm
@putout/plugin-remove-duplicate-keys npm
@putout/plugin-remove-duplicate-interface-keys npm
@putout/plugin-remove-duplicate-case npm
@putout/plugin-remove-unused-expressions npm
@putout/plugin-remove-unused-private-fields npm
@putout/plugin-remove-useless-variables npm
@putout/plugin-remove-useless-map npm
@putout/plugin-remove-useless-new npm
@putout/plugin-remove-useless-constructor npm
@putout/plugin-remove-useless-mapped-types npm
@putout/plugin-remove-useless-mapping-modifiers npm
@putout/plugin-remove-useless-return npm
@putout/plugin-remove-useless-continue npm
@putout/plugin-remove-useless-operand npm
@putout/plugin-remove-useless-array-constructor npm
@putout/plugin-remove-useless-conditions npm
@putout/plugin-remove-useless-type-conversion npm
@putout/plugin-remove-useless-functions npm
@putout/plugin-remove-useless-typeof npm
@putout/plugin-remove-useless-types npm
@putout/plugin-remove-useless-types-from-constants npm
@putout/plugin-remove-useless-array-from npm
@putout/plugin-remove-useless-spread npm
@putout/plugin-remove-useless-arguments npm
@putout/plugin-remove-useless-escape npm
@putout/plugin-remove-useless-template-expressions npm
@putout/plugin-remove-useless-for-of npm
@putout/plugin-remove-process-exit npm
@putout/plugin-remove-debugger npm
@putout/plugin-remove-iife npm
@putout/plugin-remove-double-negations npm
@putout/plugin-remove-unreachable-code npm
@putout/plugin-remove-console npm
@putout/plugin-remove-empty npm
@putout/plugin-remove-empty-pattern npm
@putout/plugin-remove-constant-conditions npm
@putout/plugin-remove-boolean-from-assertions npm
@putout/plugin-remove-boolean-from-logical-expressions npm
@putout/plugin-remove-duplicates-from-union npm
@putout/plugin-remove-duplicates-from-logical-expressions npm
@putout/plugin-remove-nested-blocks npm

Simplifiers

Package Version
@putout/plugin-simplify-assignment npm
@putout/plugin-simplify-logical-expressions npm
@putout/plugin-simplify-ternary npm

Promises

Next packages bundled in @putout/plugin-promises.

Package Version
@putout/plugin-add-return-await npm
@putout/plugin-remove-useless-async npm
@putout/plugin-remove-useless-await npm

Not bundled

Next packages not bundled with putout but can be installed separately.

Package Version
@putout/plugin-apply-try-catch npm
@putout/plugin-apply-montag npm
@putout/plugin-apply-early-return npm
@putout/plugin-react-hooks npm
@putout/plugin-convert-is-nan-to-number-is-nan npm
@putout/plugin-convert-spread-to-array-from npm
@putout/plugin-apply-shorthand-properties npm
@putout/plugin-apply-top-level-await npm
@putout/plugin-apply-nullish-coalescing npm
@putout/plugin-cloudcmd npm
@putout/plugin-postcss npm
@putout/plugin-jest npm
@putout/plugin-convert-any-to-primitive npm
@putout/plugin-travis npm
@putout/plugin-convert-throw npm
@putout/plugin-remove-only npm
@putout/plugin-remove-skip npm

Other

Package Version
@putout/plugin-declare-undefined-variables npm
@putout/plugin-reuse-duplicate-init npm
@putout/plugin-madrun npm
@putout/plugin-strict-mode npm
@putout/plugin-extract-sequence-expressions npm
@putout/plugin-extract-object-properties npm
@putout/plugin-putout npm
@putout/plugin-putout-config npm
@putout/plugin-tape npm
@putout/plugin-webpack npm
@putout/plugin-eslint npm
@putout/plugin-package-json npm
@putout/plugin-promises npm
@putout/plugin-gitignore npm
@putout/plugin-npmignore npm
@putout/plugin-browserlist npm
@putout/plugin-github npm
@putout/plugin-regexp npm
@putout/plugin-nodejs npm

Formatters

putout uses formatters similar to eslint's formatters. You can specify a formatter using the --format or -f flag on the command line. For example, --format codeframe uses the codeframe formatter.

The built-in formatter options are:

  • dump
  • stream
  • json
  • json-lines
  • codeframe
  • progress
  • progress-bar
  • frame (codeframe + progress)
  • memory
Package Version
@putout/formatter-dump npm
@putout/formatter-stream npm
@putout/formatter-progress npm
@putout/formatter-progress-bar npm
@putout/formatter-json npm
@putout/formatter-json-lines npm
@putout/formatter-codeframe npm
@putout/formatter-frame npm
@putout/formatter-eslint npm
@putout/formatter-memory npm

Custom Formatter

A formatter function executes on every processed file, it should return an output string.

module.exports = ({name, source, places, index, count, filesCount, errorsCount}) => {
    return '';
};

Here is list of options:

  • name - name of processed file
  • source - source code of processed file
  • index - current index
  • count - processing files count
  • filesCount - count of files with errors
  • errorsCount count 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 add section 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": {
            "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 plugins supported by 🐊Putout, their names in npm start with a prefix:

  • @putout/plugin- for official plugins
  • putout-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"

Let's consider a couple of plugin types that can be used.

Replacer

The simplest 🐊Putout plugin type, consits of 2 functions:

  • report - report error message to putout cli;
  • replace - replace key template into value template;
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 to putout cli;
  • fix - fixes paths using places array received using find function;

and one or more of this:

  • filter - filter path, should return true, or false (don't use with traverse);
  • 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.

☝️Remember: template key should be valid JavaScript, or Type name like in previous example.

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 supertape. To see a more sophisticated example look at @putout/remove-console.

πŸ€·β€β™‚οΈ 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 Putout as an 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.

☝️Remember to omit 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 putout as a runner for 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, but remember that putout 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.

Using JSCodeshift codemods with Putout

jscodeshift codemods can be added to plugins section of .putout.json with prefix jscodeshift/. This way:

Example

{
    "plugins": [
        "jscodeshift/async-await-codemod"
    ]
}

JSCodeshift codemods list

Here you can find jscodeshift codemods which feets the most main purpose of putout and advised to use:

async-await-codemod
-function makeRequest() {
-  return getJSON().then(data => {
-    console.log(data);
-    return 'done';
-  });
+ async function makeRequest() {
+  const data = await getJSON();
+  console.log(data);
+  return 'done';
}

Please send pull requests with jscodeshift codemods which can be used to simplify, fix, or make 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.

Integration with ESLint

If you see that 🐊Putout brokes formatting of your code, use eslint plugin eslint-plugin-putout.

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 this way:

putout --fix lib

To set custom eslint config file use ESLINT_CONFIG_FILE env variable:

ESLINT_CONFIG_FILE=test.eslintrc.json putout --fix lib

You can even use only eslint, because putout bundled to eslint-plugin-putout with:

eslint --fix lib

Will uses putout transformations for you :).

Integration with Babel

Putout can be used as babel plugin. Just create .babelrc.json file with configuration you need.

{
    "plugins": [
        ["putout", {
            "rules": {
                "remove-unused-variables": "off"
            }
        }]
    ]
}

Using Putout as Loader

🐊Putout can be used as loader this way:

node --no-deprecation --loader putout your-file.js

You 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

🐊Putout can have one of next 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 can't load processor <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

Example of providing invalid option:

coderaiser@localcmd:~/putout$ putout --helo
🐊 Invalid option `--helo`. Perhaps you meant `--help`
coderaiser@localcmd:~/putout$ echo $?
7

API

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.

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 rules or fixes (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 πŸ”₯, you can πŸ’ͺ! 🐊 Putout rejoice and wag its tail when see new contributions πŸ‘Ύ.

License

MIT

About

🐊 Pluggable and configurable code transformer with built-in eslint, babel plugins and jscodeshift codemods support of js, jsx typescript, flow, markdown, yaml and json

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • JavaScript 98.9%
  • Other 1.1%