diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index acee81e741..5f059cff01 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,6 @@ name: "\U0001F41E Bug report" description: Create a report to help us improve +type: Bug labels: ["pending triage"] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index a95927bea0..dfc370f979 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,6 @@ name: "\U0001F680 New feature proposal" description: Suggest an idea for this project -labels: ["feature request"] +type: Feature body: - type: markdown attributes: diff --git a/README.md b/README.md index d00e4bffc3..d7749c2175 100644 --- a/README.md +++ b/README.md @@ -177,18 +177,80 @@ To develop with upstream Volar.js modules, you can setup workspace with https:// --- -

Full-time Support by

-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Special Sponsor +
+
+ + + +

Next Generation Tooling

+
+ Platinum Sponsors +
+ + + +

An approachable, performant and versatile framework for building web user interfaces.

+
+ + + + +

Astro powers the world's fastest websites, client-side web apps, dynamic API endpoints, and everything in-between.

+
+ + + + +

Essential tools for software developers and teams.

+
+ + + +

Stay in the flow with instant dev experiences.
No more hours stashing/pulling/installing locally

+

— just click, and start coding.

+
+ Silver Sponsors +
+ + + + + +

- - - -

Boot a fresh environment in milliseconds.

- - + Become a sponsor

-

diff --git a/extensions/vscode/src/features/doctor.ts b/extensions/vscode/src/features/doctor.ts index db637d9dd8..c35b681cf5 100644 --- a/extensions/vscode/src/features/doctor.ts +++ b/extensions/vscode/src/features/doctor.ts @@ -265,7 +265,7 @@ function getPackageJsonOfWorkspacePackage(folder: string, pkg: string) { const path = require.resolve(pkg + '/package.json', { paths: [folder] }); return { path, - json: require(path) as { version: string }, + json: require(path) as { version: string; }, }; } catch { } } diff --git a/extensions/vscode/src/hybridMode.ts b/extensions/vscode/src/hybridMode.ts index 606b8b2775..21eb689f49 100644 --- a/extensions/vscode/src/hybridMode.ts +++ b/extensions/vscode/src/hybridMode.ts @@ -28,7 +28,7 @@ export const enabledHybridMode = computed(() => { return true; } return config.server.hybridMode; -}) +}); export const enabledTypeScriptPlugin = computed(() => { return ( diff --git a/package.json b/package.json index b761c8a242..598acfaeb8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "vitest run", "test:update": "vitest run --update", "lint": "tsslint --projects {tsconfig.json,packages/*/tsconfig.json,extensions/*/tsconfig.json}", - "lint:fix": "pnpm run lint -- --fix", + "lint:fix": "pnpm run lint -- --fix --vscode-settings .vscode/settings.json", "chrome": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=./extensions/vscode ../volar-starter" }, "devDependencies": { @@ -24,6 +24,9 @@ "@lerna-lite/publish": "latest", "@tsslint/cli": "latest", "@tsslint/config": "latest", + "@tsslint/eslint": "latest", + "@typescript-eslint/eslint-plugin": "latest", + "@vue/language-core": "2.2.0", "typescript": "latest", "vitest": "latest" } diff --git a/packages/component-meta/lib/base.ts b/packages/component-meta/lib/base.ts index c65a7cf43d..d0517916a0 100644 --- a/packages/component-meta/lib/base.ts +++ b/packages/component-meta/lib/base.ts @@ -138,8 +138,8 @@ export function baseCreate( const directoryExists = languageServiceHost.directoryExists?.bind(languageServiceHost); const fileExists = languageServiceHost.fileExists.bind(languageServiceHost); const getScriptSnapshot = languageServiceHost.getScriptSnapshot.bind(languageServiceHost); - const globalTypesName = `${commandLine.vueOptions.lib}_${commandLine.vueOptions.target}_${commandLine.vueOptions.strictTemplates}.d.ts`; - const globalTypesContents = `// @ts-nocheck\nexport {};\n` + vue.generateGlobalTypes(commandLine.vueOptions.lib, commandLine.vueOptions.target, commandLine.vueOptions.strictTemplates); + const globalTypesName = vue.getGlobalTypesFileName(commandLine.vueOptions); + const globalTypesContents = `// @ts-nocheck\nexport {};\n` + vue.generateGlobalTypes(commandLine.vueOptions); const globalTypesSnapshot: ts.IScriptSnapshot = { getText: (start, end) => globalTypesContents.slice(start, end), getLength: () => globalTypesContents.length, @@ -754,6 +754,14 @@ function readVueComponentDefaultProps( ...resolvePropsOption(ast, obj, printer, ts), }; } + } else if (descriptor.scriptSetup && scriptSetupRanges?.defineProps?.destructured) { + const ast = descriptor.scriptSetup.ast; + for (const [prop, initializer] of scriptSetupRanges.defineProps.destructured) { + if (initializer) { + const expText = printer?.printNode(ts.EmitHint.Expression, initializer, ast) ?? initializer.getText(ast); + result[prop] = { default: expText }; + } + } } function findObjectLiteralExpression(node: ts.Node) { diff --git a/packages/component-meta/tests/index.spec.ts b/packages/component-meta/tests/index.spec.ts index 1ca3323775..2c08b08a74 100644 --- a/packages/component-meta/tests/index.spec.ts +++ b/packages/component-meta/tests/index.spec.ts @@ -365,6 +365,17 @@ const worker = (checker: ComponentMetaChecker, withTsconfig: boolean) => describ }); }); + test('reference-type-props-destructured', () => { + const componentPath = path.resolve(__dirname, '../../../test-workspace/component-meta/reference-type-props/component-destructure.vue'); + const meta = checker.getComponentMeta(componentPath); + + expect(meta.type).toEqual(TypeMeta.Class); + + const text = meta.props.find(prop => prop.name === 'text'); + + expect(text?.default).toEqual('"foobar"'); + }); + test('reference-type-props-js', () => { const componentPath = path.resolve(__dirname, '../../../test-workspace/component-meta/reference-type-props/component-js.vue'); const meta = checker.getComponentMeta(componentPath); diff --git a/packages/language-core/index.ts b/packages/language-core/index.ts index 2ca1dc11cc..54da03f85b 100644 --- a/packages/language-core/index.ts +++ b/packages/language-core/index.ts @@ -8,7 +8,6 @@ export * from './lib/utils/parseSfc'; export * from './lib/utils/ts'; export * from './lib/virtualFile/vueFile'; -export * as scriptRanges from './lib/parsers/scriptRanges'; export { tsCodegen } from './lib/plugins/vue-tsx'; export * from './lib/utils/shared'; diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index ac0a213671..c2623a0779 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -1,7 +1,24 @@ +import type { VueCompilerOptions } from '../types'; import { getSlotsPropertyName } from '../utils/shared'; -export function generateGlobalTypes(lib: string, target: number, strictTemplates: boolean) { - const fnPropsType = `(K extends { $props: infer Props } ? Props : any)${strictTemplates ? '' : ' & Record'}`; +export function getGlobalTypesFileName(options: VueCompilerOptions) { + return [ + options.lib, + options.target, + options.checkUnknownProps, + options.checkUnknownEvents, + options.checkUnknownComponents, + ].map(v => { + if (typeof v === 'boolean') { + return v ? 1 : 0; + } + return v; + }).join('_') + '.d.ts'; +} + +export function generateGlobalTypes(options: VueCompilerOptions) { + const { lib, target, checkUnknownProps, checkUnknownEvents, checkUnknownComponents } = options; + const fnPropsType = `(K extends { $props: infer Props } ? Props : any)${checkUnknownProps ? '' : ' & Record'}`; let text = ``; if (target < 3.5) { text += ` @@ -41,14 +58,15 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates 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_WithComponent = + 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] } : N3 extends keyof LocalComponents ? N3 extends N0 ? Pick : { [K in N0]: LocalComponents[N3] } : + Self extends object ? { [K in N0]: Self } : N1 extends keyof __VLS_GlobalComponents ? N1 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N1] } : N2 extends keyof __VLS_GlobalComponents ? N2 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N2] } : N3 extends keyof __VLS_GlobalComponents ? N3 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N3] } : - ${strictTemplates ? '{}' : '{ [K in N0]: unknown }'}; + ${checkUnknownComponents ? '{}' : '{ [K in N0]: unknown }'}; type __VLS_FunctionalComponentProps = '__ctx' extends keyof __VLS_PickNotAny ? K extends { __ctx?: { props?: infer P } } ? NonNullable

: never : T extends (props: infer P, ...args: any) => any ? P : @@ -68,7 +86,7 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates : __VLS_IsFunction extends true ? { [K in onEvent]?: Events[CamelizedEvent] } : Props - )${strictTemplates ? '' : ' & Record'}; + )${checkUnknownEvents ? '' : ' & Record'}; // fix https://github.com/vuejs/language-tools/issues/926 type __VLS_UnionToIntersection = (U extends unknown ? (arg: U) => unknown : never) extends ((arg: infer P) => unknown) ? P : never; type __VLS_OverloadUnionInner = U & T extends (...args: infer A) => infer R @@ -99,23 +117,20 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates >>; type __VLS_UseTemplateRef = Readonly>; - function __VLS_getVForSourceType(source: number): [number, number, number][]; - function __VLS_getVForSourceType(source: string): [string, number, number][]; + function __VLS_getVForSourceType(source: number): [number, number][]; + function __VLS_getVForSourceType(source: string): [string, number][]; function __VLS_getVForSourceType(source: T): [ item: T[number], - key: number, index: number, ][]; function __VLS_getVForSourceType }>(source: T): [ item: T extends { [Symbol.iterator](): Iterator } ? T1 : never, - key: number, - index: undefined, + index: number, ][]; // #3845 function __VLS_getVForSourceType }>(source: T): [ item: number | (Exclude extends { [Symbol.iterator](): Iterator } ? T1 : never), - key: number, - index: undefined, + index: number, ][]; function __VLS_getVForSourceType(source: T): [ item: T[keyof T], @@ -142,8 +157,8 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates } & { props?: ${fnPropsType}; expose?(exposed: K): void; } } : T extends () => any ? (props: {}, ctx?: any) => ReturnType : T extends (...args: any) => any ? T - : (_: {}${strictTemplates ? '' : ' & Record'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${strictTemplates ? '' : ' & Record'} } }; - function __VLS_elementAsFunction(tag: T, endTag?: T): (_: T${strictTemplates ? '' : ' & Record'}) => void; + : (_: {}${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_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 04707b9967..d4552a8466 100644 --- a/packages/language-core/lib/codegen/script/component.ts +++ b/packages/language-core/lib/codegen/script/component.ts @@ -41,10 +41,10 @@ export function* generateComponent( yield generateSfcBlockSection(options.sfc.script, args.start + 1, args.end - 1, codeFeatures.all); } if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.templateRefs.size) { - yield `__typeRefs: {} as __VLS_TemplateResult['refs'],${newLine}`; + yield `__typeRefs: {} as __VLS_TemplateRefs,${newLine}`; } if (options.vueCompilerOptions.target >= 3.5 && options.templateCodegen?.singleRootElType) { - yield `__typeEl: {} as __VLS_TemplateResult['rootEl'],${newLine}`; + yield `__typeEl: {} as __VLS_TemplateEl,${newLine}`; } yield `})`; } @@ -154,7 +154,7 @@ export function* generatePropsOption( }); } if (inheritAttrs && options.templateCodegen?.inheritedAttrVars.size) { - let attrsType = `__VLS_TemplateResult['attrs']`; + let attrsType = `__VLS_TemplateAttrs`; if (hasEmitsOption) { attrsType = `Omit<${attrsType}, \`on\${string}\`>`; } diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index 9c975f325d..e9e8187e65 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -4,7 +4,7 @@ import type * as ts from 'typescript'; import type { ScriptRanges } from '../../parsers/scriptRanges'; import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges'; import type { Code, Sfc, VueCodeInformation, VueCompilerOptions } from '../../types'; -import { generateGlobalTypes } from '../globalTypes'; +import { generateGlobalTypes, getGlobalTypesFileName } from '../globalTypes'; import type { TemplateCodegenContext } from '../template/context'; import { endOfLine, generateSfcBlockSection, newLine } from '../utils'; import { generateComponentSelf } from './componentSelf'; @@ -68,7 +68,7 @@ export function* generateScript(options: ScriptCodegenOptions): Generator${newLine}`; } else { - yield `/// ${newLine}`; + yield `/// ${newLine}`; } } else { @@ -151,9 +151,7 @@ export function* generateScript(options: ScriptCodegenOptions): Generator & __VLS_BuiltInPublicProps,${newLine}` + + ` props: ${ctx.localTypes.PrettifyLocal}<__VLS_OwnProps & __VLS_PublicProps & __VLS_TemplateAttrs> & __VLS_BuiltInPublicProps,${newLine}` + ` expose(exposed: import('${options.vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.defineExpose ? 'typeof __VLS_exposed' : '{}'}>): void,${newLine}` + ` attrs: any,${newLine}` - + ` slots: __VLS_TemplateResult['slots'],${newLine}` + + ` slots: __VLS_TemplateSlots,${newLine}` + ` emit: ${emitTypes.length ? emitTypes.join(' & ') : `{}`},${newLine}` + `}${endOfLine}`; yield `})(),${newLine}`; // __VLS_setup = (async () => { @@ -170,18 +170,17 @@ function* generateSetupFunction( ]); } } - // TODO: circular reference - // for (const { callExp } of scriptSetupRanges.useAttrs) { - // setupCodeModifies.push([ - // [`(`], - // callExp.start, - // callExp.start - // ], [ - // [` as __VLS_TemplateResult['attrs'] & Record)`], - // callExp.end, - // callExp.end - // ]); - // } + for (const { callExp } of scriptSetupRanges.useAttrs) { + setupCodeModifies.push([ + [`(`], + callExp.start, + callExp.start + ], [ + [` as typeof __VLS_special.$attrs)`], + callExp.end, + callExp.end + ]); + } for (const { callExp, exp, arg } of scriptSetupRanges.useCssModule) { setupCodeModifies.push([ [`(`], @@ -216,7 +215,7 @@ function* generateSetupFunction( callExp.start, callExp.start ], [ - [` as __VLS_TemplateResult['slots'])`], + [` as typeof __VLS_special.$slots)`], callExp.end, callExp.end ]); @@ -225,7 +224,7 @@ function* generateSetupFunction( for (const { callExp, exp, arg } of scriptSetupRanges.useTemplateRef) { const templateRefType = arg ? [ - `__VLS_TemplateResult['refs'][`, + `__VLS_TemplateRefs[`, generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.all), `]` ] @@ -294,11 +293,8 @@ function* generateSetupFunction( yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges); yield* generateModelEmit(scriptSetup, scriptSetupRanges); - yield `function __VLS_template() {${newLine}`; const templateCodegenCtx = yield* generateTemplate(options, ctx); - yield `}${endOfLine}`; yield* generateComponentSelf(options, ctx, templateCodegenCtx); - yield `type __VLS_TemplateResult = ReturnType${endOfLine}`; if (syntax) { if (!options.vueCompilerOptions.skipTemplateCodegen && (options.templateCodegen?.hasSlot || scriptSetupRanges.defineSlots)) { @@ -306,7 +302,7 @@ function* generateSetupFunction( yield* generateComponent(options, ctx, scriptSetup, scriptSetupRanges); yield endOfLine; yield `${syntax} `; - yield `{} as ${ctx.localTypes.WithTemplateSlots}${endOfLine}`; + yield `{} as ${ctx.localTypes.WithTemplateSlots}${endOfLine}`; } else { yield `${syntax} `; diff --git a/packages/language-core/lib/codegen/script/template.ts b/packages/language-core/lib/codegen/script/template.ts index 37ae7e1b07..69c3d01bc7 100644 --- a/packages/language-core/lib/codegen/script/template.ts +++ b/packages/language-core/lib/codegen/script/template.ts @@ -1,6 +1,5 @@ -import * as path from 'path-browserify'; import type { Code } from '../../types'; -import { getSlotsPropertyName, hyphenateTag } from '../../utils/shared'; +import { hyphenateTag } from '../../utils/shared'; import { TemplateCodegenContext, createTemplateCodegenContext } from '../template/context'; import { generateInterpolation } from '../template/interpolation'; import { generateStyleScopedClassReferences } from '../template/styleScopedClasses'; @@ -58,7 +57,7 @@ function* generateTemplateComponents(options: ScriptCodegenOptions): Generator { ` - + getSlotsPropertyName(options.vueCompilerOptions.target) - + `: typeof ${options.scriptSetupRanges?.defineSlots?.name ?? `__VLS_slots`} }) }` - ); - } - types.push(`typeof __VLS_ctx`); yield `type __VLS_LocalComponents =`; @@ -145,16 +127,14 @@ function* generateTemplateBody( yield `const __VLS_slots = {}${endOfLine}`; } yield `const __VLS_inheritedAttrs = {}${endOfLine}`; - yield `const $refs = {}${endOfLine}`; - yield `const $el = {} as any${endOfLine}`; + yield `const __VLS_refs = {}${endOfLine}`; + yield `const __VLS_rootEl = {} as any${endOfLine}`; } - yield `return {${newLine}`; - yield ` attrs: {} as Partial,${newLine}`; - yield ` slots: ${options.scriptSetupRanges?.defineSlots?.name ?? '__VLS_slots'},${newLine}`; - yield ` refs: $refs,${newLine}`; - yield ` rootEl: $el,${newLine}`; - yield `}${endOfLine}`; + yield `type __VLS_TemplateAttrs = Partial${endOfLine}`; + yield `type __VLS_TemplateSlots = typeof ${options.scriptSetupRanges?.defineSlots?.name ?? '__VLS_slots'}${endOfLine}`; + yield `type __VLS_TemplateRefs = typeof __VLS_refs${endOfLine}`; + yield `type __VLS_TemplateEl = typeof __VLS_rootEl${endOfLine}`; } function* generateStyleScopedClasses( diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index 662ee6cff2..c435b70f44 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -103,6 +103,7 @@ export function createTemplateCodegenContext(options: Pick(); + const specialVars = new Set(); const accessExternalVariables = new Map>(); const slots: { name: string; @@ -115,7 +116,6 @@ export function createTemplateCodegenContext(options: Pick();; const blockConditions: string[] = []; const scopedClasses: { source: string; @@ -132,9 +132,9 @@ export function createTemplateCodegenContext(options: Pick { ` + + getSlotsPropertyName(options.vueCompilerOptions.target) + + `: typeof ${options.slotsAssignName ?? `__VLS_slots`} }), `; + } + else { + yield `void, `; + } yield getPossibleOriginalComponentNames(node.tag, false) .map(name => `'${name}'`) .join(`, `); @@ -207,7 +213,14 @@ export function* generateComponent( yield `// @ts-ignore${newLine}`; yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({${newLine}`; - yield* generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, false); + yield* generateElementProps( + options, + ctx, + node, + props, + options.vueCompilerOptions.checkUnknownProps, + false + ); yield `}))${endOfLine}`; yield `const `; @@ -231,7 +244,15 @@ export function* generateComponent( tagOffsets[0] + node.tag.length, ctx.codeFeatures.verification, `{${newLine}`, - ...generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, true, failedPropExps), + ...generateElementProps( + options, + ctx, + node, + props, + options.vueCompilerOptions.checkUnknownProps, + true, + failedPropExps + ), `}` ); yield `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`; @@ -269,24 +290,18 @@ export function* generateComponent( yield `let ${var_componentEvents}!: __VLS_NormalizeEmits${endOfLine}`; } - if ( - options.vueCompilerOptions.fallthroughAttributes - && ( - node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs') - || node === ctx.singleRootNode - ) - ) { - const varAttrs = ctx.getInternalVariable(); - ctx.inheritedAttrVars.add(varAttrs); - yield `var ${varAttrs}!: Parameters[0];\n`; + if (hasVBindAttrs(options, ctx, node)) { + const attrsVar = ctx.getInternalVariable(); + ctx.inheritedAttrVars.add(attrsVar); + yield `let ${attrsVar}!: Parameters[0];\n`; } const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; if (slotDir) { - yield* generateComponentSlot(options, ctx, node, slotDir); + yield* generateVSlot(options, ctx, node, slotDir); } else { - yield* generateElementChildren(options, ctx, node); + yield* generateElementChildren(options, ctx, node, true); } if (ctx.currentComponent.used) { @@ -306,7 +321,7 @@ export function* generateElement( : undefined; const failedPropExps: FailedPropExpression[] = []; - yield `__VLS_elementAsFunction(__VLS_intrinsicElements`; + yield `__VLS_asFunctionalElement(__VLS_intrinsicElements`; yield* generatePropertyAccess( options, ctx, @@ -330,7 +345,15 @@ export function* generateElement( startTagOffset + node.tag.length, ctx.codeFeatures.verification, `{${newLine}`, - ...generateElementProps(options, ctx, node, node.props, options.vueCompilerOptions.strictTemplates, true, failedPropExps), + ...generateElementProps( + options, + ctx, + node, + node.props, + options.vueCompilerOptions.checkUnknownProps, + true, + failedPropExps + ), `}` ); yield `)${endOfLine}`; @@ -349,23 +372,11 @@ export function* generateElement( ctx.singleRootElType = `typeof __VLS_nativeElements['${node.tag}']`; } - const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; - if (slotDir && ctx.currentComponent) { - yield* generateComponentSlot(options, ctx, node, slotDir); - } - else { - yield* generateElementChildren(options, ctx, node); - } - - if ( - options.vueCompilerOptions.fallthroughAttributes - && ( - node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs') - || node === ctx.singleRootNode - ) - ) { + if (hasVBindAttrs(options, ctx, node)) { ctx.inheritedAttrVars.add(`__VLS_intrinsicElements.${node.tag}`); } + + yield* generateElementChildren(options, ctx, node); } function* generateFailedPropExps( @@ -483,105 +494,6 @@ function* generateComponentGeneric( ctx.lastGenericComment = undefined; } -function* generateComponentSlot( - options: TemplateCodegenOptions, - ctx: TemplateCodegenContext, - node: CompilerDOM.ElementNode, - slotDir: CompilerDOM.DirectiveNode -): Generator { - yield `{${newLine}`; - if (ctx.currentComponent) { - ctx.currentComponent.used = true; - ctx.hasSlotElements.add(ctx.currentComponent.node); - } - const slotBlockVars: string[] = []; - yield `const {`; - if (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && slotDir.arg.content) { - yield* generateObjectProperty( - options, - ctx, - slotDir.arg.loc.source, - slotDir.arg.loc.start.offset, - slotDir.arg.isStatic ? ctx.codeFeatures.withoutHighlight : ctx.codeFeatures.all, - slotDir.arg.loc, - false, - true - ); - } - else { - yield* wrapWith( - slotDir.loc.start.offset, - slotDir.loc.start.offset + (slotDir.rawName?.length ?? 0), - ctx.codeFeatures.withoutHighlightAndCompletion, - `default` - ); - } - yield `: __VLS_thisSlot } = ${ctx.currentComponent!.ctxVar}.slots!${endOfLine}`; - - if (slotDir?.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - const slotAst = createTsAst(options.ts, slotDir, `(${slotDir.exp.content}) => {}`); - collectVars(options.ts, slotAst, slotAst, slotBlockVars); - if (!slotDir.exp.content.includes(':')) { - yield `const [`; - yield [ - slotDir.exp.content, - 'template', - slotDir.exp.loc.start.offset, - ctx.codeFeatures.all, - ]; - yield `] = __VLS_getSlotParams(__VLS_thisSlot)${endOfLine}`; - } - else { - yield `const `; - yield [ - slotDir.exp.content, - 'template', - slotDir.exp.loc.start.offset, - ctx.codeFeatures.all, - ]; - yield ` = __VLS_getSlotParam(__VLS_thisSlot)${endOfLine}`; - } - } - - for (const varName of slotBlockVars) { - ctx.addLocalVariable(varName); - } - - yield* ctx.resetDirectiveComments('end of slot children start'); - - let prev: CompilerDOM.TemplateChildNode | undefined; - for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, prev); - prev = childNode; - } - - for (const varName of slotBlockVars) { - ctx.removeLocalVariable(varName); - } - let isStatic = true; - if (slotDir?.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - isStatic = slotDir.arg.isStatic; - } - if (isStatic && slotDir && !slotDir.arg) { - yield `${ctx.currentComponent!.ctxVar}.slots!['`; - yield [ - '', - 'template', - slotDir.loc.start.offset + ( - slotDir.loc.source.startsWith('#') - ? '#'.length : slotDir.loc.source.startsWith('v-slot:') - ? 'v-slot:'.length - : 0 - ), - ctx.codeFeatures.completion, - ]; - yield `'/* empty slot name completion */]${newLine}`; - } - - yield* ctx.generateAutoImportCompletion(); - yield `}${newLine}`; -} - function* generateReferencesForElements( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, @@ -617,6 +529,21 @@ function* generateReferencesForElements( return []; } +function hasVBindAttrs( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + node: CompilerDOM.ElementNode +) { + return options.vueCompilerOptions.fallthroughAttributes && ( + node === ctx.singleRootNode || + node.props.some(prop => + prop.type === CompilerDOM.NodeTypes.DIRECTIVE + && prop.name === 'bind' + && prop.exp?.loc.source === '$attrs' + ) + ); +} + function camelizeComponentName(newName: string) { return camelize('-' + newName); } diff --git a/packages/language-core/lib/codegen/template/elementChildren.ts b/packages/language-core/lib/codegen/template/elementChildren.ts index 410df8ffae..f49265e5c7 100644 --- a/packages/language-core/lib/codegen/template/elementChildren.ts +++ b/packages/language-core/lib/codegen/template/elementChildren.ts @@ -8,7 +8,8 @@ import { generateTemplateChild } from './templateChild'; export function* generateElementChildren( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, - node: CompilerDOM.ElementNode + node: CompilerDOM.ElementNode, + isDefaultSlot: boolean = false ): Generator { yield* ctx.resetDirectiveComments('end of element children start'); let prev: CompilerDOM.TemplateChildNode | undefined; @@ -21,10 +22,9 @@ export function* generateElementChildren( // fix https://github.com/vuejs/language-tools/issues/932 if ( ctx.currentComponent - && !ctx.hasSlotElements.has(node) + && isDefaultSlot && node.children.length - && node.tagType !== CompilerDOM.ElementTypes.ELEMENT - && node.tagType !== CompilerDOM.ElementTypes.TEMPLATE + && node.tagType === CompilerDOM.ElementTypes.COMPONENT ) { ctx.currentComponent.used = true; yield `${ctx.currentComponent.ctxVar}.slots!.`; diff --git a/packages/language-core/lib/codegen/template/elementProps.ts b/packages/language-core/lib/codegen/template/elementProps.ts index c55084e82b..fa536bbfc9 100644 --- a/packages/language-core/lib/codegen/template/elementProps.ts +++ b/packages/language-core/lib/codegen/template/elementProps.ts @@ -145,7 +145,6 @@ export function* generateElementProps( prop, prop.exp, ctx.codeFeatures.all, - prop.arg?.loc.start.offset === prop.exp?.loc.start.offset, enableCodeFeatures ), `)` @@ -161,7 +160,7 @@ export function* generateElementProps( } yield `,${newLine}`; - if (prop.name === 'model' && prop.modifiers.length) { + if (isComponent && prop.name === 'model' && prop.modifiers.length) { const propertyName = prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION ? !prop.arg.isStatic ? `[__VLS_tryAsConstant(\`$\{${prop.arg.content}\}Modifiers\`)]` @@ -257,7 +256,6 @@ export function* generateElementProps( prop, prop.exp, ctx.codeFeatures.all, - false, enableCodeFeatures ) ); @@ -279,9 +277,10 @@ function* generatePropExp( prop: CompilerDOM.DirectiveNode, exp: CompilerDOM.SimpleExpressionNode | undefined, features: VueCodeInformation, - isShorthand: boolean, enableCodeFeatures: boolean ): Generator { + const isShorthand = prop.arg?.loc.start.offset === prop.exp?.loc.start.offset; + if (isShorthand && features.completion) { features = { ...features, diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index 63bcdfa5d6..c1432c80d2 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -23,6 +23,7 @@ export interface TemplateCodegenOptions { slotsAssignName?: string; propsAssignName?: string; inheritAttrs: boolean; + selfComponentName?: string; } export function* generateTemplate(options: TemplateCodegenOptions): Generator { @@ -34,26 +35,38 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator { +function* generateSlots( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext +): Generator { if (!options.hasDefineSlots) { yield `var __VLS_slots!: `; for (const { expVar, varName } of ctx.dynamicSlots) { @@ -85,21 +98,23 @@ function* generateSlots(options: TemplateCodegenOptions, ctx: TemplateCodegenCon } yield `}${endOfLine}`; } - const name = getSlotsPropertyName(options.vueCompilerOptions.target); - yield `var ${name}!: typeof ${options.slotsAssignName ?? '__VLS_slots'}${endOfLine}`; + return `typeof ${options.slotsAssignName ?? `__VLS_slots`}`; } -function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator { +function* generateInheritedAttrs( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext +): Generator { yield 'let __VLS_inheritedAttrs!: {}'; for (const varName of ctx.inheritedAttrVars) { yield ` & typeof ${varName}`; } yield endOfLine; - yield `var $attrs!: Partial & Record${endOfLine}`; if (ctx.bindingAttrLocs.length) { yield `[`; for (const loc of ctx.bindingAttrLocs) { + yield `__VLS_special.`; yield [ loc.source, 'template', @@ -110,9 +125,12 @@ function* generateInheritedAttrs(ctx: TemplateCodegenContext): Generator { } yield `]${endOfLine}`; } + return `import('${options.vueCompilerOptions.lib}').ComponentPublicInstance['$attrs'] & Partial`; } -function* generateRefs(ctx: TemplateCodegenContext): Generator { +function* generateRefs( + ctx: TemplateCodegenContext +): Generator { yield `const __VLS_refs = {${newLine}`; for (const [name, [varName, offset]] of ctx.templateRefs) { yield* generateStringLiteralKey( @@ -123,16 +141,16 @@ function* generateRefs(ctx: TemplateCodegenContext): Generator { yield `: ${varName},${newLine}`; } yield `}${endOfLine}`; - yield `var $refs!: typeof __VLS_refs${endOfLine}`; + return `typeof __VLS_refs`; } -function* generateRootEl(ctx: TemplateCodegenContext): Generator { - if (ctx.singleRootElType) { - yield `var $el!: ${ctx.singleRootElType}${endOfLine}`; - } - else { - yield `var $el!: any${endOfLine}`; - } +function* generateRootEl( + ctx: TemplateCodegenContext +): Generator { + yield `let __VLS_rootEl!: `; + yield ctx.singleRootElType ?? `any`; + yield endOfLine; + return `typeof __VLS_rootEl`; } export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator { diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts index f66fb18eea..859fe63e47 100644 --- a/packages/language-core/lib/codegen/template/interpolation.ts +++ b/packages/language-core/lib/codegen/template/interpolation.ts @@ -9,7 +9,7 @@ export function* generateInterpolation( options: { ts: typeof ts, destructuredPropNames: Set | undefined, - templateRefNames: Set | undefined + templateRefNames: Set | undefined; }, ctx: TemplateCodegenContext, source: string, @@ -71,6 +71,12 @@ export function* generateInterpolation( } } +interface CtxVar { + text: string; + isShorthand: boolean; + offset: number; +}; + function* forEachInterpolationSegment( ts: typeof import('typescript'), destructuredPropNames: Set | undefined, @@ -80,20 +86,16 @@ function* forEachInterpolationSegment( offset: number | undefined, ast: ts.SourceFile ): Generator<[fragment: string, offset: number | undefined, type?: 'errorMappingOnly' | 'startText' | 'endText']> { - let ctxVars: { - text: string, - isShorthand: boolean, - offset: number, - }[] = []; + let ctxVars: CtxVar[] = []; const varCb = (id: ts.Identifier, isShorthand: boolean) => { const text = getNodeText(ts, id, ast); if ( - ctx.hasLocalVariable(text) || + ctx.hasLocalVariable(text) // https://github.com/vuejs/core/blob/245230e135152900189f13a4281302de45fdcfaa/packages/compiler-core/src/transforms/transformExpression.ts#L342-L352 - isGloballyAllowed(text) || - text === 'require' || - text.startsWith('__VLS_') + || isGloballyAllowed(text) + || text === 'require' + || text.startsWith('__VLS_') ) { // localVarOffsets.push(localVar.getStart(ast)); } @@ -132,7 +134,7 @@ function* forEachInterpolationSegment( const curVar = ctxVars[i]; const nextVar = ctxVars[i + 1]; - yield* generateVar(code, destructuredPropNames, templateRefNames, curVar, nextVar); + yield* generateVar(code, ctx.specialVars, destructuredPropNames, templateRefNames, curVar, nextVar); if (nextVar.isShorthand) { yield [code.slice(curVar.offset + curVar.text.length, nextVar.offset + nextVar.text.length), curVar.offset + curVar.text.length]; @@ -144,7 +146,7 @@ function* forEachInterpolationSegment( } const lastVar = ctxVars.at(-1)!; - yield* generateVar(code, destructuredPropNames, templateRefNames, lastVar); + yield* generateVar(code, ctx.specialVars, 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']; } @@ -156,18 +158,11 @@ function* forEachInterpolationSegment( function* generateVar( code: string, + specialVars: Set, destructuredPropNames: Set | undefined, templateRefNames: Set | undefined, - curVar: { - text: string, - isShorthand: boolean, - offset: number, - }, - nextVar: { - text: string, - isShorthand: boolean, - offset: number, - } = curVar + curVar: CtxVar, + nextVar: CtxVar = curVar ): Generator<[fragment: string, offset: number | undefined, type?: 'errorMappingOnly']> { // fix https://github.com/vuejs/language-tools/issues/1205 // fix https://github.com/vuejs/language-tools/issues/1264 @@ -181,7 +176,10 @@ function* generateVar( yield [`)`, undefined]; } else { - if (!isDestructuredProp) { + if (specialVars.has(curVar.text)) { + yield [`__VLS_special.`, undefined]; + } + else if (!isDestructuredProp) { yield [`__VLS_ctx.`, undefined]; } yield [code.slice(curVar.offset, curVar.offset + curVar.text.length), curVar.offset]; diff --git a/packages/language-core/lib/codegen/template/slotOutlet.ts b/packages/language-core/lib/codegen/template/slotOutlet.ts index a8b07fb40c..332e433f67 100644 --- a/packages/language-core/lib/codegen/template/slotOutlet.ts +++ b/packages/language-core/lib/codegen/template/slotOutlet.ts @@ -53,14 +53,28 @@ export function* generateSlotOutlet( startTagOffset + node.tag.length, ctx.codeFeatures.verification, `{${newLine}`, - ...generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), true, true), + ...generateElementProps( + options, + ctx, + node, + node.props.filter(prop => prop !== nameProp), + true, + true + ), `}` ); yield `)${endOfLine}`; } else { yield `var ${varSlot} = {${newLine}`; - yield* generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), options.vueCompilerOptions.strictTemplates, true); + yield* generateElementProps( + options, + ctx, + node, + node.props.filter(prop => prop !== nameProp), + options.vueCompilerOptions.checkUnknownProps, + true + ); yield `}${endOfLine}`; if ( diff --git a/packages/language-core/lib/codegen/template/templateChild.ts b/packages/language-core/lib/codegen/template/templateChild.ts index 90b810dee9..922c33e6cc 100644 --- a/packages/language-core/lib/codegen/template/templateChild.ts +++ b/packages/language-core/lib/codegen/template/templateChild.ts @@ -8,6 +8,7 @@ import { generateInterpolation } from './interpolation'; import { generateSlotOutlet } from './slotOutlet'; import { generateVFor } from './vFor'; import { generateVIf } from './vIf'; +import { generateVSlot } from './vSlot'; // @ts-ignore const transformContext: CompilerDOM.TransformContext = { @@ -83,9 +84,17 @@ export function* generateTemplateChild( else if (vIfNode) { yield* generateVIf(options, ctx, vIfNode); } + else if (node.tagType === CompilerDOM.ElementTypes.SLOT) { + yield* generateSlotOutlet(options, ctx, node); + } else { - if (node.tagType === CompilerDOM.ElementTypes.SLOT) { - yield* generateSlotOutlet(options, ctx, node); + const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode; + if ( + node.tagType === CompilerDOM.ElementTypes.TEMPLATE + && ctx.currentComponent + && slotDir + ) { + yield* generateVSlot(options, ctx, node, slotDir); } else if ( node.tagType === CompilerDOM.ElementTypes.ELEMENT diff --git a/packages/language-core/lib/codegen/template/vSlot.ts b/packages/language-core/lib/codegen/template/vSlot.ts new file mode 100644 index 0000000000..8be7bb9c66 --- /dev/null +++ b/packages/language-core/lib/codegen/template/vSlot.ts @@ -0,0 +1,109 @@ +import * as CompilerDOM from '@vue/compiler-dom'; +import type { Code } from '../../types'; +import { collectVars, createTsAst, endOfLine, newLine, wrapWith } from '../utils'; +import type { TemplateCodegenContext } from './context'; +import type { TemplateCodegenOptions } from './index'; +import { generateObjectProperty } from './objectProperty'; +import { generateTemplateChild } from './templateChild'; + +export function* generateVSlot( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + node: CompilerDOM.ElementNode, + slotDir: CompilerDOM.DirectiveNode +): Generator { + if (!ctx.currentComponent) { + return; + } + ctx.currentComponent.used = true; + const slotBlockVars: string[] = []; + yield `{${newLine}`; + + yield `const { `; + if (slotDir.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && slotDir.arg.content) { + yield* generateObjectProperty( + options, + ctx, + slotDir.arg.loc.source, + slotDir.arg.loc.start.offset, + slotDir.arg.isStatic ? ctx.codeFeatures.withoutHighlight : ctx.codeFeatures.all, + slotDir.arg.loc, + false, + true + ); + } + else { + yield* wrapWith( + slotDir.loc.start.offset, + slotDir.loc.start.offset + (slotDir.rawName?.length ?? 0), + ctx.codeFeatures.withoutHighlightAndCompletion, + `default` + ); + } + yield `: __VLS_thisSlot } = ${ctx.currentComponent.ctxVar}.slots!${endOfLine}`; + + if (slotDir.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { + const slotAst = createTsAst(options.ts, slotDir, `(${slotDir.exp.content}) => {}`); + collectVars(options.ts, slotAst, slotAst, slotBlockVars); + if (!slotDir.exp.content.includes(':')) { + yield `const [`; + yield [ + slotDir.exp.content, + 'template', + slotDir.exp.loc.start.offset, + ctx.codeFeatures.all, + ]; + yield `] = __VLS_getSlotParams(__VLS_thisSlot)${endOfLine}`; + } + else { + yield `const `; + yield [ + slotDir.exp.content, + 'template', + slotDir.exp.loc.start.offset, + ctx.codeFeatures.all, + ]; + yield ` = __VLS_getSlotParam(__VLS_thisSlot)${endOfLine}`; + } + } + + for (const varName of slotBlockVars) { + ctx.addLocalVariable(varName); + } + + yield* ctx.resetDirectiveComments('end of slot children start'); + + let prev: CompilerDOM.TemplateChildNode | undefined; + for (const childNode of node.children) { + yield* generateTemplateChild(options, ctx, childNode, prev); + prev = childNode; + } + + for (const varName of slotBlockVars) { + ctx.removeLocalVariable(varName); + } + + let isStatic = true; + if (slotDir.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { + isStatic = slotDir.arg.isStatic; + } + if (isStatic && !slotDir.arg) { + yield `${ctx.currentComponent.ctxVar}.slots!['`; + yield [ + '', + 'template', + slotDir.loc.start.offset + ( + slotDir.loc.source.startsWith('#') + ? '#'.length + : slotDir.loc.source.startsWith('v-slot:') + ? 'v-slot:'.length + : 0 + ), + ctx.codeFeatures.completion, + ]; + yield `'/* empty slot name completion */]${endOfLine}`; + } + + yield* ctx.generateAutoImportCompletion(); + yield `}${newLine}`; +} diff --git a/packages/language-core/lib/codegen/utils/index.ts b/packages/language-core/lib/codegen/utils/index.ts index a39915e108..bdd2c02fdb 100644 --- a/packages/language-core/lib/codegen/utils/index.ts +++ b/packages/language-core/lib/codegen/utils/index.ts @@ -32,7 +32,7 @@ export function collectVars( results: string[] = [] ) { const identifiers = collectIdentifiers(ts, node, []); - for (const [id] of identifiers) { + for (const { id } of identifiers) { results.push(getNodeText(ts, id, ast)); } return results; @@ -41,15 +41,16 @@ export function collectVars( export function collectIdentifiers( ts: typeof import('typescript'), node: ts.Node, - results: [id: ts.Identifier, isRest: boolean][] = [], - isRest = false + results: Array<{ id: ts.Identifier, isRest: boolean, initializer: ts.Expression | undefined; }> = [], + isRest = false, + initializer: ts.Expression | undefined = undefined ) { if (ts.isIdentifier(node)) { - results.push([node, isRest]); + results.push({ id: node, isRest, initializer }); } else if (ts.isObjectBindingPattern(node)) { for (const el of node.elements) { - collectIdentifiers(ts, el.name, results, !!el.dotDotDotToken); + collectIdentifiers(ts, el.name, results, !!el.dotDotDotToken, el.initializer); } } else if (ts.isArrayBindingPattern(node)) { diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index 0eebc5071e..5ed7b21a7b 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -20,14 +20,14 @@ type DefineProp = { defaultValue?: TextRange; required?: boolean; isModel?: boolean; -} +}; type DefineProps = CallExpressionRange & { name?: string; - destructured?: Set; + destructured?: Map; destructuredRest?: string; statement: TextRange; -} +}; type WithDefaults = Pick; @@ -35,20 +35,20 @@ type DefineEmits = CallExpressionRange & { name?: string; hasUnionTypeArg?: boolean; statement: TextRange; -} +}; type DefineSlots = CallExpressionRange & { name?: string; isObjectBindingPattern?: boolean; statement: TextRange; -} +}; type DefineExpose = CallExpressionRange; type DefineOptions = { name?: string; inheritAttrs?: string; -} +}; type UseAttrs = CallExpressionRange; @@ -58,7 +58,7 @@ type UseSlots = CallExpressionRange; type UseTemplateRef = CallExpressionRange & { name?: string; -} +}; export interface ScriptSetupRanges extends ReturnType { } @@ -285,15 +285,15 @@ export function parseScriptSetupRanges( }; if (ts.isVariableDeclaration(parent)) { if (ts.isObjectBindingPattern(parent.name)) { - defineProps.destructured = new Set(); + defineProps.destructured = new Map(); const identifiers = collectIdentifiers(ts, parent.name, []); - for (const [id, isRest] of identifiers) { + for (const { id, isRest, initializer } of identifiers) { const name = _getNodeText(id); if (isRest) { defineProps.destructuredRest = name; } else { - defineProps.destructured.add(name); + defineProps.destructured.set(name, initializer); } } } diff --git a/packages/language-core/lib/parsers/vueCompilerOptions.ts b/packages/language-core/lib/parsers/vueCompilerOptions.ts index 89450c89bd..33747b84e6 100644 --- a/packages/language-core/lib/parsers/vueCompilerOptions.ts +++ b/packages/language-core/lib/parsers/vueCompilerOptions.ts @@ -1,8 +1,8 @@ -import type { VueCompilerOptions } from '../types'; +import type { RawVueCompilerOptions } from '../types'; const syntaxReg = /^\s*@(?.+?)\s+(?.+?)\s*$/m; -export function parseVueCompilerOptions(comments: string[]): Partial | undefined { +export function parseVueCompilerOptions(comments: string[]): RawVueCompilerOptions | undefined { const entries = comments .map(text => { try { diff --git a/packages/language-core/lib/plugins/vue-template-inline-ts.ts b/packages/language-core/lib/plugins/vue-template-inline-ts.ts index be9a869356..3a1e427259 100644 --- a/packages/language-core/lib/plugins/vue-template-inline-ts.ts +++ b/packages/language-core/lib/plugins/vue-template-inline-ts.ts @@ -94,7 +94,7 @@ const plugin: VueLanguagePlugin = ctx => { } if (prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && !prop.arg.isStatic) { addFormatCodes( - prop.arg.content, + prop.arg.loc.source, prop.arg.loc.start.offset, formatBrackets.normal ); @@ -105,17 +105,51 @@ const plugin: VueLanguagePlugin = ctx => { ) { if (prop.name === 'on' && prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { const ast = createTsAst(ctx.modules.typescript, prop.exp, prop.exp.content); + if (isCompoundExpression(ctx.modules.typescript, ast)) { + addFormatCodes( + prop.exp.loc.source, + prop.exp.loc.start.offset, + formatBrackets.event + ); + } + else { + const lines = prop.exp.content.split('\n'); + const firstLineEmpty = lines[0].trim() === ''; + const lastLineEmpty = lines[lines.length - 1].trim() === ''; + if (lines.length <= 1 || (!firstLineEmpty && !lastLineEmpty)) { + addFormatCodes( + prop.exp.loc.source, + prop.exp.loc.start.offset, + formatBrackets.normal + ); + } + else { + addFormatCodes( + prop.exp.loc.source, + prop.exp.loc.start.offset, + ['(', ');'] + ); + } + } + } + else if (prop.name === 'slot') { + addFormatCodes( + prop.exp.loc.source, + prop.exp.loc.start.offset, + formatBrackets.params + ); + } + else if (prop.rawName === 'v-for') { + // #2586 addFormatCodes( - prop.exp.content, + prop.exp.loc.source, prop.exp.loc.start.offset, - isCompoundExpression(ctx.modules.typescript, ast) - ? formatBrackets.event - : formatBrackets.normal + formatBrackets.for ); } else { addFormatCodes( - prop.exp.content, + prop.exp.loc.source, prop.exp.loc.start.offset, formatBrackets.normal ); @@ -131,7 +165,7 @@ const plugin: VueLanguagePlugin = ctx => { const branch = node.branches[i]; if (branch.condition?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { addFormatCodes( - branch.condition.content, + branch.condition.loc.source, branch.condition.loc.start.offset, formatBrackets.if ); @@ -146,8 +180,14 @@ const plugin: VueLanguagePlugin = ctx => { const { leftExpressionRange, leftExpressionText } = parseVForNode(node); const { source } = node.parseResult; if (leftExpressionRange && leftExpressionText && source.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { - const start = leftExpressionRange.start; - const end = source.loc.start.offset + source.content.length; + let start = leftExpressionRange.start; + let end = source.loc.start.offset + source.content.length; + while (templateContent[start - 1] === ' ' || templateContent[start - 1] === '(') { + start--; + } + while (templateContent[end] === ' ' || templateContent[end] === ')') { + end++; + } addFormatCodes( templateContent.slice(start, end), start, @@ -174,14 +214,44 @@ const plugin: VueLanguagePlugin = ctx => { // {{ ... }} const [content, start] = parseInterpolationNode(node, templateContent); const lines = content.split('\n'); - addFormatCodes( - content, - start, - lines.length <= 1 ? formatBrackets.curly : [ - lines[0].trim() === '' ? '(' : formatBrackets.curly[0], - lines[lines.length - 1].trim() === '' ? ');' : formatBrackets.curly[1], - ] - ); + const firstLineEmpty = lines[0].trim() === ''; + const lastLineEmpty = lines[lines.length - 1].trim() === ''; + + if (content.includes('=>')) { // arrow function + if (lines.length <= 1 || (!firstLineEmpty && !lastLineEmpty)) { + addFormatCodes( + content, + start, + formatBrackets.normal + ); + } + else { + addFormatCodes( + content, + start, + ['(', ');'] + ); + } + } + else { + if (lines.length <= 1 || (!firstLineEmpty && !lastLineEmpty)) { + addFormatCodes( + content, + start, + formatBrackets.curly + ); + } + else { + addFormatCodes( + content, + start, + [ + firstLineEmpty ? '(' : '(0 +', + lastLineEmpty ? ');' : '+ 0);' + ] + ); + } + } } } diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index a378b240e8..ee669a8e5c 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -1,12 +1,14 @@ import type { Mapping } from '@volar/language-core'; +import { camelize, capitalize } from '@vue/shared'; import { computed, unstable } from 'alien-signals'; +import * as path from 'path-browserify'; import { generateScript } from '../codegen/script'; import { generateTemplate } from '../codegen/template'; import { parseScriptRanges } from '../parsers/scriptRanges'; import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges'; import { parseVueCompilerOptions } from '../parsers/vueCompilerOptions'; import type { Code, Sfc, VueLanguagePlugin } from '../types'; -import { resolveVueCompilerOptions } from '../utils/ts'; +import { CompilerOptionsResolver } from '../utils/ts'; export const tsCodegen = new WeakMap>(); @@ -81,9 +83,12 @@ function createTsx( }); const vueCompilerOptions = computed(() => { const options = parseVueCompilerOptions(_sfc.comments); - return options - ? resolveVueCompilerOptions(options, ctx.vueCompilerOptions) - : ctx.vueCompilerOptions; + if (options) { + const resolver = new CompilerOptionsResolver(); + resolver.addConfig(options, path.dirname(fileName)); + return resolver.build(ctx.vueCompilerOptions); + } + return ctx.vueCompilerOptions; }); const scriptRanges = computed(() => _sfc.script @@ -128,7 +133,7 @@ function createTsx( ); const destructuredPropNames = unstable.computedSet( computed(() => { - const newNames = new Set(scriptSetupRanges.get()?.defineProps?.destructured); + const newNames = new Set(scriptSetupRanges.get()?.defineProps?.destructured?.keys()); const rest = scriptSetupRanges.get()?.defineProps?.destructuredRest; if (rest) { newNames.add(rest); @@ -153,6 +158,19 @@ function createTsx( const value = scriptSetupRanges.get()?.defineOptions?.inheritAttrs ?? scriptRanges.get()?.exportDefault?.inheritAttrsOption; return value !== 'false'; }); + const selfComponentName = computed(() => { + const { exportDefault } = scriptRanges.get() ?? {}; + if (_sfc.script && exportDefault?.nameOption) { + const { nameOption } = exportDefault; + return _sfc.script.content.slice(nameOption.start + 1, nameOption.end - 1); + } + const { defineOptions } = scriptSetupRanges.get() ?? {}; + if (_sfc.scriptSetup && defineOptions?.name) { + return defineOptions.name; + } + const baseName = path.basename(fileName); + return capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.')))); + }); const generatedTemplate = computed(() => { if (vueCompilerOptions.get().skipTemplateCodegen || !_sfc.template) { @@ -174,6 +192,7 @@ function createTsx( slotsAssignName: slotsAssignName.get(), propsAssignName: propsAssignName.get(), inheritAttrs: inheritAttrs.get(), + selfComponentName: selfComponentName.get(), }); let current = codegen.next(); @@ -186,7 +205,7 @@ function createTsx( return { ...current.value, - codes: codes, + codes, }; }); const generatedScript = computed(() => { diff --git a/packages/language-core/lib/types.ts b/packages/language-core/lib/types.ts index 6d1e05cac5..0a9ea03d88 100644 --- a/packages/language-core/lib/types.ts +++ b/packages/language-core/lib/types.ts @@ -10,7 +10,8 @@ export type { SFCParseResult } from '@vue/compiler-sfc'; export { VueEmbeddedCode }; export type RawVueCompilerOptions = Partial> & { - target?: 'auto' | 2 | 2.7 | 3 | 3.3; + strictTemplates?: boolean; + target?: 'auto' | 2 | 2.7 | 3 | 3.3 | 3.5 | 99 | number; plugins?: string[]; }; @@ -28,7 +29,9 @@ export interface VueCompilerOptions { vitePressExtensions: string[]; petiteVueExtensions: string[]; jsxSlots: boolean; - strictTemplates: boolean; + checkUnknownProps: boolean; + checkUnknownEvents: boolean; + checkUnknownComponents: boolean; skipTemplateCodegen: boolean; fallthroughAttributes: boolean; dataAttributes: string[]; diff --git a/packages/language-core/lib/utils/ts.ts b/packages/language-core/lib/utils/ts.ts index ff9e3471a1..d8605b0f75 100644 --- a/packages/language-core/lib/utils/ts.ts +++ b/packages/language-core/lib/utils/ts.ts @@ -1,7 +1,7 @@ import { camelize } from '@vue/shared'; import { posix as path } from 'path-browserify'; import type * as ts from 'typescript'; -import { generateGlobalTypes } from '../codegen/globalTypes'; +import { generateGlobalTypes, getGlobalTypesFileName } from '../codegen/globalTypes'; import { getAllExtensions } from '../languagePlugin'; import type { RawVueCompilerOptions, VueCompilerOptions, VueLanguagePlugin } from '../types'; @@ -23,18 +23,19 @@ export function createParsedCommandLineByJson( const proxyHost = proxyParseConfigHostForExtendConfigPaths(parseConfigHost); ts.parseJsonConfigFileContent(json, proxyHost.host, rootDir, {}, configFileName); - let vueOptions: Partial = {}; + const resolver = new CompilerOptionsResolver(); for (const extendPath of proxyHost.extendConfigPaths.reverse()) { try { - vueOptions = { - ...vueOptions, - ...getPartialVueCompilerOptions(ts, ts.readJsonConfigFile(extendPath, proxyHost.host.readFile)), - }; + const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile); + const obj = ts.convertToObject(configFile, []); + const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {}; + resolver.addConfig(rawOptions, path.dirname(configFile.fileName)); } catch (err) { } } - const resolvedVueOptions = resolveVueCompilerOptions(vueOptions); + const resolvedVueOptions = resolver.build(); + if (skipGlobalTypesSetup) { resolvedVueOptions.__setupedGlobalTypes = true; } @@ -78,18 +79,19 @@ export function createParsedCommandLine( const config = ts.readJsonConfigFile(tsConfigPath, proxyHost.host.readFile); ts.parseJsonSourceFileConfigFileContent(config, proxyHost.host, path.dirname(tsConfigPath), {}, tsConfigPath); - let vueOptions: Partial = {}; + const resolver = new CompilerOptionsResolver(); for (const extendPath of proxyHost.extendConfigPaths.reverse()) { try { - vueOptions = { - ...vueOptions, - ...getPartialVueCompilerOptions(ts, ts.readJsonConfigFile(extendPath, proxyHost.host.readFile)), - }; + const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile); + const obj = ts.convertToObject(configFile, []); + const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {}; + resolver.addConfig(rawOptions, path.dirname(configFile.fileName)); } catch (err) { } } - const resolvedVueOptions = resolveVueCompilerOptions(vueOptions); + const resolvedVueOptions = resolver.build(); + if (skipGlobalTypesSetup) { resolvedVueOptions.__setupedGlobalTypes = true; } @@ -126,7 +128,7 @@ export function createParsedCommandLine( return { fileNames: [], options: {}, - vueOptions: resolveVueCompilerOptions({}), + vueOptions: getDefaultCompilerOptions(), errors: [], }; } @@ -153,76 +155,106 @@ function proxyParseConfigHostForExtendConfigPaths(parseConfigHost: ts.ParseConfi }; } -function getPartialVueCompilerOptions( - ts: typeof import('typescript'), - tsConfigSourceFile: ts.TsConfigSourceFile -) { - - const folder = path.dirname(tsConfigSourceFile.fileName); - const obj = ts.convertToObject(tsConfigSourceFile, []); - const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {}; - const result: Partial = { - ...rawOptions as any, - }; - const target = rawOptions.target ?? 'auto'; +export class CompilerOptionsResolver { + options: Omit = {}; + fallbackTarget: number | undefined; + target: number | undefined; + plugins: VueLanguagePlugin[] = []; - if (target === 'auto') { - const resolvedPath = resolvePath('vue/package.json'); - if (resolvedPath) { - const vuePackageJson = require(resolvedPath); - const versionNumbers = vuePackageJson.version.split('.'); - result.target = Number(versionNumbers[0] + '.' + versionNumbers[1]); - } - else { - // console.warn('Load vue/package.json failed from', folder); - } - } - else { - result.target = target; - } - if (rawOptions.plugins) { - const plugins = rawOptions.plugins - .map((pluginPath: string) => { - try { - const resolvedPath = resolvePath(pluginPath); - if (resolvedPath) { - const plugin = require(resolvedPath); - plugin.__moduleName = pluginPath; - return plugin; + addConfig(options: RawVueCompilerOptions, rootDir: string) { + for (const key in options) { + switch (key) { + case 'target': + const target = options.target!; + if (typeof target === 'string') { + this.target = findVueVersion(rootDir); } else { - console.warn('[Vue] Load plugin failed:', pluginPath); + this.target = target; } - } - catch (error) { - console.warn('[Vue] Resolve plugin path failed:', pluginPath, error); - } - return []; - }); + break; + case 'plugins': + this.plugins = (options.plugins ?? []) + .map((pluginPath: string) => { + try { + const resolvedPath = resolvePath(pluginPath, rootDir); + if (resolvedPath) { + const plugin = require(resolvedPath); + plugin.__moduleName = pluginPath; + return plugin; + } + else { + console.warn('[Vue] Load plugin failed:', pluginPath); + } + } + catch (error) { + console.warn('[Vue] Resolve plugin path failed:', pluginPath, error); + } + return []; + }); + break; + default: + // @ts-expect-error + this.options[key] = options[key]; + break; + } + } + if (this.target === undefined) { + this.fallbackTarget = findVueVersion(rootDir); + } + } - result.plugins = plugins; + build(defaults?: VueCompilerOptions): VueCompilerOptions { + const target = this.target ?? this.fallbackTarget; + defaults ??= getDefaultCompilerOptions(target, this.options.lib, this.options.strictTemplates); + return { + ...defaults, + ...this.options, + plugins: this.plugins, + macros: { + ...defaults.macros, + ...this.options.macros, + }, + composables: { + ...defaults.composables, + ...this.options.composables, + }, + // 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( + this.options.experimentalModelPropName ?? defaults.experimentalModelPropName + ).map(([k, v]) => [camelize(k), v])), + }; } +} - return result; +function findVueVersion(rootDir: string) { + const resolvedPath = resolvePath('vue/package.json', rootDir); + if (resolvedPath) { + const vuePackageJson = require(resolvedPath); + const versionNumbers = vuePackageJson.version.split('.'); + return Number(versionNumbers[0] + '.' + versionNumbers[1]); + } + else { + // console.warn('Load vue/package.json failed from', folder); + } +} - function resolvePath(scriptPath: string) { - try { - if (require?.resolve) { - return require.resolve(scriptPath, { paths: [folder] }); - } - else { - // console.warn('failed to resolve path:', scriptPath, 'require.resolve is not supported in web'); - } +function resolvePath(scriptPath: string, root: string) { + try { + if (require?.resolve) { + return require.resolve(scriptPath, { paths: [root] }); } - catch (error) { - // console.warn(error); + else { + // console.warn('failed to resolve path:', scriptPath, 'require.resolve is not supported in web'); } } + catch (error) { + // console.warn(error); + } } -function getDefaultOptions(options: Partial): VueCompilerOptions { - const target = options.target ?? 3.3; - const lib = options.lib ?? 'vue'; +export function getDefaultCompilerOptions(target = 99, lib = 'vue', strictTemplates = false): VueCompilerOptions { return { target, lib, @@ -230,7 +262,9 @@ function getDefaultOptions(options: Partial): VueCompilerOpt vitePressExtensions: [], petiteVueExtensions: [], jsxSlots: false, - strictTemplates: false, + checkUnknownProps: strictTemplates, + checkUnknownEvents: strictTemplates, + checkUnknownComponents: strictTemplates, skipTemplateCodegen: false, fallthroughAttributes: false, dataAttributes: [], @@ -256,40 +290,26 @@ function getDefaultOptions(options: Partial): VueCompilerOpt plugins: [], experimentalDefinePropProposal: false, experimentalResolveStyleCssClasses: 'scoped', - experimentalModelPropName: null! + experimentalModelPropName: { + '': { + input: true + }, + value: { + input: { type: 'text' }, + textarea: true, + select: true + } + }, }; -}; +} -export function resolveVueCompilerOptions( - options: Partial, - defaults: VueCompilerOptions = getDefaultOptions(options) -): VueCompilerOptions { +/** + * @deprecated use `getDefaultCompilerOptions` instead + */ +export function resolveVueCompilerOptions(options: Partial): VueCompilerOptions { return { - ...defaults, + ...getDefaultCompilerOptions(options.target, options.lib), ...options, - macros: { - ...defaults.macros, - ...options.macros, - }, - composables: { - ...defaults.composables, - ...options.composables, - }, - - // 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( - options.experimentalModelPropName ?? defaults.experimentalModelPropName ?? { - '': { - input: true - }, - value: { - input: { type: 'text' }, - textarea: true, - select: true - } - } - ).map(([k, v]) => [camelize(k), v])), }; } @@ -309,8 +329,8 @@ export function setupGlobalTypes(rootDir: string, vueOptions: VueCompilerOptions } dir = parentDir; } - const globalTypesPath = path.join(dir, 'node_modules', '.vue-global-types', `${vueOptions.lib}_${vueOptions.target}_${vueOptions.strictTemplates}.d.ts`); - const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueOptions.lib, vueOptions.target, vueOptions.strictTemplates); + const globalTypesPath = path.join(dir, 'node_modules', '.vue-global-types', getGlobalTypesFileName(vueOptions)); + const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueOptions); host.writeFile(globalTypesPath, globalTypesContents); return { absolutePath: globalTypesPath }; } catch { } diff --git a/packages/language-core/schemas/vue-tsconfig.schema.json b/packages/language-core/schemas/vue-tsconfig.schema.json index 5c6f33426f..d2f8613a01 100644 --- a/packages/language-core/schemas/vue-tsconfig.schema.json +++ b/packages/language-core/schemas/vue-tsconfig.schema.json @@ -45,6 +45,21 @@ "default": false, "markdownDescription": "Strict props, component type-checking in templates." }, + "checkUnknownProps": { + "type": "boolean", + "default": false, + "markdownDescription": "Check unknown props. If not set, uses the 'strictTemplates' value." + }, + "checkUnknownEvents": { + "type": "boolean", + "default": false, + "markdownDescription": "Check unknown events. If not set, uses the 'strictTemplates' value." + }, + "checkUnknownComponents": { + "type": "boolean", + "default": false, + "markdownDescription": "Check unknown components. If not set, uses the 'strictTemplates' value." + }, "skipTemplateCodegen": { "type": "boolean", "default": false, diff --git a/packages/language-plugin-pug/index.ts b/packages/language-plugin-pug/index.ts index e445237429..4abb1b1765 100644 --- a/packages/language-plugin-pug/index.ts +++ b/packages/language-plugin-pug/index.ts @@ -1,7 +1,9 @@ import { SourceMap } from '@volar/source-map'; -import type { VueLanguagePlugin } from '@vue/language-core'; +import type { CompilerDOM, VueLanguagePlugin } from '@vue/language-core'; import * as pug from 'volar-service-pug/lib/languageService'; +const classRegex = /^class\s*=/; + const plugin: VueLanguagePlugin = ({ modules }) => { return { @@ -71,6 +73,13 @@ const plugin: VueLanguagePlugin = ({ modules }) => { options?.onWarn?.(createProxyObject(warning)); }, onError(error) { + // #5099 + if ( + error.code === 2 satisfies CompilerDOM.ErrorCodes.DUPLICATE_ATTRIBUTE + && classRegex.test(pugFile.htmlCode.slice(error.loc?.start.offset)) + ) { + return; + } options?.onError?.(createProxyObject(error)); }, }); diff --git a/packages/language-server/lib/initialize.ts b/packages/language-server/lib/initialize.ts index 88c5c626f1..776710c6f0 100644 --- a/packages/language-server/lib/initialize.ts +++ b/packages/language-server/lib/initialize.ts @@ -1,6 +1,6 @@ import type { LanguageServer } from '@volar/language-server'; import { createTypeScriptProject } from '@volar/language-server/node'; -import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, resolveVueCompilerOptions, VueCompilerOptions } from '@vue/language-core'; +import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, getDefaultCompilerOptions, getGlobalTypesFileName, VueCompilerOptions } from '@vue/language-core'; import { Disposable, getFullLanguageServicePlugins, InitializeParams } from '@vue/language-service'; import type * as ts from 'typescript'; @@ -35,7 +35,7 @@ export function initialize( } else { compilerOptions = ts.getDefaultCompilerOptions(); - vueCompilerOptions = resolveVueCompilerOptions({}); + vueCompilerOptions = getDefaultCompilerOptions(); } vueCompilerOptions.__test = params.initializationOptions.typescript.disableAutoImportCache; updateFileWatcher(vueCompilerOptions); @@ -55,8 +55,8 @@ export function initialize( const directoryExists = project.typescript.languageServiceHost.directoryExists?.bind(project.typescript.languageServiceHost); const fileExists = project.typescript.languageServiceHost.fileExists.bind(project.typescript.languageServiceHost); const getScriptSnapshot = project.typescript.languageServiceHost.getScriptSnapshot.bind(project.typescript.languageServiceHost); - const globalTypesName = `${vueCompilerOptions.lib}_${vueCompilerOptions.target}_${vueCompilerOptions.strictTemplates}.d.ts`; - const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueCompilerOptions.lib, vueCompilerOptions.target, vueCompilerOptions.strictTemplates); + const globalTypesName = getGlobalTypesFileName(vueCompilerOptions); + const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueCompilerOptions); const globalTypesSnapshot: ts.IScriptSnapshot = { getText: (start, end) => globalTypesContents.slice(start, end), getLength: () => globalTypesContents.length, diff --git a/packages/language-server/node.ts b/packages/language-server/node.ts index 24c37db0ec..20ced42547 100644 --- a/packages/language-server/node.ts +++ b/packages/language-server/node.ts @@ -1,5 +1,5 @@ import { createConnection, createServer, loadTsdkByPath } from '@volar/language-server/node'; -import { createParsedCommandLine, createVueLanguagePlugin, resolveVueCompilerOptions } from '@vue/language-core'; +import { createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core'; import { getHybridModeLanguageServicePlugins } from '@vue/language-service'; import * as namedPipeClient from '@vue/typescript-plugin/lib/client'; import { createHybridModeProject } from './lib/hybridModeProject'; @@ -23,7 +23,7 @@ connection.onInitialize(params => { const commandLine = configFileName ? createParsedCommandLine(ts, ts.sys, configFileName) : { - vueOptions: resolveVueCompilerOptions({}), + vueOptions: getDefaultCompilerOptions(), options: ts.getDefaultCompilerOptions(), }; commandLine.vueOptions.__test = params.initializationOptions.typescript.disableAutoImportCache; diff --git a/packages/language-server/tests/completions.spec.ts b/packages/language-server/tests/completions.spec.ts index dcc67b3a06..1e56bf6307 100644 --- a/packages/language-server/tests/completions.spec.ts +++ b/packages/language-server/tests/completions.spec.ts @@ -187,8 +187,8 @@ describe('Completions', async () => { "component", "slot", "template", - "fixture", "BaseTransition", + "Fixture", ] `); }); diff --git a/packages/language-service/data/language-blocks/zh-cn.json b/packages/language-service/data/language-blocks/zh-cn.json index 8176b91713..a6a2fee6e0 100644 --- a/packages/language-service/data/language-blocks/zh-cn.json +++ b/packages/language-service/data/language-blocks/zh-cn.json @@ -8,7 +8,7 @@ "name": "src", "description": { "kind": "markdown", - "value": "如果你更喜欢将 `*.vue` 组件分散到多个文件中,可以为一个语块使用 `src` 这个 attribute 来导入一个外部文件:\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n```\n\n得出的 class 将被哈希化以避免冲突,实现了同样的将 CSS 仅作用于当前组件的效果。\n\n参考 [CSS Modules spec](https://github.com/css-modules/css-modules) 以查看更多详情,例如 [global exceptions](https://github.com/css-modules/css-modules/blob/master/docs/composition.md#exceptions) 和 [composition](https://github.com/css-modules/css-modules/blob/master/docs/composition.md#composition)。\n\n### 自定义注入名称 \n\n你可以通过给 `module` attribute 一个值来自定义注入 class 对象的属性名:\n\n```vue\n\n\n\n```\n\n### 与组合式 API 一同使用 \n\n可以通过 `useCssModule` API 在 `setup()` 和 `\n\n\n\n\n```" }, "references": "api/sfc-css-features.html#css-modules" } @@ -167,7 +167,7 @@ "name": "src", "description": { "kind": "markdown", - "value": "如果你更喜欢将 `*.vue` 组件分散到多个文件中,可以为一个语块使用 `src` 这个 attribute 来导入一个外部文件:\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n\n\n```\n\n请注意 `src` 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:\n\n- 相对路径需要以 `./` 开头\n- 你也可以从 npm 依赖中导入资源\n\n```vue\n\n