Skip to content

Commit 7958a7f

Browse files
machtyKazariEX
andauthored
chore(language-core): add docs for @vue-expect-error support (vuejs#5176)
Co-authored-by: KazariEX <[email protected]>
1 parent 2034d5c commit 7958a7f

File tree

4 files changed

+113
-0
lines changed

4 files changed

+113
-0
lines changed

packages/language-core/lib/codegen/template/context.ts

+106
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,103 @@ import type { TemplateCodegenOptions } from './index';
77

88
export type TemplateCodegenContext = ReturnType<typeof createTemplateCodegenContext>;
99

10+
/**
11+
* Creates and returns a Context object used for generating type-checkable TS code
12+
* from the template section of a .vue file.
13+
*
14+
* ## Implementation Notes for supporting `@vue-ignore`, `@vue-expect-error`, and `@vue-skip` directives.
15+
*
16+
* Vue language tooling supports a number of directives for suppressing diagnostics within
17+
* Vue templates (https://github.com/vuejs/language-tools/pull/3215)
18+
*
19+
* Here is an overview for how support for how @vue-expect-error is implemented within this file
20+
* (@vue-expect-error is the most complicated directive to support due to its behavior of raising
21+
* a diagnostic when it is annotating a piece of code that doesn't actually have any errors/warning/diagnostics).
22+
*
23+
* Given .vue code:
24+
*
25+
* ```vue
26+
* <script setup lang="ts">
27+
* defineProps<{
28+
* knownProp1: string;
29+
* knownProp2: string;
30+
* knownProp3: string;
31+
* knownProp4_will_trigger_unused_expect_error: string;
32+
* }>();
33+
* </script>
34+
*
35+
* <template>
36+
* {{ knownProp1 }}
37+
* {{ error_unknownProp }} <!-- ERROR: Property 'error_unknownProp' does not exist on type [...] -->
38+
* {{ knownProp2 }}
39+
* <!-- @vue-expect-error This suppresses an Unknown Property Error -->
40+
* {{ suppressed_error_unknownProp }}
41+
* {{ knownProp3 }}
42+
* <!-- @vue-expect-error This will trigger Unused '@ts-expect-error' directive.ts(2578) -->
43+
* {{ knownProp4_will_trigger_unused_expect_error }}
44+
* </template>
45+
* ```
46+
*
47+
* The above code should raise two diagnostics:
48+
*
49+
* 1. Property 'error_unknownProp' does not exist on type [...]
50+
* 2. Unused '@ts-expect-error' directive.ts(2578) -- this is the bottom `@vue-expect-error` directive
51+
* that covers code that doesn't actually raise an error -- note that all `@vue-...` directives
52+
* will ultimately translate into `@ts-...` diagnostics.
53+
*
54+
* The above code will produce the following type-checkable TS code (note: omitting asterisks
55+
* to prevent VSCode syntax double-greying out double-commented code).
56+
*
57+
* ```ts
58+
* ( __VLS_ctx.knownProp1 );
59+
* ( __VLS_ctx.error_unknownProp ); // ERROR: Property 'error_unknownProp' does not exist on type [...]
60+
* ( __VLS_ctx.knownProp2 );
61+
* // @vue-expect-error start
62+
* ( __VLS_ctx.suppressed_error_unknownProp );
63+
* // @ts-expect-error __VLS_TS_EXPECT_ERROR
64+
* ;
65+
* // @vue-expect-error end of INTERPOLATION
66+
* ( __VLS_ctx.knownProp3 );
67+
* // @vue-expect-error start
68+
* ( __VLS_ctx.knownProp4_will_trigger_unused_expect_error );
69+
* // @ts-expect-error __VLS_TS_EXPECT_ERROR
70+
* ;
71+
* // @vue-expect-error end of INTERPOLATION
72+
* ```
73+
*
74+
* In the generated code, there are actually 3 diagnostic errors that'll be raised in the first
75+
* pass on this generated code (but through cleverness described below, not all of them will be
76+
* propagated back to the original .vue file):
77+
*
78+
* 1. Property 'error_unknownProp' does not exist on type [...]
79+
* 2. Unused '@ts-expect-error' directive.ts(2578) from the 1st `@ts-expect-error __VLS_TS_EXPECT_ERROR`
80+
* 3. Unused '@ts-expect-error' directive.ts(2578) from the 2nd `@ts-expect-error __VLS_TS_EXPECT_ERROR`
81+
*
82+
* Be sure to pay careful attention to the mixture of `@vue-expect-error` and `@ts-expect-error`;
83+
* Within the TS file, the only "real" directives recognized by TS are going to be prefixed with `@ts-`;
84+
* any `@vue-` prefixed directives in the comments are only for debugging purposes.
85+
*
86+
* As mentioned above, there are 3 diagnostics errors that'll be generated for the above code, but
87+
* only 2 should be propagated back to the original .vue file.
88+
*
89+
* (The reason we structure things this way is somewhat complicated, but in short it allows us
90+
* to lean on TS as much as possible to generate actual `unused @ts-expect-error directive` errors
91+
* while covering a number of edge cases.)
92+
*
93+
* So, we need a way to dynamically decide whether each of the `@ts-expect-error __VLS_TS_EXPECT_ERROR`
94+
* directives should be reported as an unused directive or not.
95+
*
96+
* To do this, we'll make use of the `shouldReport` callback that'll optionally be provided to the
97+
* `verification` property of the `CodeInformation` object attached to the mapping between source .vue
98+
* and generated .ts code. The `verification` property determines whether "verification" (which includes
99+
* semantic diagnostics) should be performed on the generated .ts code, and `shouldReport`, if provided,
100+
* can be used to determine whether a given diagnostic should be reported back "upwards" to the original
101+
* .vue file or not.
102+
*
103+
* See the comments in the code below for how and where we use this hook to keep track of whether
104+
* an error/diagnostic was encountered for a region of code covered by a `@vue-expect-error` directive,
105+
* and additionally how we use that to determine whether to propagate diagnostics back upward.
106+
*/
10107
export function createTemplateCodegenContext(options: Pick<TemplateCodegenOptions, 'scriptSetupBindingNames' | 'edited'>) {
11108
let ignoredError = false;
12109
let expectErrorToken: {
@@ -22,12 +119,18 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
22119
function resolveCodeFeatures(features: VueCodeInformation) {
23120
if (features.verification) {
24121
if (ignoredError) {
122+
// We are currently in a region of code covered by a @vue-ignore directive, so don't
123+
// even bother performing any type-checking: set verification to false.
25124
return {
26125
...features,
27126
verification: false,
28127
};
29128
}
30129
if (expectErrorToken) {
130+
// We are currently in a region of code covered by a @vue-expect-error directive. We need to
131+
// keep track of the number of errors encountered within this region so that we can know whether
132+
// we will need to propagate an "unused ts-expect-error" diagnostic back to the original
133+
// .vue file or not.
31134
const token = expectErrorToken;
32135
return {
33136
...features,
@@ -161,6 +264,9 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
161264
expectErrorToken.node.loc.end.offset,
162265
{
163266
verification: {
267+
// If no errors/warnings/diagnostics were reported within the region of code covered
268+
// by the @vue-expect-error directive, then we should allow any `unused @ts-expect-error`
269+
// diagnostics to be reported upward.
164270
shouldReport: () => token.errors === 0,
165271
},
166272
},

packages/language-core/lib/codegen/template/element.ts

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ export function* generateComponent(
225225
ctx.resolveCodeFeatures({
226226
verification: {
227227
shouldReport(_source, code) {
228+
// https://typescript.tv/errors/#ts6133
228229
return String(code) !== '6133';
229230
},
230231
}

packages/language-core/lib/codegen/template/elementProps.ts

+2
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ function getPropsCodeInfo(
389389
},
390390
verification: strictPropsCheck || {
391391
shouldReport(_source, code) {
392+
// https://typescript.tv/errors/#ts2353
393+
// https://typescript.tv/errors/#ts2561
392394
if (String(code) === '2353' || String(code) === '2561') {
393395
return false;
394396
}

packages/language-service/lib/plugins/vue-directive-comments.ts

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ const cmds = [
1010

1111
const directiveCommentReg = /<!--\s*@/;
1212

13+
/**
14+
* A language service plugin that provides completion for Vue directive comments,
15+
* e.g. if user is writing `<!-- |` in they'll be provided completions for `@vue-expect-error`, `@vue-generic`, etc.
16+
*/
1317
export function create(): LanguageServicePlugin {
1418
return {
1519
name: 'vue-directive-comments',

0 commit comments

Comments
 (0)