@@ -7,6 +7,103 @@ import type { TemplateCodegenOptions } from './index';
7
7
8
8
export type TemplateCodegenContext = ReturnType < typeof createTemplateCodegenContext > ;
9
9
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
+ */
10
107
export function createTemplateCodegenContext ( options : Pick < TemplateCodegenOptions , 'scriptSetupBindingNames' | 'edited' > ) {
11
108
let ignoredError = false ;
12
109
let expectErrorToken : {
@@ -22,12 +119,18 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
22
119
function resolveCodeFeatures ( features : VueCodeInformation ) {
23
120
if ( features . verification ) {
24
121
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.
25
124
return {
26
125
...features ,
27
126
verification : false ,
28
127
} ;
29
128
}
30
129
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.
31
134
const token = expectErrorToken ;
32
135
return {
33
136
...features ,
@@ -161,6 +264,9 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
161
264
expectErrorToken . node . loc . end . offset ,
162
265
{
163
266
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.
164
270
shouldReport : ( ) => token . errors === 0 ,
165
271
} ,
166
272
} ,
0 commit comments