diff --git a/extensions/vscode-vue-language-features/schemas/vue-tsconfig.schema.json b/extensions/vscode-vue-language-features/schemas/vue-tsconfig.schema.json index 369c17d5d..7eb4a33ca 100644 --- a/extensions/vscode-vue-language-features/schemas/vue-tsconfig.schema.json +++ b/extensions/vscode-vue-language-features/schemas/vue-tsconfig.schema.json @@ -36,6 +36,10 @@ "type": "boolean", "markdownDescription": "https://github.com/johnsoncodehk/volar/issues/577" }, + "experimentalAllowTypeNarrowingInInlineHandlers": { + "type": "boolean", + "markdownDescription": "https://github.com/johnsoncodehk/volar/issues/1249" + }, "experimentalResolveStyleCssClasses": { "enum": [ "scoped", diff --git a/packages/vue-code-gen/src/generators/template.ts b/packages/vue-code-gen/src/generators/template.ts index d3e110cbf..2f27480cb 100644 --- a/packages/vue-code-gen/src/generators/template.ts +++ b/packages/vue-code-gen/src/generators/template.ts @@ -49,9 +49,9 @@ export function generate( sourceLang: string, templateAst: CompilerDOM.RootNode, isVue2: boolean, + allowTypeNarrowingInEventExpressions: boolean, cssScopedClasses: string[] = [], htmlToTemplate: (htmlStart: number, htmlEnd: number) => { start: number, end: number; } | undefined, - isScriptSetup: boolean, searchTexts: { getEmitCompletion(tag: string): string, getPropsCompletion(tag: string): string, @@ -91,6 +91,7 @@ export function generate( const localVars: Record = {}; const identifiers = new Set(); const scopedClasses: { className: string, offset: number; }[] = []; + const blockConditions: string[] = []; tsFormatCodeGen.addText('export { };\n'); @@ -393,6 +394,9 @@ export function generate( } else if (node.type === CompilerDOM.NodeTypes.IF) { // v-if / v-else-if / v-else + + let originalBlockConditionsLength = blockConditions.length; + for (let i = 0; i < node.branches.length; i++) { const branch = node.branches[i]; @@ -404,6 +408,8 @@ export function generate( else tsCodeGen.addText('else'); + let addedBlockCondition = false; + if (branch.condition?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { tsCodeGen.addText(` `); writeInterpolation( @@ -421,13 +427,24 @@ export function generate( branch.condition.loc.start.offset, formatBrackets.round, ); + + if (allowTypeNarrowingInEventExpressions) { + blockConditions.push(branch.condition.content); + addedBlockCondition = true; + } } tsCodeGen.addText(` {\n`); for (const childNode of branch.children) { visitNode(childNode, parentEl); } tsCodeGen.addText('}\n'); + + if (addedBlockCondition) { + blockConditions[blockConditions.length - 1] = `!(${blockConditions[blockConditions.length - 1]})`; + } } + + blockConditions.length = originalBlockConditionsLength; } else if (node.type === CompilerDOM.NodeTypes.FOR) { // v-for @@ -747,8 +764,17 @@ export function generate( const _exp = prop.exp; const expIndex = jsChildNode.children.findIndex(child => typeof child === 'object' && child.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && child.content === _exp.content); const expNode = jsChildNode.children[expIndex] as CompilerDOM.SimpleExpressionNode; - const prefix = jsChildNode.children.filter((child, i) => typeof child === 'string' && i < expIndex).map(child => child as string).join(''); - const suffix = jsChildNode.children.filter((child, i) => typeof child === 'string' && i > expIndex).map(child => child as string).join(''); + let prefix = jsChildNode.children.filter((child, i) => typeof child === 'string' && i < expIndex).map(child => child as string).join(''); + let suffix = jsChildNode.children.filter((child, i) => typeof child === 'string' && i > expIndex).map(child => child as string).join(''); + + if (prefix && blockConditions.length) { + prefix = prefix.replace('(', '{ '); + suffix = suffix.replace(')', '} '); + prefix += '\n'; + for (const blockCondition of blockConditions) { + prefix += `if (!(${blockCondition})) return;\n`; + } + } writeInterpolation( expNode.content, diff --git a/packages/vue-typescript/src/types.ts b/packages/vue-typescript/src/types.ts index 9ce484d28..3e9b60cbd 100644 --- a/packages/vue-typescript/src/types.ts +++ b/packages/vue-typescript/src/types.ts @@ -17,4 +17,5 @@ export interface VueCompilerOptions { experimentalTemplateCompilerOptionsRequirePath?: string; experimentalDisableTemplateSupport?: boolean; experimentalResolveStyleCssClasses?: 'scoped' | 'always' | 'never'; + experimentalAllowTypeNarrowingInInlineHandlers?: boolean; } diff --git a/packages/vue-typescript/src/use/useSfcTemplateScript.ts b/packages/vue-typescript/src/use/useSfcTemplateScript.ts index 473197164..3e34c8f77 100644 --- a/packages/vue-typescript/src/use/useSfcTemplateScript.ts +++ b/packages/vue-typescript/src/use/useSfcTemplateScript.ts @@ -71,9 +71,9 @@ export function useSfcTemplateScript( templateData.value.lang, sfcTemplateCompileResult.value.ast, compilerOptions.experimentalCompatMode === 2, + !!compilerOptions.experimentalAllowTypeNarrowingInInlineHandlers, Object.values(cssScopedClasses.value).map(map => Object.keys(map)).flat(), templateData.value.htmlToTemplate, - !!scriptSetup.value, { getEmitCompletion: SearchTexts.EmitCompletion, getPropsCompletion: SearchTexts.PropsCompletion,