Skip to content

Since v10.0.0 cannot lint Vue with export type #2702

Closed
vuejs/vue-eslint-parser
#254
@Shinigami92

Description

@Shinigami92

Checklist

  • I have tried restarting my IDE and the issue persists.
  • I have read the FAQ and my problem is not listed.

Tell us about your environment

  • ESLint version: 9.21.0
  • eslint-plugin-vue version: 10.0.0
  • Vue version: 3.5.13
  • Node version: 22.14.0
  • Operating System: macos

Please show your full configuration:

import { includeIgnoreFile } from "@eslint/compat";
import eslint from "@eslint/js";
import eslintPluginStylistic from "@stylistic/eslint-plugin";
import eslintPluginVitest from "@vitest/eslint-plugin";
import type { Linter } from "eslint";
import eslintPluginFileProgress from "eslint-plugin-file-progress";
import eslintPluginImportX from "eslint-plugin-import-x";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import eslintPluginVue from "eslint-plugin-vue";
import { resolve } from "node:path";
import tseslint from "typescript-eslint";
import eslintParserVue from "vue-eslint-parser";

const gitignorePath = resolve(import.meta.dirname, ".gitignore");

const eslintRules: Linter.RulesRecord = {
  "arrow-body-style": ["error", "as-needed"],
  curly: "error",
  eqeqeq: ["error", "always", { null: "ignore" }],
  "no-else-return": "error",
};

const tsRules: Linter.RulesRecord = {
  // https://typescript-eslint.io/rules/no-unused-vars/#how-to-use
  "no-unused-vars": "off",
  "@typescript-eslint/no-unused-vars": [
    "error",
    {
      args: "all",
      argsIgnorePattern: "^_",
      caughtErrors: "all",
      caughtErrorsIgnorePattern: "^_",
      destructuredArrayIgnorePattern: "^_",
      varsIgnorePattern: "^_",
      ignoreRestSiblings: true,
    },
  ],

  // Opinionated configuration
  "@typescript-eslint/array-type": [
    "error",
    { default: "array-simple", readonly: "generic" },
  ],
  "@typescript-eslint/consistent-type-exports": "error",
  "@typescript-eslint/consistent-type-imports": [
    "error",
    {
      disallowTypeAnnotations: false,
      fixStyle: "separate-type-imports",
      prefer: "type-imports",
    },
  ],
  "@typescript-eslint/no-inferrable-types": [
    "error",
    { ignoreParameters: true },
  ],
};

const config = tseslint.config(
  //#region global
  includeIgnoreFile(gitignorePath),
  {
    name: "manual ignores",
    ignores: [
      "eslint.config.ts",
      "packages/storybook/.storybook",
      "packages/storybook/scripts/storybook-publish.mjs",
    ],
  },
  {
    name: "linter options",
    linterOptions: {
      reportUnusedDisableDirectives: "error",
    },
  },
  //#endregion

  //#region eslint (js)
  eslint.configs.recommended,
  {
    name: "eslint overrides",
    rules: eslintRules,
  },
  //#endregion

  //#region typescript-eslint
  ...tseslint.configs.recommendedTypeChecked,
  ...tseslint.configs.stylisticTypeChecked,
  {
    name: "typescript-eslint overrides",
    plugins: {
      "@typescript-eslint": tseslint.plugin,
    },
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      parserOptions: {
        parser: tseslint.parser,
        project: "./tsconfig.json",
        warnOnUnsupportedTypeScriptVersion: false,
      },
    },
    rules: {
      ...eslintRules,

      // TODO cquadflieg 2024-10-11: These are even double enabled by typescript-eslint
      "no-cond-assign": "off",
      "no-constant-binary-expression": "off",
      "no-control-regex": "off",
      "no-empty": "off",
      "no-fallthrough": "off",
      "no-prototype-builtins": "off",
      "no-unused-private-class-members": "off",
      "no-useless-escape": "off",

      // TODO cquadflieg 2024-10-30: Investigate later if these should be re-enabled (included in recommendedTypeChecked)
      "@typescript-eslint/await-thenable": "off",
      "@typescript-eslint/no-duplicate-type-constituents": "off",
      "@typescript-eslint/no-empty-object-type": "off",
      "@typescript-eslint/no-explicit-any": "off",
      "@typescript-eslint/no-floating-promises": "off",
      "@typescript-eslint/no-implied-eval": "off",
      "@typescript-eslint/no-misused-promises": "off",
      "@typescript-eslint/no-redundant-type-constituents": "off",
      "@typescript-eslint/no-this-alias": "off",
      "@typescript-eslint/no-unnecessary-type-assertion": "off",
      "@typescript-eslint/no-unsafe-argument": "off",
      "@typescript-eslint/no-unsafe-assignment": "off",
      "@typescript-eslint/no-unsafe-call": "off",
      "@typescript-eslint/no-unsafe-function-type": "off",
      "@typescript-eslint/no-unsafe-member-access": "off",
      "@typescript-eslint/no-unsafe-return": "off",
      "@typescript-eslint/no-unused-expressions": "off",
      "@typescript-eslint/no-wrapper-object-types": "off",
      "@typescript-eslint/only-throw-error": "off",
      "@typescript-eslint/prefer-promise-reject-errors": "off",
      "@typescript-eslint/require-await": "off",
      "@typescript-eslint/restrict-plus-operands": "off",
      "@typescript-eslint/restrict-template-expressions": "off",
      "@typescript-eslint/unbound-method": "off",

      // TODO cquadflieg 2024-10-11: Investigate later if these should be re-enabled (included in stylisticTypeChecked)
      "@typescript-eslint/class-literal-property-style": "off",
      "@typescript-eslint/dot-notation": "off",
      "@typescript-eslint/no-empty-function": "off",
      "@typescript-eslint/prefer-regexp-exec": "off",
      "@typescript-eslint/prefer-string-starts-ends-with": "off",

      ...tsRules,
    },
  },
  //#endregion

  //#region import
  eslintPluginImportX.flatConfigs.recommended,
  eslintPluginImportX.flatConfigs.typescript,
  {
    name: "import overrides",
    languageOptions: {
      parser: tseslint.parser,
      ecmaVersion: "latest",
      sourceType: "module",
    },
    rules: {
      // TODO cquadflieg 2024-10-18: Enable in separate MR
      "import-x/default": "off",
      "import-x/no-named-as-default": "off",

      // Opinionated configuration
      "import-x/consistent-type-specifier-style": ["error", "prefer-top-level"],
    },
    settings: {
      "import-x/extensions": [".d.ts", ".js", ".mdx", ".mts", ".ts", ".vue"],
      "import-x/parsers": {
        "@typescript-eslint/parser": [".d.ts", ".mdx", ".mts", ".ts"],
        "vue-eslint-parser": [".vue"],
      },
    },
  },
  //#endregion

  //#region stylistic
  {
    name: "stylistic overrides",
    plugins: {
      "@stylistic": eslintPluginStylistic,
    },
    rules: {
      "@stylistic/padding-line-between-statements": [
        "error",
        { blankLine: "always", prev: "block-like", next: "*" },
      ],
      "@stylistic/quotes": ["error", "double", { avoidEscape: true }],
    },
  },
  //#endregion

  //#region prettier
  eslintPluginPrettierRecommended,
  //#endregion

  //#region eslint-plugin-vue
  ...eslintPluginVue.configs["flat/recommended"],
  {
    name: "vue overrides",
    files: ["*.vue", "**/*.vue"],
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      parser: eslintParserVue,
      parserOptions: {
        parser: tseslint.parser,
        project: "./tsconfig.json",
        extraFileExtensions: [".vue"],
      },
    },
    rules: {
      ...eslintRules,

      // TODO cquadflieg 2024-10-11: Investigate later if these should be re-enabled (included in stylisticTypeChecked)
      "@typescript-eslint/consistent-type-definitions": "off",
      "@typescript-eslint/non-nullable-type-assertion-style": "off",

      ...tsRules,

      // Not needed, because it's handled by prettier
      "vue/html-indent": "off",
      "vue/max-attributes-per-line": "off",
      "vue/singleline-html-element-content-newline": "off",

      // If we use `v-html`, we know what we are doing
      "vue/no-v-html": "off",

      // TODO cquadflieg 2024-10-25: Enable in separate MR
      "vue/no-template-shadow": "off",
      "vue/require-default-prop": "off",
      "vue/require-prop-types": "off",

      // Opinionated configuration
      "vue/attribute-hyphenation": [
        "error",
        "always",
        {
          ignore: ["ariaDescribedby", "innerHTML"],
        },
      ],
      "vue/block-lang": [
        "error",
        {
          script: {
            lang: "ts",
          },
        },
      ],
      "vue/block-order": [
        "error",
        {
          order: ["script:not([setup])", "script[setup]", "template", "style"],
        },
      ],
      "vue/define-macros-order": [
        "error",
        {
          order: [
            "defineOptions",
            "defineProps",
            "defineEmits",
            "defineModel",
            "defineSlots",
          ],
          defineExposeLast: true,
        },
      ],
      "vue/html-self-closing": [
        "error",
        {
          html: {
            component: "always",
            normal: "never",
            void: "always",
          },
          svg: "always",
          math: "always",
        },
      ],
    },
  },
  //#endregion

  {
    name: "test overrides",
    files: ["*.test.ts", "**/*.test.ts"],
    plugins: {
      vitest: eslintPluginVitest,
    },
    rules: {
      "vue/one-component-per-file": "off",

      ...eslintPluginVitest.configs.recommended.rules,

      "vitest/expect-expect": "off",
      "vitest/no-alias-methods": "error",
      "vitest/prefer-each": "error",
      "vitest/prefer-to-have-length": "error",
      "vitest/valid-expect": ["error", { maxArgs: 2 }],
    },
    settings: {
      vitest: {
        typecheck: true,
      },
    },
  },

  //#region file-progress
  eslintPluginFileProgress.configs.recommended
  //#endregion
);

export default config;

What did you do?

Updated from 9.33.0 to v10.0.0

<script lang="ts">
// a comment
// another comment
export type FilterEditorListOption<
  TValue = string,
  TValueKey extends string = "value",
  TTextKey extends string = "text",
  THtmlKey extends string = "html",
  TDisabledKey extends string = "disabled",
> = Record<TValueKey, TValue> &
  Partial<Record<TTextKey, string>> &
  Partial<Record<THtmlKey, string>> &
  Partial<Record<TDisabledKey, boolean>> &
  Partial<Record<string, unknown>>;

export interface FilterEditorListProps<
  TValue = string,
  TValueKey extends string = "value",
  TTextKey extends string = "text",
  THtmlKey extends string = "html",
  TDisabledKey extends string = "disabled",
> {
  disabledField?: TDisabledKey;
  htmlField?: THtmlKey;
  textField?: TTextKey;
  valueField?: TValueKey;
  // more props
}

// omitted
</script>

<script
  setup
  lang="ts"
  generic="
    TValue = string,
    TValueKey extends string = 'value',
    TTextKey extends string = 'text',
    THtmlKey extends string = 'html',
    TDisabledKey extends string = 'disabled'
  "
>
const props = withDefaults(
  defineProps<
    FilterEditorListProps<
      TValue,
      TValueKey,
      TTextKey,
      THtmlKey,
      TDisabledKey
    >
  >(),
  {
    // @ts-expect-error: default is compatible with generic default
    disabledField: "disabled",
    // @ts-expect-error: default is compatible with generic default
    htmlField: "html",
    // @ts-expect-error: default is compatible with generic default
    textField: "text",
    // @ts-expect-error: default is compatible with generic default
    valueField: "value",
    // omitted
  }
);

// omitted
</script>

<template>
  <div>
    <!-- omitted -->
  </div>
</template>

What did you expect to happen?

Linting fine as in 9.33.0

What actually happened?

Error shows a conflict with @typescript-eslint/no-unused-vars on line 4 related to the type-keyword: export type FilterEditorListOption<

Need to find out if this has something todo with generics, or if it also happens on other places.

Oops! Something went wrong! :(

ESLint: 9.21.0

TypeError: Cannot read properties of undefined (reading 'type')
Occurred while linting /Users/shini/project/packages/app/src/components/filters/FilterEditorList.vue:4
Rule: "@typescript-eslint/no-unused-vars"
    at /Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/util/collectUnusedVariables.js:332:28
    at Array.some (<anonymous>)
    at isExported (/Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/util/collectUnusedVariables.js:322:26)
    at UnusedVarsVisitor.collectUnusedVariables (/Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/util/collectUnusedVariables.js:120:21)
    at UnusedVarsVisitor.collectUnusedVariables (/Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/util/collectUnusedVariables.js:133:18)
    at UnusedVarsVisitor.collectUnusedVariables (/Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/util/collectUnusedVariables.js:50:36)
    at collectVariables (/Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/util/collectUnusedVariables.js:603:30)
    at collectUnusedVariables (/Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unused-vars.js:279:65)
    at Program:exit (/Users/shini/project/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.26.0_@typescript-eslint+parser@8.26.0_eslint@9.21.0__2a2173ae08b530503a9273d67d87e2f1/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unused-vars.js:439:36)
    at ruleErrorHandler (/Users/shini/project/node_modules/.pnpm/eslint@9.21.0_jiti@2.4.2/node_modules/eslint/lib/linter/linter.js:1160:48)
 ELIFECYCLE  Command failed with exit code 2.

Repository to reproduce this issue

Trying to reproduce it here: https://github.com/Shinigami92/vue-eslint-2702
But right now I could not, so I need to investigate more what the cause is...
Got it! Reproducible is now it the third commit: Shinigami92/vue-eslint-2702@167481a

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions