lint-staged ignores tsconfig.json when it called through husky hooks
SerkanSipahi opened this issue · comments
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
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.
Also, please post your debug logs by enabling them:
"husky": {
"hooks": {
"pre-commit": "lint-staged --debug"
}
},
Do you by chance have tsc
installed locally in the project, or globally? This might be something that affects it.
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",
],
}
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"]
}
same issue
@sombreroEnPuntas thank you! it's working for me
adding bash fixed it for me, thanks @sombreroEnPuntas
"*.{ts,tsx}": [
"npm run lint",
"bash -c 'npm run check-types'"
],
@antoinerousseau thanks! using it in a js file with a function syntax works.
Hardcoding bash is not really a good solution for crossplatform development.
Thank you all 👍
Seems to have been solved. If someone wants to open a PR about adding this to the README, we'd appreciate it!
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'"
]
@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?
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?
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)
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 filestsc -p ./tsconfig.json
for those files that defined in theinclude
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
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
.
@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 I suggest a project-based type-check instead of checking files individually, as they're dependent.
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']
}
"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.
@sombreroEnPuntas it works , nice bro!!!!!!
这个问题解决了吗
package.json:
"lint-staged": {
"*": ["bash -c \"pnpm run typecheck\"", "eslint --fix"]
},
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.