Git Product home page Git Product logo

coderaiser / putout Goto Github PK

View Code? Open in Web Editor NEW
683.0 11.0 39.0 15.92 MB

🐊 Pluggable and configurable JavaScript Linter, code transformer and formatter, drop-in ESLint superpower replacement πŸ’ͺ with built-in support for js, jsx typescript, flow, markdown, yaml and json. Write declarative codemods in a simplest possible way 😏

Home Page: https://putout.cloudcmd.io/

License: MIT License

JavaScript 99.01% TypeScript 0.71% CSS 0.03% HTML 0.19% Shell 0.01% WebAssembly 0.06% Svelte 0.01%
javascript nodejs ast eslint babel codeshift codemod plugin transform hacktoberfest

putout's Introduction

Putout NPM version Build Status Coverage Status DeepScan

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

putout

Table of contents

πŸ€·β€β™‚οΈ In doubt about using 🐊Putout?

Check out couple variants of plugins that does the same: linting debugger statement:

'use strict';

module.exports.report = () => `Unexpected 'debugger' statement`;

module.exports.replace = () => ({
    debugger: '',
});

Choose wisely, competitors cannot even fix… 🀫

Drop-in ESLint replacement

🐊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.

πŸ™ Whom should I thank for this project exist?

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.

πŸ€·β€β™‚οΈ Why does this project exist?

🐊Putout on the other hand can make more drastic code transformations that directly affects 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 (β‰₯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 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 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

☝️Commit your code before running 🐊Putout

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.

Environment variables

🐊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

πŸ¦• Usage with Deno

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: [],
});

πŸ“ 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;

☝️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')

βœ‚οΈ 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.

☝️ 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 🎈!

πŸ› Architecture

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

putout

And here is a CLI scheme:

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

🌴 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: Parser, Loader and Runner are running 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
@putout/engine-reporter 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
@putout/processor-filesystem npm

You can disable any of them with:

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

Not bundled processors:

Package Version
@putout/processor-typescript npm
@putout/processor-html npm
@putout/processor-wasm npm

External processors:

Package Version
putout-processor-typos npm

To enable, install and 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 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);
`;

Plugins

🐊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.

No fix

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

(c) Source maps in Node.js

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

🏨 Built-in transformations

JavaScript

remove unused variables
  function show() {
-     const message = 'hello';
      console.log('hello world');
  }
remove unused for...ofvariables
-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 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)
-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
-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 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'`;
convert label to object
-const a = () => {
-    hello: 'world'
-}
+const a = () => ({
+    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;

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 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');

Math

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;

Node.js

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();

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 🐊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:

Appliers

Package Version
@putout/plugin-apply-consistent-blocks npm
@putout/plugin-apply-at npm
@putout/plugin-apply-dot-notation npm
@putout/plugin-apply-starts-with npm
@putout/plugin-apply-flat-map npm
@putout/plugin-apply-destructuring npm
@putout/plugin-apply-optional-chaining npm
@putout/plugin-apply-early-return npm
@putout/plugin-apply-template-literals npm
@putout/plugin-apply-overrides npm

Sorters

Package Version
@putout/plugin-sort-imports-by-specifiers npm

Splitters

Package Version
@putout/plugin-split-assignment-expressions npm
@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-duplicate-functions npm

Converters

Package Version
@putout/plugin-convert-arguments-to-rest npm
@putout/plugin-convert-assignment-to-declaration npm
@putout/plugin-convert-apply-to-spread npm
@putout/plugin-convert-quotes-to-backticks npm
@putout/plugin-convert-concat-to-flat npm
@putout/plugin-convert-array-copy-to-slice npm
@putout/plugin-convert-template-to-string npm
@putout/plugin-convert-index-of-to-includes npm
@putout/plugin-convert-to-arrow-function npm
@putout/plugin-convert-object-assign-to-merge-spread npm
@putout/plugin-convert-optional-to-logical npm
@putout/plugin-convert-assignment-to-arrow-function npm
@putout/plugin-convert-assignment-to-comparison npm
@putout/plugin-convert-const-to-let npm
@putout/plugin-convert-object-entries-to-array-entries npm
@putout/plugin-convert-label-to-object npm

Removers

Package Version
@putout/plugin-remove-unused-variables npm
@putout/plugin-remove-unreferenced-variables npm
@putout/plugin-remove-duplicate-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-assign npm
@putout/plugin-remove-useless-replace npm
@putout/plugin-remove-useless-map npm
@putout/plugin-remove-useless-constructor npm
@putout/plugin-remove-useless-return npm
@putout/plugin-remove-useless-continue npm
@putout/plugin-remove-useless-operand npm
@putout/plugin-remove-useless-array npm
@putout/plugin-remove-useless-array-constructor npm
@putout/plugin-remove-useless-functions npm
@putout/plugin-remove-useless-spread npm
@putout/plugin-remove-useless-array-entries npm
@putout/plugin-remove-useless-arguments npm
@putout/plugin-remove-useless-escape npm
@putout/plugin-remove-useless-template-expressions npm
@putout/plugin-remove-debugger npm
@putout/plugin-remove-iife npm
@putout/plugin-remove-unreachable-code npm
@putout/plugin-remove-console npm
@putout/plugin-remove-empty npm
@putout/plugin-remove-nested-blocks npm

Simplifiers

Package Version
@putout/plugin-simplify-assignment npm
@putout/plugin-simplify-ternary npm
@putout/plugin-simplify-boolean-return npm

Declarators

Package Version
@putout/plugin-declare npm
@putout/plugin-declare-imports-first npm
@putout/plugin-declare-before-reference npm

Groups

Package Version
@putout/plugin-filesystem npm
@putout/plugin-for-of npm
@putout/plugin-conditions npm
@putout/plugin-types npm
@putout/plugin-math npm
@putout/plugin-madrun 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-generators npm
@putout/plugin-gitignore npm
@putout/plugin-npmignore npm
@putout/plugin-coverage npm
@putout/plugin-browserlist npm
@putout/plugin-github npm
@putout/plugin-regexp npm
@putout/plugin-nodejs npm
@putout/plugin-typescript npm
@putout/plugin-try-catch npm
@putout/plugin-montag npm
@putout/plugin-maybe npm
@putout/plugin-new npm
@putout/plugin-logical-expressions npm

Extractors

Package Version
@putout/plugin-extract-sequence-expressions npm
@putout/plugin-extract-object-properties npm

Reusers

Package Version
@putout/plugin-reuse-duplicate-init npm

Groupers

Package Version
@putout/plugin-group-imports-by-source npm

Not bundled

Next packages not bundled with 🐊Putout but can be installed separately.

Package Version
@putout/plugin-apply-entries npm
@putout/plugin-eslint-plugin npm
@putout/plugin-react npm
@putout/plugin-react-hook-form npm
@putout/plugin-nextjs npm
@putout/plugin-react-router npm
@putout/plugin-convert-is-nan-to-number-is-nan npm
@putout/plugin-convert-spread-to-array-from npm
@putout/plugin-convert-assert-to-with npm
@putout/plugin-apply-shorthand-properties npm
@putout/plugin-apply-nullish-coalescing npm
@putout/plugin-cloudcmd npm
@putout/plugin-postcss npm
@putout/plugin-jest npm
@putout/plugin-travis npm
@putout/plugin-convert-throw npm
@putout/plugin-printer npm
@putout/plugin-minify 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
  • time
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
@putout/formatter-time npm

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 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 need. 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 a section named 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"
    ]
}

Printer

In the eyes of mercy, no one should have hateful thoughts. Feel pity for the man who is even more at fault. The area and size of mercy is limitless.

(c) Yamamoto Tsunetomo "Hagakure"

You have also ability to define printer of your choose, it can be:

@putout/printer used by default, if you want to set any other update .putout.json with:

{
    "printer": "recast"
}

@putout/printer:

  • βœ… much simpler in support then recast;
  • βœ… opinionated and has good defaults;
  • βœ… produces code like it was processed by ESLint;
  • βœ… twice faster then recast;

recast:

  • βœ… tryies to preserve formatting, and in 70% succeeded;
  • ❌ for other cases you need eslint-plugin-putout;
  • ❌ slowest printer: involves additional parser + sophisticated printer;

babel:

You can choose any of them, but preferred is default printer.

Plugins

There are two types of plugin names 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"

🐊Putout plugins are the simplest possible way to transform AST and this is for a reason.

And the reason is JavaScript-compatible language 🦎PutoutScript which adds additional meaning to identifiers used in AST-template.

Let's dive into plugin types that you can use for you next code transformation.

Replacer

The simplest 🐊Putout plugin type consists 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);
};

☝️ Use yeoman generator yo putout, it will generate most of the plugin for you.

☝️ More information about supported plugin types you can find in @putout/engine-runner.

☝️ Find out about the way plugins load in @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 Node Type, like in previous example.

You can also use include and/or exclude instead 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();
});

// statement 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/plugin-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 earlier. 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.

πŸ›΄ 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 ./rules

This way you can keep rules specific for your project and run them on each lint.

☝️ Remember: if you want to exclude file from loading, add prefix not-rule- and 🐊Putout will ignore it (in the same way as he does for node_modules).

⏣ Integration with ESLint

Find and fix problems in your JavaScript code

(c) eslint.org

If you see that 🐊Putout breaks 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 config file for ESLint use ESLINT_CONFIG_FILE env variable:

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

To disable ESLint support use NO_ESLINT env variable:

NO_ESLINT=1 putout --fix lib

If you want to ignore ESLint warnings (if you for some reason have annoying unfixable errors 🀷) use NO_ESLINT_WARNINGS=1:

NO_ESLINT_WARNINGS=1 putout --fix lib

You can even lint without CLI using ESlint only, since 🐊Putout is bundled to eslint-plugin-putout:

eslint --fix lib

Applies 🐊Putout transformations for you :).

ESLint API

ESLint begins his work as a formatter when 🐊Putout done his transformations. That's why it is used a lot in different parts of application, for testing purpose and using API in a simplest possible way. You can access it using @putout/eslint:

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,
});

Doesn't it look similar to 🐊Putout way? It definitely is! But... It has a couple of differences you should remember:

And you can even override any of ESLint βš™οΈ options with help of 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 transformations using putout/putout ESLint rule, enable 🐊Putout with the same called flag lowercased:

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 🐊Putout transformations, so there is no need to traverse tree again.

β˜„οΈ 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"
            }
        }]
    ]
}

🐈 Integration with Yarn PnP

Since 🐊Putout has dynamic nature of loading:

  • plugins;
  • processors;
  • formatters;

It was a nice adventure to have support of such a wonderful 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 use package eslint-config-hardcore you should run ESLint this way:

PUTOUT_YARN_PNP=eslint-config-hardcore eslint .

β›“ Using Putout as Loader

🐊Putout can be used as loader this way:

node --import putout/register 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 Output Example
0 OK no errors found <empty>
1 PLACE found places with errors <violations of rules>
2 STAGE nothing in stage <empty>
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 CANNOT_LOAD_FORMATTER formatter has errors 🐊 @putout/formatter-dump: Syntax error
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 UNHANDLED unhandled exception <unhandled exception>
14 CANNOT_LINT_STAGED cannot lint staged 🐊 --staged: not git repository`
15 INTERACTIVE_CANCELED interactive canceled <empty>

Example of providing invalid option:

coderaiser@localcmd:~/putout$ putout --hello
🐊 Invalid option `--hello`. 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.
  • 🎩ESCover: Coverage for EcmaScript Modules.
  • ♨️ Speca: Write tape tests for you.
  • 🀫Goldstein: JavaScript with no limits.
  • 🎬MadCut: CLI tool to cut markdown into peaces.
  • Minify: a minifier of js, css, html and img files.
  • RedPut - CLI tool to download source of a rule and fixtures from 🐊Putout Editor and generate tests from it.
  • RedLint - Linter for your Filesystem πŸ˜πŸ’Ύ.
  • Bundler - Simplest possible bundler.

Are you also use 🐊Putout in your application? 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 plugins or fixes (17.0.0 -> 17.1.0).
  • major breaking changes, plugins remove (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

putout's People

Contributors

asieduernest12 avatar atablash avatar coderaiser avatar deepsourcebot avatar dimamachina avatar egilll avatar em- avatar fluentinstroll avatar fregante avatar hans5958 avatar jmlavoier avatar matwilko avatar mridulsharma03 avatar niranjan-kurhade avatar oprogramador avatar pcarn avatar pvinis avatar rastaalex avatar sobolevn avatar sonicdoe avatar tejaswankalluri avatar tsmmark avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

putout's Issues

Local plugins

How can I use a plugin that is local to my code, and not published on npmjs? I tried

putout(source.code, { plugins: './trace' })

and

putout(source.code, { plugins: require('./trace') })

eslint-plugin-putout can't detect JSX

When I try to lint a simple *.tsx file, like

function MyComponent() {
  return (
    <div>Hello</div>
  );
}

ESLint gives me the following error:

SyntaxError: Unterminated regular expression

With other *.tsx files it gives me the following error:

SyntaxError: Unexpected token, expected "</>/<=/>="

When I disable the putout/putout rule, ESLint and all other plugins start to work fine.

So, as I understand it, Putout can't detect that *.tsx files use JSX.

Putout doesn't work with Vue

ESlint version: 7.3.0
Bug: Putout doesn't work with Vuejs

Capture

When I comment out "putout" in the plugins's array, it works fine

This is my eslint's config file

{
  "env": {
    "browser": true,
    "es2020": true,
    "jest/globals": true,
    "jquery": true,
    "node": true,
    "react-native/react-native": true
  },
  "extends": [
    "airbnb",
    "airbnb-typescript",
    "airbnb/hooks",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:json/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:lodash/recommended",
    "plugin:node/recommended",
    "plugin:optimize-regex/recommended",
    "plugin:react-hooks/recommended",
    "plugin:react-native/all",
    "plugin:react-redux/recommended",
    "plugin:react/recommended",
    "plugin:vue/vue3-recommended",
    "plugin:vue-scoped-css/vue3-recommended",
    "plugin:security/recommended",
    "plugin:sonarjs/recommended",
    "plugin:unicorn/recommended",
    "plugin:import/errors",
    "plugin:import/typescript",
    "plugin:array-func/recommended",
    "plugin:@getify/proper-arrows/getify-says",
    "plugin:no-unsanitized/DOM",
    "plugin:putout/recommended"
  ],
  "overrides": [
    {
      "files": [
        "**/*.ts",
        "**/*.tsx"
      ],
      "parser": "@typescript-eslint/parser",
      "rules": {
        "@typescript-eslint/ban-ts-ignore": 0,
        "@typescript-eslint/explicit-function-return-type": "error",
        "@typescript-eslint/explicit-module-boundary-types": "error",
        "@typescript-eslint/interface-name-prefix": 0,
        "@typescript-eslint/no-explicit-any": 0,
        "@typescript-eslint/no-non-null-assertion": 0,
        "@typescript-eslint/no-var-requires": "error"
      }
    },
    {
      "files": [
        "**/*.vue"
      ],
      "parser": "vue-eslint-parser",
      "parserOptions": {
        "parser": "@typescript-eslint/parser"
      }
    }
  ],
  "parser": "@babel/eslint-parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "jsx": true,
    "requireConfigFile": false,
    "sourceType": "module"
  },
  "plugins": [
    "@typescript-eslint",
    "html",
    "json",
    "json-format",
    "jsx-a11y",
    "lodash",
    "node",
    "optimize-regex",
    "react",
    "react-hooks",
    "react-native",
    "react-redux",
    "security",
    "sonarjs",
    "unicorn",
    "import",
    "array-func",
    "@getify/proper-arrows",
    "jest",
    "putout"
  ],
  "rules": {
    "@getify/proper-arrows/name": 0,
    "@getify/proper-arrows/params": 0,
    "@getify/proper-arrows/return": 0,
    "@getify/proper-arrows/this": 0,
    "@getify/proper-arrows/where": 0,
    "@typescript-eslint/ban-ts-comment": 0,
    "@typescript-eslint/dot-notation": 0,
    "@typescript-eslint/explicit-function-return-type": 0,
    "@typescript-eslint/explicit-module-boundary-types": 0,
    "@typescript-eslint/naming-convention": 0,
    "@typescript-eslint/no-implied-eval": 0,
    "@typescript-eslint/no-throw-literal": 0,
    "@typescript-eslint/no-unused-expressions": 0,
    "@typescript-eslint/no-var-requires": 0,
    "@typescript-eslint/return-await": 0,
    "@typescript-eslint/space-before-function-paren": [
      "error"
    ],
    "@typescript-eslint/triple-slash-reference": 0,
    "camelcase": 0,
    "func-names": 0,
    "global-require": 0,
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "jsx": "never",
        "ts": "never",
        "tsx": "never"
      }
    ],
    "import/no-dynamic-require": 0,
    "import/no-extraneous-dependencies": 0,
    "import/no-named-as-default-member": 0,
    "import/prefer-default-export": 0,
    "indent": 0,
    "jest/no-disabled-tests": "warn",
    "jest/no-focused-tests": "error",
    "jest/no-identical-title": "error",
    "jest/prefer-to-have-length": "warn",
    "jest/valid-expect": "error",
    "jsx-a11y/click-events-have-key-events": 0,
    "jsx-a11y/label-has-associated-control": 0,
    "jsx-a11y/no-static-element-interactions": 0,
    "linebreak-style": 0,
    "lodash/chaining": 0,
    "lodash/import-scope": 0,
    "lodash/prefer-constant": 0,
    "lodash/prefer-immutable-method": 0,
    "lodash/prefer-lodash-method": 0,
    "lodash/prefer-some": 0,
    "max-classes-per-file": 0,
    "max-len": 0,
    "new-cap": 0,
    "no-async-promise-executor": 0,
    "no-console": 0,
    "no-extend-native": 0,
    "no-param-reassign": 0,
    "no-plusplus": 0,
    "no-process-exit": 0,
    "no-restricted-properties": 0,
    "no-underscore-dangle": 0,
    "node/no-missing-import": 0,
    "node/no-unpublished-import": 0,
    "node/no-unpublished-require": 0,
    "node/no-unsupported-features/es-syntax": 0,
    "putout/destructuring-as-function-argument": 0,
    "putout/keyword-spacing": 0,
    "putout/newline-function-call-arguments": 0,
    "putout/putout": [
      "error",
      {
        "plugins": [
          "remove-duplicate-interface-keys"
        ],
        "rules": {
          "apply-numeric-separators": "off",
          "convert-for-each-to-for-of": "off",
          "promises/add-missing-await": "off",
          "remove-console": "off",
          "remove-constant-conditions": "off",
          "remove-empty/import": "off",
          "remove-unused-types": "off",
          "remove-unused-variables": "off",
          "split-nested-destructuring": "off",
          "strict-mode/add": "off"
        }
      }
    ],
    "quote-props": 0,
    "react-native/no-color-literals": 0,
    "react-native/no-raw-text": 0,
    "react-native/no-single-element-style-arrays": 0,
    "react/destructuring-assignment": 0,
    "react/display-name": 0,
    "react/forbid-prop-types": 0,
    "react/jsx-filename-extension": [
      1,
      {
        "extensions": [
          ".js",
          ".jsx",
          ".tsx"
        ]
      }
    ],
    "react/jsx-fragments": 0,
    "react/jsx-props-no-spreading": 0,
    "react/no-access-state-in-setstate": 0,
    "react/prop-types": 0,
    "react/require-default-props": 0,
    "react/state-in-constructor": 0,
    "react/style-prop-object": 0,
    "security/detect-child-process": 0,
    "security/detect-non-literal-fs-filename": 0,
    "security/detect-non-literal-regexp": 0,
    "security/detect-object-injection": 0,
    "security/detect-possible-timing-attacks": 0,
    "sonarjs/cognitive-complexity": 0,
    "sonarjs/no-small-switch": 0,
    "space-before-function-paren": "off",
    "unicorn/catch-error-name": 0,
    "unicorn/filename-case": 0,
    "unicorn/no-array-for-each": 0,
    "unicorn/no-array-reduce": 0,
    "unicorn/no-process-exit": 0,
    "unicorn/no-reduce": 0,
    "unicorn/no-useless-undefined": 0,
    "unicorn/numeric-separators-style": 0,
    "unicorn/prefer-module": 0,
    "unicorn/prefer-node-protocol": 0,
    "unicorn/prefer-spread": 0,
    "unicorn/prevent-abbreviations": 0
  },
  "settings": {
    "import/resolver": {
      "node": {
        "extensions": [
          ".js",
          ".jsx",
          ".ts",
          ".tsx"
        ]
      }
    },
    "json/ignore-files": [
      "**/package-lock.json"
    ],
    "json/json-with-comments-files": [
      "**/tsconfig.json",
      ".vscode/**"
    ],
    "json/sort-package-json": "standard",
    "react": {
      "version": "latest"
    }
  }
}

False CJS detection

Hi there, if I use this:

export * from './foo.js';

I get this warning:

"use strict" directive should be on top of commonjs file (strict-mode/add) eslint(putout/putout)

putout fails with error if .eslintrc.js specifies an eslint plugin that is installed locally

If putout is installed globally (as recommended, via npm i putout -g) but eslint and a plugin, such as eslint-plugin-prettier are installed locally (via npm i eslint eslint-plugin-prettier --save-dev) and enabled in the .eslintrc.js file, attempting to run putout will fail.

Example .eslintrc.js file (must exist in the current working directory):

'use strict';

module.exports = {
    env: {
        node: true,
        es6: true,
        browser: true
    },
    plugins: ['prettier'],
    extends: ['eslint:recommended']
};

Command tested: putout --fix source.js. Note that the error does not occur when --fix is not specified (such as putout source.js).

Example of the error:

Error: Cannot find module 'eslint-plugin-prettier'
    at Function.Module._resolveFilename (module.js:548:15)
    at Function.Module._load (module.js:475:25)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at loadPlugin (/Users/sgilroy/.nvm/versions/node/v8.15.0/lib/node_modules/putout/lib/eslint.js:26:12)
    at /Users/sgilroy/.nvm/versions/node/v8.15.0/lib/node_modules/putout/lib/eslint.js:36:25
    at getLinter (/Users/sgilroy/.nvm/versions/node/v8.15.0/lib/node_modules/putout/lib/eslint.js:57:28)
    at module.exports (/Users/sgilroy/.nvm/versions/node/v8.15.0/lib/node_modules/putout/lib/eslint.js:85:36)
    at /Users/sgilroy/.nvm/versions/node/v8.15.0/lib/node_modules/putout/lib/process-file.js:77:38
    at Array.map (<anonymous>)

Project to use to reproduce the problem:
eslint-plugin-error.zip

Workaround: install the eslint plugin globally npm i eslint-plugin-prettier -g.

eslint-plugin-putout: comments break location reporting

Example:

// comment
foo;
eslint .

Expected result: reported line number should be 2

2:1  error  Unused expression statement (remove-unused-expressions)  putout/putout

Actual result: reported line number is 1

1:1  error  Unused expression statement (remove-unused-expressions)  putout/putout

breaking JSX

I had this code:

export const Hamburger = props => (
  <>
    <div className="bg-blue">
      <MenuContainer />
    </div>
  </>
);

putout changed it to:

export const Hamburger = () => (
  <>
    <div className="bg-blue">
      <MenuContainer />
    </div>
                                     </>;

so beside a formatting issue, the closing bracket is removed causing a syntax error.
Only remove-unused-variables rule was switched on.

Destructuring Array assignment with type annotation creates invalid syntax

The code I'm working with has a type annotation and that gets carried over to the destructured array assignment. This:

const nextProps: DialogProps = dialogProps[0];

Becomes:

const [nextProps: DialogProps] = dialogProps;

This is not valid syntax and breaks the TypeScript compiler. It would probably be best to strip the type annotation and let TypeScript infer the type. There is no guarantee that dialogProps has a length > 0, so the initial code is bad form anyway because the type annotation acts as an assertion as well in this case.

remove-unreachable-code deletes useful code

I faced with this issue on a real project, putout breaks the code when removing everything after the return statement.

Example

function test() {
	functionAfterReturn();
	return console.info('test');

	function functionAfterReturn() {
		console.info('functionAfterReturn');
	}
}
test()

After putout

'use strict';
function test() {
    functionAfterReturn(); //  <--- ReferenceError: functionAfterReturn is not defined
    return console.info('test');
}
test();

http://putout.cloudcmd.io/ fails to load with error "TypeError: t.iter is not a function"

When trying to navigate to http://putout.cloudcmd.io/ there is a JavaScript error and the page fails to load. The only visible content is a contribution footer. The link http://putout.cloudcmd.io/ appears in the description for the GitHub project https://github.com/coderaiser/putout/, so it may be one of the first links that a potential user will look at when exploring the project.

Tested in Chrome and Firefox. The error below is from Chrome Version 76.0.3809.100 (Official Build) (64-bit)

vendors~app-be21591da1b5607b3eb3-26.js:50 TypeError: t.iter is not a function
    at estimateLineHeights (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at attachDoc (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at new CodeMirror (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at CodeMirror (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at Editor.componentDidMount (app-6544ad61fc0ebc2847bb-26.js:2167)
    at nj (vendors~app-be21591da1b5607b3eb3-26.js:50)
    at t.unstable_runWithPriority (vendors~app-be21591da1b5607b3eb3-26.js:9)
    at vf (vendors~app-be21591da1b5607b3eb3-26.js:50)
    at ij (vendors~app-be21591da1b5607b3eb3-26.js:50)
    at eg (vendors~app-be21591da1b5607b3eb3-26.js:50)
di @ vendors~app-be21591da1b5607b3eb3-26.js:50
vendors~app-be21591da1b5607b3eb3-26.js:50 Uncaught TypeError: t.iter is not a function
    at estimateLineHeights (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at attachDoc (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at new CodeMirror (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at CodeMirror (vendors~app-be21591da1b5607b3eb3-26.js:19)
    at Editor.componentDidMount (app-6544ad61fc0ebc2847bb-26.js:2167)
    at nj (vendors~app-be21591da1b5607b3eb3-26.js:50)
    at t.unstable_runWithPriority (vendors~app-be21591da1b5607b3eb3-26.js:9)
    at vf (vendors~app-be21591da1b5607b3eb3-26.js:50)
    at ij (vendors~app-be21591da1b5607b3eb3-26.js:50)
    at eg (vendors~app-be21591da1b5607b3eb3-26.js:50)
app-6544ad61fc0ebc2847bb-26.js:2167 Unable to write to local storage.
i @ app-6544ad61fc0ebc2847bb-26.js:2167

disable formatting

I opened oprogramador#1 and I used the same .putout.json file but with "remove-unused-variables": "on",.

Generally it works fine, removing only unused variables bot for some JSX files it changed formatting.

e.g. it used to be:

export const Teams = props => (
  <>
    <header

it was changed to:

export const Teams = () => <>
  <header

ReferenceError: unknown node of type "ChainExpression" with constructor "Object"

Reproduction:

const bar = foo?.bar;

Full stack trace:

ESLint: 7.31.0

ReferenceError: unknown node of type "ChainExpression" with constructor "Object"
Occurred while linting /example/src/App.ts:1
    at Generator.print (/example/node_modules/@babel/generator/lib/printer.js:258:13)
    at Generator.generate (/example/node_modules/@babel/generator/lib/printer.js:38:10)
    at Generator.generate (/example/node_modules/@babel/generator/lib/index.js:23:18)
    at generate (/example/node_modules/@babel/generator/lib/index.js:95:14)
    at NodePath.toString (/example/node_modules/@babel/traverse/lib/path/index.js:164:35)
    at const __identifier = __b (/example/node_modules/@putout/plugin-reuse-duplicate-init/lib/reuse-duplicate-init.js:17:57)
    at module.exports (/example/node_modules/try-catch/lib/try-catch.js:5:23)
    at /example/node_modules/@putout/engine-runner/lib/template/index.js:87:21
    at NodePath._call (/example/node_modules/@babel/traverse/lib/path/context.js:53:20)
    at NodePath.call (/example/node_modules/@babel/traverse/lib/path/context.js:40:17)
    at NodePath.visit (/example/node_modules/@babel/traverse/lib/path/context.js:90:31)
    at TraversalContext.visitQueue (/example/node_modules/@babel/traverse/lib/context.js:99:16)
    at TraversalContext.visitMultiple (/example/node_modules/@babel/traverse/lib/context.js:68:17)
    at TraversalContext.visit (/example/node_modules/@babel/traverse/lib/context.js:125:19)
    at Function.traverse.node (/example/node_modules/@babel/traverse/lib/index.js:76:17)
    at NodePath.visit (/example/node_modules/@babel/traverse/lib/path/context.js:97:18)
    at TraversalContext.visitQueue (/example/node_modules/@babel/traverse/lib/context.js:99:16)
    at TraversalContext.visitSingle (/example/node_modules/@babel/traverse/lib/context.js:73:19)
    at TraversalContext.visit (/example/node_modules/@babel/traverse/lib/context.js:127:19)
    at Function.traverse.node (/example/node_modules/@babel/traverse/lib/index.js:76:17)
    at traverse (/example/node_modules/@babel/traverse/lib/index.js:56:12)
    at runWithMerge (/example/node_modules/@putout/engine-runner/lib/index.js:65:5)
    at run (/example/node_modules/@putout/engine-runner/lib/index.js:54:12)
    at module.exports.runPlugins (/example/node_modules/@putout/engine-runner/lib/index.js:32:18)
    at transform (/example/node_modules/putout/lib/putout.js:99:20)
    at module.exports.findPlaces (/example/node_modules/putout/lib/putout.js:71:12)
    at Program (/example/node_modules/eslint-plugin-putout/lib/putout/index.js:54:32)
    at /example/node_modules/eslint/lib/linter/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (/example/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (/example/node_modules/eslint/lib/linter/node-event-generator.js:293:26)
    at NodeEventGenerator.applySelectors (/example/node_modules/eslint/lib/linter/node-event-generator.js:322:22)
    at NodeEventGenerator.enterNode (/example/node_modules/eslint/lib/linter/node-event-generator.js:336:14)
    at CodePathAnalyzer.enterNode (/example/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js:711:23)
    at /example/node_modules/eslint/lib/linter/linter.js:960:32
    at Array.forEach (<anonymous>)
    at runRules (/example/node_modules/eslint/lib/linter/linter.js:955:15)
    at Linter._verifyWithoutProcessors (/example/node_modules/eslint/lib/linter/linter.js:1181:31)
    at Linter._verifyWithoutProcessors (/example/node_modules/eslint-plugin-eslint-comments/lib/utils/patch.js:181:42)
    at Linter._verifyWithConfigArray (/example/node_modules/eslint/lib/linter/linter.js:1280:21)
    at Linter.verify (/example/node_modules/eslint/lib/linter/linter.js:1235:25)
    at Linter.verifyAndFix (/example/node_modules/eslint/lib/linter/linter.js:1428:29)
    at verifyText (/example/node_modules/eslint/lib/cli-engine/cli-engine.js:234:48)
    at CLIEngine.executeOnFiles (/example/node_modules/eslint/lib/cli-engine/cli-engine.js:802:28)
    at ESLint.lintFiles (/example/node_modules/eslint/lib/eslint/eslint.js:563:23)
    at Object.execute (/example/node_modules/eslint/lib/cli.js:299:36)
    at main (/example/node_modules/eslint/bin/eslint.js:132:52)
    at Object.<anonymous> (/example/node_modules/eslint/bin/eslint.js:136:2)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
    at internal/main/run_main_module.js:17:47

removing empty imports

Running remove-unused-variables on
import { Foo } from 'bar';
produces
import 'bar';

Logically, i'd think this is expected, but I still need to get rid of these imports somehow.
Writing a plugin seems like the only option, but I have no idea how to "connect" these plugins together to get the result i'm looking for

// use remove-unused-vars
import { Foo } from 'bar';
import 'foo';
// use remove-unused-imports
import 'bar';  // <-- fix this
import 'foo'

Issue with relative paths

I had an issue with this on windows with relative paths

RangeError: path should be a 'path.relative()'d string, but got "C:\<path\to\root>\dist\main.js"

if (ignores(dir, resolvedName, options)) {

I fixed this with:

if (ignores(dir, name.replace(/^\./, cwd), options)) {

plugin-apply-destructuring changes code behavior

Before:

const objectA = { property: "" };
const objectB = { property: "foo" };

const property = objectA.property || objectB.property;

console.log(property); // prints "foo" (the value from objectB)

After:

const objectA = { property: "" };
const objectB = { property: "foo" };

const { property = objectB.property } = objectA;

console.log(property); // prints "" (the value from objectA)

Unclear how to run only a single rule

I'm unable to figure out how to run Putout with only a single rule enabled. One would assume that putout src --enable remove-unused-variables, or that only having one plugin/rule specified in .putout.json would do the trick, but that turns out not to be the case, instead all default rules are enabled.

(This is not an issue as such, but if anyone could tell me how to do this I would like to add it to the docs. I do see now what --enable does, but it's not quite what one would expect.)

regexp/remove-useless-group false positives

regexp/remove-useless-group produces too many false positives.

Example code from MDN:

const imageDescription = 'This image has a resolution of 1440Γ—900 pixels.';
const regexpSize = /([0-9]+)Γ—([0-9]+)/;
const match = imageDescription.match(regexpSize);
console.log(`Width: ${match[1]} / Height: ${match[2]}.`);

Putout recommends using /[0-9]+Γ—[0-9]+/, but the console.log() line will not work in that case.

Another example (an ember-cli-showdown initializer):

function initialize() {
  showdown.extension("smallLinks", function smallLinks() {
    return [
      {
        type: "output",
        regex: /<a (?<attributes>.*)>/u,
        replace: '<a class="link link--small" $<attributes>>',
      },
    ];
  });
}

Putout recommends using /<a .*>/u, but it will break the code (the replace will stop working properly).

Basically, there is no way to know whether capture groups will be used or not.

remove async/await

I have a class definition that looks like

class X {

    constructor() {
        this._analyzer = null;
    }

    async init(arg) {
       await something()
    }
}

and I need to strip async and await from/in method init. This is a source that is not under my control, but which I know will work without async in my own context, where I need it to be sync. How would I do this with putout?

feat: autofix error "Maximum call stack size exceeded" in array push spread

calling someArray.push(...otherArray) can be considered a code smell,
since otherArray.length is limited to about 100K in nodejs (500K in firefox)

// problem
[].push(...Array.from({ length: 1000*1000 })) // ... = spread operator
// Uncaught RangeError: Maximum call stack size exceeded

// solution
var input = Array.from({ length: 1000*1000 }); var output = [];
for (let i = 0; i < input.length; i++) output.push(input[i]);
output.length
// 1000000

// solution
Array.prototype._concat_inplace = function(other) { // aka _push_array
  for (let i = 0; i < other.length; i++) {
    this.push(other[i]);
  }
  return this; // chainable
};
var input = Array.from({ length: 1000*1000 }); var output = [];
output._concat_inplace(input), output.length;
// 1000000

see sveltejs/svelte#4694 (comment)

SyntaxError: 'import' and 'export' may only appear at the top level

ESlint version: 7.32.0
Putout: v20.0.0
eslint-plugin-putout: v10.0.0

Eslint Parser:

  "parser": "@babel/eslint-parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "jsx": true,
    "requireConfigFile": false,
    "sourceType": "module"
  },

Input file:

const testMissingColon = '123'

Output:

ESLint: 7.32.0

SyntaxError: 'import' and 'export' may only appear at the top level. (3:8)
  1 | /* @babel/template */;
  2 | {
> 3 |         import {createMockImport} from 'mock-import';
    |        ^
  4 |         const {mockImport, stopAll, reImport} = createMockImport(import.meta.url);
  5 |     }
Occurred while linting D:\Web\test\app.js:1
    at Object._raise (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:796:17)
    at Object.raiseWithData (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:789:17)
    at Object.raise (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:750:17)
    at Object.parseStatementContent (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:12899:18)
    at Object.parseStatementContent (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:9201:18)
    at Object.parseStatement (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:12806:17)
    at Object.parseBlockOrModuleBlockBody (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:13395:25)
    at Object.parseBlockBody (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:13386:10)
    at Object.parseBlock (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:13370:10)
    at Object.parseBlock (C:\Users\Virgil\AppData\Local\Yarn\Data\global\node_modules\@babel\parser\lib\index.js:10018:61)

plugin-apply-shorthand-properties: ignore imports

Currently, plugin-apply-shorthand-properties transforms this code

import evolve from "ramda/src/evolve";
import not from "ramda/src/not";
import mergeDeepRight from "ramda/src/mergeDeepRight";

export default evolve({
  isPanelVisible: not,
  items: mergeDeepRight,
});

into this:

import evolve from "ramda/src/evolve";
import isPanelVisible from "ramda/src/not";
import items from "ramda/src/mergeDeepRight";

export default evolve({
  isPanelVisible,
  items,
});

I think aliasing generic utility functions like this is a bad practice. I would like for plugin-apply-shorthand-properties to ignore cases like this.

Document usage of putout with a single rule or plugin

How can I use putout to apply a single rule, such as remove-unused-variables?

It would be helpful if there was a simple, documented approach for this. Ideally, it should be possible to do this with a single command and without creating a .putout.json file.

It seems that specifying a limited set of rules or plugins in the local .putout.json file does not by default disable rules or plugins specified in the default putout.json file. That is, it seems that the local plugins are added to the default, instead of replacing them. For the rules, it appears as though certain rules are on by default (even without appearing in the default .putout.json file) and I have not found a comprehensive list of all rules.

Using putout --disable-all source.js seems to only disable rules which are currently violated in the source files being evaluated, not all rules comprehensively.

Further, the documentation seems to be somewhat redundantly duplicated between the top level readme https://github.com/coderaiser/putout/blob/master/README.md and the readme in the putout package https://github.com/coderaiser/putout/blob/master/packages/putout/README.md which I find confusing.

plugin-regexp/remove-useless-group should not remove groups in `.split()` or `.replace()`

plugin-regexp/remove-useless-group changes:

'lala'.split(/(a)/g); // Output: ["l", "a", "l", "a", ""]

into:

'lala'.split(/a/g); // Output: ["l", "l", ""]

It also changes:

'lala'.replace(/(a)/g,'-$1-'); // Output: "l-a-l-a-"

into:

'lala'.replace(/a/g,'-$1-'); // Output: "l-$1-l-$1-"

This plugin is safe to use in .test(), but should likely ignore regex that appears in any other function (match, split, replace).

feat: remove relative require to node_modules

solution in bash:

find . -name '*.js' -or -name '*.ts' | while read f
do
  echo "$f"
  sed -E -i "s|require\('(\\.\\./)+node_modules/(.*?)'\)|require('\2')|" "$f"
done

sample from github.com/aethereans/aether-app

-const minimatch = require('../../node_modules/minimatch')
+const minimatch = require('minimatch')

convert-commonjs-to-esm breaks these imports:

-const minimatch = require('../../node_modules/minimatch')
+import minimatch from '../../node_modules/minimatch.js';

Suggestion to have `remove-empty/import` off by default

Hi. Importing a module for its side effects only is very common, such as with:

import "core-js/stable";
import "regenerator-runtime/runtime";
import "styles.scss";

plugin-remove-empty's removal of the above is pretty unexpected, I would assume that any developer writing empty imports does so knowingly for the side effects. My question is therefore whether it would be valuable to turn this transformation off by default, in order for Putout's defaults to seem more helpful and sensible to the general developer.

This is just a suggestion, if you disagree please feel free to just close this issue.

TypeError: Cannot read property 'raw' of undefined

Looks like @putout/plugin-regexp can't handle named capturing groups.

const regexp = /(?<foo>a)/;

gives the following error:

TypeError: Cannot read property 'raw' of undefined
Occurred while linting /home/user/example.js:1
    at RegExpLiteral (/home/user/node_modules/@putout/plugin-regexp/lib/remove-useless-group/index.js:14:38)

ESLint: 7.30.0
putout: 18.4.0
eslint-plugin-putout: 8.1.0
@putout/plugin-regexp: 2.3.3

Disable all rules

I'd like to use only the convert-commonjs-to-esm rule and not all the others that come in the default configuration. Is there a way to disable all rules without specifying them individually? e.g. something like "all": "off"?

Generally, I'd argue the default configuration should contain no rules, just like eslint does it.

Putout doesn't work in VS Code via ESLint

VS Code comes bundled with Node v12, but Putout now requires Node v14.

image

ESLint with Putout just doesn't work, this is what I get in VS Code log:

[Info  - 2:41:11 PM] ESLint server is starting
[Info  - 2:41:12 PM] ESLint server running in node v12.14.1
[Info  - 2:41:12 PM] ESLint server is running.
[Info  - 2:41:16 PM] ESLint library loaded from: C:\eslint-config-hardcore\node_modules\eslint\lib\api.js
[Info  - 2:41:21 PM] Failed to load plugin 'putout' declared in '.eslintrc.json Β» ./index.json Β» ./base.json': Cannot find module 'putout/parse-options' Require stack: - C:\eslint-config-hardcore\node_modules\eslint-plugin-putout\lib\putout\index.js - C:\eslint-config-hardcore\node_modules\eslint-plugin-putout\lib\index.js - C:\eslint-config-hardcore\node_modules\@eslint\eslintrc\lib\config-array-factory.js - C:\eslint-config-hardcore\node_modules\@eslint\eslintrc\lib\index.js - C:\eslint-config-hardcore\node_modules\eslint\lib\cli-engine\cli-engine.js - C:\eslint-config-hardcore\node_modules\eslint\lib\cli-engine\index.js - C:\eslint-config-hardcore\node_modules\eslint\lib\api.js - c:\Users\user\.vscode\extensions\dbaeumer.vscode-eslint-2.1.14\server\out\eslintServer.js

Error: no plugin found for a rule: "apply-shorthand-properties"

I saw this in the changelog:

  • (putout) plugin-apply-shorthand-properties: remove from default install

After I upgraded Putout to v14, I ran npm i @putout/plugin-apply-shorthand-properties, but I'm still getting this error:

eslint .

Oops! Something went wrong! :(

ESLint: 7.18.0

Error: no plugin found for a rule: "apply-shorthand-properties"

This is what I have in my .eslintrc.json:

  "rules": {
    "putout/putout": [
      "error",
      {
        "rules": {
          "apply-shorthand-properties": "on"
        }
      }
    ]
  }

Did I do something wrong? How do I use apply-shorthand-properties with Putout v14 via ESLint?

Putout never completes

Putout 13.2.0 worked fine. After I upgraded to 13.7.0, Putout can no longer complete, it just eats ~10% of my CPU and never finishes.

I'm using eslint-plugin-putout. I also tried running node_modules/.bin/putout, same result, it never finishes.

I does NOT reproduce on my CI, but it reproduces on my local machine (Windows 10, Node.js v14.15.4).

What can I do to gather more info?

Destructuring Iterable that is not an Array uses .slice()

Using the eslint plugin, I'm seeing a destructured Set be undestructured (restructured?) with a .slice() call, in typescript. So this:

const draftSet = new Set<string>([...rejected]);

becomes:

const draftSet = new Set<string>(rejected.slice());

Where rejected is a Set.

A Set, of course, does not have a slice method.

eslint-plugin-putout requires putout package

I really appreciate that 'eslint-plugin-putout' includes the following options:

{
  "putout/single-property-destructuring": "error",
  "putout/multiple-properties-destructuring": "error"
}

That said, the plugin doesn't work out-of-the-box w/ the provided instructions. 'eslint-plugin-putout' plugin requires the 'putout' package. However, its read me doesn't instruct us to npm i putout, nor does the 'eslint-plugin-putout' package install it or warn us that it needs to be intalled

The error that happens, silently in VS Code, is this

[Info  - 0:00:00 PM] Failed to load plugin 'eslint-plugin-putout' declared in '.eslintrc': 
Cannot find module 'putout' 
Require stack: - ./node_modules/eslint-plugin-putout/lib/keyword-spacing/index.js ...

My suggestion is to do the following

  1. Add putout to the npm i eslint eslint-plugin-putout line of the read me
  2. Add putout to the peerDependencies
  3. Add a try/catch around a root require('putout') w/ a clear+friendly error message

If that sounds right to y'all, I'd be happy to open a pull request to that effect

remove-useless-arguments/destructuring false positive

const foo = bar(({ baz }) => ({ baz }));
foo({ qux: "qux" });

Expected result: there should be no remove-useless-arguments/destructuring warning.

Actual result:

2:7  error  "qux" is useless argument of a function "foo" (remove-useless-arguments/destructuring)  putout/putout

Looks like putout infers the signature of foo from the fat arrow function, but it should not do that. The signature of foo is unknown here.

remove unused variables keeping methods invocation

Generally remove-unused-variables rule works correctly but sometimes there's a situation like:

const result = await API.put(id, payload);

so we need to remove unused const result = but keep await API.put(id, payload);.

This rule could accept some options:

  • to always invoke functions/ methods and remove only assignments
  • to detect in some way whether a function/ method modifies anything:
    • having knowledge about lodash methods
    • assuming that if a method name starts with get or is or check, it doesn't modify anything

eslint-plugin-putout and ESLint Shareable Configs

I am trying to integrate eslint-plugin-putout into my ESLint shareable config - eslint-config-hardcore.

The integration of putout itself was straightforward.
But I don't understand how I can override putout rules in my shareable ESLint config.

I see that it is possible to create .putout.json in projects that will be using eslint-hardcore-config, but I would like to avoid having to create and maintain .putout.json in each project. I would like my putout config to be shared along with my eslint-hardcore-config.

For example, eslint-plugin-prettier allows you to specify Prettier options like this (in .eslintrc.json):

{
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": ["error", { "singleQuote": true }]
                                   ^^^^^^^^^^^^^^^^^^^^^^^
  }
}

I would like to be able to do something like that with putout:

{
  "plugins": ["putout"],
  "rules": {
    "putout/putout": [
      "error",
      {
        "rules": {
          "apply-optional-chaining": "on",
          "apply-nullish-coalescing": "on",
          "remove-unused-variables": "off"
        }
      }
    ]
  }
}

Improve code coverage

According to ci or npm run coverage in the root of repository.
There is some red places, would be great to add tests for them in the similar to existing tests.

codemod request: trace function calls

I'm looking to instrument code for debugging so I can trace what functions are being called -- a simple console.log('function name', arguments) added as the first command in functions would go a long way. If the class name (for non-anomymous classes) could be included for method calls, that'd be fantastic.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.