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

      pFad - Phonifier reborn

      Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

      Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


      Alternative Proxies:

      Alternative Proxy

      pFad Proxy

      pFad v3 Proxy

      pFad v4 Proxy