vuejs / language-tools

⚡ High-performance Vue language tooling based-on Volar.js
https://marketplace.visualstudio.com/items?itemName=Vue.volar
MIT License
5.84k stars 397 forks source link

Using `volar` together with `@typescript-eslint/parser` for type checking rules to understand SFC component types #2189

Closed DrJume closed 1 year ago

DrJume commented 1 year ago

I'm using @typescript-eslint in a TS project with type checking enabled (https://typescript-eslint.io/linting/configs#recommended-requiring-type-checking).

This allows eslint to understand typescript types and make suggestions.

Sadly Vue SFC files are interpreted with type any, they are not correctly loaded. I found an option for @typescript-eslint/parser to specify a TS program in the options: (https://typescript-eslint.io/architecture/parser/#program). So it should be possible to pass the Volar proxy into @typescript-eslint/parser. This should allow eslint to also run type linting for Vue SFCs. But sadly I didn't get it to work.

How can I get a TS program which uses Volar and pass it to @typescript-eslint/parser?

This is my eslint config override for '*.vue' files:

...
overrides: [
    {
        files: ['*.vue'],
        extends: [
            'plugin:@typescript-eslint/recommended-requiring-type-checking',
            'plugin:@typescript-eslint/strict',
        ],
        parser: 'vue-eslint-parser',
        parserOptions: {
            parser: '@typescript-eslint/parser',
            parserOptions: {
                extraFileExtensions: ['.vue'],
                tsconfigRootDir: __dirname,
                project: ['./tsconfig.app.json'],
                program: vueTsc.createProgramProxy({
                    rootNames: ['./src/main.ts'],
                    options: compilerOptions,
                    host: ts.createCompilerHost(compilerOptions),
                }),
            },
        }
    }
]
...

I also tried to add the typescript-vue-plugin as a typescript plugin, to get SFCs to work with eslint type checking, but it also didn't work...

"compilerOptions": {
    "plugins": [
        {
            "name": "typescript-vue-plugin"
        }
    ]
}
johnsoncodehk commented 1 year ago

This is my eslint config override for '*.vue' files:

Can you provide this project? It can help me quickly understand, thanks!

DrJume commented 1 year ago

Yeah sure! https://github.com/DrJume/vue-volar-eslint

  1. Enable Takeover Mode
  2. Active ESLint VSCode Plugin
  3. Go to main.ts
  4. @typescript/eslint can't read type of .vue file correctly

It would be awesome if it would work with Volar!

image

DrJume commented 1 year ago

Is integrating Volar with @typescript-eslint/parser possible? I thought the program option would be the solution, but I couldn't get it to work... This is my current state of experimentation:

.eslintrc.cjs:

require('@rushstack/eslint-patch/modern-module-resolution')

const parser = require('@typescript-eslint/parser')
const ts = require('typescript')
const vueTsc = require('vue-tsc/out/proxy')

const tsProgram = parser.createProgram('./tsconfig.json', __dirname)

module.exports = {
  root: true,
  extends: [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    '@vue/eslint-config-typescript/recommended',
    '@vue/eslint-config-prettier',
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    tsconfigRootDir: __dirname,
  },

  overrides: [
    ...
    {
      files: ['*.ts', '*.mts'],
      extends: [
        'plugin:@typescript-eslint/recommended-requiring-type-checking',
        'plugin:@typescript-eslint/strict',
      ],
      parserOptions: {
        // project: ['./tsconfig.json'],
        program: vueTsc.createProgramProxy({ // <-- it would be so cool, if this would work!
            rootNames: ['./src/main.ts'], // tsProgram.getSourceFiles().map((sf) => sf.fileName) ?
            options: compilerOptions,
            host: ts.createCompilerHost(compilerOptions),
        }),
      },
    },
  ],
}
johnsoncodehk commented 1 year ago

It seems that ESLint TypeScript document incorrect, you should use parserOptions.programs instead of parserOptions.program, and this is works to me.

const { createComponentMetaChecker } = require('vue-component-meta')
const tsConfig = require.resolve('./tsconfig.json')
const checker = createComponentMetaChecker(tsConfig)
const program = checker.__internal__.tsLs.getProgram()

// ...
parserOptions: {
  programs: [program]
},
johnsoncodehk commented 1 year ago

Now you can use https://github.com/johnsoncodehk/volar-plugins/tree/master/packages/eslint with takeover mode, I will write more information in Discussions for it.

Please note that you need to remove require('@rushstack/eslint-patch... from your repro. (But I don't know the reason)

DrJume commented 1 year ago

Thanks for getting back with this elegant solution!

I have tried it with https://github.com/DrJume/vue-volar-eslint and it seems to work (.vue files are not any anymore!) but still gives the following error for every .vue file:

Parsing error: "parserOptions.programs" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided program instance(s): src/components/icons/IconDocumentation.vue

I dug around in the code with the help of debug output (DEBUG=* pnpm lint) and the error is thrown inside

@typescript-eslint/typescript-estree -> createProgram -> useProvidedPrograms(), because

getAstFromProgram(programInstance, parseSettings) returns undefined.

Inside getAstFromProgram() it tries to get programInstance.getSourceFile(parseSettings.filePath),

but it returns undefined!

getSourceFile() is implemented in typescript/src/compiler/program.ts. It essentially tries to access the internal Map filesByName which should be returned by program.getFilesByNameMap().

But running getFilesByNameMap() on the Volar program instance does not return a Map!


So it seems to work, and I think the error is caused by Volar for having an incompatible interface with ts.Program.

DrJume commented 1 year ago

@johnsoncodehk

It still doesn't work completely. The issue stated above still exists. Can you reopen this issue? I hope my possible explanation above can help.

johnsoncodehk commented 1 year ago

That issue cannot be solved here. It belongs to eslint-plugin-vue. For progress updates, please follow https://github.com/DrJume/vue-volar-eslint/pull/1.