Skip to content

Instantly share code, notes, and snippets.

@milichev
Last active January 24, 2022 10:59
Show Gist options
  • Select an option

  • Save milichev/db3ea9fcb2fb086db57c3f2c4f770db2 to your computer and use it in GitHub Desktop.

Select an option

Save milichev/db3ea9fcb2fb086db57c3f2c4f770db2 to your computer and use it in GitHub Desktop.

Revisions

  1. milichev revised this gist Jan 24, 2022. 1 changed file with 61 additions and 39 deletions.
    100 changes: 61 additions & 39 deletions eslint-ignore.js
    Original file line number Diff line number Diff line change
    @@ -1,24 +1,42 @@
    /* eslint-disable @typescript-eslint/no-var-requires,no-console */
    /**
    * Accepts eslint `stdout` and processes all error messages related to React Hooks, if any.
    *
    * Essentially, the "fix" is only about adding a respective `eslint-disable-line` comments to mute the error unless it is addressed appropriately.
    *
    * Requires the following modules available:
    * - lodash
    *
    * Usage:
    *
    * Run in console:
    * eslint client/components/ | node mute-eslint-hooks.js
    *
    * If something went wrong and the script should be fixed to proceed:
    * 1. Fix the script to edit the source file correctly,
    * 2. Run in console specifying the file to rollback and process again:
    * export FN=client/components/items/bulk-actions.js && git checkout -- $FN && eslint $FN | node mute-eslint-hooks.js
    */

    const readline = require('readline');
    const fs = require('fs');
    const $path = require('path');
    const _ = require('lodash');
    const { EOL } = require('os');
    /* eslint-disable @typescript-eslint/no-var-requires,no-console */
    const readline = require("readline");
    const fs = require("fs");
    const $path = require("path");
    const _ = require("lodash");
    const { EOL } = require("os");
    const cwd = process.cwd();

    collectErrors()
    .then(errorsByFile => Promise.all(errorsByFile.map(processFile)))
    .then((errorsByFile) => Promise.all(errorsByFile.map(processFile)))
    .then((results) =>
    results
    .filter(Boolean)
    .map(({ path, count, fixedCount }) => `\n${$path.relative(cwd, path)}\n fixed: ${fixedCount} / ${count}`)
    .join(EOL),
    .join(EOL)
    )
    .then(msg => msg && console.log(msg))
    .catch(err => console.error(err));
    .then((msg) => msg && console.log(msg))
    .catch((err) => console.error(err));

    function collectErrors () {
    function collectErrors() {
    return new Promise((resolve, reject) => {
    const result = [];
    const pathRe = /^(?:\/\w[-.\w]+)+\.(?:js|jsx|ts|tsx)$/;
    @@ -30,7 +48,7 @@ function collectErrors () {
    input: process.stdin,
    output: process.stdout,
    })
    .on('line', function (line) {
    .on("line", function (line) {
    const pathMatch = line.match(pathRe);
    if (pathMatch) {
    current = {
    @@ -46,7 +64,7 @@ function collectErrors () {
    const type = errorMatch[3];
    const name = errorMatch[5];
    const message = errorMatch[4].trim();
    const byLine = (current.errors[ln] || (current.errors[ln] = []));
    const byLine = current.errors[ln] || (current.errors[ln] = []);
    byLine.push({
    ln,
    ch,
    @@ -57,14 +75,14 @@ function collectErrors () {
    }
    }
    })
    .on('close', function () {
    .on("close", function () {
    this.close();
    resolve(result);
    });
    });
    }

    async function processFile ({ path, errors }) {
    async function processFile({ path, errors }) {
    const lines = await readFileLines(path);

    let fixedCount = 0;
    @@ -73,23 +91,19 @@ async function processFile ({ path, errors }) {
    Object.keys(errors).forEach((key) => {
    const ln = Number(key);
    count += errors[ln].length;
    const toSuppress = _(errors[ln])
    .filter(({ name }) => name === 'react-hooks/exhaustive-deps' || name === 'react-hooks/rules-of-hooks');
    const toSuppress = _(errors[ln]).filter(({ name }) => name === "react-hooks/exhaustive-deps" || name === "react-hooks/rules-of-hooks");
    if (!toSuppress.size()) {
    return;
    }

    const line = lines[ln - 1];
    const nameList = toSuppress
    .map('name')
    .uniq()
    .value()
    .join(',');
    const nameList = toSuppress.map("name").uniq().value().join(",");

    lines[ln - 1] =
    fixBareArrayOpening({ line, nameList }) ||
    fixArrayAfterBrace({ line, nameList }) ||
    fixRuleStatement({ line, nameList }) ||
    fixCallbackDef({ line, nameList }) ||
    `${lines[ln - 1]} // eslint-disable-line ${nameList}`;

    fixedCount += toSuppress.size();
    @@ -100,34 +114,42 @@ async function processFile ({ path, errors }) {
    return { path, fixedCount, count };
    }

    function fixBareArrayOpening ({ line, nameList }) {
    function fixBareArrayOpening({ line, nameList }) {
    const match = line.match(/^(\s+)\[/);
    return match && `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

    function fixArrayAfterBrace ({ line, nameList }) {
    const match = line.match(/^(\s*)\},\s*\[/);
    function fixArrayAfterBrace({ line, nameList }) {
    const match = line.match(/^(\s*)},\s*\[/);
    return match && `${match[1]}${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

    function fixRuleStatement ({ line, nameList }) {
    const match = line.match(/^(\s*).*use[A-Z][\w]+(?:<[^>]+>)\(/);
    function fixRuleStatement({ line, nameList }) {
    const match = line.match(/^(\s*).*use[A-Z][\w]+(?:<[^>]+>)?\(/);
    return match && `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

    function fixCallbackDef({ line, nameList }) {
    const match = line.match(/^(\s+)(?:(?:const|let|var)\s+)?\w+\s*=\s*(?:(?:function\s*\([^)]*\)\s*)|(?:\([^)]*\)\s*=>))/);
    return match && `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

    async function readFileLines (path) {
    async function readFileLines(path) {
    return new Promise((resolve) => {
    const lines = [];
    const stream = fs.createReadStream(path, { encoding: 'utf8' });
    readline.createInterface({
    input: stream,
    }).on('line', function (line) {
    lines.push(line);
    }).on('close', function () {
    stream.close();
    this.close();
    lines.push('');
    resolve(lines);
    });
    const stream = fs.createReadStream(path, { encoding: "utf8" });
    readline
    .createInterface({
    input: stream,
    })
    .on("line", function (line) {
    lines.push(line);
    })
    .on("close", function () {
    stream.close();
    this.close();
    lines.push("");
    resolve(lines);
    });
    });
    }
  2. milichev revised this gist Oct 5, 2021. 1 changed file with 46 additions and 47 deletions.
    93 changes: 46 additions & 47 deletions eslint-ignore.js
    Original file line number Diff line number Diff line change
    @@ -20,50 +20,47 @@ collectErrors()

    function collectErrors () {
    return new Promise((resolve, reject) => {
    const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    })
    .on('line', onLine)
    .on('close', onClose);

    const result = [];
    const pathRe = /^(?:\/\w[-.\w]+)+\.(?:js|jsx|ts|tsx)$/;
    const errorRe = /^\s+(\d+):(\d+)\s+(warning|error)\s+(.+)\s+([-@\/\w]+)$/;
    let current;

    function onLine (line) {
    const pathMatch = line.match(pathRe);
    if (pathMatch) {
    current = {
    path: pathMatch[0],
    errors: {},
    };
    result.push(current);
    } else {
    const errorMatch = line.match(errorRe);
    if (errorMatch) {
    const ln = Number(errorMatch[1]);
    const ch = Number(errorMatch[2]);
    const type = errorMatch[3];
    const name = errorMatch[5];
    const message = errorMatch[4].trim();
    const byLine = (current.errors[ln] || (current.errors[ln] = []));
    byLine.push({
    ln,
    ch,
    type,
    message,
    name,
    });
    readline
    .createInterface({
    input: process.stdin,
    output: process.stdout,
    })
    .on('line', function (line) {
    const pathMatch = line.match(pathRe);
    if (pathMatch) {
    current = {
    path: pathMatch[0],
    errors: {},
    };
    result.push(current);
    } else {
    const errorMatch = line.match(errorRe);
    if (errorMatch) {
    const ln = Number(errorMatch[1]);
    const ch = Number(errorMatch[2]);
    const type = errorMatch[3];
    const name = errorMatch[5];
    const message = errorMatch[4].trim();
    const byLine = (current.errors[ln] || (current.errors[ln] = []));
    byLine.push({
    ln,
    ch,
    type,
    message,
    name,
    });
    }
    }
    }
    }

    function onClose () {
    rl.close();
    resolve(result);
    }
    })
    .on('close', function () {
    this.close();
    resolve(result);
    });
    });
    }

    @@ -89,11 +86,10 @@ async function processFile ({ path, errors }) {
    .value()
    .join(',');

    lines[ln - 1] = fixBareArrayOpening({ line, nameList }) ||
    fixRuleStatement({
    line,
    nameList,
    }) ||
    lines[ln - 1] =
    fixBareArrayOpening({ line, nameList }) ||
    fixArrayAfterBrace({ line, nameList }) ||
    fixRuleStatement({ line, nameList }) ||
    `${lines[ln - 1]} // eslint-disable-line ${nameList}`;

    fixedCount += toSuppress.size();
    @@ -106,13 +102,16 @@ async function processFile ({ path, errors }) {

    function fixBareArrayOpening ({ line, nameList }) {
    const match = line.match(/^(\s+)\[/);
    if (match) {
    return `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }
    return match && `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

    function fixArrayAfterBrace ({ line, nameList }) {
    const match = line.match(/^(\s*)\},\s*\[/);
    return match && `${match[1]}${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

    function fixRuleStatement ({ line, nameList }) {
    const match = line.match(/^(\s*).*use[A-Z][\w]+\(/);
    const match = line.match(/^(\s*).*use[A-Z][\w]+(?:<[^>]+>)\(/);
    return match && `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

  3. milichev created this gist Oct 4, 2021.
    134 changes: 134 additions & 0 deletions eslint-ignore.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,134 @@
    /* eslint-disable @typescript-eslint/no-var-requires,no-console */

    const readline = require('readline');
    const fs = require('fs');
    const $path = require('path');
    const _ = require('lodash');
    const { EOL } = require('os');
    const cwd = process.cwd();

    collectErrors()
    .then(errorsByFile => Promise.all(errorsByFile.map(processFile)))
    .then((results) =>
    results
    .filter(Boolean)
    .map(({ path, count, fixedCount }) => `\n${$path.relative(cwd, path)}\n fixed: ${fixedCount} / ${count}`)
    .join(EOL),
    )
    .then(msg => msg && console.log(msg))
    .catch(err => console.error(err));

    function collectErrors () {
    return new Promise((resolve, reject) => {
    const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    })
    .on('line', onLine)
    .on('close', onClose);

    const result = [];
    const pathRe = /^(?:\/\w[-.\w]+)+\.(?:js|jsx|ts|tsx)$/;
    const errorRe = /^\s+(\d+):(\d+)\s+(warning|error)\s+(.+)\s+([-@\/\w]+)$/;
    let current;

    function onLine (line) {
    const pathMatch = line.match(pathRe);
    if (pathMatch) {
    current = {
    path: pathMatch[0],
    errors: {},
    };
    result.push(current);
    } else {
    const errorMatch = line.match(errorRe);
    if (errorMatch) {
    const ln = Number(errorMatch[1]);
    const ch = Number(errorMatch[2]);
    const type = errorMatch[3];
    const name = errorMatch[5];
    const message = errorMatch[4].trim();
    const byLine = (current.errors[ln] || (current.errors[ln] = []));
    byLine.push({
    ln,
    ch,
    type,
    message,
    name,
    });
    }
    }
    }

    function onClose () {
    rl.close();
    resolve(result);
    }
    });
    }

    async function processFile ({ path, errors }) {
    const lines = await readFileLines(path);

    let fixedCount = 0;
    let count = 0;

    Object.keys(errors).forEach((key) => {
    const ln = Number(key);
    count += errors[ln].length;
    const toSuppress = _(errors[ln])
    .filter(({ name }) => name === 'react-hooks/exhaustive-deps' || name === 'react-hooks/rules-of-hooks');
    if (!toSuppress.size()) {
    return;
    }

    const line = lines[ln - 1];
    const nameList = toSuppress
    .map('name')
    .uniq()
    .value()
    .join(',');

    lines[ln - 1] = fixBareArrayOpening({ line, nameList }) ||
    fixRuleStatement({
    line,
    nameList,
    }) ||
    `${lines[ln - 1]} // eslint-disable-line ${nameList}`;

    fixedCount += toSuppress.size();
    });

    await fs.promises.writeFile(path, lines.join(EOL), { encoding: "utf8" });

    return { path, fixedCount, count };
    }

    function fixBareArrayOpening ({ line, nameList }) {
    const match = line.match(/^(\s+)\[/);
    if (match) {
    return `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }
    }

    function fixRuleStatement ({ line, nameList }) {
    const match = line.match(/^(\s*).*use[A-Z][\w]+\(/);
    return match && `${match[1]}// eslint-disable-next-line ${nameList}${EOL}${line}`;
    }

    async function readFileLines (path) {
    return new Promise((resolve) => {
    const lines = [];
    const stream = fs.createReadStream(path, { encoding: 'utf8' });
    readline.createInterface({
    input: stream,
    }).on('line', function (line) {
    lines.push(line);
    }).on('close', function () {
    stream.close();
    this.close();
    lines.push('');
    resolve(lines);
    });
    });
    }