From a8004e06a389bf5320c006fa77e844bcf6caa697 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Feb 2025 01:25:22 +0800 Subject: [PATCH 01/37] chore: update insiders.json --- insiders.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/insiders.json b/insiders.json index 697771f184..33e1c57a32 100644 --- a/insiders.json +++ b/insiders.json @@ -1,6 +1,14 @@ { - "latest": "2.2.1", + "latest": "2.2.3", "versions": [ + { + "version": "2.2.3", + "date": "2025-02-15", + "downloads": { + "GitHub": "https://github.com/volarjs/insiders/releases/tag/v2.2.3", + "AFDIAN": "https://ifdian.net/p/6fa17e40ebc111ef849e52540025c377" + } + }, { "version": "2.2.1", "date": "2024-12-24", From a9445630196ebd2cc427e7bf390a45d6b45f647b Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Feb 2025 02:26:32 +0800 Subject: [PATCH 02/37] refactor(language-core): parse special sfc root block attrs consistently --- .../language-core/lib/codegen/script/index.ts | 7 +- .../lib/codegen/script/scriptSetup.ts | 24 +++--- .../language-core/lib/codegen/script/src.ts | 81 +++++++++---------- .../lib/codegen/script/styleModulesType.ts | 14 ++-- .../language-core/lib/codegen/utils/index.ts | 22 ++++- packages/language-core/lib/types.ts | 30 ++++--- packages/language-core/lib/utils/parseSfc.ts | 65 ++++++++++----- .../lib/virtualFile/computedSfc.ts | 53 ++++++------ 8 files changed, 171 insertions(+), 125 deletions(-) diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index fac4679d7a..f177f8ff5a 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -54,14 +54,16 @@ export function* generateScript(options: ScriptCodegenOptions): Generator`; } - yield `>(${newLine}` + yield `(${newLine}` + ` __VLS_props: NonNullable>['props'],${newLine}` + ` __VLS_ctx?: ${ctx.localTypes.PrettifyLocal}>, 'attrs' | 'emit' | 'slots'>>,${newLine}` // use __VLS_Prettify for less dts code + ` __VLS_expose?: NonNullable>['expose'],${newLine}` diff --git a/packages/language-core/lib/codegen/script/src.ts b/packages/language-core/lib/codegen/script/src.ts index a6d1989ebc..55cdbab735 100644 --- a/packages/language-core/lib/codegen/script/src.ts +++ b/packages/language-core/lib/codegen/script/src.ts @@ -1,54 +1,51 @@ -import type { Code, Sfc } from '../../types'; +import type { Code, SfcBlockAttr } from '../../types'; import { codeFeatures } from '../codeFeatures'; -import { endOfLine } from '../utils'; +import { endOfLine, generateSfcBlockAttrValue } from '../utils'; -export function* generateSrc( - script: NonNullable, - src: string -): Generator { - if (src.endsWith('.d.ts')) { - src = src.slice(0, -'.d.ts'.length); +export function* generateSrc(src: SfcBlockAttr): Generator { + if (src === true) { + return; } - else if (src.endsWith('.ts')) { - src = src.slice(0, -'.ts'.length); + let { text } = src; + + if (text.endsWith('.d.ts')) { + text = text.slice(0, -'.d.ts'.length); + } + else if (text.endsWith('.ts')) { + text = text.slice(0, -'.ts'.length); } - else if (src.endsWith('.tsx')) { - src = src.slice(0, -'.tsx'.length) + '.jsx'; + else if (text.endsWith('.tsx')) { + text = text.slice(0, -'.tsx'.length) + '.jsx'; } - if (!src.endsWith('.js') && !src.endsWith('.jsx')) { - src = src + '.js'; + if (!text.endsWith('.js') && !text.endsWith('.jsx')) { + text = text + '.js'; } yield `export * from `; - yield [ - `'${src}'`, - 'script', - script.srcOffset - 1, - { - ...codeFeatures.all, - navigation: src === script.src - ? true - : { - shouldRename: () => false, - resolveRenameEditText(newName) { - if (newName.endsWith('.jsx') || newName.endsWith('.js')) { - newName = newName.split('.').slice(0, -1).join('.'); - } - if (script?.src?.endsWith('.d.ts')) { - newName = newName + '.d.ts'; - } - else if (script?.src?.endsWith('.ts')) { - newName = newName + '.ts'; - } - else if (script?.src?.endsWith('.tsx')) { - newName = newName + '.tsx'; - } - return newName; - }, + yield* generateSfcBlockAttrValue(src, text, { + ...codeFeatures.all, + navigation: text === src.text + ? true + : { + shouldRename: () => false, + resolveRenameEditText(newName) { + if (newName.endsWith('.jsx') || newName.endsWith('.js')) { + newName = newName.split('.').slice(0, -1).join('.'); + } + if (src?.text.endsWith('.d.ts')) { + newName = newName + '.d.ts'; + } + else if (src?.text.endsWith('.ts')) { + newName = newName + '.ts'; + } + else if (src?.text.endsWith('.tsx')) { + newName = newName + '.tsx'; + } + return newName; }, - }, - ]; + }, + }); yield endOfLine; - yield `export { default } from '${src}'${endOfLine}`; + yield `export { default } from '${text}'${endOfLine}`; } diff --git a/packages/language-core/lib/codegen/script/styleModulesType.ts b/packages/language-core/lib/codegen/script/styleModulesType.ts index f501fdf490..16fc90b14f 100644 --- a/packages/language-core/lib/codegen/script/styleModulesType.ts +++ b/packages/language-core/lib/codegen/script/styleModulesType.ts @@ -15,18 +15,18 @@ export function* generateStyleModulesType( } yield `type __VLS_StyleModules = {${newLine}`; for (const [style, i] of styles) { - const { name, offset } = style.module!; - if (offset) { + if (style.module === true) { + yield `$style`; + } + else { + const { text, offset } = style.module!; yield [ - name, + text, 'main', - offset + 1, + offset, codeFeatures.all ]; } - else { - yield name; - } yield `: Record & ${ctx.localTypes.PrettifyLocal}<{}`; for (const className of style.classNames) { yield* generateCssClassProperty( diff --git a/packages/language-core/lib/codegen/utils/index.ts b/packages/language-core/lib/codegen/utils/index.ts index 252ce6bfe0..4d4abc818e 100644 --- a/packages/language-core/lib/codegen/utils/index.ts +++ b/packages/language-core/lib/codegen/utils/index.ts @@ -1,7 +1,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import { getNodeText } from '../../parsers/scriptSetupRanges'; -import type { Code, SfcBlock, VueCodeInformation } from '../../types'; +import type { Code, SfcBlock, SfcBlockAttr, VueCodeInformation } from '../../types'; export const newLine = `\n`; export const endOfLine = `;${newLine}`; @@ -99,3 +99,23 @@ export function generateSfcBlockSection(block: SfcBlock, start: number, end: num features, ]; } + +export function* generateSfcBlockAttrValue( + src: SfcBlockAttr & object, + text: string, + features: VueCodeInformation +): Generator { + const { offset, quotes } = src; + if (!quotes) { + yield [``, 'main', offset, { verification: true }]; + } + yield [ + `'${text}'`, + 'main', + quotes ? offset - 1 : offset, + features + ]; + if (!quotes) { + yield [``, 'main', offset + text.length, { __combineOffsetMapping: 2 }]; + } +} diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index 523eda9fb2..3552430170 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -107,6 +107,12 @@ export interface SfcBlock { attrs: Record; } +export type SfcBlockAttr = true | { + text: string; + offset: number; + quotes: boolean; +}; + export interface Sfc { content: string; comments: string[]; @@ -116,22 +122,17 @@ export interface Sfc { warnings: CompilerDOM.CompilerError[]; } | undefined; script: (SfcBlock & { - src: string | undefined; - srcOffset: number; + src: SfcBlockAttr | undefined; ast: ts.SourceFile; }) | undefined; scriptSetup: SfcBlock & { // https://github.com/vuejs/rfcs/discussions/436 - generic: string | undefined; - genericOffset: number; + generic: SfcBlockAttr | undefined; ast: ts.SourceFile; } | undefined; styles: readonly (SfcBlock & { scoped: boolean; - module?: { - name: string; - offset?: number; - }; + module?: SfcBlockAttr | undefined; cssVars: { text: string; offset: number; @@ -147,11 +148,16 @@ export interface Sfc { } declare module '@vue/compiler-sfc' { + interface SFCBlock { + __src?: SfcBlockAttr; + } + + interface SFCScriptBlock { + __generic?: SfcBlockAttr; + } + interface SFCStyleBlock { - __module?: { - name: string; - offset?: number; - }; + __module?: SfcBlockAttr; } } diff --git a/packages/language-core/lib/utils/parseSfc.ts b/packages/language-core/lib/utils/parseSfc.ts index 2bcbd3f616..e4e99df55c 100644 --- a/packages/language-core/lib/utils/parseSfc.ts +++ b/packages/language-core/lib/utils/parseSfc.ts @@ -1,5 +1,5 @@ import type { ElementNode, SourceLocation } from '@vue/compiler-dom'; -import * as compiler from '@vue/compiler-dom'; +import * as CompilerDOM from '@vue/compiler-dom'; import type { CompilerError, SFCBlock, SFCDescriptor, SFCParseResult, SFCScriptBlock, SFCStyleBlock, SFCTemplateBlock } from '@vue/compiler-sfc'; declare module '@vue/compiler-sfc' { @@ -11,7 +11,7 @@ declare module '@vue/compiler-sfc' { export function parse(source: string): SFCParseResult { const errors: CompilerError[] = []; - const ast = compiler.parse(source, { + const ast = CompilerDOM.parse(source, { // there are no components at SFC parsing level isNativeTag: () => true, // preserve all whitespaces @@ -36,11 +36,11 @@ export function parse(source: string): SFCParseResult { shouldForceReload: () => false, }; ast.children.forEach(node => { - if (node.type === compiler.NodeTypes.COMMENT) { + if (node.type === CompilerDOM.NodeTypes.COMMENT) { descriptor.comments.push(node.content); return; } - else if (node.type !== compiler.NodeTypes.ELEMENT) { + else if (node.type !== CompilerDOM.NodeTypes.ELEMENT) { return; } switch (node.tag) { @@ -101,41 +101,64 @@ function createBlock(node: ElementNode, source: string) { end }; const attrs: Record = {}; - const block: SFCBlock - & Pick - & Pick = { + const block: SFCBlock = { type, content, loc, attrs }; node.props.forEach(p => { - if (p.type === compiler.NodeTypes.ATTRIBUTE) { + if (p.type === CompilerDOM.NodeTypes.ATTRIBUTE) { attrs[p.name] = p.value ? p.value.content || true : true; if (p.name === 'lang') { - block.lang = p.value && p.value.content; + block.lang = p.value?.content; } else if (p.name === 'src') { - block.src = p.value && p.value.content; + block.__src = parseAttr(p, node); } - else if (type === 'style') { + else if (isScriptBlock(block)) { + if (p.name === 'setup' || p.name === 'vapor') { + block.setup = attrs[p.name]; + } + else if (p.name === 'generic') { + block.__generic = parseAttr(p, node); + } + } + else if (isStyleBlock(block)) { if (p.name === 'scoped') { block.scoped = true; } else if (p.name === 'module') { - block.__module = { - name: p.value?.content ?? '$style', - offset: p.value?.content ? p.value?.loc.start.offset - node.loc.start.offset : undefined - }; + block.__module = parseAttr(p, node); } } - else if ( - type === 'script' - && (p.name === 'setup' || p.name === 'vapor') - ) { - block.setup = attrs[p.name]; - } } }); return block; } + +function isScriptBlock(block: SFCBlock): block is SFCScriptBlock { + return block.type === 'script'; +} + +function isStyleBlock(block: SFCBlock): block is SFCStyleBlock { + return block.type === 'style'; +} + +function parseAttr(p: CompilerDOM.AttributeNode, node: CompilerDOM.ElementNode) { + if (!p.value) { + return true; + } + const text = p.value.content; + const source = p.value.loc.source; + let offset = p.value.loc.start.offset - node.loc.start.offset; + const quotes = source.startsWith('"') || source.startsWith("'"); + if (quotes) { + offset++; + } + return { + text, + offset, + quotes, + }; +} diff --git a/packages/language-core/lib/virtualFile/computedSfc.ts b/packages/language-core/lib/virtualFile/computedSfc.ts index 1e2027dbbb..2dc9f5e685 100644 --- a/packages/language-core/lib/virtualFile/computedSfc.ts +++ b/packages/language-core/lib/virtualFile/computedSfc.ts @@ -2,7 +2,7 @@ import type * as CompilerDOM from '@vue/compiler-dom'; import type { SFCBlock, SFCParseResult } from '@vue/compiler-sfc'; import { computed, pauseTracking, resumeTracking } from 'alien-signals'; import type * as ts from 'typescript'; -import type { Sfc, SfcBlock, VueLanguagePluginReturn } from '../types'; +import type { Sfc, SfcBlock, SfcBlockAttr, VueLanguagePluginReturn } from '../types'; import { parseCssClassNames } from '../utils/parseCssClassNames'; import { parseCssVars } from '../utils/parseCssVars'; import { computedArray } from '../utils/signals'; @@ -52,12 +52,8 @@ export function computedSfc( 'js', computed(() => getParseResult()?.descriptor.script ?? undefined), (block, base): NonNullable => { - const src = computed(() => block().src); - const srcOffset = computed(() => { - const _src = src(); - return _src ? getUntrackedSnapshot().getText(0, base.startTagEnd).lastIndexOf(_src) - base.startTagEnd : -1; - }); - const ast = computed(() => { + const getSrc = computedAttrValue('__src', base, block); + const getAst = computed(() => { for (const plugin of plugins) { const ast = plugin.compileSFCScript?.(base.lang, base.content); if (ast) { @@ -67,9 +63,8 @@ export function computedSfc( return ts.createSourceFile(fileName + '.' + base.lang, '', 99 satisfies ts.ScriptTarget.Latest); }); return mergeObject(base, { - get src() { return src(); }, - get srcOffset() { return srcOffset(); }, - get ast() { return ast(); }, + get src() { return getSrc(); }, + get ast() { return getAst(); }, }); } ); @@ -78,15 +73,8 @@ export function computedSfc( 'js', computed(() => getParseResult()?.descriptor.scriptSetup ?? undefined), (block, base): NonNullable => { - const generic = computed(() => { - const _block = block(); - return typeof _block.attrs.generic === 'string' ? _block.attrs.generic : undefined; - }); - const genericOffset = computed(() => { - const _generic = generic(); - return _generic !== undefined ? getUntrackedSnapshot().getText(0, base.startTagEnd).lastIndexOf(_generic) - base.startTagEnd : -1; - }); - const ast = computed(() => { + const getGeneric = computedAttrValue("__generic", base, block); + const getAst = computed(() => { for (const plugin of plugins) { const ast = plugin.compileSFCScript?.(base.lang, base.content); if (ast) { @@ -96,9 +84,8 @@ export function computedSfc( return ts.createSourceFile(fileName + '.' + base.lang, '', 99 satisfies ts.ScriptTarget.Latest); }); return mergeObject(base, { - get generic() { return generic(); }, - get genericOffset() { return genericOffset(); }, - get ast() { return ast(); }, + get generic() { return getGeneric(); }, + get ast() { return getAst(); }, }); } ); @@ -127,13 +114,7 @@ export function computedSfc( computed(() => getParseResult()?.descriptor.styles ?? []), (getBlock, i) => { const base = computedSfcBlock('style_' + i, 'css', getBlock); - const getModule = computed(() => { - const { __module } = getBlock(); - return __module ? { - name: __module.name, - offset: __module.offset ? base.start + __module.offset : undefined - } : undefined; - }); + const getModule = computedAttrValue('__module', base, getBlock); const getScoped = computed(() => !!getBlock().scoped); const getCssVars = computed(() => [...parseCssVars(base.content)]); const getClassNames = computed(() => [...parseCssClassNames(base.content)]); @@ -309,6 +290,20 @@ export function computedSfc( get end() { return getEnd(); }, }; } + + function computedAttrValue( + key: keyof T & string, + base: ReturnType, + getBlock: () => T + ) { + return computed(() => { + const val = getBlock()[key] as SfcBlockAttr | undefined; + if (typeof val === 'object') { + val.offset = base.start + val.offset; + } + return val; + }); + } } function mergeObject(a: T, b: K): T & K { From d7952bb8d4dfe7fd41a489976669c45125dffa63 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Feb 2025 02:54:51 +0800 Subject: [PATCH 03/37] refactor(language-core): extract style related codegen codes to a separate folder --- .../language-core/lib/codegen/script/index.ts | 4 +- .../lib/codegen/script/template.ts | 68 +------------------ .../lib/codegen/style/classProperty.ts | 35 ++++++++++ .../styleModulesType.ts => style/modules.ts} | 12 ++-- .../lib/codegen/style/scopedClasses.ts | 38 +++++++++++ .../lib/virtualFile/computedSfc.ts | 2 +- 6 files changed, 84 insertions(+), 75 deletions(-) create mode 100644 packages/language-core/lib/codegen/style/classProperty.ts rename packages/language-core/lib/codegen/{script/styleModulesType.ts => style/modules.ts} (78%) create mode 100644 packages/language-core/lib/codegen/style/scopedClasses.ts diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index f177f8ff5a..13f98b11ac 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -6,13 +6,13 @@ import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges'; import type { Code, Sfc, VueCompilerOptions } from '../../types'; import { codeFeatures } from '../codeFeatures'; import { generateGlobalTypes, getGlobalTypesFileName } from '../globalTypes'; +import { generateStyleModules } from '../style/modules'; import type { TemplateCodegenContext } from '../template/context'; import { endOfLine, generateSfcBlockSection, newLine } from '../utils'; import { generateComponentSelf } from './componentSelf'; import { createScriptCodegenContext, ScriptCodegenContext } from './context'; import { generateScriptSetup, generateScriptSetupImports } from './scriptSetup'; import { generateSrc } from './src'; -import { generateStyleModulesType } from './styleModulesType'; import { generateTemplate } from './template'; export interface ScriptCodegenOptions { @@ -135,7 +135,7 @@ export function* generateScript(options: ScriptCodegenOptions): Generator { - const firstClasses = new Set(); - yield `type __VLS_StyleScopedClasses = {}`; - for (let i = 0; i < options.sfc.styles.length; i++) { - const style = options.sfc.styles[i]; - const option = options.vueCompilerOptions.experimentalResolveStyleCssClasses; - if (option === 'always' || (option === 'scoped' && style.scoped)) { - for (const className of style.classNames) { - if (firstClasses.has(className.text)) { - ctx.scopedClasses.push({ - source: 'style_' + i, - className: className.text.slice(1), - offset: className.offset + 1 - }); - continue; - } - firstClasses.add(className.text); - yield* generateCssClassProperty( - i, - className.text, - className.offset, - 'boolean', - true - ); - } - } - } - yield endOfLine; -} - -export function* generateCssClassProperty( - styleIndex: number, - classNameWithDot: string, - offset: number, - propertyType: string, - optional: boolean -): Generator { - yield `${newLine} & { `; - yield [ - '', - 'style_' + styleIndex, - offset, - codeFeatures.navigation, - ]; - yield `'`; - yield [ - classNameWithDot.slice(1), - 'style_' + styleIndex, - offset + 1, - codeFeatures.navigation, - ]; - yield `'`; - yield [ - '', - 'style_' + styleIndex, - offset + classNameWithDot.length, - codeFeatures.navigationWithoutRename, - ]; - yield `${optional ? '?' : ''}: ${propertyType}`; - yield ` }`; -} - function* generateCssVars(options: ScriptCodegenOptions, ctx: TemplateCodegenContext): Generator { if (!options.sfc.styles.length) { return; diff --git a/packages/language-core/lib/codegen/style/classProperty.ts b/packages/language-core/lib/codegen/style/classProperty.ts new file mode 100644 index 0000000000..0659cf4fca --- /dev/null +++ b/packages/language-core/lib/codegen/style/classProperty.ts @@ -0,0 +1,35 @@ +import type { Code } from '../../types'; +import { codeFeatures } from '../codeFeatures'; +import { newLine } from '../utils'; + +export function* generateClassProperty( + styleIndex: number, + classNameWithDot: string, + offset: number, + propertyType: string, + optional: boolean +): Generator { + yield `${newLine} & { `; + yield [ + '', + 'style_' + styleIndex, + offset, + codeFeatures.navigation, + ]; + yield `'`; + yield [ + classNameWithDot.slice(1), + 'style_' + styleIndex, + offset + 1, + codeFeatures.navigation, + ]; + yield `'`; + yield [ + '', + 'style_' + styleIndex, + offset + classNameWithDot.length, + codeFeatures.navigationWithoutRename, + ]; + yield `${optional ? '?' : ''}: ${propertyType}`; + yield ` }`; +} diff --git a/packages/language-core/lib/codegen/script/styleModulesType.ts b/packages/language-core/lib/codegen/style/modules.ts similarity index 78% rename from packages/language-core/lib/codegen/script/styleModulesType.ts rename to packages/language-core/lib/codegen/style/modules.ts index 16fc90b14f..ffc827cc13 100644 --- a/packages/language-core/lib/codegen/script/styleModulesType.ts +++ b/packages/language-core/lib/codegen/style/modules.ts @@ -1,11 +1,11 @@ import type { Code } from '../../types'; import { codeFeatures } from '../codeFeatures'; +import type { ScriptCodegenOptions } from '../script'; +import type { ScriptCodegenContext } from '../script/context'; import { endOfLine, newLine } from '../utils'; -import type { ScriptCodegenContext } from './context'; -import type { ScriptCodegenOptions } from './index'; -import { generateCssClassProperty } from './template'; +import { generateClassProperty } from './classProperty'; -export function* generateStyleModulesType( +export function* generateStyleModules( options: ScriptCodegenOptions, ctx: ScriptCodegenContext ): Generator { @@ -29,7 +29,7 @@ export function* generateStyleModulesType( } yield `: Record & ${ctx.localTypes.PrettifyLocal}<{}`; for (const className of style.classNames) { - yield* generateCssClassProperty( + yield* generateClassProperty( i, className.text, className.offset, @@ -40,4 +40,4 @@ export function* generateStyleModulesType( yield `>${endOfLine}`; } yield `}${endOfLine}`; -} \ No newline at end of file +} diff --git a/packages/language-core/lib/codegen/style/scopedClasses.ts b/packages/language-core/lib/codegen/style/scopedClasses.ts new file mode 100644 index 0000000000..6ad946cc36 --- /dev/null +++ b/packages/language-core/lib/codegen/style/scopedClasses.ts @@ -0,0 +1,38 @@ +import type { Code } from '../../types'; +import type { ScriptCodegenOptions } from '../script'; +import type { TemplateCodegenContext } from '../template/context'; +import { endOfLine } from '../utils'; +import { generateClassProperty } from './classProperty'; + +export function* generateStyleScopedClasses( + options: ScriptCodegenOptions, + ctx: TemplateCodegenContext +): Generator { + const firstClasses = new Set(); + yield `type __VLS_StyleScopedClasses = {}`; + for (let i = 0; i < options.sfc.styles.length; i++) { + const style = options.sfc.styles[i]; + const option = options.vueCompilerOptions.experimentalResolveStyleCssClasses; + if (option === 'always' || (option === 'scoped' && style.scoped)) { + for (const className of style.classNames) { + if (firstClasses.has(className.text)) { + ctx.scopedClasses.push({ + source: 'style_' + i, + className: className.text.slice(1), + offset: className.offset + 1 + }); + continue; + } + firstClasses.add(className.text); + yield* generateClassProperty( + i, + className.text, + className.offset, + 'boolean', + true + ); + } + } + } + yield endOfLine; +} diff --git a/packages/language-core/lib/virtualFile/computedSfc.ts b/packages/language-core/lib/virtualFile/computedSfc.ts index 2dc9f5e685..5ef0d119a4 100644 --- a/packages/language-core/lib/virtualFile/computedSfc.ts +++ b/packages/language-core/lib/virtualFile/computedSfc.ts @@ -73,7 +73,7 @@ export function computedSfc( 'js', computed(() => getParseResult()?.descriptor.scriptSetup ?? undefined), (block, base): NonNullable => { - const getGeneric = computedAttrValue("__generic", base, block); + const getGeneric = computedAttrValue('__generic', base, block); const getAst = computed(() => { for (const plugin of plugins) { const ast = plugin.compileSFCScript?.(base.lang, base.content); From b085f8663e2f29f0178045a866777bc9cf1c2b38 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Feb 2025 03:47:51 +0800 Subject: [PATCH 04/37] refactor(language-core): generate jsdoc for scoped class references --- .../lib/codegen/template/element.ts | 19 +++++++++---------- .../codegen/template/styleScopedClasses.ts | 13 ++++--------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 2d8b94a677..59f27de83c 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -84,9 +84,8 @@ export function* generateComponent( } if (matchImportName) { - // hover, renaming / find references support - yield `// @ts-ignore${newLine}`; // #2304 - yield `/** @type { [`; + // navigation support + yield `/** @type {[`; for (const tagOffset of tagOffsets) { yield `typeof `; if (var_originalComponent === node.tag) { @@ -113,7 +112,7 @@ export function* generateComponent( } yield `, `; } - yield `] } */${endOfLine}`; + yield `]} */${endOfLine}`; } else if (dynamicTagInfo) { yield `const ${var_originalComponent} = (`; @@ -167,8 +166,8 @@ export function* generateComponent( const camelizedTag = camelize(node.tag); if (variableNameRegex.test(camelizedTag)) { - // renaming / find references support - yield `/** @type { [`; + // navigation support + yield `/** @type {[`; for (const tagOffset of tagOffsets) { for (const shouldCapitalize of (node.tag[0] === node.tag[0].toUpperCase() ? [false] : [true, false])) { const expectName = shouldCapitalize ? capitalize(camelizedTag) : camelizedTag; @@ -186,7 +185,7 @@ export function* generateComponent( yield `, `; } } - yield `] } */${endOfLine}`; + yield `]} */${endOfLine}`; // auto import support if (options.edited) { yield `// @ts-ignore${newLine}`; // #2304 @@ -504,8 +503,8 @@ function* generateReferencesForElements( ) { const [content, startOffset] = normalizeAttributeValue(prop.value); - yield `// @ts-ignore navigation for \`const ${content} = ref()\`${newLine}`; - yield `/** @type { typeof __VLS_ctx`; + // navigation support for `const foo = ref()` + yield `/** @type {typeof __VLS_ctx`; yield* generatePropertyAccess( options, ctx, @@ -514,7 +513,7 @@ function* generateReferencesForElements( ctx.codeFeatures.navigation, prop.value.loc ); - yield ` } */${endOfLine}`; + yield `} */${endOfLine}`; if (variableNameRegex.test(content) && !options.templateRefNames.has(content)) { ctx.accessExternalVariable(content, startOffset); diff --git a/packages/language-core/lib/codegen/template/styleScopedClasses.ts b/packages/language-core/lib/codegen/template/styleScopedClasses.ts index 43f2a4ce9b..d7c2c1c7c2 100644 --- a/packages/language-core/lib/codegen/template/styleScopedClasses.ts +++ b/packages/language-core/lib/codegen/template/styleScopedClasses.ts @@ -10,22 +10,18 @@ export function* generateStyleScopedClassReferences( ctx: TemplateCodegenContext, withDot = false ): Generator { - if (!ctx.emptyClassOffsets.length && !ctx.scopedClasses.length) { - return; - } - - yield `[`; for (const offset of ctx.emptyClassOffsets) { - yield `'`; + yield `/** @type {__VLS_StyleScopedClasses['`; yield [ '', 'template', offset, ctx.codeFeatures.additionalCompletion, ]; - yield `', `; + yield `']} */${endOfLine}`; } for (const { source, className, offset } of ctx.scopedClasses) { + yield `/** @type {__VLS_StyleScopedClasses[`; yield [ '', source, @@ -43,9 +39,8 @@ export function* generateStyleScopedClassReferences( offset + className.length, ctx.codeFeatures.navigationWithoutRename, ]; - yield `, `; + yield `]} */${endOfLine}`; } - yield `] as (keyof __VLS_StyleScopedClasses)[]${endOfLine}`; function* escapeString(source: string, className: string, offset: number, escapeTargets: string[]): Generator { let count = 0; From 89006f5d36bcf2a19e03246cb068641e486b9dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Sun, 16 Feb 2025 04:27:16 +0800 Subject: [PATCH 05/37] feat(language-service): map sfc compiler errors outside the template inner content (#5045) --- packages/component-meta/lib/base.ts | 18 +++--- .../language-core/lib/virtualFile/vueFile.ts | 20 +++--- .../lib/ideFeatures/nameCasing.ts | 10 +-- .../lib/plugins/vue-autoinsert-dotvalue.ts | 6 +- .../plugins/vue-complete-define-assignment.ts | 5 +- .../lib/plugins/vue-document-drop.ts | 11 ++-- .../lib/plugins/vue-document-links.ts | 8 ++- .../lib/plugins/vue-extract-file.ts | 4 +- .../lib/plugins/vue-inlayhints.ts | 12 ++-- .../language-service/lib/plugins/vue-sfc.ts | 63 ++++++++++++++++--- .../lib/plugins/vue-template.ts | 8 +-- packages/typescript-plugin/lib/common.ts | 4 +- .../lib/requests/collectExtractProps.ts | 2 +- 13 files changed, 114 insertions(+), 57 deletions(-) diff --git a/packages/component-meta/lib/base.ts b/packages/component-meta/lib/base.ts index 7e61eac0e1..a944fd7a03 100644 --- a/packages/component-meta/lib/base.ts +++ b/packages/component-meta/lib/base.ts @@ -719,6 +719,7 @@ function readVueComponentDefaultProps( default?: string; required?: boolean; }> = {}; + const { sfc } = root; scriptSetupWorker(); scriptWorker(); @@ -727,12 +728,12 @@ function readVueComponentDefaultProps( function scriptSetupWorker() { - const ast = root._sfc.scriptSetup?.ast; + const ast = sfc.scriptSetup?.ast; if (!ast) { return; } - const codegen = vue.tsCodegen.get(root._sfc); + const codegen = vue.tsCodegen.get(sfc); const scriptSetupRanges = codegen?.getScriptSetupRanges(); if (scriptSetupRanges?.withDefaults?.argNode) { @@ -785,13 +786,14 @@ function readVueComponentDefaultProps( function scriptWorker() { - const sfc = root._sfc; + const ast = sfc.script?.ast; + if (!ast) { + return; + } - if (sfc.script) { - const scriptResult = readTsComponentDefaultProps(sfc.script.ast, 'default', printer, ts); - for (const [key, value] of Object.entries(scriptResult)) { - result[key] = value; - } + const scriptResult = readTsComponentDefaultProps(ast, 'default', printer, ts); + for (const [key, value] of Object.entries(scriptResult)) { + result[key] = value; } } } diff --git a/packages/language-core/lib/virtualFile/vueFile.ts b/packages/language-core/lib/virtualFile/vueFile.ts index af6633abc8..200403b995 100644 --- a/packages/language-core/lib/virtualFile/vueFile.ts +++ b/packages/language-core/lib/virtualFile/vueFile.ts @@ -17,9 +17,10 @@ export class VueVirtualCode implements VirtualCode { // computeds - _vueSfc = computedVueSfc(this.plugins, this.fileName, this.languageId, this._snapshot); - _sfc = computedSfc(this.ts, this.plugins, this.fileName, this._snapshot, this._vueSfc); - _mappings = computed(() => { + private _vueSfc = computedVueSfc(this.plugins, this.fileName, this.languageId, this._snapshot); + private _sfc = computedSfc(this.ts, this.plugins, this.fileName, this._snapshot, this._vueSfc); + private _embeddedCodes = computedEmbeddedCodes(this.plugins, this.fileName, this._sfc); + private _mappings = computed(() => { const snapshot = this._snapshot(); return [{ sourceOffsets: [0], @@ -28,16 +29,21 @@ export class VueVirtualCode implements VirtualCode { data: allCodeFeatures, }]; }); - _embeddedCodes = computedEmbeddedCodes(this.plugins, this.fileName, this._sfc); // others - get embeddedCodes() { - return this._embeddedCodes(); - } get snapshot() { return this._snapshot(); } + get vueSfc() { + return this._vueSfc(); + } + get sfc() { + return this._sfc; + } + get embeddedCodes() { + return this._embeddedCodes(); + } get mappings() { return this._mappings(); } diff --git a/packages/language-service/lib/ideFeatures/nameCasing.ts b/packages/language-service/lib/ideFeatures/nameCasing.ts index 5dd123585c..3ebb4d57dc 100644 --- a/packages/language-service/lib/ideFeatures/nameCasing.ts +++ b/packages/language-service/lib/ideFeatures/nameCasing.ts @@ -24,7 +24,7 @@ export async function convertTagName( return; } - const { template } = root._sfc; + const { template } = root.sfc; if (!template) { return; } @@ -71,7 +71,7 @@ export async function convertAttrName( return; } - const { template } = root._sfc; + const { template } = root.sfc; if (!template) { return; } @@ -172,8 +172,8 @@ export async function detect( const result = new Set(); - if (file._sfc.template?.ast) { - for (const element of vue.forEachElementNode(file._sfc.template.ast)) { + if (file.sfc.template?.ast) { + for (const element of vue.forEachElementNode(file.sfc.template.ast)) { if (element.tagType === 1 satisfies CompilerDOM.ElementTypes) { if (element.tag !== hyphenateTag(element.tag)) { // TagName @@ -208,7 +208,7 @@ function getTemplateTagsAndAttrs(sourceFile: VirtualCode): Tags { if (!(sourceFile instanceof vue.VueVirtualCode)) { return; } - const ast = sourceFile._sfc.template?.ast; + const ast = sourceFile.sfc.template?.ast; const tags: Tags = new Map(); if (ast) { for (const node of vue.forEachElementNode(ast)) { diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index eb08119bd5..3b3b6794cf 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -60,10 +60,8 @@ export function create( return; } - const blocks = [ - root._sfc.script, - root._sfc.scriptSetup, - ].filter(block => !!block); + const { sfc } = root; + const blocks = [sfc.script, sfc.scriptSetup].filter(block => !!block); if (!blocks.length) { return; } diff --git a/packages/language-service/lib/plugins/vue-complete-define-assignment.ts b/packages/language-service/lib/plugins/vue-complete-define-assignment.ts index 092d31feeb..07e9182619 100644 --- a/packages/language-service/lib/plugins/vue-complete-define-assignment.ts +++ b/packages/language-service/lib/plugins/vue-complete-define-assignment.ts @@ -36,8 +36,9 @@ export function create(): LanguageServicePlugin { return; } - const codegen = tsCodegen.get(root._sfc); - const scriptSetup = root._sfc.scriptSetup; + const { sfc } = root; + const codegen = tsCodegen.get(sfc); + const scriptSetup = sfc.scriptSetup; const scriptSetupRanges = codegen?.getScriptSetupRanges(); if (!scriptSetup || !scriptSetupRanges) { return; diff --git a/packages/language-service/lib/plugins/vue-document-drop.ts b/packages/language-service/lib/plugins/vue-document-drop.ts index 9233e91859..0dcc71dbba 100644 --- a/packages/language-service/lib/plugins/vue-document-drop.ts +++ b/packages/language-service/lib/plugins/vue-document-drop.ts @@ -55,17 +55,16 @@ export function create( return; } - let baseName = importUri.slice(importUri.lastIndexOf('/') + 1); - baseName = baseName.slice(0, baseName.lastIndexOf('.')); - - const newName = capitalize(camelize(baseName)); - const sfc = root._sfc; + const { sfc } = root; const script = sfc.scriptSetup ?? sfc.script; - if (!script) { return; } + let baseName = importUri.slice(importUri.lastIndexOf('/') + 1); + baseName = baseName.slice(0, baseName.lastIndexOf('.')); + const newName = capitalize(camelize(baseName)); + const additionalEdit: vscode.WorkspaceEdit = {}; const code = [...forEachEmbeddedCode(root)].find(code => code.id === (sfc.scriptSetup ? 'scriptsetup_raw' : 'script_raw'))!; const lastImportNode = getLastImportNode(ts, script.ast); diff --git a/packages/language-service/lib/plugins/vue-document-links.ts b/packages/language-service/lib/plugins/vue-document-links.ts index a93cea8689..c92a454cf2 100644 --- a/packages/language-service/lib/plugins/vue-document-links.ts +++ b/packages/language-service/lib/plugins/vue-document-links.ts @@ -27,7 +27,9 @@ export function create(): LanguageServicePlugin { } const result: vscode.DocumentLink[] = []; - const codegen = tsCodegen.get(root._sfc); + + const { sfc } = root; + const codegen = tsCodegen.get(sfc); const scopedClasses = codegen?.getGeneratedTemplate()?.scopedClasses ?? []; const styleClasses = new Map(); const option = root.vueCompilerOptions.experimentalResolveStyleCssClasses; - for (let i = 0; i < root._sfc.styles.length; i++) { - const style = root._sfc.styles[i]; + for (let i = 0; i < sfc.styles.length; i++) { + const style = sfc.styles[i]; if (option === 'always' || (option === 'scoped' && style.scoped)) { for (const className of style.classNames) { if (!styleClasses.has(className.text.slice(1))) { diff --git a/packages/language-service/lib/plugins/vue-extract-file.ts b/packages/language-service/lib/plugins/vue-extract-file.ts index 8c87985f96..c730c33c57 100644 --- a/packages/language-service/lib/plugins/vue-extract-file.ts +++ b/packages/language-service/lib/plugins/vue-extract-file.ts @@ -53,7 +53,7 @@ export function create( return; } - const sfc = root._sfc; + const { sfc } = root; const script = sfc.scriptSetup ?? sfc.script; if (!sfc.template || !script) { return; @@ -95,7 +95,7 @@ export function create( return codeAction; } - const sfc = root._sfc; + const { sfc } = root; const script = sfc.scriptSetup ?? sfc.script; if (!sfc.template || !script) { return codeAction; diff --git a/packages/language-service/lib/plugins/vue-inlayhints.ts b/packages/language-service/lib/plugins/vue-inlayhints.ts index b5253c1478..f10a06c7d2 100644 --- a/packages/language-service/lib/plugins/vue-inlayhints.ts +++ b/packages/language-service/lib/plugins/vue-inlayhints.ts @@ -30,21 +30,21 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { const result: vscode.InlayHint[] = []; - const codegen = tsCodegen.get(virtualCode._sfc); + const codegen = tsCodegen.get(virtualCode.sfc); const inlayHints = [ ...codegen?.getGeneratedTemplate()?.inlayHints ?? [], ...codegen?.getGeneratedScript()?.inlayHints ?? [], ]; const scriptSetupRanges = codegen?.getScriptSetupRanges(); - if (scriptSetupRanges?.defineProps?.destructured && virtualCode._sfc.scriptSetup?.ast) { + if (scriptSetupRanges?.defineProps?.destructured && virtualCode.sfc.scriptSetup?.ast) { const setting = 'vue.inlayHints.destructuredProps'; const enabled = await getSettingEnabled(setting); if (enabled) { for (const [prop, isShorthand] of findDestructuredProps( ts, - virtualCode._sfc.scriptSetup.ast, + virtualCode.sfc.scriptSetup.ast, scriptSetupRanges.defineProps.destructured.keys() )) { const name = prop.text; @@ -62,9 +62,9 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { } const blocks = [ - virtualCode._sfc.template, - virtualCode._sfc.script, - virtualCode._sfc.scriptSetup, + virtualCode.sfc.template, + virtualCode.sfc.script, + virtualCode.sfc.scriptSetup, ]; const start = document.offsetAt(range.start); const end = document.offsetAt(range.end); diff --git a/packages/language-service/lib/plugins/vue-sfc.ts b/packages/language-service/lib/plugins/vue-sfc.ts index a94ded1900..3b26e3cfa3 100644 --- a/packages/language-service/lib/plugins/vue-sfc.ts +++ b/packages/language-service/lib/plugins/vue-sfc.ts @@ -10,7 +10,7 @@ import { loadLanguageBlocks } from './data'; let sfcDataProvider: html.IHTMLDataProvider | undefined; export function create(): LanguageServicePlugin { - const htmlPlugin = createHtmlService({ + const htmlService = createHtmlService({ documentSelector: ['vue-root-tags'], useDefaultDataProvider: false, getCustomData(context) { @@ -23,7 +23,7 @@ export function create(): LanguageServicePlugin { const formatSettings = await context.env.getConfiguration?.('html.format') ?? {}; const blockTypes = ['template', 'script', 'style']; - for (const customBlock of root._sfc.customBlocks) { + for (const customBlock of root.sfc.customBlocks) { blockTypes.push(customBlock.type); } @@ -41,14 +41,21 @@ export function create(): LanguageServicePlugin { }, }); return { - ...htmlPlugin, + ...htmlService, name: 'vue-sfc', + capabilities: { + ...htmlService.capabilities, + diagnosticProvider: { + interFileDependencies: false, + workspaceDiagnostics: false, + } + }, create(context) { - const htmlPluginInstance = htmlPlugin.create(context); + const htmlServiceInstance = htmlService.create(context); return { - ...htmlPluginInstance, + ...htmlServiceInstance, provideDocumentLinks: undefined, @@ -73,11 +80,53 @@ export function create(): LanguageServicePlugin { return options; }, + provideDiagnostics(document, token) { + return worker(document, context, async root => { + const { vueSfc, sfc } = root; + if (!vueSfc) { + return; + } + + const originalResult = await htmlServiceInstance.provideDiagnostics?.(document, token); + const sfcErrors: vscode.Diagnostic[] = []; + const { template } = sfc; + + const { + startTagEnd = Infinity, + endTagStart = -Infinity + } = template ?? {}; + + for (const error of vueSfc.errors) { + if ('code' in error) { + const start = error.loc?.start.offset ?? 0; + const end = error.loc?.end.offset ?? 0; + if (end < startTagEnd || start >= endTagStart) { + sfcErrors.push({ + range: { + start: document.positionAt(start), + end: document.positionAt(end), + }, + severity: 1 satisfies typeof vscode.DiagnosticSeverity.Error, + code: error.code, + source: 'vue', + message: error.message, + }); + } + } + } + + return [ + ...originalResult ?? [], + ...sfcErrors + ]; + }); + }, + provideDocumentSymbols(document) { return worker(document, context, root => { const result: vscode.DocumentSymbol[] = []; - const sfc = root._sfc; + const { sfc } = root; if (sfc.template) { result.push({ @@ -162,7 +211,7 @@ export function create(): LanguageServicePlugin { }, async provideCompletionItems(document, position, context, token) { - const result = await htmlPluginInstance.provideCompletionItems?.(document, position, context, token); + const result = await htmlServiceInstance.provideCompletionItems?.(document, position, context, token); if (!result) { return; } diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index a90763ae77..d33fee1f99 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -365,7 +365,7 @@ export function create( const originalResult = await baseServiceInstance.provideDiagnostics?.(document, token); const templateErrors: vscode.Diagnostic[] = []; - const { template } = root._sfc; + const { template } = root.sfc; if (template) { @@ -428,7 +428,7 @@ export function create( return; } - const { template } = root._sfc; + const { template } = root.sfc; if (!template) { return; } @@ -520,7 +520,7 @@ export function create( })()); return []; } - const scriptSetupRanges = tsCodegen.get(vueCode._sfc)?.getScriptSetupRanges(); + const scriptSetupRanges = tsCodegen.get(vueCode.sfc)?.getScriptSetupRanges(); const names = new Set(); const tags: html.ITagData[] = []; @@ -534,7 +534,7 @@ export function create( } for (const binding of scriptSetupRanges?.bindings ?? []) { - const name = vueCode._sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end); + const name = vueCode.sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end); if (casing.tag === TagNameCasing.Kebab) { names.add(hyphenateTag(name)); } diff --git a/packages/typescript-plugin/lib/common.ts b/packages/typescript-plugin/lib/common.ts index 9824dbb200..6f74181a44 100644 --- a/packages/typescript-plugin/lib/common.ts +++ b/packages/typescript-plugin/lib/common.ts @@ -113,7 +113,7 @@ function getCompletionEntryDetails(language: Language, asScriptId: (fileNa const { fileName } = args[6]?.__isAutoImport; const sourceScript = language.scripts.get(asScriptId(fileName)); if (sourceScript?.generated?.root instanceof VueVirtualCode) { - const sfc = sourceScript.generated.root._vueSfc(); + const sfc = sourceScript.generated.root.vueSfc; if (!sfc?.descriptor.script && !sfc?.descriptor.scriptSetup) { for (const codeAction of details?.codeActions ?? []) { for (const change of codeAction.changes) { @@ -197,7 +197,7 @@ function getEncodedSemanticClassifications( const sourceScript = language.scripts.get(asScriptId(fileName)); const root = sourceScript?.generated?.root; if (root instanceof VueVirtualCode) { - const { template } = root._sfc; + const { template } = root.sfc; if (template) { for (const componentSpan of getComponentSpans.call( { typescript: ts, languageService }, diff --git a/packages/typescript-plugin/lib/requests/collectExtractProps.ts b/packages/typescript-plugin/lib/requests/collectExtractProps.ts index 0c4ee82265..ad9b9972de 100644 --- a/packages/typescript-plugin/lib/requests/collectExtractProps.ts +++ b/packages/typescript-plugin/lib/requests/collectExtractProps.ts @@ -28,7 +28,7 @@ export function collectExtractProps( const checker = program.getTypeChecker(); const script = sourceScript.generated?.languagePlugin.typescript?.getServiceScript(root); const maps = script ? [...language.maps.forEach(script.code)].map(([_sourceScript, map]) => map) : []; - const sfc = root._sfc; + const { sfc } = root; sourceFile.forEachChild(function visit(node) { if ( From 3f696a27fa514ea15dedcb6856b5dee098d93b7a Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Feb 2025 04:35:18 +0800 Subject: [PATCH 06/37] fix(language-core): generate generic to the correct location --- packages/language-core/lib/codegen/script/scriptSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index 4b796e92e9..84aa6d86ad 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -44,7 +44,7 @@ export function* generateScriptSetup( yield `<`; yield [ scriptSetup.generic.text, - scriptSetup.name, + 'main', scriptSetup.generic.offset, codeFeatures.all, ]; From b6580514e659cc1aab0676fe418ba0a5e2f0ead3 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <32807958+zhiyuanzmj@users.noreply.github.com> Date: Sun, 16 Feb 2025 11:57:49 +0800 Subject: [PATCH 07/37] fix(language-core): move generateSfcBlockSection to the end to fix missing comma errors (#5184) --- packages/language-core/lib/codegen/script/component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/language-core/lib/codegen/script/component.ts b/packages/language-core/lib/codegen/script/component.ts index 83910fbf19..4a9a091439 100644 --- a/packages/language-core/lib/codegen/script/component.ts +++ b/packages/language-core/lib/codegen/script/component.ts @@ -37,16 +37,16 @@ export function* generateComponent( } yield* generatePropsOption(options, ctx, scriptSetup, scriptSetupRanges, !!emitOptionCodes.length, true); } - if (options.sfc.script && options.scriptRanges?.exportDefault?.args) { - const { args } = options.scriptRanges.exportDefault; - yield generateSfcBlockSection(options.sfc.script, args.start + 1, args.end - 1, codeFeatures.all); - } if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.templateRefs.size) { yield `__typeRefs: {} as __VLS_TemplateRefs,${newLine}`; } if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.singleRootElType) { yield `__typeEl: {} as __VLS_RootEl,${newLine}`; } + if (options.sfc.script && options.scriptRanges?.exportDefault?.args) { + const { args } = options.scriptRanges.exportDefault; + yield generateSfcBlockSection(options.sfc.script, args.start + 1, args.end - 1, codeFeatures.all); + } yield `})`; } From 938a11a08c6d28afbce52108596fa9e992e3aca9 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 16 Feb 2025 14:58:56 +0800 Subject: [PATCH 08/37] refactor(language-core): simplify the mapping merging process in codegen --- .../language-core/lib/codegen/utils/index.ts | 6 +++--- packages/language-core/lib/types.ts | 3 +-- .../lib/virtualFile/computedEmbeddedCodes.ts | 16 +++------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/language-core/lib/codegen/utils/index.ts b/packages/language-core/lib/codegen/utils/index.ts index 4d4abc818e..7bdac7e272 100644 --- a/packages/language-core/lib/codegen/utils/index.ts +++ b/packages/language-core/lib/codegen/utils/index.ts @@ -5,7 +5,7 @@ import type { Code, SfcBlock, SfcBlockAttr, VueCodeInformation } from '../../typ export const newLine = `\n`; export const endOfLine = `;${newLine}`; -export const combineLastMapping: VueCodeInformation = { __combineLastMapping: true }; +export const combineLastMapping: VueCodeInformation = { __combineOffset: 1 }; export const variableNameRegex = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/; export function* wrapWith( @@ -22,7 +22,7 @@ export function* wrapWith( } yield wrapCode; } - yield ['', 'template', endOffset, { __combineOffsetMapping: offset }]; + yield ['', 'template', endOffset, { __combineOffset: offset }]; } export function collectVars( @@ -116,6 +116,6 @@ export function* generateSfcBlockAttrValue( features ]; if (!quotes) { - yield [``, 'main', offset + text.length, { __combineOffsetMapping: 2 }]; + yield [``, 'main', offset + text.length, { __combineOffset: 2 }]; } } diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index 3552430170..5935b1b163 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -16,8 +16,7 @@ export type RawVueCompilerOptions = Partial; diff --git a/packages/language-core/lib/virtualFile/computedEmbeddedCodes.ts b/packages/language-core/lib/virtualFile/computedEmbeddedCodes.ts index 77555633d8..b40dc3ac54 100644 --- a/packages/language-core/lib/virtualFile/computedEmbeddedCodes.ts +++ b/packages/language-core/lib/virtualFile/computedEmbeddedCodes.ts @@ -201,29 +201,19 @@ function computedPluginEmbeddedCodes( ]; })); const newMappings: typeof mappings = []; - let lastValidMapping: typeof mappings[number] | undefined; for (let i = 0; i < mappings.length; i++) { const mapping = mappings[i]; - if (mapping.data.__combineOffsetMapping !== undefined) { - const offsetMapping = mappings[i - mapping.data.__combineOffsetMapping]; + if (mapping.data.__combineOffset !== undefined) { + const offsetMapping = mappings[i - mapping.data.__combineOffset]; if (typeof offsetMapping === 'string' || !offsetMapping) { - throw new Error('Invalid offset mapping, mappings: ' + mappings.length + ', i: ' + i + ', offset: ' + mapping.data.__combineOffsetMapping); + throw new Error('Invalid offset mapping, mappings: ' + mappings.length + ', i: ' + i + ', offset: ' + mapping.data.__combineOffset); } offsetMapping.sourceOffsets.push(...mapping.sourceOffsets); offsetMapping.generatedOffsets.push(...mapping.generatedOffsets); offsetMapping.lengths.push(...mapping.lengths); continue; } - else if (mapping.data.__combineLastMapping) { - lastValidMapping!.sourceOffsets.push(...mapping.sourceOffsets); - lastValidMapping!.generatedOffsets.push(...mapping.generatedOffsets); - lastValidMapping!.lengths.push(...mapping.lengths); - continue; - } - else { - lastValidMapping = mapping; - } newMappings.push(mapping); } From adfa3f8031b6c22222c8c3f79f070c517dd9b28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Sun, 16 Feb 2025 15:10:22 +0800 Subject: [PATCH 09/37] fix(language-core): use return values of `useSlots` and `useAttrs` directly in the template (#5185) --- .../lib/codegen/template/index.ts | 8 ++ .../lib/parsers/scriptSetupRanges.ts | 61 +++++++------- packages/language-core/lib/plugins/vue-tsx.ts | 79 +++++++++++++------ 3 files changed, 93 insertions(+), 55 deletions(-) diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index e329204109..c40ff298b5 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -21,6 +21,8 @@ export interface TemplateCodegenOptions { hasDefineSlots?: boolean; slotsAssignName?: string; propsAssignName?: string; + slotsReferenceNames: Set; + attrsReferenceNames: Set; inheritAttrs: boolean; selfComponentName?: string; } @@ -34,6 +36,12 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator[0], appendGlobalTypes: boolean ) { const ts = ctx.modules.typescript; + const getLang = computed(() => { - return !_sfc.script && !_sfc.scriptSetup ? 'ts' - : _sfc.scriptSetup && _sfc.scriptSetup.lang !== 'js' ? _sfc.scriptSetup.lang - : _sfc.script && _sfc.script.lang !== 'js' ? _sfc.script.lang + return !sfc.script && !sfc.scriptSetup ? 'ts' + : sfc.scriptSetup && sfc.scriptSetup.lang !== 'js' ? sfc.scriptSetup.lang + : sfc.script && sfc.script.lang !== 'js' ? sfc.script.lang : 'js'; }); + const getResolvedOptions = computed(() => { - const options = parseVueCompilerOptions(_sfc.comments); + const options = parseVueCompilerOptions(sfc.comments); if (options) { const resolver = new CompilerOptionsResolver(); resolver.addConfig(options, path.dirname(fileName)); @@ -89,33 +91,37 @@ function createTsx( } return ctx.vueCompilerOptions; }); + const getScriptRanges = computed(() => - _sfc.script - ? parseScriptRanges(ts, _sfc.script.ast, !!_sfc.scriptSetup, false) + sfc.script + ? parseScriptRanges(ts, sfc.script.ast, !!sfc.scriptSetup, false) : undefined ); + const getScriptSetupRanges = computed(() => - _sfc.scriptSetup - ? parseScriptSetupRanges(ts, _sfc.scriptSetup.ast, getResolvedOptions()) + sfc.scriptSetup + ? parseScriptSetupRanges(ts, sfc.scriptSetup.ast, getResolvedOptions()) : undefined ); + const getSetupBindingNames = computedSet( computed(() => { const newNames = new Set(); const bindings = getScriptSetupRanges()?.bindings; - if (_sfc.scriptSetup && bindings) { + if (sfc.scriptSetup && bindings) { for (const { range } of bindings) { - newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end)); + newNames.add(sfc.scriptSetup.content.slice(range.start, range.end)); } } return newNames; }) ); + const getSetupImportComponentNames = computedSet( computed(() => { const newNames = new Set(); const bindings = getScriptSetupRanges()?.bindings; - if (_sfc.scriptSetup && bindings) { + if (sfc.scriptSetup && bindings) { for (const { range, moduleName, isDefaultImport, isNamespace } of bindings) { if ( moduleName @@ -123,13 +129,14 @@ function createTsx( && !isNamespace && ctx.vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext)) ) { - newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end)); + newNames.add(sfc.scriptSetup.content.slice(range.start, range.end)); } } } return newNames; }) ); + const getSetupDestructuredPropNames = computedSet( computed(() => { const newNames = new Set(getScriptSetupRanges()?.defineProps?.destructured?.keys()); @@ -140,6 +147,7 @@ function createTsx( return newNames; }) ); + const getSetupTemplateRefNames = computedSet( computed(() => { const newNames = new Set( @@ -150,29 +158,52 @@ function createTsx( return newNames; }) ); + const setupHasDefineSlots = computed(() => !!getScriptSetupRanges()?.defineSlots); + const getSetupSlotsAssignName = computed(() => getScriptSetupRanges()?.defineSlots?.name); + const getSetupPropsAssignName = computed(() => getScriptSetupRanges()?.defineProps?.name); + const getSetupInheritAttrs = computed(() => { const value = getScriptSetupRanges()?.defineOptions?.inheritAttrs ?? getScriptRanges()?.exportDefault?.inheritAttrsOption; return value !== 'false'; }); + + const getSetupSlotsReferenceName = computedSet( + computed(() => { + const newNames = new Set( + getScriptSetupRanges()?.useSlots.map(({ name }) => name).filter(name => name !== undefined) + ); + return newNames; + }) + ); + + const getSetupAttrsReferenceName = computedSet( + computed(() => { + const newNames = new Set( + getScriptSetupRanges()?.useAttrs.map(({ name }) => name).filter(name => name !== undefined) + ); + return newNames; + }) + ); + const getComponentSelfName = computed(() => { const { exportDefault } = getScriptRanges() ?? {}; - if (_sfc.script && exportDefault?.nameOption) { + if (sfc.script && exportDefault?.nameOption) { const { nameOption } = exportDefault; - return _sfc.script.content.slice(nameOption.start + 1, nameOption.end - 1); + return sfc.script.content.slice(nameOption.start + 1, nameOption.end - 1); } const { defineOptions } = getScriptSetupRanges() ?? {}; - if (_sfc.scriptSetup && defineOptions?.name) { + if (sfc.scriptSetup && defineOptions?.name) { return defineOptions.name; } const baseName = path.basename(fileName); return capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.')))); }); - const getGeneratedTemplate = computed(() => { - if (getResolvedOptions().skipTemplateCodegen || !_sfc.template) { + const getGeneratedTemplate = computed(() => { + if (getResolvedOptions().skipTemplateCodegen || !sfc.template) { return; } @@ -181,7 +212,7 @@ function createTsx( ts, compilerOptions: ctx.compilerOptions, vueCompilerOptions: getResolvedOptions(), - template: _sfc.template, + template: sfc.template, edited: getResolvedOptions().__test || (fileEditTimes.get(fileName) ?? 0) >= 2, scriptSetupBindingNames: getSetupBindingNames(), scriptSetupImportComponentNames: getSetupImportComponentNames(), @@ -190,12 +221,13 @@ function createTsx( hasDefineSlots: setupHasDefineSlots(), slotsAssignName: getSetupSlotsAssignName(), propsAssignName: getSetupPropsAssignName(), + slotsReferenceNames: getSetupSlotsReferenceName(), + attrsReferenceNames: getSetupAttrsReferenceName(), inheritAttrs: getSetupInheritAttrs(), selfComponentName: getComponentSelfName(), }); let current = codegen.next(); - while (!current.done) { const code = current.value; codes.push(code); @@ -207,15 +239,17 @@ function createTsx( codes, }; }); + const getGeneratedScript = computed(() => { - const codes: Code[] = []; const linkedCodeMappings: Mapping[] = []; let generatedLength = 0; + + const codes: Code[] = []; const codegen = generateScript({ ts, compilerOptions: ctx.compilerOptions, vueCompilerOptions: getResolvedOptions(), - sfc: _sfc, + sfc: sfc, edited: getResolvedOptions().__test || (fileEditTimes.get(fileName) ?? 0) >= 2, fileName, lang: getLang(), @@ -231,7 +265,6 @@ function createTsx( fileEditTimes.set(fileName, (fileEditTimes.get(fileName) ?? 0) + 1); let current = codegen.next(); - while (!current.done) { const code = current.value; codes.push(code); From e36b3f0773f7ac22f1e0ba4be761aad700a714ef Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Feb 2025 16:14:19 +0800 Subject: [PATCH 10/37] fix(language-core): handle edge case of default slot name mismatch --- .../language-core/lib/codegen/globalTypes.ts | 4 ++-- .../lib/codegen/template/slotOutlet.ts | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 5c6b22a285..9dec17e995 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -172,9 +172,9 @@ export function generateGlobalTypes({ : T extends () => any ? (props: {}, ctx?: any) => ReturnType : T extends (...args: any) => any ? T : (_: {}${checkUnknownProps ? '' : ' & Record'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${checkUnknownProps ? '' : ' & Record'} } }; - function __VLS_asFunctionalElement(tag: T, endTag?: T): (_: T${checkUnknownComponents ? '' : ' & Record'}) => void; function __VLS_functionalComponentArgsRest any>(t: T): 2 extends Parameters['length'] ? [any] : []; - function __VLS_normalizeSlot(s: S): S extends () => infer R ? (props: {}) => R : S; + function __VLS_asFunctionalElement(tag: T, endTag?: T): (attrs: T${checkUnknownComponents ? '' : ' & Record'}) => void; + function __VLS_asFunctionalSlot(slot: S): (props: NonNullable extends (props: infer P) => any ? P : {}) => void; function __VLS_tryAsConstant(t: T): T; } `; diff --git a/packages/language-core/lib/codegen/template/slotOutlet.ts b/packages/language-core/lib/codegen/template/slotOutlet.ts index 3d42e0353b..6398b4de4d 100644 --- a/packages/language-core/lib/codegen/template/slotOutlet.ts +++ b/packages/language-core/lib/codegen/template/slotOutlet.ts @@ -15,6 +15,7 @@ export function* generateSlotOutlet( node: CompilerDOM.SlotOutletNode ): Generator { const startTagOffset = node.loc.start.offset + options.template.content.slice(node.loc.start.offset).indexOf(node.tag); + const startTagEndOffset = startTagOffset + node.tag.length; const propsVar = ctx.getInternalVariable(); const nameProp = node.props.find(prop => { if (prop.type === CompilerDOM.NodeTypes.ATTRIBUTE) { @@ -30,7 +31,7 @@ export function* generateSlotOutlet( }); if (options.hasDefineSlots) { - yield `__VLS_normalizeSlot(`; + yield `__VLS_asFunctionalSlot(`; if (nameProp) { let codes: Generator | Code[]; if (nameProp.type === CompilerDOM.NodeTypes.ATTRIBUTE && nameProp.value) { @@ -78,16 +79,23 @@ export function* generateSlotOutlet( } else { yield* wrapWith( - node.loc.start.offset, - node.loc.end.offset, + startTagOffset, + startTagEndOffset, ctx.codeFeatures.verification, - `${options.slotsAssignName ?? '__VLS_slots'}['default']` + `${options.slotsAssignName ?? '__VLS_slots'}[`, + ...wrapWith( + startTagOffset, + startTagEndOffset, + ctx.codeFeatures.verification, + `'default'` + ), + `]` ); } - yield `)?.(`; + yield `)(`; yield* wrapWith( startTagOffset, - startTagOffset + node.tag.length, + startTagEndOffset, ctx.codeFeatures.verification, `{${newLine}`, ...generateElementProps( @@ -154,7 +162,7 @@ export function* generateSlotOutlet( else { ctx.slots.push({ name: 'default', - tagRange: [startTagOffset, startTagOffset + node.tag.length], + tagRange: [startTagOffset, startTagEndOffset], nodeLoc: node.loc, propsVar, }); From 0a25c4caa513612a51e9e8582ed9f8a85f4e7ec0 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Feb 2025 20:13:48 +0800 Subject: [PATCH 11/37] fix(language-core): combine dollar variable keys from the upper level interface partial-revert #5185 --- .../lib/codegen/template/index.ts | 10 +--------- .../lib/codegen/template/slotOutlet.ts | 4 ++-- .../lib/parsers/scriptSetupRanges.ts | 12 ++++------- packages/language-core/lib/plugins/vue-tsx.ts | 20 ------------------- 4 files changed, 7 insertions(+), 39 deletions(-) diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index c40ff298b5..b61e99bd66 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -21,8 +21,6 @@ export interface TemplateCodegenOptions { hasDefineSlots?: boolean; slotsAssignName?: string; propsAssignName?: string; - slotsReferenceNames: Set; - attrsReferenceNames: Set; inheritAttrs: boolean; selfComponentName?: string; } @@ -36,12 +34,6 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator { - const newNames = new Set( - getScriptSetupRanges()?.useSlots.map(({ name }) => name).filter(name => name !== undefined) - ); - return newNames; - }) - ); - - const getSetupAttrsReferenceName = computedSet( - computed(() => { - const newNames = new Set( - getScriptSetupRanges()?.useAttrs.map(({ name }) => name).filter(name => name !== undefined) - ); - return newNames; - }) - ); - const getComponentSelfName = computed(() => { const { exportDefault } = getScriptRanges() ?? {}; if (sfc.script && exportDefault?.nameOption) { @@ -221,8 +203,6 @@ function createTsx( hasDefineSlots: setupHasDefineSlots(), slotsAssignName: getSetupSlotsAssignName(), propsAssignName: getSetupPropsAssignName(), - slotsReferenceNames: getSetupSlotsReferenceName(), - attrsReferenceNames: getSetupAttrsReferenceName(), inheritAttrs: getSetupInheritAttrs(), selfComponentName: getComponentSelfName(), }); From 50d3a8eba571e240c945335af9e22c277d749d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:17:19 +0800 Subject: [PATCH 12/37] fix(language-core): use trick to avoid `TS4081` error on slots (#5189) --- .../lib/codegen/script/scriptSetup.ts | 9 +++++- .../lib/codegen/template/context.ts | 1 - .../lib/codegen/template/index.ts | 16 ++++++++-- .../tsc/tests/__snapshots__/dts.spec.ts.snap | 32 +++++++++---------- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index 84aa6d86ad..86b78964b1 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -298,7 +298,14 @@ function* generateSetupFunction( yield* generateComponentSelf(options, ctx, templateCodegenCtx); if (syntax) { - if (!options.vueCompilerOptions.skipTemplateCodegen && (options.templateCodegen?.hasSlot || scriptSetupRanges.defineSlots)) { + if ( + !options.vueCompilerOptions.skipTemplateCodegen + && ( + scriptSetupRanges.defineSlots + || options.templateCodegen?.slots.length + || options.templateCodegen?.dynamicSlots.length + ) + ) { yield `const __VLS_component = `; yield* generateComponent(options, ctx, scriptSetup, scriptSetupRanges); yield endOfLine; diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index e9e7ac3c84..193e6a8cf6 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -86,7 +86,6 @@ export function createTemplateCodegenContext(options: Pick { if (!options.hasDefineSlots) { + const hoistVars = new Map(); + + // trick to avoid TS 4081 (#5186) + for (const slot of ctx.dynamicSlots) { + hoistVars.set(slot.expVar, slot.expVar = ctx.getInternalVariable()); + hoistVars.set(slot.propsVar, slot.propsVar = ctx.getInternalVariable()); + } + for (const slot of ctx.slots) { + hoistVars.set(slot.propsVar, slot.propsVar = ctx.getInternalVariable()); + } + for (const [originalVar, hoistVar] of hoistVars) { + yield `var ${hoistVar} = ${originalVar}${endOfLine}`; + } + const name = getSlotsPropertyName(options.vueCompilerOptions.target); yield `type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex`; for (const { expVar, propsVar } of ctx.dynamicSlots) { - ctx.hasSlot = true; yield `${newLine}& { [K in NonNullable]?: (props: typeof ${propsVar}) => any }`; } for (const slot of ctx.slots) { yield `${newLine}& { `; - ctx.hasSlot = true; if (slot.name && slot.offset !== undefined) { yield* generateObjectProperty( options, diff --git a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap index ec52582c1b..cf987b2092 100644 --- a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap +++ b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap @@ -587,25 +587,25 @@ export {}; exports[`vue-tsc-dts > Input: template-slots/component.vue, Output: template-slots/component.vue.d.ts 1`] = ` "declare const __VLS_ctx: InstanceType<__VLS_PickNotAny {}>>; -declare var __VLS_0: {}; -declare var __VLS_1: { +declare var __VLS_4: {}; +declare var __VLS_5: { num: number; }; -declare var __VLS_2: { +declare var __VLS_6: { str: string; }; -declare var __VLS_3: { +declare var __VLS_7: { num: number; str: string; }; type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex & { - 'no-bind'?: (props: typeof __VLS_0) => any; + 'no-bind'?: (props: typeof __VLS_4) => any; } & { - default?: (props: typeof __VLS_1) => any; + default?: (props: typeof __VLS_5) => any; } & { - 'named-slot'?: (props: typeof __VLS_2) => any; + 'named-slot'?: (props: typeof __VLS_6) => any; } & { - vbind?: (props: typeof __VLS_3) => any; + vbind?: (props: typeof __VLS_7) => any; }>; declare const __VLS_self: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; @@ -664,25 +664,25 @@ type __VLS_WithSlots = T & { exports[`vue-tsc-dts > Input: template-slots/component-no-script.vue, Output: template-slots/component-no-script.vue.d.ts 1`] = ` "declare const __VLS_ctx: InstanceType<__VLS_PickNotAny {}>>; -declare var __VLS_0: {}; -declare var __VLS_1: { +declare var __VLS_4: {}; +declare var __VLS_5: { num: number; }; -declare var __VLS_2: { +declare var __VLS_6: { str: string; }; -declare var __VLS_3: { +declare var __VLS_7: { num: number; str: string; }; type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex & { - 'no-bind'?: (props: typeof __VLS_0) => any; + 'no-bind'?: (props: typeof __VLS_4) => any; } & { - default?: (props: typeof __VLS_1) => any; + default?: (props: typeof __VLS_5) => any; } & { - 'named-slot'?: (props: typeof __VLS_2) => any; + 'named-slot'?: (props: typeof __VLS_6) => any; } & { - vbind?: (props: typeof __VLS_3) => any; + vbind?: (props: typeof __VLS_7) => any; }>; declare const __VLS_self: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; From b9ae179ea33df0007f41eebcdb4a5b7b6b0faa94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:41:08 +0800 Subject: [PATCH 13/37] fix(language-core): hoist the variables that may cause `TS4081` (#5192) --- .../lib/codegen/template/context.ts | 19 ++++++++++++++- .../lib/codegen/template/element.ts | 14 +++++++---- .../lib/codegen/template/index.ts | 20 ++++------------ .../lib/codegen/template/slotOutlet.ts | 8 +++---- .../tsc/tests/__snapshots__/dts.spec.ts.snap | 24 +++++++++---------- 5 files changed, 48 insertions(+), 37 deletions(-) diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index 193e6a8cf6..70e2688d71 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -43,6 +43,7 @@ export function createTemplateCodegenContext(options: Pick(); const localVars = new Map(); const specialVars = new Set(); const accessExternalVariables = new Map>(); @@ -67,7 +68,10 @@ export function createTemplateCodegenContext(options: Pick(); - const templateRefs = new Map(); + const templateRefs = new Map(); return { codeFeatures: new Proxy(codeFeatures, { @@ -116,6 +120,19 @@ export function createTemplateCodegenContext(options: Pick { return `__VLS_${variableId++}`; }, + getHoistVariable: (originalVar: string) => { + let name = hoistVars.get(originalVar); + if (name === undefined) { + hoistVars.set(originalVar, name = `__VLS_${variableId++}`); + } + return name; + }, + generateHoistVariables: function* () { + // trick to avoid TS 4081 (#5186) + for (const [originalVar, hoistVar] of hoistVars) { + yield `var ${hoistVar} = ${originalVar}${endOfLine}`; + } + }, ignoreError: function* (): Generator { if (!ignoredError) { ignoredError = true; diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 59f27de83c..053e61a2ed 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -272,7 +272,10 @@ export function* generateComponent( yield `${endOfLine}`; if (refName) { - ctx.templateRefs.set(refName, [varName, offset!]); + ctx.templateRefs.set(refName, { + varName: ctx.getHoistVariable(varName), + offset: offset! + }); } if (isRootNode) { ctx.singleRootElType = `NonNullable['$el']`; @@ -358,11 +361,14 @@ export function* generateElement( const [refName, offset] = yield* generateVScope(options, ctx, node, node.props); if (refName) { - let refValue = `__VLS_nativeElements['${node.tag}']`; + let element = `__VLS_nativeElements['${node.tag}']`; if (isVForChild) { - refValue = `[${refValue}]`; + element = `[${element}]`; } - ctx.templateRefs.set(refName, [refValue, offset!]); + ctx.templateRefs.set(refName, { + varName: element, + offset: offset! + }); } if (ctx.singleRootNode === node) { ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`; diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index 028ed9e985..2066f69cdc 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -45,6 +45,9 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator { if (!options.hasDefineSlots) { - const hoistVars = new Map(); - - // trick to avoid TS 4081 (#5186) - for (const slot of ctx.dynamicSlots) { - hoistVars.set(slot.expVar, slot.expVar = ctx.getInternalVariable()); - hoistVars.set(slot.propsVar, slot.propsVar = ctx.getInternalVariable()); - } - for (const slot of ctx.slots) { - hoistVars.set(slot.propsVar, slot.propsVar = ctx.getInternalVariable()); - } - for (const [originalVar, hoistVar] of hoistVars) { - yield `var ${hoistVar} = ${originalVar}${endOfLine}`; - } - const name = getSlotsPropertyName(options.vueCompilerOptions.target); yield `type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex`; for (const { expVar, propsVar } of ctx.dynamicSlots) { @@ -145,7 +133,7 @@ function* generateTemplateRefs( ctx: TemplateCodegenContext ): Generator { yield `type __VLS_TemplateRefs = {${newLine}`; - for (const [name, [varName, offset]] of ctx.templateRefs) { + for (const [name, { varName, offset }] of ctx.templateRefs) { yield* generateObjectProperty( options, ctx, diff --git a/packages/language-core/lib/codegen/template/slotOutlet.ts b/packages/language-core/lib/codegen/template/slotOutlet.ts index 09a8b62a2b..ac425cd65f 100644 --- a/packages/language-core/lib/codegen/template/slotOutlet.ts +++ b/packages/language-core/lib/codegen/template/slotOutlet.ts @@ -131,7 +131,7 @@ export function* generateSlotOutlet( offset: nameProp.loc.start.offset + nameProp.loc.source.indexOf(nameProp.value.content, nameProp.name.length), tagRange: [startTagOffset, startTagOffset + node.tag.length], nodeLoc: node.loc, - propsVar, + propsVar: ctx.getHoistVariable(propsVar), }); } else if ( @@ -155,8 +155,8 @@ export function* generateSlotOutlet( ); yield `)${endOfLine}`; ctx.dynamicSlots.push({ - expVar, - propsVar, + expVar: ctx.getHoistVariable(expVar), + propsVar: ctx.getHoistVariable(propsVar), }); } else { @@ -164,7 +164,7 @@ export function* generateSlotOutlet( name: 'default', tagRange: [startTagOffset, startTagEndOffset], nodeLoc: node.loc, - propsVar, + propsVar: ctx.getHoistVariable(propsVar), }); } } diff --git a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap index cf987b2092..ee57e0d31b 100644 --- a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap +++ b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap @@ -587,11 +587,11 @@ export {}; exports[`vue-tsc-dts > Input: template-slots/component.vue, Output: template-slots/component.vue.d.ts 1`] = ` "declare const __VLS_ctx: InstanceType<__VLS_PickNotAny {}>>; -declare var __VLS_4: {}; -declare var __VLS_5: { +declare var __VLS_1: {}; +declare var __VLS_3: { num: number; }; -declare var __VLS_6: { +declare var __VLS_5: { str: string; }; declare var __VLS_7: { @@ -599,11 +599,11 @@ declare var __VLS_7: { str: string; }; type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex & { - 'no-bind'?: (props: typeof __VLS_4) => any; + 'no-bind'?: (props: typeof __VLS_1) => any; } & { - default?: (props: typeof __VLS_5) => any; + default?: (props: typeof __VLS_3) => any; } & { - 'named-slot'?: (props: typeof __VLS_6) => any; + 'named-slot'?: (props: typeof __VLS_5) => any; } & { vbind?: (props: typeof __VLS_7) => any; }>; @@ -664,11 +664,11 @@ type __VLS_WithSlots = T & { exports[`vue-tsc-dts > Input: template-slots/component-no-script.vue, Output: template-slots/component-no-script.vue.d.ts 1`] = ` "declare const __VLS_ctx: InstanceType<__VLS_PickNotAny {}>>; -declare var __VLS_4: {}; -declare var __VLS_5: { +declare var __VLS_1: {}; +declare var __VLS_3: { num: number; }; -declare var __VLS_6: { +declare var __VLS_5: { str: string; }; declare var __VLS_7: { @@ -676,11 +676,11 @@ declare var __VLS_7: { str: string; }; type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex & { - 'no-bind'?: (props: typeof __VLS_4) => any; + 'no-bind'?: (props: typeof __VLS_1) => any; } & { - default?: (props: typeof __VLS_5) => any; + default?: (props: typeof __VLS_3) => any; } & { - 'named-slot'?: (props: typeof __VLS_6) => any; + 'named-slot'?: (props: typeof __VLS_5) => any; } & { vbind?: (props: typeof __VLS_7) => any; }>; From a9e3c754e93d154c612447deb4c42ae41e8e8db0 Mon Sep 17 00:00:00 2001 From: rgehbt <74761884+Gehbt@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:11:50 +0800 Subject: [PATCH 14/37] fix(language-core): adjust regex match for `@vue-generic` to improve offset calculation (#5193) Co-authored-by: KazariEX <85992002+KazariEX@users.noreply.github.com> --- packages/language-core/lib/codegen/template/templateChild.ts | 4 ++-- packages/language-core/lib/plugins/vue-template-inline-ts.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index 922c33e6cc..71571c8b89 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -46,12 +46,12 @@ export function* generateTemplateChild( yield* ctx.expectError(prevNode); } else { - const match = prevNode.loc.source.match(/^$)/); if (match) { const { content } = match.groups ?? {}; ctx.lastGenericComment = { content, - offset: prevNode.loc.start.offset + match[0].indexOf(content) + offset: prevNode.loc.start.offset + match[1].length }; } } diff --git a/packages/language-core/lib/plugins/vue-template-inline-ts.ts b/packages/language-core/lib/plugins/vue-template-inline-ts.ts index 3a1e427259..72e711124f 100644 --- a/packages/language-core/lib/plugins/vue-template-inline-ts.ts +++ b/packages/language-core/lib/plugins/vue-template-inline-ts.ts @@ -73,12 +73,12 @@ const plugin: VueLanguagePlugin = ctx => { function visit(node: CompilerDOM.TemplateChildNode | CompilerDOM.SimpleExpressionNode) { if (node.type === CompilerDOM.NodeTypes.COMMENT) { - const match = node.loc.source.match(/^$)/); if (match) { const { content } = match.groups ?? {}; addFormatCodes( content, - node.loc.start.offset + match[0].indexOf(content), + node.loc.start.offset + match[1].length, formatBrackets.generic ); } From 947c442aacc408fc79aeb4fb86dded9d54b22f28 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Tue, 18 Feb 2025 22:53:21 +0800 Subject: [PATCH 15/37] refactor(language-core): use a single regexp to parse directive comments --- .../lib/codegen/template/templateChild.ts | 47 +++++++++++-------- .../lib/plugins/vue-template-inline-ts.ts | 6 +-- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index 71571c8b89..5bcce65a7e 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -10,6 +10,8 @@ import { generateVFor } from './vFor'; import { generateVIf } from './vIf'; import { generateVSlot } from './vSlot'; +const commentDirectiveRegex = /^$/; + // @ts-ignore const transformContext: CompilerDOM.TransformContext = { onError: () => { }, @@ -34,25 +36,32 @@ export function* generateTemplateChild( isVForChild: boolean = false ): Generator { if (prevNode?.type === CompilerDOM.NodeTypes.COMMENT) { - const commentText = prevNode.content.trim().split(' ')[0]; - if (/^@vue-skip\b[\s\S]*/.test(commentText)) { - yield `// @vue-skip${newLine}`; - return; - } - else if (/^@vue-ignore\b[\s\S]*/.test(commentText)) { - yield* ctx.ignoreError(); - } - else if (/^@vue-expect-error\b[\s\S]*/.test(commentText)) { - yield* ctx.expectError(prevNode); - } - else { - const match = prevNode.loc.source.match(/(^$)/); - if (match) { - const { content } = match.groups ?? {}; - ctx.lastGenericComment = { - content, - offset: prevNode.loc.start.offset + match[1].length - }; + const match = prevNode.loc.source.match(commentDirectiveRegex); + if (match) { + const { name, content } = match.groups!; + switch (name) { + case 'skip': { + yield `// @vue-skip${newLine}`; + return; + } + case 'ignore': { + yield* ctx.ignoreError(); + break; + } + case 'expect-error': { + yield* ctx.expectError(prevNode); + break; + } + case 'generic': { + const text = content.trim(); + if (text.startsWith('{') && text.endsWith('}')) { + ctx.lastGenericComment = { + content: text.slice(1, -1), + offset: prevNode.loc.start.offset + prevNode.loc.source.indexOf('{') + 1, + }; + } + break; + } } } } diff --git a/packages/language-core/lib/plugins/vue-template-inline-ts.ts b/packages/language-core/lib/plugins/vue-template-inline-ts.ts index 72e711124f..0b7afd29fc 100644 --- a/packages/language-core/lib/plugins/vue-template-inline-ts.ts +++ b/packages/language-core/lib/plugins/vue-template-inline-ts.ts @@ -73,12 +73,12 @@ const plugin: VueLanguagePlugin = ctx => { function visit(node: CompilerDOM.TemplateChildNode | CompilerDOM.SimpleExpressionNode) { if (node.type === CompilerDOM.NodeTypes.COMMENT) { - const match = node.loc.source.match(/(^$)/); + const match = node.loc.source.match(/^$/); if (match) { - const { content } = match.groups ?? {}; + const { content } = match.groups!; addFormatCodes( content, - node.loc.start.offset + match[1].length, + node.loc.start.offset + node.loc.source.indexOf('{') + 1, formatBrackets.generic ); } From 1f9a4f81d1bf93def4d4f40b3aa5d51086b979a5 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Tue, 18 Feb 2025 23:52:30 +0800 Subject: [PATCH 16/37] refactor(language-core): drop invalid `v-scope` implemention --- .../language-core/lib/codegen/globalTypes.ts | 15 ++- .../lib/codegen/template/element.ts | 115 ++++++------------ .../lib/codegen/template/elementChildren.ts | 22 +--- .../lib/codegen/template/elementEvents.ts | 22 ++-- .../lib/codegen/template/templateChild.ts | 8 +- .../lib/codegen/template/vSlot.ts | 20 +++ 6 files changed, 85 insertions(+), 117 deletions(-) diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 9dec17e995..65ff237c33 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -160,15 +160,18 @@ export function generateGlobalTypes({ : T extends (...args: any) => any ? T : __VLS_unknownDirective; - function __VLS_withScope(ctx: T, scope: K): ctx is T & K; function __VLS_makeOptional(t: T): { [K in keyof T]?: T[K] }; function __VLS_asFunctionalComponent any ? InstanceType : unknown>(t: T, instance?: K): T extends new (...args: any) => any - ? (props: ${fnPropsType}, ctx?: any) => __VLS_Element & { __ctx?: { - attrs?: any, - slots?: K extends { ${getSlotsPropertyName(target)}: infer Slots } ? Slots : any, - emit?: K extends { $emit: infer Emit } ? Emit : any - } & { props?: ${fnPropsType}; expose?(exposed: K): void; } } + ? (props: ${fnPropsType}, ctx?: any) => __VLS_Element & { + __ctx?: { + attrs?: any; + slots?: K extends { ${getSlotsPropertyName(target)}: infer Slots } ? Slots : any; + emit?: K extends { $emit: infer Emit } ? Emit : any; + expose?(exposed: K): void; + props?: ${fnPropsType}; + } + } : T extends () => any ? (props: {}, ctx?: any) => ReturnType : T extends (...args: any) => any ? T : (_: {}${checkUnknownProps ? '' : ' & Record'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${checkUnknownProps ? '' : ' & Record'} } }; diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 053e61a2ed..6c104abc61 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -14,7 +14,7 @@ import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generatePropertyAccess } from './propertyAccess'; import { collectStyleScopedClassReferences } from './styleScopedClasses'; -import { generateVSlot } from './vSlot'; +import { generateImplicitDefaultSlot, generateVSlot } from './vSlot'; const colonReg = /:/g; @@ -33,16 +33,14 @@ export function* generateComponent( const failedPropExps: FailedPropExpression[] = []; const possibleOriginalNames = getPossibleOriginalComponentNames(node.tag, true); const matchImportName = possibleOriginalNames.find(name => options.scriptSetupImportComponentNames.has(name)); - const var_originalComponent = matchImportName ?? ctx.getInternalVariable(); - const var_functionalComponent = ctx.getInternalVariable(); - const var_componentInstance = ctx.getInternalVariable(); - const var_componentEmit = ctx.getInternalVariable(); - const var_componentEvents = ctx.getInternalVariable(); - const var_defineComponentCtx = ctx.getInternalVariable(); + const componentOriginalVar = matchImportName ?? ctx.getInternalVariable(); + const componentFunctionalVar = ctx.getInternalVariable(); + const componentVNodeVar = ctx.getInternalVariable(); + const componentCtxVar = ctx.getInternalVariable(); const isComponentTag = node.tag.toLowerCase() === 'component'; ctx.currentComponent = { - ctxVar: var_defineComponentCtx, + ctxVar: componentCtxVar, used: false }; @@ -88,9 +86,9 @@ export function* generateComponent( yield `/** @type {[`; for (const tagOffset of tagOffsets) { yield `typeof `; - if (var_originalComponent === node.tag) { + if (componentOriginalVar === node.tag) { yield [ - var_originalComponent, + componentOriginalVar, 'template', tagOffset, ctx.codeFeatures.withoutHighlightAndCompletion, @@ -115,7 +113,7 @@ export function* generateComponent( yield `]} */${endOfLine}`; } else if (dynamicTagInfo) { - yield `const ${var_originalComponent} = (`; + yield `const ${componentOriginalVar} = (`; yield* generateInterpolation( options, ctx, @@ -144,7 +142,7 @@ export function* generateComponent( yield `)${endOfLine}`; } else if (!isComponentTag) { - yield `const ${var_originalComponent} = ({} as __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', __VLS_LocalComponents, `; + yield `const ${componentOriginalVar} = ({} as __VLS_WithComponent<'${getCanonicalComponentName(node.tag)}', __VLS_LocalComponents, `; if (options.selfComponentName && possibleOriginalNames.includes(options.selfComponentName)) { yield `typeof __VLS_self & (new () => { ` + getSlotsPropertyName(options.vueCompilerOptions.target) @@ -204,11 +202,11 @@ export function* generateComponent( } } else { - yield `const ${var_originalComponent} = {} as any${endOfLine}`; + yield `const ${componentOriginalVar} = {} as any${endOfLine}`; } yield `// @ts-ignore${newLine}`; - yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({${newLine}`; + yield `const ${componentFunctionalVar} = __VLS_asFunctionalComponent(${componentOriginalVar}, new ${componentOriginalVar}({${newLine}`; yield* generateElementProps( options, ctx, @@ -230,9 +228,9 @@ export function* generateComponent( }, } }), - var_componentInstance + componentVNodeVar ); - yield ` = ${var_functionalComponent}`; + yield ` = ${componentFunctionalVar}`; yield* generateComponentGeneric(ctx); yield `(`; yield* wrapWith( @@ -251,18 +249,26 @@ export function* generateComponent( ), `}` ); - yield `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`; + yield `, ...__VLS_functionalComponentArgsRest(${componentFunctionalVar}))${endOfLine}`; yield* generateFailedPropExps(options, ctx, failedPropExps); + yield* generateElementEvents(options, ctx, node, componentFunctionalVar, componentVNodeVar, componentCtxVar); + yield* generateElementDirectives(options, ctx, node); - const [refName, offset] = yield* generateVScope(options, ctx, node, props); + if (hasVBindAttrs(options, ctx, node)) { + const attrsVar = ctx.getInternalVariable(); + ctx.inheritedAttrVars.add(attrsVar); + yield `let ${attrsVar}!: Parameters[0];\n`; + } + + const [refName, offset] = yield* generateElementReference(options, ctx, node); const isRootNode = node === ctx.singleRootNode; if (refName || isRootNode) { - const varName = ctx.getInternalVariable(); + const componentInstanceVar = ctx.getInternalVariable(); ctx.currentComponent.used = true; - yield `var ${varName} = {} as (Parameters>[0] | null)`; + yield `var ${componentInstanceVar} = {} as (Parameters>[0] | null)`; if (node.codegenNode?.type === CompilerDOM.NodeTypes.VNODE_CALL && node.codegenNode.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION && node.codegenNode.props.properties.some(({ key }) => key.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && key.content === 'ref_for') @@ -273,38 +279,29 @@ export function* generateComponent( if (refName) { ctx.templateRefs.set(refName, { - varName: ctx.getHoistVariable(varName), + varName: ctx.getHoistVariable(componentInstanceVar), offset: offset! }); } if (isRootNode) { - ctx.singleRootElType = `NonNullable['$el']`; + ctx.singleRootElType = `NonNullable['$el']`; } } - const usedComponentEventsVar = yield* generateElementEvents(options, ctx, node, var_functionalComponent, var_componentInstance, var_componentEvents); - if (usedComponentEventsVar) { - ctx.currentComponent.used = true; - yield `let ${var_componentEmit}!: typeof ${var_defineComponentCtx}.emit${endOfLine}`; - yield `let ${var_componentEvents}!: __VLS_NormalizeEmits${endOfLine}`; - } - - if (hasVBindAttrs(options, ctx, node)) { - const attrsVar = ctx.getInternalVariable(); - ctx.inheritedAttrVars.add(attrsVar); - yield `let ${attrsVar}!: Parameters[0];\n`; - } + collectStyleScopedClassReferences(options, ctx, node); const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; if (slotDir) { yield* generateVSlot(options, ctx, node, slotDir); } else { - yield* generateElementChildren(options, ctx, node, true); + // #932: reference for default slot + yield* generateImplicitDefaultSlot(ctx, node); + yield* generateElementChildren(options, ctx, node); } if (ctx.currentComponent.used) { - yield `var ${var_defineComponentCtx}!: __VLS_PickFunctionalComponentCtx${endOfLine}`; + yield `var ${componentCtxVar}!: __VLS_PickFunctionalComponentCtx${endOfLine}`; } } @@ -358,8 +355,9 @@ export function* generateElement( yield `)${endOfLine}`; yield* generateFailedPropExps(options, ctx, failedPropExps); + yield* generateElementDirectives(options, ctx, node); - const [refName, offset] = yield* generateVScope(options, ctx, node, node.props); + const [refName, offset] = yield* generateElementReference(options, ctx, node); if (refName) { let element = `__VLS_nativeElements['${node.tag}']`; if (isVForChild) { @@ -378,6 +376,8 @@ export function* generateElement( ctx.inheritedAttrVars.add(`__VLS_intrinsicElements.${node.tag}`); } + collectStyleScopedClassReferences(options, ctx, node); + yield* generateElementChildren(options, ctx, node); } @@ -402,45 +402,6 @@ function* generateFailedPropExps( } } -function* generateVScope( - options: TemplateCodegenOptions, - ctx: TemplateCodegenContext, - node: CompilerDOM.ElementNode, - props: (CompilerDOM.AttributeNode | CompilerDOM.DirectiveNode)[] -): Generator { - const vScope = props.find(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && (prop.name === 'scope' || prop.name === 'data')); - let inScope = false; - let originalConditionsNum = ctx.blockConditions.length; - - if (vScope?.type === CompilerDOM.NodeTypes.DIRECTIVE && vScope.exp) { - - const scopeVar = ctx.getInternalVariable(); - const condition = `__VLS_withScope(__VLS_ctx, ${scopeVar})`; - - yield `const ${scopeVar} = `; - yield [ - vScope.exp.loc.source, - 'template', - vScope.exp.loc.start.offset, - ctx.codeFeatures.all, - ]; - yield endOfLine; - yield `if (${condition}) {${newLine}`; - ctx.blockConditions.push(condition); - inScope = true; - } - - yield* generateElementDirectives(options, ctx, node); - const [refName, offset] = yield* generateReferencesForElements(options, ctx, node); // - collectStyleScopedClassReferences(options, ctx, node); - - if (inScope) { - yield `}${newLine}`; - ctx.blockConditions.length = originalConditionsNum; - } - return [refName, offset]; -} - function getCanonicalComponentName(tagText: string) { return variableNameRegex.test(tagText) ? tagText @@ -496,7 +457,7 @@ function* generateComponentGeneric( ctx.lastGenericComment = undefined; } -function* generateReferencesForElements( +function* generateElementReference( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode diff --git a/packages/language-core/lib/codegen/template/elementChildren.ts b/packages/language-core/lib/codegen/template/elementChildren.ts index f49265e5c7..65de04b9ce 100644 --- a/packages/language-core/lib/codegen/template/elementChildren.ts +++ b/packages/language-core/lib/codegen/template/elementChildren.ts @@ -1,6 +1,5 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; -import { endOfLine, wrapWith } from '../utils'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateTemplateChild } from './templateChild'; @@ -8,8 +7,7 @@ import { generateTemplateChild } from './templateChild'; export function* generateElementChildren( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, - node: CompilerDOM.ElementNode, - isDefaultSlot: boolean = false + node: CompilerDOM.ElementNode ): Generator { yield* ctx.resetDirectiveComments('end of element children start'); let prev: CompilerDOM.TemplateChildNode | undefined; @@ -18,22 +16,4 @@ export function* generateElementChildren( prev = childNode; } yield* ctx.generateAutoImportCompletion(); - - // fix https://github.com/vuejs/language-tools/issues/932 - if ( - ctx.currentComponent - && isDefaultSlot - && node.children.length - && node.tagType === CompilerDOM.ElementTypes.COMPONENT - ) { - ctx.currentComponent.used = true; - yield `${ctx.currentComponent.ctxVar}.slots!.`; - yield* wrapWith( - node.children[0].loc.start.offset, - node.children[node.children.length - 1].loc.end.offset, - ctx.codeFeatures.navigation, - `default` - ); - yield endOfLine; - } } diff --git a/packages/language-core/lib/codegen/template/elementEvents.ts b/packages/language-core/lib/codegen/template/elementEvents.ts index e6ec49a56a..e09be5dbd8 100644 --- a/packages/language-core/lib/codegen/template/elementEvents.ts +++ b/packages/language-core/lib/codegen/template/elementEvents.ts @@ -12,11 +12,12 @@ export function* generateElementEvents( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode, - componentVar: string, - componentInstanceVar: string, - eventsVar: string -): Generator { - let usedComponentEventsVar = false; + componentFunctionalVar: string, + componentVNodeVar: string, + componentCtxVar: string +): Generator { + let emitVar: string | undefined; + let eventsVar: string | undefined; let propsVar: string | undefined; for (const prop of node.props) { if ( @@ -26,10 +27,14 @@ export function* generateElementEvents( && !prop.arg.loc.source.startsWith('[') && !prop.arg.loc.source.endsWith(']') ) { - usedComponentEventsVar = true; - if (!propsVar) { + ctx.currentComponent!.used = true; + if (!emitVar) { + emitVar = ctx.getInternalVariable(); + eventsVar = ctx.getInternalVariable(); propsVar = ctx.getInternalVariable(); - yield `let ${propsVar}!: __VLS_FunctionalComponentProps${endOfLine}`; + yield `let ${emitVar}!: typeof ${componentCtxVar}.emit${endOfLine}`; + yield `let ${eventsVar}!: __VLS_NormalizeEmits${endOfLine}`; + yield `let ${propsVar}!: __VLS_FunctionalComponentProps${endOfLine}`; } let source = prop.arg.loc.source; let start = prop.arg.loc.start.offset; @@ -48,7 +53,6 @@ export function* generateElementEvents( yield `}${endOfLine}`; } } - return usedComponentEventsVar; } export function* generateEventArg( diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index 5bcce65a7e..feedbcf3ba 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -183,14 +183,14 @@ export function getVForNode(node: CompilerDOM.ElementNode) { } function getVIfNode(node: CompilerDOM.ElementNode) { - const forDirective = node.props.find( + const ifDirective = node.props.find( (prop): prop is CompilerDOM.DirectiveNode => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'if' ); - if (forDirective) { + if (ifDirective) { let ifNode: CompilerDOM.IfNode | undefined; - CompilerDOM.processIf(node, forDirective, transformContext, _ifNode => { + CompilerDOM.processIf(node, ifDirective, transformContext, _ifNode => { ifNode = { ..._ifNode }; return undefined; }); @@ -198,7 +198,7 @@ function getVIfNode(node: CompilerDOM.ElementNode) { for (const branch of ifNode.branches) { branch.children = [{ ...node, - props: node.props.filter(prop => prop !== forDirective), + props: node.props.filter(prop => prop !== ifDirective), }]; } return ifNode; diff --git a/packages/language-core/lib/codegen/template/vSlot.ts b/packages/language-core/lib/codegen/template/vSlot.ts index 8be7bb9c66..11597f4163 100644 --- a/packages/language-core/lib/codegen/template/vSlot.ts +++ b/packages/language-core/lib/codegen/template/vSlot.ts @@ -107,3 +107,23 @@ export function* generateVSlot( yield* ctx.generateAutoImportCompletion(); yield `}${newLine}`; } + +export function* generateImplicitDefaultSlot( + ctx: TemplateCodegenContext, + node: CompilerDOM.ElementNode +) { + if (!ctx.currentComponent) { + return; + } + if (node.children.length) { + ctx.currentComponent.used = true; + yield `${ctx.currentComponent.ctxVar}.slots!.`; + yield* wrapWith( + node.children[0].loc.start.offset, + node.children[node.children.length - 1].loc.end.offset, + ctx.codeFeatures.navigation, + `default` + ); + yield endOfLine; + } +} From 72c7059430c56ea5d27865312ebd9540660e5d01 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Wed, 19 Feb 2025 02:09:37 +0800 Subject: [PATCH 17/37] refactor(language-core): improve type declaration of `v-for` --- .../language-core/lib/codegen/globalTypes.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 65ff237c33..eaef02986d 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -131,19 +131,12 @@ export function generateGlobalTypes({ }; type __VLS_UseTemplateRef = Readonly>; - function __VLS_getVForSourceType(source: number): [number, number][]; - function __VLS_getVForSourceType(source: string): [string, number][]; - function __VLS_getVForSourceType(source: T): [ - item: T[number], - index: number, - ][]; - function __VLS_getVForSourceType }>(source: T): [ - item: T extends { [Symbol.iterator](): Iterator } ? T1 : never, - index: number, - ][]; - // #3845 - function __VLS_getVForSourceType }>(source: T): [ - item: number | (Exclude extends { [Symbol.iterator](): Iterator } ? T1 : never), + function __VLS_getVForSourceType>(source: T): [ + item: T extends number ? number + : T extends string ? string + : T extends any[] ? T[number] + : T extends Iterable ? T1 + : any, index: number, ][]; function __VLS_getVForSourceType(source: T): [ From 32e455f4cd0286402a6098c2d8b776fbbaad2bfd Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Wed, 19 Feb 2025 03:09:41 +0800 Subject: [PATCH 18/37] fix(language-core): correct codegen of native element refs --- .../language-core/lib/codegen/globalTypes.ts | 10 ++--- .../lib/codegen/template/context.ts | 2 +- .../lib/codegen/template/element.ts | 38 +++++++++---------- .../lib/codegen/template/index.ts | 4 +- .../lib/codegen/template/templateChild.ts | 2 +- .../vue3/#4777/template-ref.vue | 16 -------- .../vue3/templateRef/components.d.ts | 7 ---- .../passedFixtures/vue3/templateRef/main.vue | 6 +-- .../missing-import.vue} | 0 .../{template-ref.vue => template-refs.vue} | 12 ++++-- .../vue3/templateRef_native/main.vue | 38 ------------------- 11 files changed, 36 insertions(+), 99 deletions(-) delete mode 100644 test-workspace/tsc/passedFixtures/vue3/#4777/template-ref.vue delete mode 100644 test-workspace/tsc/passedFixtures/vue3/templateRef/components.d.ts rename test-workspace/tsc/passedFixtures/vue3/{templateRef_missingImport/main.vue => templateRef/missing-import.vue} (100%) rename test-workspace/tsc/passedFixtures/vue3/templateRef/{template-ref.vue => template-refs.vue} (74%) delete mode 100644 test-workspace/tsc/passedFixtures/vue3/templateRef_native/main.vue diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index eaef02986d..0a996c24df 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -45,11 +45,7 @@ export function generateGlobalTypes({ const __VLS_unref: typeof import('${lib}').unref; const __VLS_placeholder: any; - const __VLS_nativeElements = { - ...{} as SVGElementTagNameMap, - ...{} as HTMLElementTagNameMap, - }; - + type __VLS_NativeElements = __VLS_SpreadMerge; type __VLS_IntrinsicElements = ${( target >= 3.3 ? `import('${lib}/jsx-runtime').JSX.IntrinsicElements;` @@ -68,7 +64,7 @@ export function generateGlobalTypes({ type __VLS_GlobalDirectives = import('${lib}').GlobalDirectives; type __VLS_IsAny = 0 extends 1 & T ? true : false; type __VLS_PickNotAny = __VLS_IsAny extends true ? B : A; - type __VLS_unknownDirective = (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown) => void; + type __VLS_SpreadMerge = Omit & B; type __VLS_WithComponent = N1 extends keyof LocalComponents ? N1 extends N0 ? Pick : { [K in N0]: LocalComponents[N1] } : N2 extends keyof LocalComponents ? N2 extends N0 ? Pick : { [K in N0]: LocalComponents[N2] } : @@ -152,7 +148,7 @@ export function generateGlobalTypes({ ? NonNullable : T extends (...args: any) => any ? T - : __VLS_unknownDirective; + : (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown) => void; function __VLS_makeOptional(t: T): { [K in keyof T]?: T[K] }; function __VLS_asFunctionalComponent any ? InstanceType : unknown>(t: T, instance?: K): T extends new (...args: any) => any diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index 70e2688d71..b4f75c9b9a 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -69,7 +69,7 @@ export function createTemplateCodegenContext(options: Pick(); const templateRefs = new Map(); diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 6c104abc61..60b117754f 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -21,7 +21,8 @@ const colonReg = /:/g; export function* generateComponent( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, - node: CompilerDOM.ElementNode + node: CompilerDOM.ElementNode, + isVForChild: boolean ): Generator { const tagOffsets = [node.loc.start.offset + options.template.content.slice(node.loc.start.offset).indexOf(node.tag)]; if (!node.isSelfClosing && options.template.lang === 'html') { @@ -255,12 +256,6 @@ export function* generateComponent( yield* generateElementEvents(options, ctx, node, componentFunctionalVar, componentVNodeVar, componentCtxVar); yield* generateElementDirectives(options, ctx, node); - if (hasVBindAttrs(options, ctx, node)) { - const attrsVar = ctx.getInternalVariable(); - ctx.inheritedAttrVars.add(attrsVar); - yield `let ${attrsVar}!: Parameters[0];\n`; - } - const [refName, offset] = yield* generateElementReference(options, ctx, node); const isRootNode = node === ctx.singleRootNode; @@ -269,18 +264,15 @@ export function* generateComponent( ctx.currentComponent.used = true; yield `var ${componentInstanceVar} = {} as (Parameters>[0] | null)`; - if (node.codegenNode?.type === CompilerDOM.NodeTypes.VNODE_CALL - && node.codegenNode.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION - && node.codegenNode.props.properties.some(({ key }) => key.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && key.content === 'ref_for') - ) { + if (isVForChild) { yield `[]`; } yield `${endOfLine}`; - if (refName) { + if (refName && offset) { ctx.templateRefs.set(refName, { - varName: ctx.getHoistVariable(componentInstanceVar), - offset: offset! + typeExp: `typeof ${ctx.getHoistVariable(componentInstanceVar)}`, + offset }); } if (isRootNode) { @@ -288,6 +280,12 @@ export function* generateComponent( } } + if (hasVBindAttrs(options, ctx, node)) { + const attrsVar = ctx.getInternalVariable(); + ctx.inheritedAttrVars.add(attrsVar); + yield `let ${attrsVar}!: Parameters[0]${endOfLine}`; + } + collectStyleScopedClassReferences(options, ctx, node); const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; @@ -358,18 +356,18 @@ export function* generateElement( yield* generateElementDirectives(options, ctx, node); const [refName, offset] = yield* generateElementReference(options, ctx, node); - if (refName) { - let element = `__VLS_nativeElements['${node.tag}']`; + if (refName && offset) { + let typeExp = `__VLS_NativeElements['${node.tag}']`; if (isVForChild) { - element = `[${element}]`; + typeExp += `[]`; } ctx.templateRefs.set(refName, { - varName: element, - offset: offset! + typeExp, + offset }); } if (ctx.singleRootNode === node) { - ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`; + ctx.singleRootElType = `__VLS_NativeElements['${node.tag}']`; } if (hasVBindAttrs(options, ctx, node)) { diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index 2066f69cdc..00e920ed0f 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -133,7 +133,7 @@ function* generateTemplateRefs( ctx: TemplateCodegenContext ): Generator { yield `type __VLS_TemplateRefs = {${newLine}`; - for (const [name, { varName, offset }] of ctx.templateRefs) { + for (const [name, { typeExp, offset }] of ctx.templateRefs) { yield* generateObjectProperty( options, ctx, @@ -141,7 +141,7 @@ function* generateTemplateRefs( offset, ctx.codeFeatures.navigationAndCompletion ); - yield `: typeof ${varName},${newLine}`; + yield `: ${typeExp},${newLine}`; } yield `}${endOfLine}`; return `__VLS_TemplateRefs`; diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index feedbcf3ba..9dcc5998aa 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -113,7 +113,7 @@ export function* generateTemplateChild( } else { const { currentComponent } = ctx; - yield* generateComponent(options, ctx, node); + yield* generateComponent(options, ctx, node, isVForChild); ctx.currentComponent = currentComponent; } } diff --git a/test-workspace/tsc/passedFixtures/vue3/#4777/template-ref.vue b/test-workspace/tsc/passedFixtures/vue3/#4777/template-ref.vue deleted file mode 100644 index 872d57f801..0000000000 --- a/test-workspace/tsc/passedFixtures/vue3/#4777/template-ref.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/test-workspace/tsc/passedFixtures/vue3/templateRef/components.d.ts b/test-workspace/tsc/passedFixtures/vue3/templateRef/components.d.ts deleted file mode 100644 index deac04a9e0..0000000000 --- a/test-workspace/tsc/passedFixtures/vue3/templateRef/components.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module 'vue' { - export interface GlobalComponents { - GenericGlobal: typeof import('./generic.vue')['default']; - } -} - -export { }; diff --git a/test-workspace/tsc/passedFixtures/vue3/templateRef/main.vue b/test-workspace/tsc/passedFixtures/vue3/templateRef/main.vue index 50c8109f39..5e5ea4fff2 100644 --- a/test-workspace/tsc/passedFixtures/vue3/templateRef/main.vue +++ b/test-workspace/tsc/passedFixtures/vue3/templateRef/main.vue @@ -1,7 +1,7 @@ diff --git a/test-workspace/tsc/passedFixtures/vue3/templateRef_missingImport/main.vue b/test-workspace/tsc/passedFixtures/vue3/templateRef/missing-import.vue similarity index 100% rename from test-workspace/tsc/passedFixtures/vue3/templateRef_missingImport/main.vue rename to test-workspace/tsc/passedFixtures/vue3/templateRef/missing-import.vue diff --git a/test-workspace/tsc/passedFixtures/vue3/templateRef/template-ref.vue b/test-workspace/tsc/passedFixtures/vue3/templateRef/template-refs.vue similarity index 74% rename from test-workspace/tsc/passedFixtures/vue3/templateRef/template-ref.vue rename to test-workspace/tsc/passedFixtures/vue3/templateRef/template-refs.vue index c40f9dc438..f375343f00 100644 --- a/test-workspace/tsc/passedFixtures/vue3/templateRef/template-ref.vue +++ b/test-workspace/tsc/passedFixtures/vue3/templateRef/template-refs.vue @@ -1,6 +1,7 @@