Skip to content

Instantly share code, notes, and snippets.

@alizbazar
Last active January 29, 2021 03:51
Show Gist options
  • Save alizbazar/d1b140453f6da35896b66acae0ee8daf to your computer and use it in GitHub Desktop.
Save alizbazar/d1b140453f6da35896b66acae0ee8daf to your computer and use it in GitHub Desktop.
Gradually rolling out eslint rules resulting in many errors in a legacy project
/*
This script is meant to be used with lint-staged receiving changed files as arguments.
It checks if file has "rules-disabled-on" header present with date in the past.
If yes, it reports files that need to be reformatted.
*/
const _ = require('lodash')
const fs = require('fs')
const moment = require('moment')
const errors = []
process.argv.slice(2).forEach(filepath => {
const lines = fs.readFileSync(filepath, 'utf8').split('\n')
const rulesDisabledPattern = /^\/\* rules-disabled-on ([0-9-]+) \*\/$/
const rulesDisabledLineIndex = _.findIndex(lines, line => rulesDisabledPattern.test(line))
if (rulesDisabledLineIndex === -1) {
return
}
const date = rulesDisabledPattern.exec(lines[rulesDisabledLineIndex])[1]
if (moment().format('YYYY-MM-DD') === date) {
return
}
const ruleLinePattern = /^\/\* eslint-disable ([^ ]+) \*\/$/
const rules = lines
.slice(0, rulesDisabledLineIndex)
.filter(line => line.trim())
.map(line => ruleLinePattern.exec(line))
.map(m => m && m[1])
.filter(_.identity)
errors.push({ filepath, rules })
})
const formatError = ({ filepath, rules }) => [`${filepath}:`, ...rules.map(rule => `\t${rule}`)].join('\n')
if (errors.length) {
const rulesReport = errors.map(formatError).join('\n\n')
// eslint-disable-next-line no-console
console.error(`Legacy disabled eslint rules found in:
${rulesReport}
Remove disabled rules and fix the issues before committing!
`)
process.exit(1)
}
process.exit()
/*
This script prepends all JS files containing eslint errors with a header like this:
*/
/* eslint-disable func-names */
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-fallthrough */
/* rules-disabled-on 2019-11-11 */
/*
Run the script from the root of the project directory.
Pass eslint errors JSON file as argument.
Save errors in JSON by running `eslint --format=json-with-metadata`
*/
const { map, filter, orderBy, flow } = require('lodash/fp')
const fs = require('fs')
const path = require('path')
const moment = require('moment')
const errorJSONFilePath = process.argv[2]
const errors = JSON.parse(fs.readFileSync(path.resolve(__dirname, errorJSONFilePath), 'utf8')).results
const getFilesWithErrors = flow(
filter((d) => d.errorCount + d.warningCount > 0),
map(({ filePath, messages }) => {
const rules = messages.reduce((acc, { ruleId }) => {
acc.add(ruleId)
return acc
}, new Set())
return { filePath, rules }
}),
orderBy([(d) => d.rules.size], ['desc'])
)
const constructHeaderFactory = (dateToday) =>
flow(
(rules) => rules.map((rule) => `/* eslint-disable ${rule} */`),
(lines) => [...lines, `/* rules-disabled-on ${dateToday} */`],
(lines) => lines.join('\n')
)
const constructHeader = constructHeaderFactory(moment().format('YYYY-MM-DD'))
const prependFile = (txt, filePath) => {
const content = fs.readFileSync(filePath, 'utf8')
fs.writeFileSync(filePath, `${txt}${content}`)
}
console.log('files with errors', getFilesWithErrors(errors))
// eslint-disable-next-line no-restricted-syntax
for (const file of getFilesWithErrors(errors)) {
const { filePath, rules } = file
const header = constructHeader(Array.from(rules))
prependFile(`${header}\n\n`, filePath)
// eslint-disable-next-line no-console
console.log('wrote', filePath)
}
const formatAndLint = ['node scripts/check-for-legacy-headers.js', 'prettier --write', 'eslint --max-warnings 0 --fix']
module.exports = {
'*.graphql': 'prettier --write',
// unfortunately, tsc cannot be run for specific files and still respect tsconfig :'(
'*.{ts,tsx}': [...formatAndLint, () => 'yarn tsc -p ./tsconfig.json --noEmit'],
'*.{js,jsx}': [...formatAndLint],
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment