Challenges with TS ESLint Type Aware Linting in Vue #12921
NonSpicyBurrito
started this conversation in
General Discussions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I have recently been working on some Vue projects and faced some challenges with using TS ESLint type aware linting in Vue. These challenges aren't new, as many of them already have existing open issues. I'm not familiar with the inner working of Vue, ESLint, TS ESLint, to offer any technical insight. Instead, the purpose of this post is to document these challenges, from the perspective of the developer with real world use cases, so hopefully it can raise some awareness about them and serve as a resource to the team.
What is Type Aware Linting
Type aware linting (or typed linting, linting with type information) is a feature of typescript-eslint where it leverages type information from TS to statically analyze your code.
An example could be:
In this code,
auth.refreshToken()
is asynchronous and returns a promise, which is not awaited and thus the code has the problem of alerting user about successful token refresh without confirming that it indeed succeeded. @typescript-eslint/no-floating-promises can leverage the type information provided by TS thatauth.refreshToken()
indeed returns a promise, to warn you about the issue (or alternatively to know thatauth.refreshToken()
does not return a promise and thus the code is fine).Type aware linting is a huge part of what makes TS ESLint powerful and is adopted by many.
Vue specific TS features
Vue has specific TS features, and to support these features the tooling ecosystem needs its Vue flavored counterparts like
vue-tsc
and language server. TS ESLint currently does not support these.*.vue
typesWhen Vue components are used as values, for example:
TS ESLint cannot see the types of
*.vue
files, and thus the code triggers @typescript-eslint/no-unsafe-assignment: "Unsafe assignment of an error typed value."One workaround to this problem is to simply disable the rule, which is what @vue/eslint-config-typescript currently does, however disabling the rule is not ideal as it won't be able to catch the problems it's intended for.
The better workaround is imo using a shim:
This shim serves no purpose for type checking with
vue-tsc
asvue-tsc
already has the ability to resolve*.vue
types, it's merely there to let TS ESLint have a fallback type so to not trigger false positives.The workaround however may not work in certain cases, for example:
In this case,
showModal(modal, props)
is a generic function over themodal
argument, and the types ofprops
and return value are derived from the modal component's props and emits. Depending on how the types are written, the above shim will result in TS ESLint seeing an undesirable type forresult
and triggering false positives again.Instead, a "better" shim may be used:
With this shim,
showModal
can then be modified to handlenever
gracefully (perhaps it already works without further modification) so that whennever
is encountered the return value is alsonever
. This then allows usage site to be modified into:During type checking,
vue-tsc
will make sure that the assignment is legal, and TS ESLint will be able to seeresult
asboolean
for rest of the analysis, preventing further false positives. While this is not ideal as you now have to explicitly type annotate the return type, it is however the best solution that does not compromise type checking or linting.While the above workarounds solve the problem of component types, it does not solve the problem for module exports, for example:
Similarly, TS ESLint cannot see type information from
*.vue
and report false positives:Unlike the previous problem, this however has no satisfactory workaround. The best you can do is to put module exports into a separate file, effectively rendering using Vue SFC as modules unusable.
useTemplateRef
The type of
useTemplateRef
is enhanced to be a specific type depending on how it's used in template. This information is also invisible to TS ESLint:This can be worked around by using a similar technique as previously:
Similarly, this is a workaround that does not compromise type checking or linting, but less than ideal.
Linting in Template
Vue SFC templates simply do not get linted by TS ESLint, not just type aware linting. In an ideal world, templates should be simply the markup and contains no logic, and thus linting templates with TS ESLint may not be as useful, but that's rarely the case in real world code bases with logic sprinkled in template. Additionally, some type aware linting rules provide values even for markup.
Logic in Template
Logics in template do not get linted, including event handlers, props, text interpolations. Even for non complex code, there can be issues worth linting for, for example:
In this case, @typescript-eslint/restrict-template-expressions may be configured to disallow
user.profile
to be interpolated due to it being an object and would result in undesirable[object Object]
.Switch Exhaustiveness Check
Discriminated union is a widely used construct in TS, and @typescript-eslint/switch-exhaustiveness-check is one of the best rules to help with it. For example:
Upon refactoring to add a new
Admin
variant, the rule will detect that theswitch
does not exhaustively handle all variants, which is a huge help in refactoring a large code base.Due to the lack of type aware linting for template as well as a lack of an equivalent construct, such a rule does not exist to help refactoring similar template code:
A workaround is to move each code into its own component, and rely on type checking rather than linting:
When a new variant is added, a type error will be triggered on
userDisplays[user.type]
. While this works, it is not ideal as now handling of each variant must now be its own component.Conclusion
The post documents some challenges when using TS ESLint type aware linting in Vue with real world use cases.
Beta Was this translation helpful? Give feedback.
All reactions