@@ -7,6 +7,103 @@ import type { TemplateCodegenOptions } from './index';
77
88export 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+ */
10107export 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 } ,
0 commit comments