vuejs / vue-eslint-parser

The ESLint custom parser for `.vue` files.
MIT License
435 stars 74 forks source link

vue-eslint-parser and typescript-eslint problems #104

Open yoyo930021 opened 3 years ago

yoyo930021 commented 3 years ago

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!

ota-meshi commented 3 years ago

Thank you for organizing information. (I take a long time to read English, so first I'm trying to understand performance issues. ๐Ÿ˜…) I understand, thanks to your explanation, that the main cause is to call as many TS parsers as there are directives.

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?

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.

yoyo930021 commented 3 years ago

(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?

mariusheine commented 3 years ago

Are there any information, progress or ideas for the problem with the [no-unsafe-*] rules?

JounQin commented 3 years ago

@ota-meshi Even after #116, there is no way to only enable typescript rules for <script lang="ts">, right? Because we can only enable rules for *.vue files, not *.vue/*.ts.

ota-meshi commented 3 years ago

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

ota-meshi commented 2 years ago

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.

tjk commented 2 years ago

@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?

lovetingyuan commented 2 years ago

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

ota-meshi commented 2 years ago

There is no way to do that.

simllll commented 2 years ago

If I run it via CLI e.g. eslint src/file.ts --ext ts it throws 7:13 error Parsing error: Unexpected token { for image

it seems it always picks "espree" instead of the rule that should forward it to the typesint-eslint/parser in case it's a ts file?

a possible workaround for now:

overrides: [
        {
            // enable the rule specifically for TypeScript files
            files: ['*.ts', '*.tsx'],
            parserOptions: {
                parser: '@typescript-eslint/parser'
            }
        }
    ],
yoyo930021 commented 2 years ago

If I run it via CLI e.g. eslint src/file.ts --ext ts it throws 7:13 error Parsing error: Unexpected token { for image

it seems it always picks "espree" instead of the rule that should forward it to the typesint-eslint/parser in case it's a ts file?

a possible workaround for now:

overrides: [
      {
          // enable the rule specifically for TypeScript files
          files: ['*.ts', '*.tsx'],
          parserOptions: {
              parser: '@typescript-eslint/parser'
          }
      }
  ],

Try this config.

{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "parser": {
            "ts": "@typescript-eslint/parser",
            "js": "@typescript-eslint/parser",
            "<template>": "espree"
        }
    }
}
yoyo930021 commented 2 years ago

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

Test result:

Project 1

The effect is remarkable.

yoyo930021 commented 2 years ago

Are there any information, progress or ideas for the problem with the [no-unsafe-*] rules?

No method other than hacking @typescript-eslint/parser. =_=

MatthiasKunnen commented 2 years ago
{
    "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

lovetingyuan commented 2 years ago
{
    "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

MatthiasKunnen commented 2 years ago

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'],
     },
}
lovetingyuan commented 2 years ago

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

MatthiasKunnen commented 2 years ago

I already tried that before. The problem is not with the js parser setting. With or without it, set to espreeor @typescript-eslint/parser, vue files cause the aforementioned error unless a script with lang="ts" is present.

Try out this repository: https://github.com/MatthiasKunnen/eslint-plugin-vue-ts-script.

lovetingyuan commented 2 years ago

I already tried that before. The problem is not with the js parser setting. With or without it, set to espreeor @typescript-eslint/parser, vue files cause the aforementioned error unless a script with lang="ts" is present.

Try out this repository: https://github.com/MatthiasKunnen/eslint-plugin-vue-ts-script.

of course you have to set the lang attribute. If it is omitted, the build tool also can not process your vue sfc written with ts.

MatthiasKunnen commented 2 years ago

of course you have to set the lang attribute. If it is omitted, the build tool also can not process your vue sfc written with ts.

@lovetingyuan, have you even looked at the linked repository? Please refrain from further replies with irrelevant information which cause unnecessary notifications and time waste for subscribers to this issue.

rndmerle commented 2 years ago

This config also works on our project (even if we had to keep that under an override for { files: ['**/*.vue'], parser: 'vue-eslint-parser' }. And I confirm we had to add <script lang="ts">export default {}</script> to some of our stateless components missing it.

simllll commented 2 years ago

Try this config.

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

that's exactly what I have, but still need to override it for .ts files to get it working correctly.

Darkproduct commented 2 years ago

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.

segevfiner commented 1 year ago

~Getting the same error. It looks like tsconfigRootDir and project isn't getting passed along to to @typescript-eslint/parser. Though even if it will get passed, it won't necessarily work due to the other issues described in this issue.~

Scratch that. It is probably the missing <script setup lang="ts"> issue. I wonder if vue-eslint-parser can workaround this.

segevfiner commented 1 year ago

Feels like we need something like vue-tsc but for @typescript-eslint/parser.

When this will finally work, we will be able to also create "@vue/eslint-config-typescript/recommended-requiring-type-checking" config to easily enable it.

segevfiner commented 1 year ago

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"],
  },
};
segevfiner commented 1 year ago

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.

ota-meshi commented 1 year ago

*[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.

jacekkarczmarczyk commented 1 year ago

I've tried the solution, my config is:

module.exports = {
  extends: ['jkarczm/vuetify'],
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: {
      ts: '@typescript-eslint/parser',
      '<template>': 'espree',
    },
    project: 'tsconfig.json',
  }
}

First tests show quite good results in terms of performance, however I've encountered another problem:

Parsing error: The keyword 'public' is reserved  vue/no-parsing-error

I'm using it ATM with vue 2.6 and vue cli, I'm in the process of switching to to 2.7, and maybe vite, so I won't bother now with further testing until I do the switch, but the code that uses public works perfectly fine and validates with my original eslint config

props: {
  public: Boolean,
  ...
}

and in template:

<v-col v-if="!public">...</v-col>

Not sure who's fault is this, just letting you know about the problem.

ota-meshi commented 1 year ago

After copying the source code of the poc branch and building it, try using it like this:

const parser = require('vue-eslint-parser')

module.exports = {
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: {
        ts: parser.createTSESLintParserForVue(),
        "<template>": {
            parseForESLint(code, options) {
                return require("@typescript-eslint/parser").parseForESLint(
                    code,
                    { ...options, project: undefined },
                )
            },
        },
    },
  }
}
jacekkarczmarczyk commented 1 year ago

Thanks for the suggestion, I've ended up with the following error

 ERROR  TypeError: Cannot read config file: C:\cygwin64\home\PC\karpc\kpc\web\.eslintrc.js
        Error: parser.createTSESLintParserForVue is not a function

It might because of some old version of vue-eslint-parser coming from eslint-plugin-vuetify

=> Found "vue-eslint-parser@9.1.0"
info Has been hoisted to "vue-eslint-parser"
info Reasons this module exists
   - Hoisted from "eslint-config-jkarczm#@vue#eslint-config-typescript#vue-eslint-parser"
   - Hoisted from "eslint-config-jkarczm#eslint-plugin-vue#vue-eslint-parser"
info Disk size without dependencies: "1.2MB"
info Disk size with unique dependencies: "7.67MB"
info Disk size with transitive dependencies: "8.39MB"
info Number of shared dependencies: 14
=> Found "eslint-plugin-vuetify#vue-eslint-parser@7.11.0"
info Reasons this module exists
   - "eslint-config-jkarczm#eslint-plugin-vuetify#eslint-plugin-vue" depends on it
   - Hoisted from "eslint-config-jkarczm#eslint-plugin-vuetify#eslint-plugin-vue#vue-eslint-parser"
info Disk size without dependencies: "980KB"
info Disk size with unique dependencies: "7.42MB"
info Disk size with transitive dependencies: "8.14MB"
info Number of shared dependencies: 14

I'll try to create a simplified repro when I have time, might not be very soon though

ota-meshi commented 1 year ago

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.

yuntian001 commented 1 year ago

I ran into a similar problem with @typescript-eslint/naming-convention getting an Error:

Error while loading rule '@typescript-eslint/naming-convention': 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.

The reason is that @typescript-eslint/naming-convention rules are applied to <script> in .vue, and now you cannot set separate rules for <script> and <script lang="ts"> in .vue . Or set .vue to only detect the <script lang="ts"> part

segevfiner commented 1 year ago

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

yoyo930021 commented 1 year ago

@johnsoncodehk also have some works.

kieselai commented 1 year ago

I have published a new package so that you can try the method described in #104 (comment) as a separate package.

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

THANK YOU! I haven't tested thoroughly, but it seems to clear up all of my issues after installing and configuring :)

DavidVaness commented 1 year ago

Just ran into this issue today, thank you @ota-meshi for this.

I searched on github for vue examples using this and followed this example which seemed to work well.

Following your demo/readme did not work out for me ota.

@kieselai mind sharing your config?

kieselai commented 1 year ago

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"]
};
romanhrynevych commented 11 months ago

Hello everyone, maybe someone can help me, i try to config ESLint with TypeScript-ESLint correctly, all errors are now showing, exapt which are inside