Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lint-staged ignores tsconfig.json when it called through husky hooks #825

Closed
SerkanSipahi opened this issue Apr 2, 2020 · 34 comments
Closed

Comments

@SerkanSipahi
Copy link

Description

lint-staged ignores tsconfig.json when it called through husky hooks (see "Steps to reproduce")

Steps to reproduce

Create these files (test.js, tsconfig, package.json):

test.ts

export class Test {
  static get observedAttributes() {
    return ['foo'];
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNEXT"
  }
}

package.json

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,ts}": [
      "tsc --noEmit"
    ]
  }
}

Then on command line write:

// everything works fine (it uses tsconfig.json)!
tsc --noEmit

// throw an error because it ignores tsconfig.json
git commit // error TS1056: Accessors are only available when targeting ECMAScript 5 and higher

Environment

  • OS: macOS Catalina
  • Node.js: v13
  • lint-staged: v10.1.0
@iiroj
Copy link
Member

iiroj commented Apr 2, 2020

Can you try explicitly setting the config file for tsc?

"lint-staged": {
    "*.{js,ts}": [
      "tsc -p tsconfig.json --noEmit"
    ]
  }

We have a couple of issues like this, and I can't say for certain what causes it. I assume it might be a wrong working directory.

@iiroj
Copy link
Member

iiroj commented Apr 2, 2020

Also, please post your debug logs by enabling them:

"husky": {
    "hooks": {
      "pre-commit": "lint-staged --debug"
    }
  },

@iiroj
Copy link
Member

iiroj commented Apr 2, 2020

Do you by chance have tsc installed locally in the project, or globally? This might be something that affects it.

@antoinerousseau
Copy link

It's because you get the filenames passed as an argument. Try using the function syntax.
This is what I use:

// lint-staged.config.js
module.exports = {
  "*.{js,jsx}": [
    "eslint --cache --fix",
  ],
  "*.{ts,tsx}": [
    () => "tsc --skipLibCheck --noEmit", 
    "eslint --cache --fix",
  ],
}

@sombreroEnPuntas
Copy link

Similar issue with tsc.
Passing files AND project config is not possible.

When invoked from husky, it does not find the project folder or configuration :$
When invoked from bash, it finds the project folder... but the config prevents passing files as args.

Here's my setup. It works as long as all changes are staged :P

// package.json
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "yarn eslint --quiet --fix",
      "bash -c tsc --noEmit" // notice bash!
    ]
  },
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
      "pre-commit": "lint-staged"
    }
  },
// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  // notice the filters!
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

@mercury-oe
Copy link

same issue

@sombreroEnPuntas thank you! it's working for me

@or2008
Copy link

or2008 commented Nov 14, 2020

adding bash fixed it for me, thanks @sombreroEnPuntas

 "*.{ts,tsx}": [
    "npm run lint",
    "bash -c 'npm run check-types'"
],

@ivancuric
Copy link

@antoinerousseau thanks! using it in a js file with a function syntax works.
Hardcoding bash is not really a good solution for crossplatform development.

@SerkanSipahi
Copy link
Author

Thank you all 👍

@iiroj
Copy link
Member

iiroj commented Nov 22, 2020

Seems to have been solved. If someone wants to open a PR about adding this to the README, we'd appreciate it!

@iiroj iiroj closed this as completed Nov 22, 2020
orlando added a commit to debtcollective/packages that referenced this issue Dec 23, 2020
@ikushlianski
Copy link

The bash solution helped me. Please don't forget to add single qoutes around the command itself (after bash -c)

"*.{ts,tsx}": [
    "npm run lint",
    "bash -c 'npm run check-types'"
]

@stephanoparaskeva
Copy link

@sombreroEnPuntas
basch -c tsc --noEmit I think this means that it's not only running tsc on the staged files but the whole project right?

@jantimon
Copy link

@stephanoparaskeva https://github.com/gustavopch/tsc-files fixes this

@mad-gav
Copy link

mad-gav commented Jul 2, 2021

I've tried lots of different ways to get this working on a project (gatsby)

"lint-staged": {
    "*/**/*.{js,ts,tsx}": [
      "yarn type-check",
      "eslint"
    ]

but when i run this i get weird react native errors when the project isn't react native

node_modules/@types/react-native/globals.d.ts(40,15): error TS2300: Duplicate identifier 'FormData'.
node_modules/@types/react-native/globals.d.ts(85,5): error TS2717: Subsequent property declarations must have the same type.  Property 'body' must be of type 'BodyInit', but here has type 'string | ArrayBuffer | DataView | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | ... 6 more ... | FormData'.
node_modules/@types/react-native/globals.d.ts(112,14): error TS2300: Duplicate identifier 'RequestInfo'.
node_modules/@types/react-native/globals.d.ts(131,13): error TS2403: Subsequent variable declarations must have the same type.  Variable 'Response' must be of type '{ new (body?: BodyInit, init?: ResponseInit): Response; prototype: Response; error(): Response; redirect(url: string, status?: number): Response; }', but here has type '{ new (body?: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | ... 6 more ... | FormData, init?: ResponseInit): Response; prototype: Response; error: () => Response; redirect: (url: string, status?: number) => Response; }'.
node_modules/@types/react-native/globals.d.ts(207,5): error TS2717: Subsequent property declarations must have the same type.  Property 'abort' must be of type 'ProgressEvent<XMLHttpRequestEventTarget>', but here has type 'ProgressEvent<EventTarget>'.
node_modules/@types/react-native/globals.d.ts(208,5): error TS2717: Subsequent property declarations must have the same type.  Property 'error' must be of type 'ProgressEvent<XMLHttpRequestEventTarget>', but here has type 'ProgressEvent<EventTarget>'.
node_modules/@types/react-native/globals.d.ts(209,5): error TS2717: Subsequent property declarations must have the same type.  Property 'load' must be of type 'ProgressEvent<XMLHttpRequestEventTarget>', but here has type 'ProgressEvent<EventTarget>'.
node_modules/@types/react-native/globals.d.ts(210,5): error TS2717: Subsequent property declarations must have the same type.  Property 'loadend' must be of type 'ProgressEvent<XMLHttpRequestEventTarget>', but here has type 'ProgressEvent<EventTarget>'.
node_modules/@types/react-native/globals.d.ts(211,5): error TS2717: Subsequent property declarations must have the same type.  Property 'loadstart' must be of type 'ProgressEvent<XMLHttpRequestEventTarget>', but here has type 'ProgressEvent<EventTarget>'.
node_modules/@types/react-native/globals.d.ts(212,5): error TS2717: Subsequent property declarations must have the same type.  Property 'progress' must be of type 'ProgressEvent<XMLHttpRequestEventTarget>', but here has type 'ProgressEvent<EventTarget>'.
node_modules/@types/react-native/globals.d.ts(213,5): error TS2717: Subsequent property declarations must have the same type.  Property 'timeout' must be of type```

any ideas?

@dbgordon09
Copy link

I managed to get around this by emulating the config with the appropriate flags for the tsc command.

const tscFlags = [
  "--target es5",
  "--allowJs",
  "--skipLibCheck",
  "--strict",
  "--forceConsistentCasingInFileNames",
  "--noEmit",
  "--esModuleInterop",
  "--module esnext",
  "--moduleResolution node",
  "--resolveJsonModule",
  "--isolatedModules",
  "--noImplicitAny",
  "--jsx preserve",
];

module.exports = {
  "**/*.ts?(x)": (filenames) =>
    `tsc ${tscFlags.join(" ")} ${filenames.map((fN) => `"${fN}"`).join(" ")}`,
  "**/*.(ts|js)?(x)": () => [
    "prettier --ignore-unknown --write",
    "npm run lint",
  ],
};

Not the most elegant solution😅 But works like a charm telling the TypeScript compiler to only compile files that are staged, while mapping onto the output of your actual tsconfig.json.

Also! You'll notice the string escaping of the file names. 👀 That is to get around issues on Windows where paths to the files contain spaces. (In my case my username)

@haixiangyan
Copy link

I know it's little bit late for this issue, but I want to share my solution for this problem.

First of all, there are only 2 ways to use tsc to do type checking:

  • tsc xxx.ts --noEmit only for some specific files
  • tsc -p ./tsconfig.json for those files that defined in the include field

When you use lint-staged, it will append the key (which is like **/*.ts) to your command, it would be like this tsc --noEmit **/.*.ts, which is perfectly fine for the first case I said.

But here's the thing: if you only changing one file like people.ts that has the import syntax: import Button from 'button', and the command will looks like this tsc --noEmit people.ts, and it will ignore the button.ts file! That would cause the problem even if you know it was right.

Some people would suggest the second case to do the type checking, like changing the setting to be:

'**/*.{ts,tsx}': [
  () => "tsc-files --noEmit -p tsconfig.json",
  "eslint --cache --fix",
],

So that it would ignore the **/*.ts pattern, but tsc would scan the whole project to slow down your commit.

It seems like this is a dilemma. I would suggest putting the tsc -p tsconfig.json --noEmit --skipLibCheck in pre-push using husky:

husky add .husky/pre-push
# pre-push
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

tsc -p ./tsconfig.json --noEmit --skipLibCheck

@souljorje
Copy link

Yet another 🚲 which allows to run tsc only on staged files.

package.json

"scripts": {
	"lint-staged": "lint-staged"
},
"devDependencies": {
	"husky": "^7.0.4",
	"lint-staged": "^12.3.4",
	"vue-tsc": "^0.31.4"
}

vue-tsc is like tsc, but also for .vue type cheking
why lint-staged is in scripts? see below.

.lintstagedrc.js

module.exports = {
  "*.{ts,vue}": [
    () => "./node_modules/.bin/vue-tsc --noEmit",
    "./node_modules/.bin/eslint -f stylish --fix"
  ]
}

.husky/pre-commit

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# 🚲 stash unstaged changes so vue-tsc won't cover 'em, then pop 'em after
# https://stackoverflow.com/a/71150883/12496886
# https://github.com/okonet/lint-staged/issues/825
unstaged_files=$(git diff --name-only | tr '\n' ' ');

if [[ -n $unstaged_files ]]; then
    (git stash push $unstaged_files && NODE_ENV=production npm run lint-staged && git stash pop) || git stash pop #handle error
else
    NODE_ENV=production npm run lint-staged
fi

For some reason lint-staged instead of npm run lint-staged throws an error lint-staged: command not found.

⚠️ use it on your risk

@iiroj
Copy link
Member

iiroj commented Feb 18, 2022

@souljorje your shell script can't find the lint-staged executable automatically from inside node_modules/. You can probably just use npx lint-staged instead of needing the package.json script. 👍

@shikelong
Copy link

I use lint-staged in a monorepo. I have set tsc cli's parameters manually. But when I trigger lint-staged check. tsc can not read My custom global.d.ts's configuration.

This is my lint-staged.config.js

image

image

This is my global.d.ts file.
image

How to fix it? thanks.

@msudgh
Copy link

msudgh commented Oct 18, 2022

@shikelong I suggest a project-based type-check instead of checking files individually, as they're dependent.

#825 (comment)

@fatlinesofcode
Copy link

fatlinesofcode commented Dec 1, 2022

const getTscFlags = () => {
  const compilerOptions = {
    allowJs: true,
    allowSyntheticDefaultImports: true,
    esModuleInterop: true,
    isolatedModules: true,
    jsx: 'react-native',
    lib: ['es2017'],
    types: ['react-native', 'jest'],
    moduleResolution: 'node',
    noEmit: true,
    target: 'esnext',
    skipLibCheck: true,
    resolveJsonModule: true
  }

  return Object.keys(compilerOptions)
    .flatMap(key => {
      const value = compilerOptions[key]
      if (Array.isArray(value)) {
        return `${key} ${value.join(',')}`
      }
      if (typeof value === 'string') {
        return `${key} ${value}`
      }
      return key
    })
    .map(key => `--${key}`)
    .join(' ')
}

module.exports = {
  '*.{js,jsx}': ['eslint --fix', 'jest -u --bail --findRelatedTests'],
  '*.{ts,tsx}': [`tsc ${getTscFlags()}`, 'eslint --fix', 'jest -u --bail --findRelatedTests']
}

@limuyao1996
Copy link

"I would suggest putting the tsc -p tsconfig.json --noEmit --skipLibCheck in pre-push using husky"

I think Using Husky will alse scan the whole project.

@1071125093
Copy link

@sombreroEnPuntas it works , nice bro!!!!!!

@benBenXuChao
Copy link

我在 monorepo 中使用 lint-staged。我已经手动设置了 tsc cli 的参数。但是当我触发 lint-staged 检查时。tsc 无法读取我的自定义 global.d.ts 的配置。

这是我的 lint-staged.config.js

图像

图像

这是我的 global.d.ts 文件。 图像

如何解决?谢谢。
Has this problem been solved

@benBenXuChao
Copy link

这个问题解决了吗

sitogi added a commit to sitogi/vite-react-boilerplate that referenced this issue Apr 10, 2023
- Executing via bash to load tsconfig.json in lint-staged
- see: lint-staged/lint-staged#825 (comment)
hanbyul-here added a commit to NASA-IMPACT/veda-ui that referenced this issue Jan 22, 2024
Fixed the problem (ts check does not scope the type check to the staged
files. ex. throwing module not found error if the module is imported in
a staged file, but not added to the stage) of ts-check in precommit hook
by following:
lint-staged/lint-staged#825 (comment)
@lvjiaxuan
Copy link

package.json:

"lint-staged": {
  "*": ["bash -c \"pnpm run typecheck\"", "eslint --fix"]
},

@lfuelling
Copy link

When using bash -c, make sure that you put the params into quotes, for example the code shared by @sombreroEnPuntas (bash -c tsc --noEmit) causes the --noEmit to be passed to bash instead of tsc.

@zdenkolini
Copy link

don't use bash -c, it will run tsc on your whole project instead of just the staged files.
this happens as the additional file arguments are omitted from the tsc options, and it then executes tsc on your whole project. this can take quite some time if your project has multiple hundreds of .ts|.tsx files.
installing tsc-files and using it instead of tsc works as it uses the projects tsconfig.json, and accepts a list of files to run tsc on.

TLDR;
throw a thumbs up (👍) on this issue microsoft/TypeScript#27379, and use tsc-files instead of tsc in the mean time

detailed explanation:
to get this to work with only tsc the ideal config (in a perfect world) should be:

tsc --project tsconfig.json --noEmit src/staged-file.ts src/other-staged-file.ts

where the src/staged-file.ts and src/other-staged-file.ts are provided by lint-staged.
this is not possible because when running the command, you will get the following error:

error TS5042: Option 'project' cannot be mixed with source files on a command line.

to circumvent this, you need to provide your tsconfig.json options as CLI arguments:

tsc --noEmit --strict --baseUrl "." --paths { "app/*": ["./src/app/*"], # ...

it's not feasible to maintain such code, so the remaining options are either writing a script in your lint-staged.config.js file to parse your tsconfig.json and generate the above mentioned CLI arguments.
or just use the tsc-files package and wait for microsoft/TypeScript#27379 to be resolved.

@MartinX3
Copy link

I use quarkus with the default vite-plugin-checker system for eslint and vue-tsc.
It does live scanning of changes and just works.

@ehtishamsajjad
Copy link

it's not feasible to maintain such code, so the remaining options are either writing a script in your lint-staged.config.js file to parse your tsconfig.json and generate the above mentioned CLI arguments. or just use the tsc-files package and wait for microsoft/TypeScript#27379 to be resolved.

@zdenkolini Thank you for sharing tsc-files, it works perfectly in my project.

@lveillard
Copy link

lveillard commented Jun 10, 2024

in my case it fails running it in folders as npx lint-staged

this is my .lintstagedrc.json file

{
  "*.{js,jsx,ts,tsx}": ["eslint --fix", "eslint"],
  "**/*.ts?(x)": "pnpm run types",
  "*.json": ["prettier --write --ignore-path .gitignore"]
}

when running pnpm run types, it reads node_modules even if they are excluded in the tsconfig at the same level as the .lintstagedrc.json file. So the types check fail.

Obviously running pnpm run types in the same directory directly does work

using last version: ^15.2.5",


adding

  "**/*.ts?(x)": "pnpm run types --skipLibCheck",

which is already in my tsconfig fixes it for me, for some reason

@raythurnvoid
Copy link

raythurnvoid commented Jul 4, 2024

Best option is to use sh -c 'tsc --noEmit' or bash -c 'tsc --noEmit'. It works on Windows as well.

It type checks all the files even non-staged ones, but that's fine, a change in a file may break type checking in some other file.

    "*.{js,ts,tsx,jsx}": [
      "sh -c 'tsc --noEmit'",
      "eslint --fix",
      "prettier --write"
    ]

@lucianobarauna
Copy link

@zdenkolini
I solved it by following the ts-files solution. We are not using NextJS, our project uses vite.
Thanks!

@domnantas
Copy link
Contributor

domnantas commented Oct 4, 2024

I would suggest not using tsc-files together with lint-staged, it is very likely going to bite you in the ass. Prettier and eslint with file list makes sense, because those actions are not dependent on other files. Typechecking is different. Consider this:

// types.ts
export type User = {
  name: string;
}
// user.ts
import { User } from './types'

const user: User = {
  name: 'John Doe'
}

Now let's say you make some changes to types.ts:

// types.ts
export type User = {
-  name: string;
+  fullName: string;
}

lint-staged with tsc-files will only check the types.ts, it will not throw the error, even though types in user.ts are no longer correct. I would suggest typechecking the whole project instead. It seems there are two methods to do that with lint-staged:

  1. via bash to ignore the file list passed by lint-staged (probably not great for cross-platform):
// package.json
"lint-staged": {
    "*.{js,jsx,ts,tsx}": "bash -c 'tsc --noEmit'"
},
  1. via lint-staged.config.js (requires additional file):
// lint-staged.config.js
export default {
  '**/*.ts?(x)': () => 'tsc --noEmit', // notice the empty function parameters and not passing the file list to the command
}

tsc is pretty fast, even in large projects. If you're running into tsc performance issues, check out this page: https://github.com/microsoft/TypeScript/wiki/Performance

epineapple added a commit to pineapple-electric/pe-sqlite-for-rxdb that referenced this issue Oct 14, 2024
lint-staged passed a list of files to tsc.  tsc cannot find its
configuration when this happens.  For the workaround, see
lint-staged/lint-staged#825.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests