How Do I Run Prettier Only on Files That I Want to Commit?
Asked Answered
H

7

30

Using Husky, I've set up my package.json with a precommit hook so that my JavaScript code is formatted using Prettier before every commit:

{
  "name": "prettier-demo",
  "scripts": {
    "precommit": "prettier --write **/*.js && git add ."
  },
  "devDependencies": {
    "husky": "^0.14.3",
    "prettier": "^1.8.2"
  }
}

This works fine, but there are two drawbacks:

  1. If I have a large project with thousands of JavaScript files, I have to wait for Prettier to process all of them, even if only a few have changed; this can take very long and gets on my nerves quickly when it is done with every commit

  2. Sometimes I want to stage just a couple of files for committing, leaving other changes out of the commit; because I do a git add . after running Prettier, all my changes will always end up in the commit

How can I run Prettier before every commit only on the files that have been staged, ignoring unstaged or unchanged files?

Hollishollister answered 2/12, 2017 at 16:51 Comment(0)
S
34

You can do that using lint-staged:

Linting makes more sense when running before committing your code. By doing that you can ensure no errors are going into repository and enforce code style. But running a lint process on a whole project is slow and linting results can be irrelevant. Ultimately you only want to lint files that will be committed.

This project contains a script that will run arbitrary npm and shell tasks with a list of staged files as an argument, filtered by a specified glob pattern.

Install lint-staged and husky, which is required for pre-commit hooks, with this command:

npm install --save-dev lint-staged husky

Change your package.json as follows:

{
  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "*.js": [
      "prettier --write",
      "git add"
    ]
  }
}
Seventeenth answered 2/12, 2017 at 17:29 Comment(8)
@PatrickHund I agree, but I also like your hands-on attitude :) You can still undelete your answer if you want. – Seventeenth
No, that's fine. Another dev on my team had actually set the precommit up with lint-staged, but it didn't work properly, because he put "git add ." in the config instead of "git add". So I wrote that bash script, could have just removed the dot and be done with it πŸ˜€ – Hollishollister
I did this with prettier-eslint, same exact syntax as answer. "lint-staged": { "*.{js,less,json}": [ "prettier-eslint --write", "git add" ] }, – Forbearance
I have also seen a 'precise-commits' repo, stating it only prettifies the parts of file staged i.e. staged hunks and not the entire file. What do you think about this? I haven't tried it yet. github.com/nrwl/precise-commits – Galibi
@AmirEldor I think that is a bad idea as this could lead to inconsistent formatting within a single file. – Seventeenth
@str, true, however, it might prevent accidental merge conflicts if two people prettify the same file as a whole in two branches (unless git is smart about similar lines from two branches). Also, git annotate info would be preserved rather than having the same person annotated on the whole file after they prettify it. – Galibi
@AmirEldor Git is smart enough to handle that without merge conflicts. I would rather have messy annotations than messy code. But that is up to you ;) – Seventeenth
Is there a way to run linters only against newly created (added) files? #56540744 – Gromwell
E
25

I found that just running:

prettier --write $(git diff --name-only --diff-filter d | grep '\.js$' | xargs)

was quite enough for my needs, just made an alias and used that.

Elwina answered 30/6, 2020 at 17:20 Comment(7)
that actually returned some results that had .js in the path. this worked better for me: git diff --name-only | grep '\.ts$' – Epencephalon
also I just learned that this will fail when a file is removed. Prettier will try to format the removed file. To make it work use prettier --write $(git diff --name-only --diff-filter d | grep '\.ts$' | xargs) – Downdraft
Thanks @NitsanBaleli I updated the message to include your grep match fix and filter deleted fixes ! – Elwina
If you're working on a TypeScript project, this will match .js, .ts, .jsx and .tsx: prettier --write $(git diff --name-only --diff-filter d | grep -e '\.[tj]sx\?$' | xargs) – Malversation
Does anyone know if there's a way to make prettier --write pass if there is no file/dir/glob arguments supplied to it? In this situation this is caused by there being no output from the git diff command – Fencer
Note that this will only take unstaged files, you can switch with this command unstaged:git diff, staged:git diff --staged, both:git diff HEAD – Vladimir
You could use --cached for staged files. – Shanteshantee
A
9

I used this package pretty-quick

and then added the following package in my package.json

"pretty-quick": "pretty-quick" 

Then in my pre-commit hook under .husky/pre-commit,

I added

`npm run pretty-quick`
Alpers answered 6/7, 2021 at 16:26 Comment(2)
Sadly, pretty-quick doesn't receiving any updates for several years and dosen't work with prettier >= 3.x.x – Unzip
pretty-quick now works again for Prettier v3. github.com/prettier/pretty-quick/issues/… – Queensland
G
6

If you don't want to add the devDependency lint-staged you can also do the same with a Bash script:

#!/usr/bin/env bash
# chmod +x this and save in your PATH. Assumes `prettier` is in your `devDependencies` already
BASE=$(git merge-base master HEAD) # change master to whatever your trunk branch is
FILES=$(git diff --name-only $BASE HEAD | xargs)

npx prettier --list-different $FILES

# Want eslint too?
# npx eslint --ignore-path=.prettierignore $FILES
Groningen answered 2/7, 2019 at 1:15 Comment(0)
F
4

The prettier docs has a section on this.

I use pretty-quick

npx husky-init
npm install --save-dev pretty-quick
npx husky set .husky/pre-commit "npx pretty-quick --staged"
Firecure answered 22/5, 2022 at 8:54 Comment(0)
V
1

We can construct such command in three steps:

  1. Take all changed files using git diff

    • git diff --name-only --diff-filter d <- only unstaged files
    • git diff --name-only --diff-filter d --staged <- only staged files
    • git diff --name-only --diff-filter d HEAD <- both

    note:

    • --name-only only file paths, without diffs (absolute to project root)
    • --relative switch paths to be relative to current directory instead
    • --diff-filter=d excludes deleted files, otherwise prettier fails
  2. Filter out files by extension

    note: under windows it is not possible to use "or" in grep -E '(json|css)', but it is possible to get the same behavior with additional -e PATTERN commands.

    • grep -e '\.[jt]sx\?$' -e '\.css$' -e '\.json$' to match js|jsx|ts|tsx, css and json

    • grep -e 'src.*\.[jt]sx\?$' use src.* to specify directory

  3. Put that all together

    • -u With --ignore-unknown (or -u), prettier will ignore unknown files matched by patterns.

    • npx prettierΒ will execute prettier from local node_modules. To run global prettier, use just prettier <command>

npx prettier -u --write $(git diff --name-only --diff-filter=d HEAD | grep '\.js$' | xargs)

For Windows users using UnxUtils or Git for Windows:

  • npx.cmd prettierΒ will execute prettier from local node_modules. To run global prettier, use just prettier <command>
git diff --name-only --diff-filter=d HEAD | grep -e '\.js$' | xargs npx.cmd prettier -u --write

When used as a script in package.json, you can omit npx

"scripts": {
  "format:changed": "git diff --name-only --diff-filter=d HEAD | grep -e 'src.*\\.[jt]sx\\?$' -e 'src.*\\.json$' -e 'src.*\\.css$' -e 'src.*\\.md$' | xargs prettier -u --write"
}
Vladimir answered 15/9, 2023 at 23:12 Comment(0)
R
0

You can try this out this only checks the modified files and performs eslint, prettier and adds it to git.

Below code works for me:

const { resolve } = require("path");
const { ESLint } = require("eslint");

module.exports = {
  "./src/**/*.{ts,js,json}": async (fileNames) => {
    const escapedFileNames = fileNames.map((fileName) => resolve(fileName)).join(" ");
    const filesToLint = await removeIgnoredFiles(fileNames);

    return [
      `eslint --max-warnings=0 --fix ${filesToLint}`,
      `prettier --relative --write ${escapedFileNames}`,
      `git add ${escapedFileNames}`,
    ];
  },
};

async function removeIgnoredFiles(files) {
  const eslint = new ESLint();
  const isIgnored = await Promise.all(files.map((file) => eslint.isPathIgnored(file)));
  return files.filter((_, i) => !isIgnored[i]).join(" ");
}
Roshan answered 22/3 at 10:3 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.