vue-eslint-parser: vue-eslint-parser and typescript-eslint problems

Reason: https://github.com/typescript-eslint/typescript-eslint/issues/2865#issuecomment-742942987

Hi, I’m using vue-eslint-parser and typescript-eslint in company project. At the same time, I also help to contribute typescript-eslint project for fixing vue problems.

This problems are only happens when using type rules in typescript-eslint. You need to add parserOptions.project in project to enable it. https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md

There are two main issues:

performance issue

issues: https://github.com/vuejs/vue-eslint-parser/issues/65, https://github.com/typescript-eslint/typescript-eslint/issues/1180

The problem is that vue-eslint-parser is designed with the AST not type information. The vue-eslint-parser will split code frame from template to @typescript-eslint/parser. https://github.com/typescript-eslint/typescript-eslint/issues/1180#issuecomment-552297706 Typescript program need to parse full project for generate type information. When parsing code frame, typescript program will reparse file AST and rebuild type information in project. The vue-eslint-parser will pass more code frames and same file path in single Vue SFC.

Possible solutions:

  1. Ignore parserOptions.project to close get type information when pass code frame.
  2. Try to use ESLint processors, like Svelte. Ref: https://github.com/typescript-eslint/typescript-eslint/issues/1180#issuecomment-553007193

[no-unsafe-*] rules

issues: https://github.com/typescript-eslint/typescript-eslint/issues/2865

Strictly speaking, it isn’t a problem with this project. The typescript program can’t parse Vue SFC file directly.

You may think it’s strange, but why is it working now? Because @typescript-eslint/parser will prioritize the content delivered from ESLint. Typescript program will update by ESLint content not same as typescript program read. But this problem is happens when ESLint not send file content and content from typescript program reading. Example:

// App.vue
import HelloWorld from './components/HelloWorld.vue' // <- typescript program can't parse it. because it will read including template and style.

export default {
  name: 'App',
  components: {
    HelloWorld // <- so type information is `any`
  }
}

This problem will also have in <script setup> RFC.

Possible solutions:

  1. Override HelloWorld.vue content.
  2. Mock a HelloWorld.vue.ts file.

Maybe we need a common project for hacking typescript program.

Thanks for watching. Have a good day!

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 18
  • Comments: 45 (12 by maintainers)

Commits related to this issue

Most upvoted comments

I changed parserOptions.parser to allow multiple parsers. If someone has an environment where they can try out performance issues, can you use the following configuration to see if it improves performance and share the results?

{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "parser": {
            "ts": "@typescript-eslint/parser",
            "<template>": "espree"
        }
    }
}

If performance improves, I think it’s a useful workaround for many.

It seems the Vue project should look at the parser @ota-meshi made and integrate it into their ecosystem.

I have published a new package so that you can try the method described in https://github.com/vuejs/vue-eslint-parser/issues/104#issuecomment-1260706409 as a separate package.

https://github.com/ota-meshi/typescript-eslint-parser-for-extra-files Please note that it is still experimental.

{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "parser": {
            "ts": "@typescript-eslint/parser",
            "<template>": "espree"
        }
    }
}

I have an issue with this, if a vue file does not contain at least one TS script, the following error occurs:

Error: Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.
Occurred while linting src/views/EmptyView.vue
    at Object.getParserServices (node_modules/@typescript-eslint/eslint-plugin-tslint/node_modules/@typescript-eslint/experimental-utils/dist/eslint-utils/getParserServices.js:16:15)
    at create (node_modules/@typescript-eslint/eslint-plugin-tslint/dist/rules/config.js:70:65)
    at Object.create (node_modules/@typescript-eslint/eslint-plugin-tslint/node_modules/@typescript-eslint/experimental-utils/dist/eslint-utils/RuleCreator.js:13:24)
    at createRuleListeners (node_modules/eslint/lib/linter/linter.js:761:21)
    at node_modules/eslint/lib/linter/linter.js:931:31
    at Array.forEach (<anonymous>)
    at runRules (node_modules/eslint/lib/linter/linter.js:876:34)
    at Linter._verifyWithoutProcessors (node_modules/eslint/lib/linter/linter.js:1175:31)
    at node_modules/eslint/lib/linter/linter.js:1299:29
    at Array.map (<anonymous>)

Vue file:

<template>
    <div class="view" />
</template>

Adding <script lang="ts"></script> fixes the issue.

Reproduction repo: https://github.com/MatthiasKunnen/eslint-plugin-vue-ts-script

I’m currently struggling to get linting to work on my Laravel - Vue2 project with JavaScript and TypeScript. For debugging I created this small project, but I stumble from one issue to another.

First it seemed like I can’t get the fragmented parser option, introduced in https://github.com/typescript-eslint/typescript-eslint/issues/1180 to work, then I find typescript-eslint @TYPED_LINTING.md and now I’m stuck at:

$ eslint --config resources/js/.eslintrc.js --fix resources/js --ext .vue,.js,.ts

C:\fakepath\ts-test\resources\js\.eslintrc.js
  0:0  error  Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: resources\js\.eslintrc.js.
The file must be included in at least one of the projects provided

I’ve added those files to the tsconfig.json and even tried creating a tsconfig.eslint.json. But this doesn’t work.

It it not at all clear to me, how @typescript-eslint/parser and this project should be configured to get proper linting.

@ota-meshi Thank you for the help. I’m sorry for being late to reply.

Test result:

Project 1

  • Previous
    • 56.40s
    • 60.02s
  • Now
    • 31.76s
    • 34.21s

Project 2

  • Previous
    • 355.39s
    • 333.68s
  • Now
    • 51.07s
    • 56.20s

The effect is remarkable.

I did. The first code block in my comment is a quote, not my actual config. My config is:

{
     parser: 'vue-eslint-parser',
     parserOptions: {
         parser: {
             '<template>': 'espree',
             js: 'espree',
             ts: '@typescript-eslint/parser',
         },
         project: ['./tsconfig.json'],
         extraFileExtensions: ['.vue'],
     },
}

In fact, @typescript-eslint/parser will not be applied for pure ts file, so you have to change js: 'espree' to js: '@typescript-eslint/parser' You can see here: https://github.com/vuejs/vue-eslint-parser/blob/184dc09d8b573597cc691df8aa78093ece2349a3/src/common/parser-options.ts#L71

trash for vue3 ts lint。。。

Was there any progress in getting this supported by this project?

I changed parserOptions.parser to allow multiple parsers. If someone has an environment where they can try out performance issues, can you use the following configuration to see if it improves performance and share the results?

{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "parser": {
            "ts": "@typescript-eslint/parser",
            "<template>": "espree"
        }
    }
}

If performance improves, I think it’s a useful workaround for many.

how to set different parser options for each parserOptions.parser ? @ota-meshi

One additional hurdle that I haven’t seen mentioned yet: vue-eslint-parser does not appear to support either EXPERIMENTAL_useProjectService: true (which supports project references via TypeScript’s project service) or project: true (which uses the nearest tsconfig.json as the project configuration for each file). You have to use the traditional way of passing the tsconfigs as an array of glob patterns.

I dont know if this issue is the right place, but I want to make it popular because I sinked in around 2h of try and error for this.

extraFileExtensions: ['.vue'] is not read if it is in the eslint config overrides section. It needs to be in parserOptions.extraFileExtensions in the root config. All other override stuff, even parserOptions then can be put into overrides.files: ['*.vue']

I changed parserOptions.parser to allow multiple parsers. If someone has an environment where they can try out performance issues, can you use the following configuration to see if it improves performance and share the results?

{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "parser": {
            "ts": "@typescript-eslint/parser",
            "<template>": "espree"
        }
    }
}

If performance improves, I think it’s a useful workaround for many.

I got Parsing error: Maximum call stack size exceeded by the config.

Sure @DavidVaness, I’m referencing other files, but this should have all the important bits:

const parserForVue = require("typescript-eslint-parser-for-extra-files");

module.exports = {
  extends: [
    "plugin:vue/vue3-recommended",
    "@vue/eslint-config-typescript",
    "./eslint-shared-config-web.json",
  ],
  env: {
    browser: true,
  },

  parser: "vue-eslint-parser",
  parserOptions: {
    project: true,
    parser: {
      js: "@babel/eslint-parser",
      ts: parserForVue,
      "<template>": parserForVue,
      vue: "vue-eslint-parser",
    },
    ecmaVersion: "latest",
  },
  plugins: ["@typescript-eslint"]
};

[no-unsafe-*] rules

I started a PoC to provide type information when importing components. But I still don’t know if it works.

https://github.com/vuejs/vue-eslint-parser/tree/ts-poc

PR: #169

Please refer to the following changes regarding usage. https://github.com/vuejs/vue-eslint-parser/pull/169/files#diff-f0d52672037b3876b9ee0769efb0a5ff277fed8a2eac978f5f7c13dca4ba52c6R30-R40

If you can copy this source code and try it, please try it and give me your opinion.

@yoyo930021 you can probably push directly to the branch. Feel free to push your changes to the branch if you need to.

As a partial workaround we can add: shims-vue.d.ts:

declare module "*.vue" {
  import { Component } from "vue";
  const component: Component;
  export default component;
}

But I’m not sure if this disrupts Volar and/or vue-tsc, as such a shim was explicitly removed from the create-vue project template in the past, Volar documents to remove such shims, and it’s partial as all the components props, exposed, etc. will be typed as any.

Specifying parserOptions.parser to always use the TypeScript parser seems to fix the issue with needing empty to have a script tag with lang="ts". The issue is that parsing with espree while type check rules are active creates AST that isn’t linked with type information which confuses the type checking rules making them think that you didn’t specify parserOptions.project.

Now we are left with how to wire in SFC type information so they are not typed as any, like vue-tsc does. I think @typescript-eslint/parser is creating the ts.Program here https://github.com/typescript-eslint/typescript-eslint/blob/c4310b1aac35c7d31b826f0602eca6a5900a09ee/packages/typescript-estree/src/parser.ts#L557, unless you otherwise pass it parserOptions.programs, so we either need to monkey patch in a similar style to vue-tsc to make it create ts.Program objects that wire in type information, or to create a utility function that will create appropriate program instances upfront (Possibly lazy initialized?) and pass them via parserOptions.programs.

Edit: So typescript-eslint has two flows for creating the program, in the second one which is used by default it is actually using createWatchProgram.

Example:

/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");

module.exports = {
  root: true,
  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/eslint-config-typescript/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "@vue/eslint-config-prettier",
  ],
  parser: "vue-eslint-parser",
  parserOptions: {
    parser: require.resolve("@typescript-eslint/parser"),
    tsconfigRootDir: __dirname,
    project: ["./tsconfig.json", "./tsconfig.config.json"],
  },
};
{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "parser": {
            "ts": "@typescript-eslint/parser",
            "<template>": "espree"
        }
    }
}

I have an issue with this, if a vue file does not contain at least one TS script, the following error occurs:

Error: Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.
Occurred while linting src/views/EmptyView.vue
    at Object.getParserServices (node_modules/@typescript-eslint/eslint-plugin-tslint/node_modules/@typescript-eslint/experimental-utils/dist/eslint-utils/getParserServices.js:16:15)
    at create (node_modules/@typescript-eslint/eslint-plugin-tslint/dist/rules/config.js:70:65)
    at Object.create (node_modules/@typescript-eslint/eslint-plugin-tslint/node_modules/@typescript-eslint/experimental-utils/dist/eslint-utils/RuleCreator.js:13:24)
    at createRuleListeners (node_modules/eslint/lib/linter/linter.js:761:21)
    at node_modules/eslint/lib/linter/linter.js:931:31
    at Array.forEach (<anonymous>)
    at runRules (node_modules/eslint/lib/linter/linter.js:876:34)
    at Linter._verifyWithoutProcessors (node_modules/eslint/lib/linter/linter.js:1175:31)
    at node_modules/eslint/lib/linter/linter.js:1299:29
    at Array.map (<anonymous>)

Vue file:

<template>
    <div class="view" />
</template>

Adding <script lang="ts"></script> fixes the issue.

Then you have to set parserOptions.project

@ota-meshi I tried your patch and it made a huge difference!

Before (~10 seconds):

$ npx eslint --debug --fix frontend-next/src/components/connection/filters/app-filter.vue
...
  eslint:cli-engine Lint .../frontend-next/src/components/connection/filters/app-filter.vue +0ms
  eslint:linter Linting code for .../frontend-next/src/components/connection/filters/app-filter.vue (pass 1) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: .../frontend-next/src/components/connection/filters/app-filter.vue +0ms
  eslint:linter Apply the processor: 'vue/.vue' +0ms
  eslint:linter A code block was found: '(unnamed)' +1ms
  eslint:linter Generating fixed text for .../frontend-next/src/components/connection/filters/app-filter.vue (pass 1) +9s
...
.../frontend-next/src/components/connection/filters/app-filter.vue
  108:22  warning  The "update:value" event has been triggered but not declared on `emits` option  vue/require-explicit-emits

After (~instant):

$ npx eslint --debug --fix frontend-next/src/components/connection/filters/app-filter.vue
...
  eslint:cli-engine Lint .../frontend-next/src/components/connection/filters/app-filter.vue +0ms
  eslint:linter Linting code for .../frontend-next/src/components/connection/filters/app-filter.vue (pass 1) +0ms
  eslint:linter Verify +0ms
  eslint:linter With ConfigArray: .../frontend-next/src/components/connection/filters/app-filter.vue +1ms
  eslint:linter Apply the processor: 'vue/.vue' +0ms
  eslint:linter A code block was found: '(unnamed)' +0ms
  eslint:traverser Unknown node type "ChainExpression": Estimated visitor keys ["type","start","end","loc","range","expression"] +0ms
  eslint:linter Generating fixed text for .../frontend-next/src/components/connection/filters/app-filter.vue (pass 1) +135ms
...
.../frontend-next/src/components/connection/filters/app-filter.vue
  108:22  warning  The "update:value" event has been triggered but not declared on `emits` option  vue/require-explicit-emits

Produced same lint warning both times so reassuring. Is Unknown node type "ChainExpression": Estimated visitor keys ["type","start","end","loc","range","expression"] something to look into?

I know it. I wrote #116 as “Related” because it may solve the performance issue depending on how it is used.

(I take a long time to read English, so first I’m trying to understand performance issues. 😅)

My English is poor. If you don’t understand what do I talk about, maybe I can ask for help. 😅

Ignore parserOptions.project to close get type information when pass code frame.

If this method solves the problem, I think it’s the simplest change. Is it possible for you to make this change and see if it improves performance?

I will try again about this solution, and send a PR. Maybe we can split two parser options for code frame and script. Ref: https://github.com/vuejs/vue-eslint-parser/issues/65#issuecomment-793925259

Try to use ESLint processors, like Svelte. Ref: typescript-eslint/typescript-eslint#1180 (comment)

I don’t think we can use the ESLint processors. It’s difficult to change in vue-eslint-parser to do something similar to preprocessors, but I think it’s more likely than ESLint processors. As far as I can think of now, it’s difficult to analyze variables in template scope.

In the long run, we hope that the template can get the type information and rules. There is actually no difference between parser and processors. The parser also can compile templates to typescript and get type information with @typescript-eslint. Perhaps the question is whether there is a need?