diff --git a/packages/language-core/lib/codegen/globalTypes.ts b/packages/language-core/lib/codegen/globalTypes.ts index 1f7c027f01..2e8f91c23e 100644 --- a/packages/language-core/lib/codegen/globalTypes.ts +++ b/packages/language-core/lib/codegen/globalTypes.ts @@ -115,9 +115,11 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates function __VLS_getSlotParams(slot: T): Parameters<__VLS_PickNotAny, (...args: any[]) => any>>; // @ts-ignore function __VLS_getSlotParam(slot: T): Parameters<__VLS_PickNotAny, (...args: any[]) => any>>[0]; - function __VLS_directiveAsFunction(dir: T): T extends (...args: any) => any - ? T | __VLS_unknownDirective - : NonNullable<(T & Record)['created' | 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated' | 'beforeUnmount' | 'unmounted']>; + function __VLS_asFunctionalDirective(dir: T): T extends import('${lib}').ObjectDirective + ? NonNullable + : T extends (...args: any) => any + ? T + : __VLS_unknownDirective; function __VLS_withScope(ctx: T, scope: K): ctx is T & K; function __VLS_makeOptional(t: T): { [K in keyof T]?: T[K] }; function __VLS_nonNullable(t: T): T extends null | undefined ? never : T; diff --git a/packages/language-core/lib/codegen/template/elementDirectives.ts b/packages/language-core/lib/codegen/template/elementDirectives.ts index 06869d0392..3c67bce054 100644 --- a/packages/language-core/lib/codegen/template/elementDirectives.ts +++ b/packages/language-core/lib/codegen/template/elementDirectives.ts @@ -7,6 +7,8 @@ import { generateCamelized } from './camelized'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; +import { generateObjectProperty } from './objectProperty'; +import { generateStringLiteralKey } from './stringLiteralKey'; export function* generateElementDirectives( options: TemplateCodegenOptions, @@ -15,85 +17,158 @@ export function* generateElementDirectives( ): Generator { for (const prop of node.props) { if ( - prop.type === CompilerDOM.NodeTypes.DIRECTIVE - && prop.name !== 'slot' - && prop.name !== 'on' - && prop.name !== 'model' - && prop.name !== 'bind' - && prop.name !== 'scope' - && prop.name !== 'data' + prop.type !== CompilerDOM.NodeTypes.DIRECTIVE + || prop.name === 'slot' + || prop.name === 'on' + || prop.name === 'model' + || prop.name === 'bind' + || prop.name === 'scope' + || prop.name === 'data' ) { - ctx.accessExternalVariable(camelize('v-' + prop.name), prop.loc.start.offset); + continue; + } + ctx.accessExternalVariable(camelize('v-' + prop.name), prop.loc.start.offset); + + yield* wrapWith( + prop.loc.start.offset, + prop.loc.end.offset, + ctx.codeFeatures.verification, + `__VLS_asFunctionalDirective(`, + ...generateIdentifier(ctx, prop), + `)(null!, { ...__VLS_directiveBindingRestFields, `, + ...generateArg(options, ctx, prop), + ...generateModifiers(options, ctx, prop), + ...generateValue(options, ctx, prop), + `}, null!, null!)` + ); + yield endOfLine; + } +} - if (prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && !prop.arg.isStatic) { - yield* generateInterpolation( - options, - ctx, - prop.arg.content, - prop.arg.loc, - prop.arg.loc.start.offset + prop.arg.loc.source.indexOf(prop.arg.content), - ctx.codeFeatures.all, - '(', - ')' - ); - yield endOfLine; +function* generateIdentifier( + ctx: TemplateCodegenContext, + prop: CompilerDOM.DirectiveNode +): Generator { + const rawName = 'v-' + prop.name; + yield* wrapWith( + prop.loc.start.offset, + prop.loc.start.offset + rawName.length, + ctx.codeFeatures.verification, + `__VLS_directives.`, + ...generateCamelized( + rawName, + prop.loc.start.offset, + { + ...ctx.codeFeatures.all, + verification: false, + completion: { + // fix https://github.com/vuejs/language-tools/issues/1905 + isAdditional: true, + }, + navigation: { + resolveRenameNewName: camelize, + resolveRenameEditText: getPropRenameApply(prop.name), + }, } + ) + ); +} - yield* wrapWith( - prop.loc.start.offset, - prop.loc.end.offset, - ctx.codeFeatures.verification, - `__VLS_directiveAsFunction(__VLS_directives.`, - ...generateCamelized( - 'v-' + prop.name, - prop.loc.start.offset, - { - ...ctx.codeFeatures.all, - verification: false, - completion: { - // fix https://github.com/vuejs/language-tools/issues/1905 - isAdditional: true, - }, - navigation: { - resolveRenameNewName: camelize, - resolveRenameEditText: getPropRenameApply(prop.name), - }, - } - ), - `)(null!, { ...__VLS_directiveBindingRestFields, `, - ...( - prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION - ? [ - ...wrapWith( - prop.exp.loc.start.offset, - prop.exp.loc.end.offset, - ctx.codeFeatures.verification, - 'value' - ), - ': ', - ...wrapWith( - prop.exp.loc.start.offset, - prop.exp.loc.end.offset, - ctx.codeFeatures.verification, - ...generateInterpolation( - options, - ctx, - prop.exp.content, - prop.exp.loc, - prop.exp.loc.start.offset, - ctx.codeFeatures.all, - '(', - ')' - ) - ) - ] - : [`undefined`] - ), - `}, null!, null!)` - ); - yield endOfLine; - } +function* generateArg( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + prop: CompilerDOM.DirectiveNode +): Generator { + const { arg } = prop; + if (arg?.type !== CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { + return; + } + + const startOffset = arg.loc.start.offset + arg.loc.source.indexOf(arg.content); + + yield* wrapWith( + startOffset, + startOffset + arg.content.length, + ctx.codeFeatures.verification, + 'arg' + ); + yield ': '; + if (arg.isStatic) { + yield* generateStringLiteralKey( + arg.content, + startOffset, + ctx.codeFeatures.withoutHighlight + ); + } + else { + yield* generateInterpolation( + options, + ctx, + arg.content, + arg.loc, + startOffset, + ctx.codeFeatures.all, + '(', + ')' + ); } + yield ', '; +} + +function* generateModifiers( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + prop: CompilerDOM.DirectiveNode +): Generator { + if (options.vueCompilerOptions.target < 3.5) { + return; + } + + yield 'modifiers: { '; + for (const mod of prop.modifiers) { + yield* generateObjectProperty( + options, + ctx, + mod.content, + mod.loc.start.offset, + ctx.codeFeatures.withoutHighlight + ); + yield ': true, '; + } + yield '}, '; +} + +function* generateValue( + options: TemplateCodegenOptions, + ctx: TemplateCodegenContext, + prop: CompilerDOM.DirectiveNode +): Generator { + if (prop.exp?.type !== CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) { + return; + } + + yield* wrapWith( + prop.exp.loc.start.offset, + prop.exp.loc.end.offset, + ctx.codeFeatures.verification, + 'value' + ); + yield ': '; + yield* wrapWith( + prop.exp.loc.start.offset, + prop.exp.loc.end.offset, + ctx.codeFeatures.verification, + ...generateInterpolation( + options, + ctx, + prop.exp.content, + prop.exp.loc, + prop.exp.loc.start.offset, + ctx.codeFeatures.all, + '(', + ')' + ) + ); } function getPropRenameApply(oldName: string) { diff --git a/packages/language-core/package.json b/packages/language-core/package.json index 41a82e1ee4..ad5828e53e 100644 --- a/packages/language-core/package.json +++ b/packages/language-core/package.json @@ -14,9 +14,9 @@ }, "dependencies": { "@volar/language-core": "~2.4.1", - "@vue/compiler-dom": "^3.5.2", + "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", - "@vue/shared": "^3.5.2", + "@vue/shared": "^3.5.0", "alien-signals": "^0.2.0", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", @@ -27,7 +27,7 @@ "@types/node": "latest", "@types/path-browserify": "^1.0.1", "@volar/typescript": "~2.4.1", - "@vue/compiler-sfc": "^3.5.2" + "@vue/compiler-sfc": "^3.5.0" }, "peerDependencies": { "typescript": "*" diff --git a/packages/language-server/tests/completions.spec.ts b/packages/language-server/tests/completions.spec.ts index 48caa8df7e..c914252e89 100644 --- a/packages/language-server/tests/completions.spec.ts +++ b/packages/language-server/tests/completions.spec.ts @@ -200,6 +200,28 @@ describe('Completions', async () => { await requestCompletionItem('fixture.vue', 'vue', ``, 'v-pre'); }); + // FIXME: + it.skip('Directive Modifiers', async () => { + expect( + (await requestCompletionList('fixture.vue', 'vue', ` + + + + `)).items.map(item => item.label) + ).toMatchInlineSnapshot(` + [ + "attr", + "prop" + ] + `); + }); + it('$event argument', async () => { await requestCompletionItem('fixture.vue', 'vue', ``, 'event'); }); diff --git a/packages/language-service/package.json b/packages/language-service/package.json index 722b49839d..f1821c1dd6 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -20,9 +20,9 @@ "@volar/language-core": "~2.4.1", "@volar/language-service": "~2.4.1", "@volar/typescript": "~2.4.1", - "@vue/compiler-dom": "^3.4.0", + "@vue/compiler-dom": "^3.5.0", "@vue/language-core": "2.1.6", - "@vue/shared": "^3.4.0", + "@vue/shared": "^3.5.0", "@vue/typescript-plugin": "2.1.6", "alien-signals": "^0.2.0", "path-browserify": "^1.0.1", diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index 4d0b4f7bb3..56128f016d 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -15,7 +15,7 @@ "dependencies": { "@volar/typescript": "~2.4.1", "@vue/language-core": "2.1.6", - "@vue/shared": "^3.4.0" + "@vue/shared": "^3.5.0" }, "devDependencies": { "@types/node": "latest" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2e6446e58..c452282607 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,14 +105,14 @@ importers: specifier: ~2.4.1 version: 2.4.2 '@vue/compiler-dom': - specifier: ^3.5.2 - version: 3.5.3 + specifier: ^3.5.0 + version: 3.5.12 '@vue/compiler-vue2': specifier: ^2.7.16 version: 2.7.16 '@vue/shared': - specifier: ^3.5.2 - version: 3.5.3 + specifier: ^3.5.0 + version: 3.5.12 alien-signals: specifier: ^0.2.0 version: 0.2.0 @@ -142,8 +142,8 @@ importers: specifier: ~2.4.1 version: 2.4.2 '@vue/compiler-sfc': - specifier: ^3.5.2 - version: 3.5.3 + specifier: ^3.5.0 + version: 3.5.12 packages/language-plugin-pug: dependencies: @@ -200,14 +200,14 @@ importers: specifier: ~2.4.1 version: 2.4.2 '@vue/compiler-dom': - specifier: ^3.4.0 - version: 3.5.3 + specifier: ^3.5.0 + version: 3.5.12 '@vue/language-core': specifier: 2.1.6 version: link:../language-core '@vue/shared': - specifier: ^3.4.0 - version: 3.5.3 + specifier: ^3.5.0 + version: 3.5.12 '@vue/typescript-plugin': specifier: 2.1.6 version: link:../typescript-plugin @@ -292,8 +292,8 @@ importers: specifier: 2.1.6 version: link:../language-core '@vue/shared': - specifier: ^3.4.0 - version: 3.5.3 + specifier: ^3.5.0 + version: 3.5.12 devDependencies: '@types/node': specifier: latest @@ -308,7 +308,7 @@ importers: specifier: npm:typescript@latest version: typescript@5.5.4 vue: - specifier: ^3.5.12 + specifier: ^3.5.0 version: 3.5.12(typescript@5.5.4) vue-component-type-helpers: specifier: 2.1.6 @@ -1210,18 +1210,12 @@ packages: '@vue/compiler-core@3.5.12': resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} - '@vue/compiler-core@3.5.3': - resolution: {integrity: sha512-adAfy9boPkP233NTyvLbGEqVuIfK/R0ZsBsIOW4BZNfb4BRpRW41Do1u+ozJpsb+mdoy80O20IzAsHaihRb5qA==} - '@vue/compiler-dom@3.4.38': resolution: {integrity: sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==} '@vue/compiler-dom@3.5.12': resolution: {integrity: sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==} - '@vue/compiler-dom@3.5.3': - resolution: {integrity: sha512-wnzFArg9zpvk/811CDOZOadJRugf1Bgl/TQ3RfV4nKfSPok4hi0w10ziYUQR6LnnBAUlEXYLUfZ71Oj9ds/+QA==} - '@vue/compiler-sfc@2.7.16': resolution: {integrity: sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==} @@ -1231,18 +1225,12 @@ packages: '@vue/compiler-sfc@3.5.12': resolution: {integrity: sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==} - '@vue/compiler-sfc@3.5.3': - resolution: {integrity: sha512-P3uATLny2tfyvMB04OQFe7Sczteno7SLFxwrOA/dw01pBWQHB5HL15a8PosoNX2aG/EAMGqnXTu+1LnmzFhpTQ==} - '@vue/compiler-ssr@3.4.38': resolution: {integrity: sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==} '@vue/compiler-ssr@3.5.12': resolution: {integrity: sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==} - '@vue/compiler-ssr@3.5.3': - resolution: {integrity: sha512-F/5f+r2WzL/2YAPl7UlKcJWHrvoZN8XwEBLnT7S4BXwncH25iDOabhO2M2DWioyTguJAGavDOawejkFXj8EM1w==} - '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} @@ -1280,9 +1268,6 @@ packages: '@vue/shared@3.5.12': resolution: {integrity: sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==} - '@vue/shared@3.5.3': - resolution: {integrity: sha512-Jp2v8nylKBT+PlOUjun2Wp/f++TfJVFjshLzNtJDdmFJabJa7noGMncqXRM1vXGX+Yo2V7WykQFNxusSim8SCA==} - JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -4513,15 +4498,7 @@ snapshots: '@vue/shared': 3.5.12 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 - - '@vue/compiler-core@3.5.3': - dependencies: - '@babel/parser': 7.25.6 - '@vue/shared': 3.5.3 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 '@vue/compiler-dom@3.4.38': dependencies: @@ -4533,11 +4510,6 @@ snapshots: '@vue/compiler-core': 3.5.12 '@vue/shared': 3.5.12 - '@vue/compiler-dom@3.5.3': - dependencies: - '@vue/compiler-core': 3.5.3 - '@vue/shared': 3.5.3 - '@vue/compiler-sfc@2.7.16': dependencies: '@babel/parser': 7.25.6 @@ -4568,19 +4540,7 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.11 postcss: 8.4.47 - source-map-js: 1.2.0 - - '@vue/compiler-sfc@3.5.3': - dependencies: - '@babel/parser': 7.25.6 - '@vue/compiler-core': 3.5.3 - '@vue/compiler-dom': 3.5.3 - '@vue/compiler-ssr': 3.5.3 - '@vue/shared': 3.5.3 - estree-walker: 2.0.2 - magic-string: 0.30.11 - postcss: 8.4.45 - source-map-js: 1.2.0 + source-map-js: 1.2.1 '@vue/compiler-ssr@3.4.38': dependencies: @@ -4592,11 +4552,6 @@ snapshots: '@vue/compiler-dom': 3.5.12 '@vue/shared': 3.5.12 - '@vue/compiler-ssr@3.5.3': - dependencies: - '@vue/compiler-dom': 3.5.3 - '@vue/shared': 3.5.3 - '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 @@ -4650,8 +4605,6 @@ snapshots: '@vue/shared@3.5.12': {} - '@vue/shared@3.5.3': {} - JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 diff --git a/test-workspace/package.json b/test-workspace/package.json index 74c638bb35..31dbb567f1 100644 --- a/test-workspace/package.json +++ b/test-workspace/package.json @@ -4,7 +4,7 @@ "devDependencies": { "typescript-next": "npm:typescript@next", "typescript-stable": "npm:typescript@latest", - "vue": "^3.5.12", + "vue": "^3.5.0", "vue-component-type-helpers": "2.1.6", "vue2": "npm:vue@2.7.16", "vue3.4": "npm:vue@3.4.38" diff --git a/test-workspace/tsc/passedFixtures/vue2/tsconfig.json b/test-workspace/tsc/passedFixtures/vue2/tsconfig.json index cc86334474..191e683f1e 100644 --- a/test-workspace/tsc/passedFixtures/vue2/tsconfig.json +++ b/test-workspace/tsc/passedFixtures/vue2/tsconfig.json @@ -30,6 +30,7 @@ "../vue3/defineEmits", "../vue3/defineModel", "../vue3/defineProp_B", + "../vue3/directives/arg-modifiers.vue", "../vue3/directives/option.vue", "../vue3/events", "../vue3/no-script-block", diff --git a/test-workspace/tsc/passedFixtures/vue3.4/tsconfig.json b/test-workspace/tsc/passedFixtures/vue3.4/tsconfig.json index 8a5d76fcfa..be049952e7 100644 --- a/test-workspace/tsc/passedFixtures/vue3.4/tsconfig.json +++ b/test-workspace/tsc/passedFixtures/vue3.4/tsconfig.json @@ -18,5 +18,6 @@ "../vue3/components", "../vue3/defineModel", "../vue3/defineProp_B", + "../vue3/directives/arg-modifiers.vue", ], } diff --git a/test-workspace/tsc/passedFixtures/vue3/directives/arg-modifiers.vue b/test-workspace/tsc/passedFixtures/vue3/directives/arg-modifiers.vue new file mode 100644 index 0000000000..e6e59fbcf7 --- /dev/null +++ b/test-workspace/tsc/passedFixtures/vue3/directives/arg-modifiers.vue @@ -0,0 +1,19 @@ + + + diff --git a/test-workspace/tsc/passedFixtures/vue3/directives/main.vue b/test-workspace/tsc/passedFixtures/vue3/directives/main.vue index 05bf31f895..29f5ff6ea4 100644 --- a/test-workspace/tsc/passedFixtures/vue3/directives/main.vue +++ b/test-workspace/tsc/passedFixtures/vue3/directives/main.vue @@ -1,19 +1,19 @@ diff --git a/test-workspace/tsc/passedFixtures/vue3/directives/option.vue b/test-workspace/tsc/passedFixtures/vue3/directives/option.vue index 10878f25f9..25c12e16df 100644 --- a/test-workspace/tsc/passedFixtures/vue3/directives/option.vue +++ b/test-workspace/tsc/passedFixtures/vue3/directives/option.vue @@ -1,12 +1,12 @@ @@ -15,5 +15,5 @@ export default {