Search code examples
typescripteslintpre-commit-hooktypescript-eslintpre-commit.com

typescript-eslint/pre-commit hook (monorepo): Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser


We recently migrated to a monorepo and we are still trying to set everything up. We use typescript and eslint with prettier. The structure is the following:

root
  - common
    - folder_a
      - file_a.ts
  - build_scripts
    - script_a.js
  - project_a
    - components
      - component_a
        - Component.tsx
    - tsconfig.json
    - .eslintrc.json
    - node_modules
    - package.json
  - .prettierignore
  - .prettierrc.yml
  - .eslintignore
  - .eslintrc.base.json
  - tsconfig.base.json
  - node_modules
  - package.json
  - Makefile
  - webpack.config.js
  - .pre-commit-config.yaml

The common folder contains code that is used across multiple projects and isn't considered a project of its own (no tsconfig.json). All sub-projects that contain their own tsconfig.json and .eslintrc.json file, are extending the base ones, as you can see below:

tsconfig.base.json

{
    "compilerOptions": {
        "lib": ["ES2017", "DOM"],
        "target": "ES6",
        "module": "commonjs",
        "baseUrl": ".",
        "sourceMap": true,
        "alwaysStrict": true,
        "noUnusedParameters": true,
        "noUnusedLocals": true,
        "noEmitOnError": true,
        "paths": {
            "@common/*": ["./common/*"]
        }
    },
    // include all projects that don't have their own
    // tsconfig.json file AND need to be compiled/linted
    // => very important because typescript-eslint will
    // otherwise load ALL files in memory for type-checking
    // and consequently eslint will crash with an OOM error
    "include": ["common/**/*"]
}

.eslintrc.base.json (rules omitted for brevity)

{
    "env": {
        "es2020": true,
        "node": true
    },
    "extends": [
        "plugin:destructuring/recommended",
        "airbnb-base",
        "prettier",
        "plugin:prettier/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "project": "./tsconfig.base.json",
        "ecmaVersion": 11,
        "sourceType": "module"
    },
    "plugins": [
        "prettier",
        "@typescript-eslint",
        "destructuring"
    ],
    "rules": {}
}

.eslintignore

*/**/build
Makefile
**/*.js
**/*.json

.pre-commit-config.yaml (taken from the example in https://github.com/pre-commit/pre-commit/issues/466#issuecomment-274282684)

- repo: local
  hooks:
    - id: lint_fix
      name: lint_fix_project_a
      entry: node_modules/.bin/eslint --report-unused-disable-directives --fix -c project_a/.eslintrc.json --ext .ts,.tsx
      files: ^project_a/
      types: [file]
      language: node

package.json

{
  "name": "web",
  "version": "1.0.0",
  "private": true,
  "dependencies": {},
  "devDependencies": {
    "@babel/core": "7.2.2",,
    "@typescript-eslint/eslint-plugin": "4.15.1",
    "@typescript-eslint/parser": "4.15.1",
    "eslint": "7.20.0",
    "eslint-config-airbnb": "18.2.1",
    "eslint-config-airbnb-base": "14.2.0",
    "eslint-config-prettier": "6.10.1",
    "eslint-config-standard": "14.1.1",
    "eslint-import-resolver-typescript": "2.0.0",
    "eslint-plugin-destructuring": "2.2.0",
    "eslint-plugin-import": "2.22.1",
    "eslint-plugin-node": "11.1.0",
    "eslint-plugin-prettier": "3.1.3",
    "eslint-plugin-promise": "4.2.1",
    "eslint-plugin-standard": "4.0.1",
    "eslint-webpack-plugin": "2.5.4",
    "eslint-plugin-jsx-a11y": "6.4.1",
    "eslint-plugin-react": "7.22.0",
    "eslint-plugin-react-hooks": "4.2.0",
    "express": "4.0.0",
    "prettier": "2.1.2",
    "typescript": "3.9.7"
  }
}

project_a/tsconfig.json

{
  "extends": "../tsconfig.base.json",
  "compilerOptions": {
    "lib": ["ES2017", "DOM", "DOM.Iterable"],
    "jsx": "react",
    "baseUrl": ".",
    "paths": {
      "@common/*": ["../common/*"],
      "@components/*": ["./components/*"]
    }
  },
  // overwrite the base "includes"
  "include": ["**/*"],
  "exclude": [
    "**/node_modules/*",
    "**/build/*"
  ]
}

project_a/.eslintrc.json (rules omitted for brevity)

{
    "extends": [
        "plugin:react/recommended",
        "plugin:destructuring/recommended",
        "airbnb",
        "airbnb/hooks",
        "prettier",
        "plugin:prettier/recommended",
        "prettier/react",
        "../.eslintrc.base.json"
    ],
    "env": {
        "browser": true,
        "commonjs": true,
        "node": false
    },
    "parserOptions": {
        "project": "./tsconfig.json",
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "plugins": [
        "prettier",
        "react",
        "@typescript-eslint",
        "destructuring"
    ],
    "rules": {}
}

Bundling project_a with webpack, using ts-loader, eslint-webpack-plugin and project_a/tsconfig.json works perfectly fine. No linting errors reported. Same for VSCode, the vscode-eslint extension doesn't report anything.

Also running the following inside project_a is successful without any linting errors.

../node_modules/.bin/eslint . --ext .ts,.tsx --fix -c .eslintrc.json

However, when changing a file in project_a and trying to stage and commit, the following error is thrown:

/home/web/project_a/components/component_a/Component.tsx

0:0 error Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser. The file does not match your project config: project_a/components/component_a/Component.tsx.

The file must be included in at least one of the projects provided

It makes no sense to me, why this error would only be thrown in that specific use-case, when linting via the pre-commit hook. Also, it's very peculiar, since project_a/tsconfig.json includes all files in that folder (except node_modules and build artifacts).

"include": ["**/*"]

It seems to me it might be related to us having added

"include": ["common/**/*"]

in ./tsconfig.json. That was added (as per the comment), because having a file from common open in VSCode and trying to save some changes, would make eslint crash, since according to various sources, the @typescript-eslint/parser would include all files in memory to gather type information for linting.

All related Github issues just mention that the file in question is just not included, as per the error itslef, but I don't see how this is happening in my scenario.


Solution

  • It turns out that @typescript-eslint/parser resolves the provided tsconfig.json relative to the current working directory, which I missed while reading the docs. Thus, since the pre-commit hook is always run from the root directory, it was using tsconfig.base.json instead of the one in project_a.

    In order to fix this, the following steps need to be taken (according to https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-567365174):

    • rename project_a/.eslintrc.json to project_a/.eslintrc.js
    • add parserOptions.tsconfigRootDir like the following:
    • module.exports = {
        parserOptions: {
          project: './tsconfig.json'
          tsconfigRootDir: __dirname // important option
        }
      }