Last active
January 24, 2022 10:59
-
-
Save milichev/db3ea9fcb2fb086db57c3f2c4f770db2 to your computer and use it in GitHub Desktop.
suppress react-hooks eslint warnings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 | |
*/ | |
/* 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 result = []; | |
const pathRe = /^(?:\/\w[-.\w]+)+\.(?:js|jsx|ts|tsx)$/; | |
const errorRe = /^\s+(\d+):(\d+)\s+(warning|error)\s+(.+)\s+([-@\/\w]+)$/; | |
let current; | |
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, | |
}); | |
} | |
} | |
}) | |
.on("close", function () { | |
this.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 }) || | |
fixArrayAfterBrace({ line, nameList }) || | |
fixRuleStatement({ line, nameList }) || | |
fixCallbackDef({ 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+)\[/); | |
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]+(?:<[^>]+>)?\(/); | |
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) { | |
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); | |
}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment