angulareslinttypescript-eslintangular-eslint

Why eslint checks index.html file?


Why eslint checks index.html file?

I'm Trying to lint an analogjs project created with nx monorepo with this eslint config (i know what the problem in that config because if i delete path to the config from extends array of my root eslint config everything works fine).


Getting an error

ESLint: 9.12.0

TypeError: Cannot read properties of undefined (reading 'at')
Occurred while linting C:\Users\sergd\work\pohorony.by\pohorony.by\packages\analog-app\index.html:2
Rule: "@typescript-eslint/ban-ts-comment"
    at Program (C:\Users\sergd\work\pohorony.by\pohorony.by\node_modules\@typescript-eslint\eslint-plugin\dist\rules\ban-ts-comment.js:118:50)
    at ruleErrorHandler (C:\Users\sergd\work\pohorony.by\pohorony.by\node_modules\eslint\lib\linter\linter.js:1084:48)

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>MyApp</title>
    <base href="/" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
    <link rel="stylesheet" href="/src/styles.css" />
  </head>
  <body>
    <analog-app-root></analog-app-root>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

eslint.base.json

{
  "env": { "browser": true, "es2021": true, "node": true },
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "./.eslintrc.angular.json", "prettier"],
  "ignorePatterns": ["*dist*"],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "project": true,
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "overrides": [
    {
      "files": ["*.ts"],
      "rules": {
        "@typescript-eslint/array-type": ["error", { "default": "array" }],
        "@typescript-eslint/explicit-function-return-type": "error",
        "@typescript-eslint/lines-between-class-members": "off",
        "@typescript-eslint/no-explicit-any": "off",
        "@typescript-eslint/no-non-null-assertion": "off",
        "@typescript-eslint/no-unnecessary-type-assertion": "error",
        "@typescript-eslint/no-unused-vars": "off",
        "block-spacing": ["warn", "always"],
        "curly": ["warn", "all"],
        "eol-last": "error",
        "import/prefer-default-export": "off",
        "import/no-unresolved": "error",
        "max-len": [
          "error",
          {
            "code": 120,
            "ignoreComments": true,
            "tabWidth": 2,
            "ignoreStrings": true
          }
        ],
        "max-lines-per-function": ["error", 80],
        "max-classes-per-file": "off",
        "no-console": "off",
        "no-debugger": "off",
        "no-multiple-empty-lines": ["error", { "max": 2, "maxBOF": 0, "maxEOF": 1 }],
        "no-param-reassign": ["error", { "props": false }],
        "no-plusplus": "off",
        "no-return-assign": 1,
        "no-restricted-syntax": ["off", "ForInStatement"],
        "no-underscore-dangle": "off",
        "no-template-curly-in-string": "warn",
        "no-unused-vars": "off",
        "object-curly-spacing": ["warn", "always"],
        "spaced-comment": ["error", "always", { "markers": ["!", "?", "//", "todo", "*"] }],

        "class-methods-use-this": "off",
        "@typescript-eslint/no-inferrable-types": "off",
        "@typescript-eslint/explicit-member-accessibility": [
          "error",
          {
            "accessibility": "explicit",
            "overrides": {
              "accessors": "explicit",
              "constructors": "off",
              "methods": "explicit",
              "properties": "explicit",
              "parameterProperties": "explicit"
            }
          }
        ]
      }
    },
    {
      "files": ["*.spec.ts", "*.test.ts"],
      "rules": {
        "max-lines-per-function": "off"
      }
    }
  ]
}


.eslintrc.angular.json

{
  "overrides": [
    {
      "files": ["*.ts"],
      "extends": ["plugin:@angular-eslint/template/process-inline-templates"],
      "rules": {
        "@angular-eslint/contextual-lifecycle": "error",
        "@angular-eslint/no-async-lifecycle-method": "error",
        "@angular-eslint/sort-lifecycle-methods": "error",
        "@angular-eslint/no-attribute-decorator": "warn",
        "@angular-eslint/component-max-inline-declarations": [
          "error",
          {
            "template": 10,
            "styles": 0,
            "animations": 40
          }
        ],
        "@angular-eslint/consistent-component-styles": "error",
        "@angular-eslint/contextual-decorator": "error",
        "@angular-eslint/component-class-suffix": "error",
        "@angular-eslint/directive-class-suffix": "error",
        "@angular-eslint/no-conflicting-lifecycle": "error",
        "@angular-eslint/no-duplicates-in-metadata-arrays": "error",
        "@angular-eslint/no-empty-lifecycle-method": "error",
        "@angular-eslint/no-input-rename": "error",
        "@angular-eslint/no-inputs-metadata-property": "error",
        "@angular-eslint/no-lifecycle-call": "error",
        "@angular-eslint/no-output-native": "error",
        "@angular-eslint/no-output-on-prefix": "error",
        "@angular-eslint/no-output-rename": "error",
        "@angular-eslint/no-outputs-metadata-property": "error",
        "@angular-eslint/no-queries-metadata-property": "error",
        "@angular-eslint/prefer-on-push-component-change-detection": "error",
        "@angular-eslint/prefer-output-readonly": "error",
        "@angular-eslint/prefer-standalone": "error",
        "@angular-eslint/use-lifecycle-interface": "error",
        "@angular-eslint/use-pipe-transform-interface": "error"
      }
    },
    {
      "files": ["*.html"],
      "rules": {
        "@angular-eslint/template/alt-text": "error",
        "@angular-eslint/template/no-call-expression": "error",
        "@angular-eslint/template/banana-in-box": "warn",
        "@angular-eslint/template/button-has-type": "error",
        "@angular-eslint/template/elements-content": "error",
        "@angular-eslint/template/eqeqeq": "error",
        "@angular-eslint/template/interactive-supports-focus": "error",
        "@angular-eslint/template/label-has-associated-control": "error",
        "@angular-eslint/template/mouse-events-have-key-events": "error",
        "@angular-eslint/template/no-inline-styles": [
          "error",
          {
            "allowBindToStyle": true,
            "allowNgStyle": true
          }
        ],
        "@angular-eslint/template/no-interpolation-in-attributes": "error",
        "@angular-eslint/template/no-negated-async": "error",
        "@angular-eslint/template/role-has-required-aria": "error"
      }
    }
  ]
}

I have tried to remove all the rules and plugin:@typescript-eslint/recommended and everything works fine.


Solution

  • The problem is that you are extending from the plugin:@typescript-eslint/recommended config on root level for all files and not just for the TypeScript files. Because you have configured ESLint to lint the *.html files, ESLint now tries to parse HTML files and to apply TypeScript related rules on them. To fix it, move all TypeScript-specific configuration to the *.ts overrides block in your eslint.base.json:

    {
      "env": { "browser": true, "es2021": true, "node": true },
      "extends": ["eslint:recommended", "./.eslintrc.angular.json", "prettier"],
      "ignorePatterns": ["*dist*"],
      "overrides": [
        {
          "files": ["*.ts"],
          "extends": ["plugin:@typescript-eslint/recommended"],
          "parser": "@typescript-eslint/parser",
          "parserOptions": {
            "ecmaVersion": "latest",
            "project": true,
            "sourceType": "module"
          },
          "plugins": ["@typescript-eslint"],
          "rules": {
            // your custom rules
          }
        },
        {
          "files": ["*.spec.ts", "*.test.ts"],
          "rules": {
            "max-lines-per-function": "off"
          }
        }
      ]
    }