Skip to content

Instantly share code, notes, and snippets.

@dahjelle
Created July 13, 2016 16:48
Show Gist options
  • Save dahjelle/8ddedf0aebd488208a9a7c829f19b9e8 to your computer and use it in GitHub Desktop.
Save dahjelle/8ddedf0aebd488208a9a7c829f19b9e8 to your computer and use it in GitHub Desktop.
Pre-commit hook for eslint, linting *only* staged changes.
#!/bin/bash
for file in $(git diff --cached --name-only | grep -E '\.(js|jsx)$')
do
git show ":$file" | node_modules/.bin/eslint --stdin --stdin-filename "$file" # we only want to lint the staged changes, not any un-staged changes
if [ $? -ne 0 ]; then
echo "ESLint failed on staged file '$file'. Please check your code and try again. You can run ESLint manually via npm run eslint."
exit 1 # exit with failure status
fi
done
@artem-litvinov
Copy link

@SalTor take a look at this. It works well for me.

@suyanhanx
Copy link

Thank you very much.

@jancimajek
Copy link

One-liner version of the above:

git diff --diff-filter=d --cached --name-only | grep -E '\.(js|jsx)$' | xargs -I % sh -c 'git show ":%" | eslint --stdin --stdin-filename "%";'

@richistron
Copy link

you can do this in one line git diff --cached --name-only | grep ".js$" | xargs ./node_modules/.bin/eslint

@Brantron
Copy link

nice, @richistron

@luuuis
Copy link

luuuis commented May 11, 2018

@jancimajek's one-liner is great but does not handle spaces (and other funny chars) in the filename. Using git diff -z with xargs -0 handles that nicely: luuuis/pre-commit.sh.

The @richistron one liner will lint the changed files in the local checkout, which is not necessarily what is being committed to Git if you have not staged every hunk in a file.

@TNT-Likely
Copy link

thanks

@sampathBlam
Copy link

Thanks @dahjelle.

@lifenstein
Copy link

The scripts above works well enough for a handful of changes, but for large projects when things like a project-wide search & replace is performed, it takes ages because the files are sent one by one to eslint. Running over the whole project only takes 20 seconds, but the commit hook takes 4 minutes! Is there a way to batch the files instead of calling eslint for every file one by one?

@mihow
Copy link

mihow commented Feb 4, 2020

This is great! @lifenstein That seems like a less common case, so when it happens you could run git commit --no-verify to skip the 4 minute check and then run eslint directly. For most commits this will be much faster than linting the entire project every time.

@Alynva
Copy link

Alynva commented Feb 10, 2020

One-liner version of the above:

git diff --diff-filter=d --cached --name-only | grep -E '\.(js|jsx)$' | xargs -I % sh -c 'git show ":%" | eslint --stdin --stdin-filename "%";'

I had to change the @jancimajek version a little bit, because % is reserved in my shell and eslint is not global, so:

git diff --diff-filter=d --cached --name-only | grep -E '\.(js|jsx)$' | xargs -I A sh -c 'git show ":A" | .\\node_modules\\.bin\\eslint --stdin --stdin-filename "A";'

@mholtzhausen
Copy link

Thank you to OP -- very useful. I adapted and my lint-staged.sh file looks like this:

#!/bin/bash
clear
fileList=$(git diff --diff-filter=d --cached --name-only | grep -E '\.(js|vue)$')
if [ ${#fileList} -lt 1 ]; then
    echo -e "You have no staged .js or .vue files to test\n"
    exit
fi
npx eslint ${fileList[*]} "$@"
if [ $? -ne 0 ]; then
    echo -e "\nPlease fix the above linting issues before committing.\n"
    exit 1
fi

Benefits are:

  • using npx allows running local or global eslint
  • Passing the file list to eslint limits this to a single run -- so eslint only has to start up once, and not once for every file
  • It only exits after all staged files have been tested
  • Has an out for when no files are staged
  • allows you to configure eslint by adding arguments to shell command. ie. ./lint-staged.sh --fix

@kuladeeparun
Copy link

Thanks @nemesarial! This works like a charm

@cowlicks
Copy link

This does not lint "only staged changes" it lints each file that is changed. Which is annoying because it can block your commit for stuff that isn't even in the commit! Eslint should just have a --diff options like flake8

@aperkaz
Copy link

aperkaz commented Mar 11, 2021

There is an cleaner way to accomplish this, with husky:

// package.json
...
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,ts,tsx}": [
      "prettier --write",
      "eslint --ext .ts,.tsx,.js --fix"
    ]
  },

@bartenra
Copy link

@Mouvedia
Copy link

@zhyd1997
Copy link

zhyd1997 commented Oct 3, 2021

@aperkaz

LGTM, but maybe eslint should be run before prettier.

Note: If you use ESLint, make sure lint-staged runs it before Prettier, not after.
ref: https://prettier.io/docs/en/install.html#git-hooks

@swateek
Copy link

swateek commented May 9, 2022

There is an cleaner way to accomplish this, with husky:

// package.json
...
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,ts,tsx}": [
      "prettier --write",
      "eslint --ext .ts,.tsx,.js --fix"
    ]
  },

this worked for me & I also took @zhyd1997's suggestion

@Naveen9453
Copy link

  },

@aperkaz
Where exactly i need to place above lines inside package.json file.

@aperkaz
Copy link

aperkaz commented Jul 19, 2023

@Naveen9453 after the dependencies section is a good place, although I tend to locate it at the end of the file. No strict placement requirements though.

@timheilman
Copy link

package.json configuration of husky no longer works, by design.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment