diff --git a/.github/workflows/extension-build.yml b/.github/workflows/extension-build.yml
new file mode 100644
index 0000000000..993380f68d
--- /dev/null
+++ b/.github/workflows/extension-build.yml
@@ -0,0 +1,31 @@
+name: extension
+
+on:
+ push:
+ branches:
+ - 'master'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: pnpm/action-setup@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: pnpm
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Build
+ run: pnpm --filter ./extensions/vscode run pack
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: Extension
+ path: ./extensions/vscode/volar-*.vsix
diff --git a/.github/workflows/release.yml b/.github/workflows/extension-release.yml
similarity index 97%
rename from .github/workflows/release.yml
rename to .github/workflows/extension-release.yml
index 01e60841a7..6102269aa7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/extension-release.yml
@@ -1,4 +1,4 @@
-name: release
+name: extension
on:
workflow_dispatch:
diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml
index 66ce7606d9..8ab6a806bb 100644
--- a/.github/workflows/pkg.pr.new.yml
+++ b/.github/workflows/pkg.pr.new.yml
@@ -1,4 +1,4 @@
-name: Publish Any Commit
+name: publish-any-commit
on: [push, pull_request]
jobs:
@@ -6,17 +6,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- - name: Checkout code
- uses: actions/checkout@v4
+ - uses: actions/checkout@v4
- - run: |
- npm install -g corepack@latest
- corepack enable
+ - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- cache: "pnpm"
+ cache: pnpm
- name: Install dependencies
run: pnpm install
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae673de21f..9fc0f3547c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,36 @@
> [Join the Insiders Program](https://github.com/vuejs/language-tools/wiki/Get-Insiders-Edition) for more exclusive features and updates.
+## 2.2.4 official, 2.2.5 insiders (2025-02-22)
+
+### Features
+
+- feat(language-service): map sfc compiler errors outside the template inner content (#5045) - Thanks to @KazariEX!
+- feat(language-core): introduce options to control type inference of `$attrs`, `$el`, `$refs` and `$slots` (#5135) - Thanks to @KazariEX!
+- feat(language-core): enhance single root nodes collection (#4819) - Thanks to @KazariEX!
+
+### Bug Fixes
+
+- fix(language-core): move `generateSfcBlockSection` to the end to fix missing comma errors (#5184) - Thanks to @zhiyuanzmj!
+- fix(language-core): handle edge case of default slot name mismatch - Thanks to @KazariEX!
+- fix(language-core): combine dollar variable keys from the upper level interface - Thanks to @KazariEX!
+- fix(language-core): hoist the variables that may cause `TS4081` (#5192) - Thanks to @KazariEX!
+- fix(language-core): adjust regex match for `@vue-generic` to improve offset calculation (#5193) - Thanks to @Gehbt!
+- fix(language-core): correct codegen of native element refs - Thanks to @KazariEX!
+- fix(language-core): ignore latex block content (#5151) - Thanks to @KazariEX!
+- fix(language-core): do not emit `undefined` for model with default value (#5198) - Thanks to @RylanBueckert-Broadsign!
+- fix(language-service): typescript-semantic renaming first in style blocks (#4685) - Thanks to @KazariEX!
+- fix(typescript-plugin): prevent removed components from appearing in the completion list - Thanks to @KazariEX!
+
+### Other Changes
+
+- refactor(language-core): drop invalid `v-scope` implemention - Thanks to @KazariEX!
+- refactor(language-core): improve type declaration of `v-for` - Thanks to @KazariEX!
+- test: enable `declaration` to track more errors - Thanks to @KazariEX!
+- refactor(language-core): remove semantic highlight of style module names - Thanks to @KazariEX!
+- chore(language-core): add docs for `@vue-expect-error` support (#5176) - Thanks to @machty!
+- ci: upload extension as artifact for each commit - Thanks to @KazariEX!
+
## 2.2.2 official, 2.2.3 insiders (2025-02-15)
### Features
diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json
index b9e0b0e421..13e5f94fba 100644
--- a/extensions/vscode/package.json
+++ b/extensions/vscode/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "volar",
- "version": "2.2.2",
+ "version": "2.2.4",
"repository": {
"type": "git",
"url": "https://github.com/vuejs/language-tools.git",
@@ -567,9 +567,9 @@
"@types/vscode": "^1.82.0",
"@volar/vscode": "~2.4.11",
"@vscode/vsce": "^3.2.1",
- "@vue/language-core": "2.2.2",
- "@vue/language-server": "2.2.2",
- "@vue/typescript-plugin": "2.2.2",
+ "@vue/language-core": "2.2.4",
+ "@vue/language-server": "2.2.4",
+ "@vue/typescript-plugin": "2.2.4",
"esbuild": "^0.25.0",
"esbuild-visualizer": "^0.7.0",
"reactive-vscode": "^0.2.9",
diff --git a/insiders.json b/insiders.json
index 697771f184..336237e694 100644
--- a/insiders.json
+++ b/insiders.json
@@ -1,6 +1,22 @@
{
- "latest": "2.2.1",
+ "latest": "2.2.5",
"versions": [
+ {
+ "version": "2.2.5",
+ "date": "2025-02-22",
+ "downloads": {
+ "GitHub": "https://github.com/volarjs/insiders/releases/tag/v2.2.5",
+ "AFDIAN": "https://afdian.com/p/708861e2f1c611ef9fc552540025c377"
+ }
+ },
+ {
+ "version": "2.2.3",
+ "date": "2025-02-15",
+ "downloads": {
+ "GitHub": "https://github.com/volarjs/insiders/releases/tag/v2.2.3",
+ "AFDIAN": "https://afdian.com/p/6fa17e40ebc111ef849e52540025c377"
+ }
+ },
{
"version": "2.2.1",
"date": "2024-12-24",
diff --git a/lerna.json b/lerna.json
index 83f3407f0c..4344415445 100644
--- a/lerna.json
+++ b/lerna.json
@@ -6,5 +6,5 @@
"packages/*",
"test-workspace"
],
- "version": "2.2.2"
+ "version": "2.2.4"
}
diff --git a/package.json b/package.json
index 0187fafb40..ff76d56067 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "packageManager": "pnpm@9.4.0",
+ "packageManager": "pnpm@10.4.1",
"scripts": {
"build": "tsc -b",
"watch": "pnpm run build && pnpm run \"/^watch:.*/\"",
@@ -28,5 +28,13 @@
"@typescript-eslint/eslint-plugin": "^8.19.0",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
+ },
+ "pnpm": {
+ "onlyBuiltDependencies": [
+ "@tsslint/core",
+ "@vscode/vsce-sign",
+ "esbuild",
+ "keytar"
+ ]
}
}
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/component-meta/package.json b/packages/component-meta/package.json
index e8771fffd1..508cdb99c7 100644
--- a/packages/component-meta/package.json
+++ b/packages/component-meta/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-component-meta",
- "version": "2.2.2",
+ "version": "2.2.4",
"license": "MIT",
"files": [
"**/*.js",
@@ -14,9 +14,9 @@
},
"dependencies": {
"@volar/typescript": "~2.4.11",
- "@vue/language-core": "2.2.2",
+ "@vue/language-core": "2.2.4",
"path-browserify": "^1.0.1",
- "vue-component-type-helpers": "2.2.2"
+ "vue-component-type-helpers": "2.2.4"
},
"peerDependencies": {
"typescript": "*"
diff --git a/packages/component-type-helpers/package.json b/packages/component-type-helpers/package.json
index 354d5b5912..21a6177dca 100644
--- a/packages/component-type-helpers/package.json
+++ b/packages/component-type-helpers/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-component-type-helpers",
- "version": "2.2.2",
+ "version": "2.2.4",
"license": "MIT",
"files": [
"**/*.js",
diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts
index 5c6b22a285..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] } :
@@ -131,19 +127,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): [
@@ -159,22 +148,25 @@ export function generateGlobalTypes({
? NonNullable
: T extends (...args: any) => any
? T
- : __VLS_unknownDirective;
- function __VLS_withScope(ctx: T, scope: K): ctx is T & K;
+ : (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
- ? (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'} } };
- 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/script/component.ts b/packages/language-core/lib/codegen/script/component.ts
index 83910fbf19..20a6d42858 100644
--- a/packages/language-core/lib/codegen/script/component.ts
+++ b/packages/language-core/lib/codegen/script/component.ts
@@ -37,16 +37,24 @@ 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) {
+ if (
+ options.vueCompilerOptions.target >= 3.5
+ && options.vueCompilerOptions.inferComponentDollarRefs
+ && options.templateCodegen?.templateRefs.size
+ ) {
yield `__typeRefs: {} as __VLS_TemplateRefs,${newLine}`;
}
- if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.singleRootElType) {
+ if (
+ options.vueCompilerOptions.target >= 3.5
+ && options.vueCompilerOptions.inferComponentDollarEl
+ && options.templateCodegen?.singleRootElTypes.length
+ ) {
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 `})`;
}
diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts
index fac4679d7a..fd38b1fc86 100644
--- a/packages/language-core/lib/codegen/script/index.ts
+++ b/packages/language-core/lib/codegen/script/index.ts
@@ -12,7 +12,6 @@ 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 {
@@ -54,14 +53,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}`
@@ -167,16 +171,18 @@ function* generateSetupFunction(
]);
}
}
- for (const { callExp } of scriptSetupRanges.useAttrs) {
- setupCodeModifies.push([
- [`(`],
- callExp.start,
- callExp.start
- ], [
- [` as typeof __VLS_special.$attrs)`],
- callExp.end,
- callExp.end
- ]);
+ if (options.vueCompilerOptions.inferTemplateDollarAttrs) {
+ for (const { callExp } of scriptSetupRanges.useAttrs) {
+ setupCodeModifies.push([
+ [`(`],
+ callExp.start,
+ callExp.start
+ ], [
+ [` as typeof __VLS_dollars.$attrs)`],
+ callExp.end,
+ callExp.end
+ ]);
+ }
}
for (const { callExp, exp, arg } of scriptSetupRanges.useCssModule) {
setupCodeModifies.push([
@@ -200,22 +206,24 @@ function* generateSetupFunction(
]);
if (arg) {
setupCodeModifies.push([
- [`(__VLS_placeholder)`],
+ [`__VLS_placeholder`],
arg.start,
arg.end
]);
}
}
- for (const { callExp } of scriptSetupRanges.useSlots) {
- setupCodeModifies.push([
- [`(`],
- callExp.start,
- callExp.start
- ], [
- [` as typeof __VLS_special.$slots)`],
- callExp.end,
- callExp.end
- ]);
+ if (options.vueCompilerOptions.inferTemplateDollarSlots) {
+ for (const { callExp } of scriptSetupRanges.useSlots) {
+ setupCodeModifies.push([
+ [`(`],
+ callExp.start,
+ callExp.start
+ ], [
+ [` as typeof __VLS_dollars.$slots)`],
+ callExp.end,
+ callExp.end
+ ]);
+ }
}
const isTs = options.lang !== 'js' && options.lang !== 'jsx';
for (const { callExp, exp, arg } of scriptSetupRanges.useTemplateRef) {
@@ -254,7 +262,7 @@ function* generateSetupFunction(
}
if (arg) {
setupCodeModifies.push([
- [`(__VLS_placeholder)`],
+ [`__VLS_placeholder`],
arg.start,
arg.end
]);
@@ -294,7 +302,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;
@@ -521,7 +536,7 @@ function* generateModelEmit(
const [propName, localName] = getPropAndLocalName(scriptSetup, defineModel);
yield `'update:${propName}': [value: `;
yield* generateDefinePropType(scriptSetup, propName, localName, defineModel);
- if (!defineModel.required) {
+ if (!defineModel.required && defineModel.defaultValue === undefined) {
yield ` | undefined`;
}
yield `]${endOfLine}`;
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/template.ts b/packages/language-core/lib/codegen/script/template.ts
index c9f8d2223d..56bc80aafb 100644
--- a/packages/language-core/lib/codegen/script/template.ts
+++ b/packages/language-core/lib/codegen/script/template.ts
@@ -1,7 +1,9 @@
import type { Code } from '../../types';
import { hyphenateTag } from '../../utils/shared';
import { codeFeatures } from '../codeFeatures';
-import { TemplateCodegenContext, createTemplateCodegenContext } from '../template/context';
+import { generateStyleModules } from '../style/modules';
+import { generateStyleScopedClasses } from '../style/scopedClasses';
+import { type TemplateCodegenContext, createTemplateCodegenContext } from '../template/context';
import { generateInterpolation } from '../template/interpolation';
import { generateStyleScopedClassReferences } from '../template/styleScopedClasses';
import { endOfLine, newLine } from '../utils';
@@ -115,6 +117,7 @@ function* generateTemplateBody(
): Generator {
yield* generateStyleScopedClasses(options, templateCodegenCtx);
yield* generateStyleScopedClassReferences(templateCodegenCtx, true);
+ yield* generateStyleModules(options);
yield* generateCssVars(options, templateCodegenCtx);
if (options.templateCodegen) {
@@ -133,71 +136,6 @@ function* generateTemplateBody(
}
}
-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* 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..b1fe9ea2ce
--- /dev/null
+++ b/packages/language-core/lib/codegen/style/classProperty.ts
@@ -0,0 +1,34 @@
+import type { Code } from '../../types';
+import { codeFeatures } from '../codeFeatures';
+import { newLine } from '../utils';
+
+export function* generateClassProperty(
+ styleIndex: number,
+ classNameWithDot: string,
+ offset: number,
+ propertyType: string
+): 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.navigation,
+ ];
+ yield `: ${propertyType}`;
+ yield ` }`;
+}
diff --git a/packages/language-core/lib/codegen/script/styleModulesType.ts b/packages/language-core/lib/codegen/style/modules.ts
similarity index 55%
rename from packages/language-core/lib/codegen/script/styleModulesType.ts
rename to packages/language-core/lib/codegen/style/modules.ts
index f501fdf490..c2c343c3d4 100644
--- a/packages/language-core/lib/codegen/script/styleModulesType.ts
+++ b/packages/language-core/lib/codegen/style/modules.ts
@@ -1,13 +1,11 @@
import type { Code } from '../../types';
import { codeFeatures } from '../codeFeatures';
+import type { ScriptCodegenOptions } from '../script';
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(
- options: ScriptCodegenOptions,
- ctx: ScriptCodegenContext
+export function* generateStyleModules(
+ options: ScriptCodegenOptions
): Generator {
const styles = options.sfc.styles.map((style, i) => [style, i] as const).filter(([style]) => style.module);
if (!styles.length && !options.scriptSetupRanges?.useCssModule.length) {
@@ -15,29 +13,28 @@ 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,
- codeFeatures.all
+ offset,
+ codeFeatures.withoutHighlight
];
}
- else {
- yield name;
- }
- yield `: Record & ${ctx.localTypes.PrettifyLocal}<{}`;
+ yield `: Record & __VLS_PrettifyGlobal<{}`;
for (const className of style.classNames) {
- yield* generateCssClassProperty(
+ yield* generateClassProperty(
i,
className.text,
className.offset,
- 'string',
- false
+ 'string'
);
}
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..455ee25781
--- /dev/null
+++ b/packages/language-core/lib/codegen/style/scopedClasses.ts
@@ -0,0 +1,41 @@
+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 option = options.vueCompilerOptions.experimentalResolveStyleCssClasses;
+ const styles = options.sfc.styles
+ .map((style, i) => [style, i] as const)
+ .filter(([style]) => option === 'always' || (option === 'scoped' && style.scoped));
+ if (!styles.length) {
+ return;
+ }
+
+ const firstClasses = new Set();
+ yield `type __VLS_StyleScopedClasses = {}`;
+ for (const [style, i] of styles) {
+ 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'
+ );
+ }
+ }
+ yield endOfLine;
+}
diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts
index e9e7ac3c84..cad062b01b 100644
--- a/packages/language-core/lib/codegen/template/context.ts
+++ b/packages/language-core/lib/codegen/template/context.ts
@@ -7,6 +7,103 @@ import type { TemplateCodegenOptions } from './index';
export type TemplateCodegenContext = ReturnType;
+/**
+ * Creates and returns a Context object used for generating type-checkable TS code
+ * from the template section of a .vue file.
+ *
+ * ## Implementation Notes for supporting `@vue-ignore`, `@vue-expect-error`, and `@vue-skip` directives.
+ *
+ * Vue language tooling supports a number of directives for suppressing diagnostics within
+ * Vue templates (https://github.com/vuejs/language-tools/pull/3215)
+ *
+ * Here is an overview for how support for how @vue-expect-error is implemented within this file
+ * (@vue-expect-error is the most complicated directive to support due to its behavior of raising
+ * a diagnostic when it is annotating a piece of code that doesn't actually have any errors/warning/diagnostics).
+ *
+ * Given .vue code:
+ *
+ * ```vue
+ *
+ *
+ *
+ * {{ knownProp1 }}
+ * {{ error_unknownProp }}
+ * {{ knownProp2 }}
+ *
+ * {{ suppressed_error_unknownProp }}
+ * {{ knownProp3 }}
+ *
+ * {{ knownProp4_will_trigger_unused_expect_error }}
+ *
+ * ```
+ *
+ * The above code should raise two diagnostics:
+ *
+ * 1. Property 'error_unknownProp' does not exist on type [...]
+ * 2. Unused '@ts-expect-error' directive.ts(2578) -- this is the bottom `@vue-expect-error` directive
+ * that covers code that doesn't actually raise an error -- note that all `@vue-...` directives
+ * will ultimately translate into `@ts-...` diagnostics.
+ *
+ * The above code will produce the following type-checkable TS code (note: omitting asterisks
+ * to prevent VSCode syntax double-greying out double-commented code).
+ *
+ * ```ts
+ * ( __VLS_ctx.knownProp1 );
+ * ( __VLS_ctx.error_unknownProp ); // ERROR: Property 'error_unknownProp' does not exist on type [...]
+ * ( __VLS_ctx.knownProp2 );
+ * // @vue-expect-error start
+ * ( __VLS_ctx.suppressed_error_unknownProp );
+ * // @ts-expect-error __VLS_TS_EXPECT_ERROR
+ * ;
+ * // @vue-expect-error end of INTERPOLATION
+ * ( __VLS_ctx.knownProp3 );
+ * // @vue-expect-error start
+ * ( __VLS_ctx.knownProp4_will_trigger_unused_expect_error );
+ * // @ts-expect-error __VLS_TS_EXPECT_ERROR
+ * ;
+ * // @vue-expect-error end of INTERPOLATION
+ * ```
+ *
+ * In the generated code, there are actually 3 diagnostic errors that'll be raised in the first
+ * pass on this generated code (but through cleverness described below, not all of them will be
+ * propagated back to the original .vue file):
+ *
+ * 1. Property 'error_unknownProp' does not exist on type [...]
+ * 2. Unused '@ts-expect-error' directive.ts(2578) from the 1st `@ts-expect-error __VLS_TS_EXPECT_ERROR`
+ * 3. Unused '@ts-expect-error' directive.ts(2578) from the 2nd `@ts-expect-error __VLS_TS_EXPECT_ERROR`
+ *
+ * Be sure to pay careful attention to the mixture of `@vue-expect-error` and `@ts-expect-error`;
+ * Within the TS file, the only "real" directives recognized by TS are going to be prefixed with `@ts-`;
+ * any `@vue-` prefixed directives in the comments are only for debugging purposes.
+ *
+ * As mentioned above, there are 3 diagnostics errors that'll be generated for the above code, but
+ * only 2 should be propagated back to the original .vue file.
+ *
+ * (The reason we structure things this way is somewhat complicated, but in short it allows us
+ * to lean on TS as much as possible to generate actual `unused @ts-expect-error directive` errors
+ * while covering a number of edge cases.)
+ *
+ * So, we need a way to dynamically decide whether each of the `@ts-expect-error __VLS_TS_EXPECT_ERROR`
+ * directives should be reported as an unused directive or not.
+ *
+ * To do this, we'll make use of the `shouldReport` callback that'll optionally be provided to the
+ * `verification` property of the `CodeInformation` object attached to the mapping between source .vue
+ * and generated .ts code. The `verification` property determines whether "verification" (which includes
+ * semantic diagnostics) should be performed on the generated .ts code, and `shouldReport`, if provided,
+ * can be used to determine whether a given diagnostic should be reported back "upwards" to the original
+ * .vue file or not.
+ *
+ * See the comments in the code below for how and where we use this hook to keep track of whether
+ * an error/diagnostic was encountered for a region of code covered by a `@vue-expect-error` directive,
+ * and additionally how we use that to determine whether to propagate diagnostics back upward.
+ */
export function createTemplateCodegenContext(options: Pick) {
let ignoredError = false;
let expectErrorToken: {
@@ -22,12 +119,18 @@ export function createTemplateCodegenContext(options: Pick();
const localVars = new Map();
- const specialVars = new Set();
+ const dollarVars = new Set();
const accessExternalVariables = new Map>();
const slots: {
name: string;
@@ -67,7 +171,10 @@ export function createTemplateCodegenContext(options: Pick();
- const templateRefs = new Map();
+ const templateRefs = new Map();
return {
codeFeatures: new Proxy(codeFeatures, {
@@ -79,14 +186,13 @@ export function createTemplateCodegenContext(options: Pick(),
accessExternalVariable(name: string, offset?: number) {
let arr = accessExternalVariables.get(name);
if (!arr) {
@@ -117,6 +223,24 @@ 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)
+ if (hoistVars.size) {
+ yield `// @ts-ignore${newLine}`;
+ yield `var `;
+ for (const [originalVar, hoistVar] of hoistVars) {
+ yield `${hoistVar} = ${originalVar}, `;
+ }
+ yield endOfLine;
+ }
+ },
ignoreError: function* (): Generator {
if (!ignoredError) {
ignoredError = true;
@@ -140,6 +264,9 @@ export function createTemplateCodegenContext(options: Pick token.errors === 0,
},
},
diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts
index 2d8b94a677..38aba2e045 100644
--- a/packages/language-core/lib/codegen/template/element.ts
+++ b/packages/language-core/lib/codegen/template/element.ts
@@ -14,14 +14,15 @@ 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;
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') {
@@ -33,16 +34,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
};
@@ -84,14 +83,13 @@ 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) {
+ if (componentOriginalVar === node.tag) {
yield [
- var_originalComponent,
+ componentOriginalVar,
'template',
tagOffset,
ctx.codeFeatures.withoutHighlightAndCompletion,
@@ -113,10 +111,10 @@ export function* generateComponent(
}
yield `, `;
}
- yield `] } */${endOfLine}`;
+ yield `]} */${endOfLine}`;
}
else if (dynamicTagInfo) {
- yield `const ${var_originalComponent} = (`;
+ yield `const ${componentOriginalVar} = (`;
yield* generateInterpolation(
options,
ctx,
@@ -125,8 +123,8 @@ export function* generateComponent(
dynamicTagInfo.tag,
dynamicTagInfo.offsets[0],
dynamicTagInfo.astHolder,
- '(',
- ')'
+ `(`,
+ `)`
);
if (dynamicTagInfo.offsets[1] !== undefined) {
yield `,`;
@@ -138,14 +136,14 @@ export function* generateComponent(
dynamicTagInfo.tag,
dynamicTagInfo.offsets[1],
dynamicTagInfo.astHolder,
- '(',
- ')'
+ `(`,
+ `)`
);
}
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)
@@ -167,8 +165,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 +184,7 @@ export function* generateComponent(
yield `, `;
}
}
- yield `] } */${endOfLine}`;
+ yield `]} */${endOfLine}`;
// auto import support
if (options.edited) {
yield `// @ts-ignore${newLine}`; // #2304
@@ -205,11 +203,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,
@@ -227,13 +225,14 @@ export function* generateComponent(
ctx.resolveCodeFeatures({
verification: {
shouldReport(_source, code) {
+ // https://typescript.tv/errors/#ts6133
return String(code) !== '6133';
},
}
}),
- var_componentInstance
+ componentVNodeVar
);
- yield ` = ${var_functionalComponent}`;
+ yield ` = ${componentFunctionalVar}`;
yield* generateComponentGeneric(ctx);
yield `(`;
yield* wrapWith(
@@ -252,57 +251,57 @@ 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);
- const isRootNode = node === ctx.singleRootNode;
+ const [refName, offset] = yield* generateElementReference(options, ctx, node);
+ const tag = hyphenateTag(node.tag);
+ const isRootNode = ctx.singleRootNodes.has(node) && !options.vueCompilerOptions.fallthroughComponentNames.includes(tag);
if (refName || isRootNode) {
- const varName = ctx.getInternalVariable();
+ const componentInstanceVar = ctx.getInternalVariable();
ctx.currentComponent.used = true;
- yield `var ${varName} = {} 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')
- ) {
+ yield `var ${componentInstanceVar} = {} as (Parameters>[0] | null)`;
+ if (isVForChild) {
yield `[]`;
}
yield `${endOfLine}`;
- if (refName) {
- ctx.templateRefs.set(refName, [varName, offset!]);
+ if (refName && offset) {
+ ctx.templateRefs.set(refName, {
+ typeExp: `typeof ${ctx.getHoistVariable(componentInstanceVar)}`,
+ offset
+ });
}
if (isRootNode) {
- ctx.singleRootElType = `NonNullable['$el']`;
+ ctx.singleRootElTypes.push(`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();
+ yield `let ${attrsVar}!: Parameters[0]${endOfLine}`;
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}`;
}
}
@@ -356,23 +355,29 @@ 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);
- if (refName) {
- let refValue = `__VLS_nativeElements['${node.tag}']`;
+ const [refName, offset] = yield* generateElementReference(options, ctx, node);
+ if (refName && offset) {
+ let typeExp = `__VLS_NativeElements['${node.tag}']`;
if (isVForChild) {
- refValue = `[${refValue}]`;
+ typeExp += `[]`;
}
- ctx.templateRefs.set(refName, [refValue, offset!]);
+ ctx.templateRefs.set(refName, {
+ typeExp,
+ offset
+ });
}
- if (ctx.singleRootNode === node) {
- ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`;
+ if (ctx.singleRootNodes.has(node)) {
+ ctx.singleRootElTypes.push(`__VLS_NativeElements['${node.tag}']`);
}
if (hasVBindAttrs(options, ctx, node)) {
ctx.inheritedAttrVars.add(`__VLS_intrinsicElements.${node.tag}`);
}
+ collectStyleScopedClassReferences(options, ctx, node);
+
yield* generateElementChildren(options, ctx, node);
}
@@ -397,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
@@ -491,7 +457,7 @@ function* generateComponentGeneric(
ctx.lastGenericComment = undefined;
}
-function* generateReferencesForElements(
+function* generateElementReference(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
node: CompilerDOM.ElementNode
@@ -504,8 +470,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 +480,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);
@@ -532,7 +498,7 @@ function hasVBindAttrs(
node: CompilerDOM.ElementNode
) {
return options.vueCompilerOptions.fallthroughAttributes && (
- node === ctx.singleRootNode ||
+ (options.inheritAttrs && ctx.singleRootNodes.has(node)) ||
node.props.some(prop =>
prop.type === CompilerDOM.NodeTypes.DIRECTIVE
&& prop.name === 'bind'
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/elementDirectives.ts b/packages/language-core/lib/codegen/template/elementDirectives.ts
index e861c8220c..94630b05b3 100644
--- a/packages/language-core/lib/codegen/template/elementDirectives.ts
+++ b/packages/language-core/lib/codegen/template/elementDirectives.ts
@@ -7,6 +7,7 @@ import { endOfLine, wrapWith } from '../utils';
import { generateCamelized } from '../utils/camelized';
import { generateStringLiteralKey } from '../utils/stringLiteralKey';
import type { TemplateCodegenContext } from './context';
+import { generatePropExp } from './elementProps';
import type { TemplateCodegenOptions } from './index';
import { generateInterpolation } from './interpolation';
import { generateObjectProperty } from './objectProperty';
@@ -176,21 +177,12 @@ function* generateValue(
`value`
);
yield `: `;
- yield* wrapWith(
- exp.loc.start.offset,
- exp.loc.end.offset,
- ctx.codeFeatures.verification,
- ...generateInterpolation(
- options,
- ctx,
- 'template',
- ctx.codeFeatures.all,
- exp.content,
- exp.loc.start.offset,
- exp.loc,
- `(`,
- `)`
- )
+ yield* generatePropExp(
+ options,
+ ctx,
+ prop,
+ exp,
+ ctx.codeFeatures.all
);
}
diff --git a/packages/language-core/lib/codegen/template/elementEvents.ts b/packages/language-core/lib/codegen/template/elementEvents.ts
index e6ec49a56a..97ce5f20ac 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(
@@ -94,8 +98,8 @@ export function* generateEventExpression(
prop: CompilerDOM.DirectiveNode
): Generator {
if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
- let prefix = '(';
- let suffix = ')';
+ let prefix = `(`;
+ let suffix = `)`;
let isFirstMapping = true;
const ast = createTsAst(options.ts, prop.exp, prop.exp.content);
@@ -104,10 +108,10 @@ export function* generateEventExpression(
yield `(...[$event]) => {${newLine}`;
ctx.addLocalVariable('$event');
- prefix = '';
- suffix = '';
+ prefix = ``;
+ suffix = ``;
for (const blockCondition of ctx.blockConditions) {
- prefix += `if (!(${blockCondition})) return${endOfLine}`;
+ prefix += `if (!${blockCondition}) return${endOfLine}`;
}
}
diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts
index 01ed50a01c..de35e27dc3 100644
--- a/packages/language-core/lib/codegen/template/elementProps.ts
+++ b/packages/language-core/lib/codegen/template/elementProps.ts
@@ -61,14 +61,14 @@ export function* generateElementProps(
&& prop.arg.loc.source.startsWith('[')
&& prop.arg.loc.source.endsWith(']')
) {
- failedPropExps?.push({ node: prop.arg, prefix: '(', suffix: ')' });
- failedPropExps?.push({ node: prop.exp, prefix: '() => {', suffix: '}' });
+ failedPropExps?.push({ node: prop.arg, prefix: `(`, suffix: `)` });
+ failedPropExps?.push({ node: prop.exp, prefix: `() => {`, suffix: `}` });
}
else if (
!prop.arg
&& prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION
) {
- failedPropExps?.push({ node: prop.exp, prefix: '(', suffix: ')' });
+ failedPropExps?.push({ node: prop.exp, prefix: `(`, suffix: `)` });
}
}
}
@@ -98,7 +98,7 @@ export function* generateElementProps(
|| options.vueCompilerOptions.dataAttributes.some(pattern => minimatch(propName!, pattern))
) {
if (prop.exp && prop.exp.constType !== CompilerDOM.ConstantTypes.CAN_STRINGIFY) {
- failedPropExps?.push({ node: prop.exp, prefix: '(', suffix: ')' });
+ failedPropExps?.push({ node: prop.exp, prefix: `(`, suffix: `)` });
}
continue;
}
@@ -139,7 +139,7 @@ export function* generateElementProps(
propName
)
),
- `: (`,
+ `: `,
...generatePropExp(
options,
ctx,
@@ -147,8 +147,7 @@ export function* generateElementProps(
prop.exp,
ctx.codeFeatures.all,
enableCodeFeatures
- ),
- `)`
+ )
);
if (enableCodeFeatures) {
yield* codes;
@@ -215,13 +214,12 @@ export function* generateElementProps(
(prop.loc as any).name_1 ??= {},
shouldCamelize
),
- `: (`,
+ `: `,
...(
prop.value
? generateAttrValue(prop.value, ctx.codeFeatures.withoutNavigation)
: [`true`]
- ),
- `)`
+ )
);
if (enableCodeFeatures) {
yield* codes;
@@ -278,7 +276,7 @@ export function* generatePropExp(
prop: CompilerDOM.DirectiveNode,
exp: CompilerDOM.SimpleExpressionNode | undefined,
features: VueCodeInformation,
- enableCodeFeatures: boolean
+ enableCodeFeatures: boolean = true
): Generator {
const isShorthand = prop.arg?.loc.start.offset === prop.exp?.loc.start.offset;
@@ -298,8 +296,8 @@ export function* generatePropExp(
exp.loc.source,
exp.loc.start.offset,
exp.loc,
- '(',
- ')'
+ `(`,
+ `)`
);
}
else {
@@ -389,6 +387,8 @@ function getPropsCodeInfo(
},
verification: strictPropsCheck || {
shouldReport(_source, code) {
+ // https://typescript.tv/errors/#ts2353
+ // https://typescript.tv/errors/#ts2561
if (String(code) === '2353' || String(code) === '2561') {
return false;
}
diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts
index e329204109..8776db6c17 100644
--- a/packages/language-core/lib/codegen/template/index.ts
+++ b/packages/language-core/lib/codegen/template/index.ts
@@ -34,17 +34,29 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator`;
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,
@@ -114,7 +123,7 @@ function* generateInheritedAttrs(
if (ctx.bindingAttrLocs.length) {
yield `[`;
for (const loc of ctx.bindingAttrLocs) {
- yield `__VLS_special.`;
+ yield `__VLS_dollars.`;
yield [
loc.source,
'template',
@@ -133,7 +142,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 +150,7 @@ function* generateTemplateRefs(
offset,
ctx.codeFeatures.navigationAndCompletion
);
- yield `: typeof ${varName},${newLine}`;
+ yield `: ${typeExp},${newLine}`;
}
yield `}${endOfLine}`;
return `__VLS_TemplateRefs`;
@@ -151,7 +160,14 @@ function* generateRootEl(
ctx: TemplateCodegenContext
): Generator {
yield `type __VLS_RootEl = `;
- yield ctx.singleRootElType ?? `any`;
+ if (ctx.singleRootElTypes.length && !ctx.singleRootNodes.has(null)) {
+ for (const type of ctx.singleRootElTypes) {
+ yield `${newLine}| ${type}`;
+ }
+ }
+ else {
+ yield `any`;
+ }
yield endOfLine;
return `__VLS_RootEl`;
}
diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts
index f305d90d63..86fee0b6be 100644
--- a/packages/language-core/lib/codegen/template/interpolation.ts
+++ b/packages/language-core/lib/codegen/template/interpolation.ts
@@ -134,7 +134,7 @@ function* forEachInterpolationSegment(
const curVar = ctxVars[i];
const nextVar = ctxVars[i + 1];
- yield* generateVar(code, ctx.specialVars, destructuredPropNames, templateRefNames, curVar);
+ yield* generateVar(code, ctx.dollarVars, destructuredPropNames, templateRefNames, curVar);
if (nextVar.isShorthand) {
yield [code.slice(curVar.offset + curVar.text.length, nextVar.offset + nextVar.text.length), curVar.offset + curVar.text.length];
@@ -146,7 +146,7 @@ function* forEachInterpolationSegment(
}
const lastVar = ctxVars.at(-1)!;
- yield* generateVar(code, ctx.specialVars, destructuredPropNames, templateRefNames, lastVar);
+ yield* generateVar(code, ctx.dollarVars, destructuredPropNames, templateRefNames, lastVar);
if (lastVar.offset + lastVar.text.length < code.length) {
yield [code.slice(lastVar.offset + lastVar.text.length), lastVar.offset + lastVar.text.length, 'endText'];
}
@@ -158,7 +158,7 @@ function* forEachInterpolationSegment(
function* generateVar(
code: string,
- specialVars: Set,
+ dollarVars: Set,
destructuredPropNames: Set | undefined,
templateRefNames: Set | undefined,
curVar: CtxVar
@@ -175,8 +175,8 @@ function* generateVar(
yield [`)`, undefined];
}
else {
- if (specialVars.has(curVar.text)) {
- yield [`__VLS_special.`, undefined];
+ if (dollarVars.has(curVar.text)) {
+ yield [`__VLS_dollars.`, undefined];
}
else if (!isDestructuredProp) {
yield [`__VLS_ctx.`, undefined];
diff --git a/packages/language-core/lib/codegen/template/slotOutlet.ts b/packages/language-core/lib/codegen/template/slotOutlet.ts
index 3d42e0353b..e4dfe97936 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) {
@@ -58,8 +59,7 @@ export function* generateSlotOutlet(
ctx,
nameProp,
nameProp.exp,
- ctx.codeFeatures.all,
- true
+ ctx.codeFeatures.all
),
`]`
];
@@ -78,16 +78,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(
@@ -123,7 +130,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 (
@@ -147,16 +154,16 @@ export function* generateSlotOutlet(
);
yield `)${endOfLine}`;
ctx.dynamicSlots.push({
- expVar,
- propsVar,
+ expVar: ctx.getHoistVariable(expVar),
+ propsVar: ctx.getHoistVariable(propsVar),
});
}
else {
ctx.slots.push({
name: 'default',
- tagRange: [startTagOffset, startTagOffset + node.tag.length],
+ tagRange: [startTagOffset, startTagEndOffset],
nodeLoc: node.loc,
- propsVar,
+ propsVar: ctx.getHoistVariable(propsVar),
});
}
}
diff --git a/packages/language-core/lib/codegen/template/styleScopedClasses.ts b/packages/language-core/lib/codegen/template/styleScopedClasses.ts
index 43f2a4ce9b..17d8f55135 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,
@@ -41,11 +37,10 @@ export function* generateStyleScopedClassReferences(
'',
source,
offset + className.length,
- ctx.codeFeatures.navigationWithoutRename,
+ ctx.codeFeatures.navigation,
];
- yield `, `;
+ yield `]} */${endOfLine}`;
}
- yield `] as (keyof __VLS_StyleScopedClasses)[]${endOfLine}`;
function* escapeString(source: string, className: string, offset: number, escapeTargets: string[]): Generator {
let count = 0;
diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts
index 922c33e6cc..8d3bb6518e 100644
--- a/packages/language-core/lib/codegen/template/templateChild.ts
+++ b/packages/language-core/lib/codegen/template/templateChild.ts
@@ -1,5 +1,6 @@
import * as CompilerDOM from '@vue/compiler-dom';
import type { Code } from '../../types';
+import { hyphenateTag } from '../../utils/shared';
import { endOfLine, newLine } from '../utils';
import type { TemplateCodegenContext } from './context';
import { generateComponent, generateElement } from './element';
@@ -10,6 +11,8 @@ import { generateVFor } from './vFor';
import { generateVIf } from './vIf';
import { generateVSlot } from './vSlot';
+const commentDirectiveRegex = /^$/;
+
// @ts-ignore
const transformContext: CompilerDOM.TransformContext = {
onError: () => { },
@@ -34,41 +37,46 @@ 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 ?? {};
+ const { content } = match.groups!;
addFormatCodes(
content,
- node.loc.start.offset + match[0].indexOf(content),
+ node.loc.start.offset + node.loc.source.indexOf('{') + 1,
formatBrackets.generic
);
}
diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts
index cfd908e3cb..af2b9e766b 100644
--- a/packages/language-core/lib/plugins/vue-tsx.ts
+++ b/packages/language-core/lib/plugins/vue-tsx.ts
@@ -69,19 +69,21 @@ export default plugin;
function createTsx(
fileName: string,
- _sfc: Sfc,
+ sfc: Sfc,
ctx: Parameters[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,34 @@ 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 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 +194,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(),
@@ -195,7 +208,6 @@ function createTsx(
});
let current = codegen.next();
-
while (!current.done) {
const code = current.value;
codes.push(code);
@@ -207,15 +219,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 +245,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);
diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts
index 523eda9fb2..accbab70be 100644
--- a/packages/language-core/lib/types.ts
+++ b/packages/language-core/lib/types.ts
@@ -16,8 +16,7 @@ export type RawVueCompilerOptions = Partial;
@@ -33,8 +32,15 @@ export interface VueCompilerOptions {
checkUnknownEvents: boolean;
checkUnknownDirectives: boolean;
checkUnknownComponents: boolean;
+ inferComponentDollarEl: boolean;
+ inferComponentDollarRefs: boolean;
+ inferTemplateDollarAttrs: boolean;
+ inferTemplateDollarEl: boolean;
+ inferTemplateDollarRefs: boolean;
+ inferTemplateDollarSlots: boolean;
skipTemplateCodegen: boolean;
fallthroughAttributes: boolean;
+ fallthroughComponentNames: string[];
dataAttributes: string[];
htmlAttributes: string[];
optionsWrapper: [string, string] | [];
@@ -107,6 +113,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 +128,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 +154,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/utils/ts.ts b/packages/language-core/lib/utils/ts.ts
index d9d4877167..2fe99e763c 100644
--- a/packages/language-core/lib/utils/ts.ts
+++ b/packages/language-core/lib/utils/ts.ts
@@ -4,6 +4,7 @@ import type * as ts from 'typescript';
import { generateGlobalTypes, getGlobalTypesFileName } from '../codegen/globalTypes';
import { getAllExtensions } from '../languagePlugin';
import type { RawVueCompilerOptions, VueCompilerOptions, VueLanguagePlugin } from '../types';
+import { hyphenateTag } from './shared';
export type ParsedCommandLine = ts.ParsedCommandLine & {
vueOptions: VueCompilerOptions;
@@ -219,6 +220,10 @@ export class CompilerOptionsResolver {
...defaults.composables,
...this.options.composables,
},
+ fallthroughComponentNames: [
+ ...defaults.fallthroughComponentNames,
+ ...this.options.fallthroughComponentNames ?? []
+ ].map(hyphenateTag),
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-dom/src/transforms/vModel.ts#L49-L51
// https://vuejs.org/guide/essentials/forms.html#form-input-bindings
experimentalModelPropName: Object.fromEntries(Object.entries(
@@ -266,8 +271,20 @@ export function getDefaultCompilerOptions(target = 99, lib = 'vue', strictTempla
checkUnknownEvents: strictTemplates,
checkUnknownDirectives: strictTemplates,
checkUnknownComponents: strictTemplates,
+ inferComponentDollarEl: false,
+ inferComponentDollarRefs: false,
+ inferTemplateDollarAttrs: false,
+ inferTemplateDollarEl: false,
+ inferTemplateDollarRefs: false,
+ inferTemplateDollarSlots: false,
skipTemplateCodegen: false,
fallthroughAttributes: false,
+ fallthroughComponentNames: [
+ 'Transition',
+ 'KeepAlive',
+ 'Teleport',
+ 'Suspense',
+ ],
dataAttributes: [],
htmlAttributes: ['aria-*'],
optionsWrapper: target >= 2.7
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);
}
diff --git a/packages/language-core/lib/virtualFile/computedSfc.ts b/packages/language-core/lib/virtualFile/computedSfc.ts
index 1e2027dbbb..fffbabc33b 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,23 @@ 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') {
+ return {
+ ...val,
+ offset: base.start + val.offset,
+ };
+ }
+ return val;
+ });
+ }
}
function mergeObject(a: T, b: K): T & K {
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-core/package.json b/packages/language-core/package.json
index 5116034662..00f7ce374e 100644
--- a/packages/language-core/package.json
+++ b/packages/language-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/language-core",
- "version": "2.2.2",
+ "version": "2.2.4",
"license": "MIT",
"files": [
"**/*.js",
diff --git a/packages/language-core/schemas/vue-tsconfig.schema.json b/packages/language-core/schemas/vue-tsconfig.schema.json
index ed60b2c699..4bd341cd37 100644
--- a/packages/language-core/schemas/vue-tsconfig.schema.json
+++ b/packages/language-core/schemas/vue-tsconfig.schema.json
@@ -65,6 +65,36 @@
"default": false,
"markdownDescription": "Check unknown components. If not set, uses the 'strictTemplates' value."
},
+ "inferComponentDollarEl": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Infer `$el` type on the component instance."
+ },
+ "inferComponentDollarRefs": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Infer `$refs` type on the component instance."
+ },
+ "inferTemplateDollarAttrs": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Infer `$attrs` type in the template and the return type of `useAttrs`."
+ },
+ "inferTemplateDollarEl": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Infer `$el` type in the template."
+ },
+ "inferTemplateDollarRefs": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Infer `$refs` type in the template."
+ },
+ "inferTemplateDollarSlots": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Infer `$slots` type in the template and the return type of `useSlots`."
+ },
"skipTemplateCodegen": {
"type": "boolean",
"default": false,
@@ -75,6 +105,16 @@
"default": false,
"markdownDescription": "Enable to support typed fallthrough attributes. Please note that enabling may significantly slow down type checking."
},
+ "fallthroughComponentNames": {
+ "type": "array",
+ "default": [
+ "Transition",
+ "KeepAlive",
+ "Teleport",
+ "Suspense"
+ ],
+ "markdownDescription": "Component names that will be transparent when collecting single root child nodes and fallthrough attributes."
+ },
"dataAttributes": {
"type": "array",
"default": [ ],
@@ -85,11 +125,6 @@
"default": [ "aria-*" ],
"markdownDescription": "A glob matcher array that should always be recognizing as HTML Attributes rather than Component props. Attribute name will never convert to camelize case."
},
- "plugins": {
- "type": "array",
- "default": [ ],
- "markdownDescription": "Plugins to be used in the SFC compiler."
- },
"optionsWrapper": {
"type": "array",
"default": [
@@ -119,6 +154,11 @@
"useTemplateRef": [ "useTemplateRef", "templateRef" ]
}
},
+ "plugins": {
+ "type": "array",
+ "default": [ ],
+ "markdownDescription": "Plugins to be used in the SFC compiler."
+ },
"experimentalResolveStyleCssClasses": {
"enum": [
"scoped",
diff --git a/packages/language-plugin-pug/package.json b/packages/language-plugin-pug/package.json
index b6c91c6a82..f285fbdd33 100644
--- a/packages/language-plugin-pug/package.json
+++ b/packages/language-plugin-pug/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/language-plugin-pug",
- "version": "2.2.2",
+ "version": "2.2.4",
"license": "MIT",
"files": [
"**/*.js",
@@ -14,7 +14,7 @@
},
"devDependencies": {
"@types/node": "^22.10.4",
- "@vue/language-core": "2.2.2"
+ "@vue/language-core": "2.2.4"
},
"dependencies": {
"@volar/source-map": "~2.4.11",
diff --git a/packages/language-server/package.json b/packages/language-server/package.json
index 20220c9b70..11c55f57d0 100644
--- a/packages/language-server/package.json
+++ b/packages/language-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/language-server",
- "version": "2.2.2",
+ "version": "2.2.4",
"license": "MIT",
"files": [
"**/*.js",
@@ -19,9 +19,9 @@
"@volar/language-core": "~2.4.11",
"@volar/language-server": "~2.4.11",
"@volar/test-utils": "~2.4.11",
- "@vue/language-core": "2.2.2",
- "@vue/language-service": "2.2.2",
- "@vue/typescript-plugin": "2.2.2",
+ "@vue/language-core": "2.2.4",
+ "@vue/language-service": "2.2.4",
+ "@vue/typescript-plugin": "2.2.4",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-uri": "^3.0.8"
}
diff --git a/packages/language-server/tests/renaming.spec.ts b/packages/language-server/tests/renaming.spec.ts
index 09cc6cf7c4..1a81801da8 100644
--- a/packages/language-server/tests/renaming.spec.ts
+++ b/packages/language-server/tests/renaming.spec.ts
@@ -104,12 +104,12 @@ describe('Renaming', async () => {
"newText": "bar",
"range": {
"end": {
- "character": 8,
- "line": 7,
+ "character": 28,
+ "line": 2,
},
"start": {
- "character": 5,
- "line": 7,
+ "character": 25,
+ "line": 2,
},
},
},
@@ -117,12 +117,12 @@ describe('Renaming', async () => {
"newText": "bar",
"range": {
"end": {
- "character": 28,
- "line": 2,
+ "character": 8,
+ "line": 7,
},
"start": {
- "character": 25,
- "line": 2,
+ "character": 5,
+ "line": 7,
},
},
},
@@ -743,12 +743,12 @@ describe('Renaming', async () => {
"newText": "stylus",
"range": {
"end": {
- "character": 23,
- "line": 15,
+ "character": 22,
+ "line": 8,
},
"start": {
- "character": 19,
- "line": 15,
+ "character": 18,
+ "line": 8,
},
},
},
@@ -756,12 +756,12 @@ describe('Renaming', async () => {
"newText": "stylus",
"range": {
"end": {
- "character": 22,
- "line": 8,
+ "character": 23,
+ "line": 15,
},
"start": {
- "character": 18,
- "line": 8,
+ "character": 19,
+ "line": 15,
},
},
},
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/css.ts b/packages/language-service/lib/plugins/css.ts
index de1dcaa276..4376e281d9 100644
--- a/packages/language-service/lib/plugins/css.ts
+++ b/packages/language-service/lib/plugins/css.ts
@@ -1,5 +1,9 @@
-import type { LanguageServicePlugin } from '@volar/language-service';
-import { create as baseCreate } from 'volar-service-css';
+import type { LanguageServicePlugin, VirtualCode } from '@volar/language-service';
+import { VueVirtualCode } from '@vue/language-core';
+import { create as baseCreate, type Provide } from 'volar-service-css';
+import * as css from 'vscode-css-languageservice';
+import type { TextDocument } from 'vscode-languageserver-textdocument';
+import { URI } from 'vscode-uri';
export function create(): LanguageServicePlugin {
const base = baseCreate({ scssDocumentSelector: ['scss', 'postcss'] });
@@ -7,6 +11,11 @@ export function create(): LanguageServicePlugin {
...base,
create(context) {
const baseInstance = base.create(context);
+ const {
+ 'css/languageService': getCssLs,
+ 'css/stylesheet': getStylesheet,
+ } = baseInstance.provide as Provide;
+
return {
...baseInstance,
async provideDiagnostics(document, token) {
@@ -18,7 +27,73 @@ export function create(): LanguageServicePlugin {
}
return diagnostics;
},
+ /**
+ * If the editing position is within the virtual code and navigation is enabled,
+ * skip the CSS renaming feature.
+ */
+ provideRenameRange(document, position) {
+ do {
+ const uri = URI.parse(document.uri);
+ const decoded = context.decodeEmbeddedDocumentUri(uri);
+ const sourceScript = decoded && context.language.scripts.get(decoded[0]);
+ const virtualCode = decoded && sourceScript?.generated?.embeddedCodes.get(decoded[1]);
+ if (!sourceScript?.generated || !virtualCode?.id.startsWith('style_')) {
+ break;
+ }
+
+ const root = sourceScript.generated.root;
+ if (!(root instanceof VueVirtualCode)) {
+ break;
+ }
+
+ const block = root.sfc.styles.find(style => style.name === decoded![1]);
+ if (!block) {
+ break;
+ }
+
+ let script: VirtualCode | undefined;
+ for (const [key, value] of sourceScript.generated.embeddedCodes) {
+ if (key.startsWith('script_')) {
+ script = value;
+ break;
+ }
+ }
+ if (!script) {
+ break;
+ }
+
+ const offset = document.offsetAt(position) + block.startTagEnd;
+ for (const { sourceOffsets, lengths, data } of script.mappings) {
+ if (
+ !sourceOffsets.length
+ || !data.navigation
+ || typeof data.navigation === 'object' && !data.navigation.shouldRename
+ ) {
+ continue;
+ }
+
+ const start = sourceOffsets[0];
+ const end = sourceOffsets.at(-1)! + lengths.at(-1)!;
+
+ if (offset >= start && offset <= end) {
+ return;
+ }
+ }
+ } while (0);
+
+ return worker(document, (stylesheet, cssLs) => {
+ return cssLs.prepareRename(document, position, stylesheet);
+ });
+ }
};
+
+ function worker(document: TextDocument, callback: (stylesheet: css.Stylesheet, cssLs: css.LanguageService) => T) {
+ const cssLs = getCssLs(document);
+ if (!cssLs) {
+ return;
+ }
+ return callback(getStylesheet(document, cssLs), cssLs);
+ }
},
};
}
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-directive-comments.ts b/packages/language-service/lib/plugins/vue-directive-comments.ts
index ee144e4f48..29c93363cd 100644
--- a/packages/language-service/lib/plugins/vue-directive-comments.ts
+++ b/packages/language-service/lib/plugins/vue-directive-comments.ts
@@ -10,6 +10,10 @@ const cmds = [
const directiveCommentReg = /
+
-
-
-
- {{ exactType(comp1?.foo, {} as 1 | undefined) }}
-
diff --git a/test-workspace/tsc/passedFixtures/vue3/#4788/main.vue b/test-workspace/tsc/passedFixtures/vue3/#4788/main.vue
deleted file mode 100644
index fb7c084754..0000000000
--- a/test-workspace/tsc/passedFixtures/vue3/#4788/main.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- {{ exactType(a, {} as 1) }}
-
-
-
diff --git a/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/useAttrs/main.vue b/test-workspace/tsc/passedFixtures/vue3/attrs/main.vue
similarity index 87%
rename from test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/useAttrs/main.vue
rename to test-workspace/tsc/passedFixtures/vue3/attrs/main.vue
index 3bad154af3..63a42d4a88 100644
--- a/test-workspace/tsc/passedFixtures/fallthroughAttributes_strictTemplate/useAttrs/main.vue
+++ b/test-workspace/tsc/passedFixtures/vue3/attrs/main.vue
@@ -1,3 +1,5 @@
+
+
diff --git a/test-workspace/tsc/passedFixtures/vue3/rootEl/base.vue b/test-workspace/tsc/passedFixtures/vue3/rootEl/base.vue
index dee0efe5f9..83ed66f279 100644
--- a/test-workspace/tsc/passedFixtures/vue3/rootEl/base.vue
+++ b/test-workspace/tsc/passedFixtures/vue3/rootEl/base.vue
@@ -1,3 +1,6 @@
+
+
+
diff --git a/test-workspace/tsc/passedFixtures/vue3/rootEl/child.vue b/test-workspace/tsc/passedFixtures/vue3/rootEl/child.vue
index ba23fc8016..684caa091c 100644
--- a/test-workspace/tsc/passedFixtures/vue3/rootEl/child.vue
+++ b/test-workspace/tsc/passedFixtures/vue3/rootEl/child.vue
@@ -1,10 +1,16 @@
+
+
+
-
- {{ exactType($el, {} as HTMLAnchorElement) }}
+
+ {{ exactType($el, {} as HTMLAnchorElement | HTMLImageElement) }}
+
+
+
diff --git a/test-workspace/tsc/passedFixtures/vue3/rootEl/main.vue b/test-workspace/tsc/passedFixtures/vue3/rootEl/main.vue
index a43fc7742b..68262319ab 100644
--- a/test-workspace/tsc/passedFixtures/vue3/rootEl/main.vue
+++ b/test-workspace/tsc/passedFixtures/vue3/rootEl/main.vue
@@ -1,3 +1,5 @@
+
+
-
+
- {{ exactType($refs.templateRef?.$refs.generic?.foo, {} as (1 | undefined)) }}
+ {{ exactType($refs.templateRefs?.$refs.generic?.foo, {} as (1 | undefined)) }}
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 71%
rename from test-workspace/tsc/passedFixtures/vue3/templateRef/template-ref.vue
rename to test-workspace/tsc/passedFixtures/vue3/templateRef/template-refs.vue
index c40f9dc438..0da7ff0c0f 100644
--- a/test-workspace/tsc/passedFixtures/vue3/templateRef/template-ref.vue
+++ b/test-workspace/tsc/passedFixtures/vue3/templateRef/template-refs.vue
@@ -1,6 +1,9 @@
+
+
-
+
{{ exactType(comp1?.foo, {} as 1 | undefined) }}
-
+
{{ exactType(comp2?.[0]?.foo, {} as number | undefined) }}
diff --git a/test-workspace/tsc/passedFixtures/vue3/templateRef_native/main.vue b/test-workspace/tsc/passedFixtures/vue3/templateRef_native/main.vue
deleted file mode 100644
index 3dc2fad988..0000000000
--- a/test-workspace/tsc/passedFixtures/vue3/templateRef_native/main.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/test-workspace/tsconfig.base.json b/test-workspace/tsconfig.base.json
index c71fa33679..e9bf65f158 100644
--- a/test-workspace/tsconfig.base.json
+++ b/test-workspace/tsconfig.base.json
@@ -10,6 +10,7 @@
"noUnusedParameters": true,
"skipLibCheck": true,
"allowJs": true,
+ "declaration": true,
"noEmit": true,
"jsx": "preserve",
"baseUrl": ".",