Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add fragment options to lint code fragment for Vue SFC performance #1180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
yoyo930021 opened this issue Nov 6, 2019 · 11 comments
Closed

Add fragment options to lint code fragment for Vue SFC performance #1180

yoyo930021 opened this issue Nov 6, 2019 · 11 comments
Labels
awaiting response Issues waiting for a reply from the OP or another party question Questions! (i.e. not a bug / enhancment / documentation)

Comments

@yoyo930021
Copy link
Contributor

yoyo930021 commented Nov 6, 2019

I think a solution for Vue SFC performance.

Solution

change

typescript-eslint/typescript-eslint project

  1. add fragment boolean option in @typescript-eslint/parser.
  2. if fragment === true use createIsolatedProgram in @typescript-eslint/typescript-estree.

vue-eslint-parser project

  1. if parse ScriptFragment, add fragment options to parserOptions.

Test result

I have 109 TypeScript files and 119 Vue SFC files in this tested project.

@typescript-eslint 2.6.0 && vue-eslint-parser 6.0.4:
real 261.93
user 306.04
sys 5.10

This Solution:
real 13.04
user 20.26
sys 1.27

Test code project

yoyo930021/typescript-eslint #commit
yoyo930021/vue-eslint-parser #commit

@bradzacher
Copy link
Member

bradzacher commented Nov 8, 2019

createIsolatedProgram parses the file as if you ran tsc --isolatedModules.
This means that typescript parses the file as if every single import had type any.
IIRC it also means that it doesn't consume @types definitions.

Essentially, using createIsolatedProgram is pretty useless for rules that require type information - it's only marginally better than creating a ts SourceCode object.


Also, your proposed option would do exactly the same thing as if you had just not passed parserOptions.project. The createIsolatedProgram code path is used when there is no project given.


We don't really want to encode specific framework information into our tooling, nor do we want to add options which only apply to one framework. We avoid it explicitly with angular and react.

I think that this is a change which would be better suited for the vue parser.
The vue parser currently has full control over the options that are passed to the parser, it'd be pretty trivial to just delete parserOptions.project, which would do the same thing, without us adding an option.

@bradzacher bradzacher added awaiting response Issues waiting for a reply from the OP or another party question Questions! (i.e. not a bug / enhancment / documentation) labels Nov 8, 2019
@yoyo930021
Copy link
Contributor Author

yoyo930021 commented Nov 9, 2019

I try to delete parserOptions.project on before, but I will get a error when having needed type rules in .eslintrc.js.
The eslint parser can't disable about needed type rules.

So I will be In trouble.

@bradzacher
Copy link
Member

Yeah, so you have to use eslint overrides to disable rules requiring type information on vue files.

I think that's much better than silently not failing, and then potentially showing incorrect errors, or missing errors due to incomplete types.

@yoyo930021
Copy link
Contributor Author

yoyo930021 commented Nov 9, 2019

Yeah, so you have to use eslint overrides to disable rules requiring type information on vue files.

I think that's much better than silently not failing, and then potentially showing incorrect errors, or missing errors due to incomplete types.

@bradzacher
I think it's not and have some misunderstanding.

Screen Shot 2019-11-10 at 12 53 47 AM

This image is a Vue file.

The Typescript code is circled in this image.
The code fragment is yellow.
The main code is red.

The main code is needed about requiring type information rules,
but the code fragment isn't.

In vue-eslint-parser, will use @typescript-eslint/parser in every typescript code.

@bradzacher
Copy link
Member

I'm with you now - sorry, I'm not all that familiar with vue, because I've never used it.

The problem still stands, however, that this will put you in a bad place with regards to those rules. If you create an isolated program for each of the fragments (yellow), essentially typescript will parse them and do this:

  • parse $style.container in isolated mode
  • lookup type of variable $style
  • find it's not declared in the code fragment, and because the parse was isolated, it means there is no way it can resolve the type.
  • set type of $style to any.
  • set type of $style.container to any.

This means that, for example, many of the rules that require type information that currently exist (like prefer-string-starts-ends-with, prefer-regexp-exec, prefer-includes, no-unnecessary-type-assertion) will all be unable to function, because they all rely on the types being something substantial that can be inspected, which any is not.

@yoyo930021
Copy link
Contributor Author

yoyo930021 commented Nov 11, 2019

In vue-eslint-parser, have the same problem now,
because The vue-eslint-parse will use @typescript-eslint/parser for code fragment and main code.

Screen Shot 2019-11-10 at 12 53 47 AM

example by image:
  • 0([$style['app-navbar'], { [$style['server-is-on-local']]: serverIsOnLocal }])
  • 0($style.container)
  • 0($style.left)
  • 0($style.right)
  • main code

will use @typescript-eslint/parser.

vue-eslint-parser will use @typescript-eslint/parser for typescript code,
and parser other code to combine complete AST.

The code fragment can't use the rules that require type information on now,
so replacing createWatchProgram to createIsolatedProgram on code fragment is no problem,
but the main code is needed createWatchProgram.
I can't custom by this in .eslintrc.js.

@bradzacher
Copy link
Member

bradzacher commented Nov 11, 2019

I don't think you're understanding me.

I'm saying that right now, typescript will do its best to consume globally defined types, etc in the fragments. That's what you get as part of the watch program.
This means anything defined in the vue typescript types will be used when resolving types for the fragments.
This means that typescript will be able to resolve a type for $style in this mode.

When in isolated mode, typescript will not use any globally defined types. This means that it cannot possibly resolve a type for $style, so it will default to any.

Sure if you're only doing very simple $style.foo expressions, then it doesn't matter too much, but if you do any more complex expressions, then it could hide all lint warnings/errors due to the any.

This is probably something that needs to be handled differently from the vue-eslint-parser side.
I suggest you open an issue there to discuss options for how the parser could handle fragments in such a way that it can pass something more conducive to an environment which wants to resolve full type information.

@yoyo930021
Copy link
Contributor Author

The typescipt code is this properties about the main code of export default object in vue template.
Example by image:

  • [this.$style['app-navbar'], { [this.$style['server-is-on-local']]: this.serverIsOnLocal }]
  • this.$style.container
  • this.$style.left
  • this.$style.right

Unless the main code of export default object add code fragments and add this to code fragments in vue-eslint-parser, The code fragment isn't have any type info on now.

The former is a big thing, and more difficult problem.
The vetur is realized and base on vue-eslint-parser to get Template AST on now.
link

@bradzacher
Copy link
Member

Jeez, there's so much tooling, and hacks required to make vue work...

If there was some changes made to the way vue parser outputs the source code, then you could have full typing and make this work.

<template>
  <nav :class="[$style['app-navbar'], { [$style['server-is-on-local']]: serverIsOnLocal }]">
    <div :class="$style.container">
      <div :class="$style.left">
        <slot name="left" />
      </div>
      <div :class="$style.right">
        <slot name="right" />
      </div>
    </div>
  </nav>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit, Mixins } from 'vue-property-decorator'
import ConfigStorage  from '@/storages/ConfigStorage'
import AppMixin from '@/components/AppMixin'

@Component({
  name: 'AppNavbar'
})
export default class AppNavbar extends Mixins(AppMixin) {
  localeKey = 'AppNavbar' as const
  get serverIsOnLocal () {
    return ConfigStorage.getInstance().url.indexOf('localhost') >= 0
  }
}
</script>

transforms to

function fragments(this: AppNavbar) {
  propertyBind_class = [this.$style['app-navbar'], { [this.$style['server-is-on-local']]: this.serverIsOnLocal }];
  propertyBind_class = this.$style.container;
  propertyBind_class = this.$style.left;
  propertyBind_class = this.$style.right;
}

import { Component, Prop, Vue, Emit, Mixins } from 'vue-property-decorator'
import ConfigStorage  from '@/storages/ConfigStorage'
import AppMixin from '@/components/AppMixin'

@Component({
  name: 'AppNavbar'
})
export default class AppNavbar extends Mixins(AppMixin) {
  localeKey = 'AppNavbar' as const
  get serverIsOnLocal () {
    return ConfigStorage.getInstance().url.indexOf('localhost') >= 0
  }
}

It also seems like this is something that could be achieved via eslint processors:
https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins

given the file above, a preprocessor could output:

export = {
  processors: {
    'typescript-vue-preprocessor': {
      preprocess(text, filename) {
        const {fragments, script} = splitVueFile(text);
        return [
          fragments.map((frag, idx) => ({
            text: frag,
            filename: filename.replace(/.vue$/, `.${idx}.fragment.vue`,
          })),
          {
            text: script,
            filename,
          },
        ];
      }
    }
  }
}

Then eslint would pass each file to the parser(s) separately, and because of the filenames, you could do this in your config

{
  "overrides": [
    {
      "files": ["*.fragment.vue"],
      "parserOptions": {
        "parser": "vue-eslint-parser",
        "parserOptions": {
          "parser": "@typescript-eslint/parser"
          // note - no project
        }
      },
      "rules": {
        // disable typecheck rules
      }
    }
  ]
}

@yoyo930021
Copy link
Contributor Author

You code have some problem about no use vue class component.
I think we need @mysticatea to get some suggestion.

@armano2
Copy link
Collaborator

armano2 commented Dec 13, 2019

@bradzacher preprocessor is nice idea, but it has few drawbacks in what you can do in eslint-plugin-vue.

some of rules do cross checking between vue template and script code, for example: https://eslint.vuejs.org/rules/no-unused-components.html and i'm unsure if this will be still possible.

eslint-plugin-vue (officail plugin that uses vue-eslint-parser) as for now does not support vue-property-decorator.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 25, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
awaiting response Issues waiting for a reply from the OP or another party question Questions! (i.e. not a bug / enhancment / documentation)
Projects
None yet
Development

No branches or pull requests

3 participants