Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/vuejs/language-tools
Browse files Browse the repository at this point in the history
  • Loading branch information
KazariEX committed Feb 20, 2025
2 parents 56b052d + 7958a7f commit eda18b0
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 0 deletions.
106 changes: 106 additions & 0 deletions packages/language-core/lib/codegen/template/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,103 @@ import type { TemplateCodegenOptions } from './index';

export type TemplateCodegenContext = ReturnType<typeof createTemplateCodegenContext>;

/**
* Creates and returns a Context object used for generating type-checkable TS code
* from the template section of a .vue file.
*
* ## Implementation Notes for supporting `@vue-ignore`, `@vue-expect-error`, and `@vue-skip` directives.
*
* Vue language tooling supports a number of directives for suppressing diagnostics within
* Vue templates (https://github.com/vuejs/language-tools/pull/3215)
*
* Here is an overview for how support for how @vue-expect-error is implemented within this file
* (@vue-expect-error is the most complicated directive to support due to its behavior of raising
* a diagnostic when it is annotating a piece of code that doesn't actually have any errors/warning/diagnostics).
*
* Given .vue code:
*
* ```vue
* <script setup lang="ts">
* defineProps<{
* knownProp1: string;
* knownProp2: string;
* knownProp3: string;
* knownProp4_will_trigger_unused_expect_error: string;
* }>();
* </script>
*
* <template>
* {{ knownProp1 }}
* {{ error_unknownProp }} <!-- ERROR: Property 'error_unknownProp' does not exist on type [...] -->
* {{ knownProp2 }}
* <!-- @vue-expect-error This suppresses an Unknown Property Error -->
* {{ suppressed_error_unknownProp }}
* {{ knownProp3 }}
* <!-- @vue-expect-error This will trigger Unused '@ts-expect-error' directive.ts(2578) -->
* {{ knownProp4_will_trigger_unused_expect_error }}
* </template>
* ```
*
* The above code should raise two diagnostics:
*
* 1. Property 'error_unknownProp' does not exist on type [...]
* 2. Unused '@ts-expect-error' directive.ts(2578) -- this is the bottom `@vue-expect-error` directive
* that covers code that doesn't actually raise an error -- note that all `@vue-...` directives
* will ultimately translate into `@ts-...` diagnostics.
*
* The above code will produce the following type-checkable TS code (note: omitting asterisks
* to prevent VSCode syntax double-greying out double-commented code).
*
* ```ts
* ( __VLS_ctx.knownProp1 );
* ( __VLS_ctx.error_unknownProp ); // ERROR: Property 'error_unknownProp' does not exist on type [...]
* ( __VLS_ctx.knownProp2 );
* // @vue-expect-error start
* ( __VLS_ctx.suppressed_error_unknownProp );
* // @ts-expect-error __VLS_TS_EXPECT_ERROR
* ;
* // @vue-expect-error end of INTERPOLATION
* ( __VLS_ctx.knownProp3 );
* // @vue-expect-error start
* ( __VLS_ctx.knownProp4_will_trigger_unused_expect_error );
* // @ts-expect-error __VLS_TS_EXPECT_ERROR
* ;
* // @vue-expect-error end of INTERPOLATION
* ```
*
* In the generated code, there are actually 3 diagnostic errors that'll be raised in the first
* pass on this generated code (but through cleverness described below, not all of them will be
* propagated back to the original .vue file):
*
* 1. Property 'error_unknownProp' does not exist on type [...]
* 2. Unused '@ts-expect-error' directive.ts(2578) from the 1st `@ts-expect-error __VLS_TS_EXPECT_ERROR`
* 3. Unused '@ts-expect-error' directive.ts(2578) from the 2nd `@ts-expect-error __VLS_TS_EXPECT_ERROR`
*
* Be sure to pay careful attention to the mixture of `@vue-expect-error` and `@ts-expect-error`;
* Within the TS file, the only "real" directives recognized by TS are going to be prefixed with `@ts-`;
* any `@vue-` prefixed directives in the comments are only for debugging purposes.
*
* As mentioned above, there are 3 diagnostics errors that'll be generated for the above code, but
* only 2 should be propagated back to the original .vue file.
*
* (The reason we structure things this way is somewhat complicated, but in short it allows us
* to lean on TS as much as possible to generate actual `unused @ts-expect-error directive` errors
* while covering a number of edge cases.)
*
* So, we need a way to dynamically decide whether each of the `@ts-expect-error __VLS_TS_EXPECT_ERROR`
* directives should be reported as an unused directive or not.
*
* To do this, we'll make use of the `shouldReport` callback that'll optionally be provided to the
* `verification` property of the `CodeInformation` object attached to the mapping between source .vue
* and generated .ts code. The `verification` property determines whether "verification" (which includes
* semantic diagnostics) should be performed on the generated .ts code, and `shouldReport`, if provided,
* can be used to determine whether a given diagnostic should be reported back "upwards" to the original
* .vue file or not.
*
* See the comments in the code below for how and where we use this hook to keep track of whether
* an error/diagnostic was encountered for a region of code covered by a `@vue-expect-error` directive,
* and additionally how we use that to determine whether to propagate diagnostics back upward.
*/
export function createTemplateCodegenContext(options: Pick<TemplateCodegenOptions, 'scriptSetupBindingNames' | 'edited'>) {
let ignoredError = false;
let expectErrorToken: {
Expand All @@ -22,12 +119,18 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
function resolveCodeFeatures(features: VueCodeInformation) {
if (features.verification) {
if (ignoredError) {
// We are currently in a region of code covered by a @vue-ignore directive, so don't
// even bother performing any type-checking: set verification to false.
return {
...features,
verification: false,
};
}
if (expectErrorToken) {
// We are currently in a region of code covered by a @vue-expect-error directive. We need to
// keep track of the number of errors encountered within this region so that we can know whether
// we will need to propagate an "unused ts-expect-error" diagnostic back to the original
// .vue file or not.
const token = expectErrorToken;
return {
...features,
Expand Down Expand Up @@ -161,6 +264,9 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
expectErrorToken.node.loc.end.offset,
{
verification: {
// If no errors/warnings/diagnostics were reported within the region of code covered
// by the @vue-expect-error directive, then we should allow any `unused @ts-expect-error`
// diagnostics to be reported upward.
shouldReport: () => token.errors === 0,
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export function* generateComponent(
ctx.resolveCodeFeatures({
verification: {
shouldReport(_source, code) {
// https://typescript.tv/errors/#ts6133
return String(code) !== '6133';
},
}
Expand Down
2 changes: 2 additions & 0 deletions packages/language-core/lib/codegen/template/elementProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ function getPropsCodeInfo(
},
verification: strictPropsCheck || {
shouldReport(_source, code) {
// https://typescript.tv/errors/#ts2353
// https://typescript.tv/errors/#ts2561
if (String(code) === '2353' || String(code) === '2561') {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const cmds = [

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

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

0 comments on commit eda18b0

Please sign in to comment.