Skip to content

Instantly share code, notes, and snippets.

@n8jadams
Last active July 8, 2021 17:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save n8jadams/a14c5526cb834cfe908ca1f12ce9b336 to your computer and use it in GitHub Desktop.
Save n8jadams/a14c5526cb834cfe908ca1f12ce9b336 to your computer and use it in GitHub Desktop.
Run eslint against your staged changes (Assumes this is placed at the top level of your repo)
const path = require('path')
const { execSync, spawnSync } = require('child_process')
const fs = require('fs')
const micromatch = require('micromatch')
const lockfile = path.resolve(__dirname, 'linter-script-lockfile')
const eslintFileTypes = ['js', 'jsx', 'ts', 'tsx']
function readGlobsFromFile(filename) {
if (!fs.existsSync(filename)) {
return []
}
return fs
.readFileSync(filename, 'utf-8')
.split('\n')
.map((f) => f.trim())
.filter(Boolean)
}
function deleteLockfile() {
fs.unlinkSync(lockfile)
}
function killFunction() {
console.log('Halting the lint/format script. Cleaning up...')
deleteLockfile()
process.exit(0)
}
process.on('SIGINT', killFunction)
process.on('SIGTERM', killFunction)
;(function main() {
// Ensure this script isn't being run already
if (fs.existsSync(lockfile)) {
console.error(
'ERROR! Cannot run linter script if it is already running elsewhere'
)
process.exit(1)
}
fs.writeFileSync(lockfile, '')
// Read in staged files
const gitStatus = execSync('git status --porcelain').toString()
const stagedFiles = []
const partiallyUnstagedCache = []
for (const line of gitStatus.split('\n')) {
const extention = line.split('.').pop()
if (/^A {2}/.test(line)) {
// Staged new file
stagedFiles.push(line.split('A ')[1])
} else if (/^M {2}/.test(line)) {
// Staged modification
stagedFiles.push(line.split('M ')[1])
} else if (/^R {2}/.test(line)) {
// Staged rename
const fileNames = line.replace('R ', '').split(' -> ')
stagedFiles.push(fileNames[1])
} else if (/^(M|A)M /.test(line) && eslintFileTypes.includes(extention)) {
// A partially staged lintable file
// Read in the unstaged filecontents
const filename = line.replace(/^(M|A)M /, '')
const unstagedFileContents = fs.readFileSync(filename, 'utf-8')
partiallyUnstagedCache.push({
filename,
filecontents: unstagedFileContents,
})
// Get the staged contents
const stagedFileContents = execSync(`git show :${filename}`).toString()
// Write the staged contents to the actual file
fs.writeFileSync(path.resolve(__dirname, filename), stagedFileContents)
stagedFiles.push(filename)
}
}
// Filter out eslint ignored files
const eslintIgnoreGlobs = readGlobsFromFile(path.resolve(__dirname, './.eslintignore'))
const stagedLintableFiles = stagedFiles.filter((f) => {
const ext = f.split('.').pop()
return (
eslintFileTypes.includes(ext) && !micromatch.isMatch(f, eslintIgnoreGlobs)
)
})
// Run the eslint command!
if (stagedLintableFiles.length > 0) {
const eslintCmd = `${path.resolve(__dirname, './node_modules/.bin/eslint')} --fix --max-warnings=0 --no-error-on-unmatched-pattern ${stagedLintableFiles.join(
' '
)}`
console.log(eslintCmd)
const eslint = spawnSync(
eslintCmd.split(' ')[0],
eslintCmd.split(' ').slice(1),
{ stdio: 'inherit', shell: true, cwd: __dirname }
)
if (eslint.status !== 0) {
// Restore the partiallyUnstagedCache
partiallyUnstagedCache.forEach(({ filename, filecontents }) => {
fs.writeFileSync(filename, filecontents)
})
deleteLockfile()
process.exit(1)
}
}
// Final step, move the unstaged changes back
partiallyUnstagedCache.forEach(({ filename, filecontents }) => {
fs.writeFileSync(filename, filecontents)
})
deleteLockfile()
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment