Skip to content

Commit 1e04fc9

Browse files
feat(language-core): typed directive arg and modifiers (#4813)
Co-authored-by: Johnson Chu <[email protected]>
1 parent acddb02 commit 1e04fc9

File tree

13 files changed

+227
-154
lines changed

13 files changed

+227
-154
lines changed

packages/language-core/lib/codegen/globalTypes.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,11 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
115115
function __VLS_getSlotParams<T>(slot: T): Parameters<__VLS_PickNotAny<NonNullable<T>, (...args: any[]) => any>>;
116116
// @ts-ignore
117117
function __VLS_getSlotParam<T>(slot: T): Parameters<__VLS_PickNotAny<NonNullable<T>, (...args: any[]) => any>>[0];
118-
function __VLS_directiveAsFunction<T extends import('${lib}').Directive>(dir: T): T extends (...args: any) => any
119-
? T | __VLS_unknownDirective
120-
: NonNullable<(T & Record<string, __VLS_unknownDirective>)['created' | 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated' | 'beforeUnmount' | 'unmounted']>;
118+
function __VLS_asFunctionalDirective<T>(dir: T): T extends import('${lib}').ObjectDirective
119+
? NonNullable<T['created' | 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated' | 'beforeUnmount' | 'unmounted']>
120+
: T extends (...args: any) => any
121+
? T
122+
: __VLS_unknownDirective;
121123
function __VLS_withScope<T, K>(ctx: T, scope: K): ctx is T & K;
122124
function __VLS_makeOptional<T>(t: T): { [K in keyof T]?: T[K] };
123125
function __VLS_nonNullable<T>(t: T): T extends null | undefined ? never : T;

packages/language-core/lib/codegen/template/elementDirectives.ts

+149-74
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { generateCamelized } from './camelized';
77
import type { TemplateCodegenContext } from './context';
88
import type { TemplateCodegenOptions } from './index';
99
import { generateInterpolation } from './interpolation';
10+
import { generateObjectProperty } from './objectProperty';
11+
import { generateStringLiteralKey } from './stringLiteralKey';
1012

1113
export function* generateElementDirectives(
1214
options: TemplateCodegenOptions,
@@ -15,85 +17,158 @@ export function* generateElementDirectives(
1517
): Generator<Code> {
1618
for (const prop of node.props) {
1719
if (
18-
prop.type === CompilerDOM.NodeTypes.DIRECTIVE
19-
&& prop.name !== 'slot'
20-
&& prop.name !== 'on'
21-
&& prop.name !== 'model'
22-
&& prop.name !== 'bind'
23-
&& prop.name !== 'scope'
24-
&& prop.name !== 'data'
20+
prop.type !== CompilerDOM.NodeTypes.DIRECTIVE
21+
|| prop.name === 'slot'
22+
|| prop.name === 'on'
23+
|| prop.name === 'model'
24+
|| prop.name === 'bind'
25+
|| prop.name === 'scope'
26+
|| prop.name === 'data'
2527
) {
26-
ctx.accessExternalVariable(camelize('v-' + prop.name), prop.loc.start.offset);
28+
continue;
29+
}
30+
ctx.accessExternalVariable(camelize('v-' + prop.name), prop.loc.start.offset);
31+
32+
yield* wrapWith(
33+
prop.loc.start.offset,
34+
prop.loc.end.offset,
35+
ctx.codeFeatures.verification,
36+
`__VLS_asFunctionalDirective(`,
37+
...generateIdentifier(ctx, prop),
38+
`)(null!, { ...__VLS_directiveBindingRestFields, `,
39+
...generateArg(options, ctx, prop),
40+
...generateModifiers(options, ctx, prop),
41+
...generateValue(options, ctx, prop),
42+
`}, null!, null!)`
43+
);
44+
yield endOfLine;
45+
}
46+
}
2747

28-
if (prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && !prop.arg.isStatic) {
29-
yield* generateInterpolation(
30-
options,
31-
ctx,
32-
prop.arg.content,
33-
prop.arg.loc,
34-
prop.arg.loc.start.offset + prop.arg.loc.source.indexOf(prop.arg.content),
35-
ctx.codeFeatures.all,
36-
'(',
37-
')'
38-
);
39-
yield endOfLine;
48+
function* generateIdentifier(
49+
ctx: TemplateCodegenContext,
50+
prop: CompilerDOM.DirectiveNode
51+
): Generator<Code> {
52+
const rawName = 'v-' + prop.name;
53+
yield* wrapWith(
54+
prop.loc.start.offset,
55+
prop.loc.start.offset + rawName.length,
56+
ctx.codeFeatures.verification,
57+
`__VLS_directives.`,
58+
...generateCamelized(
59+
rawName,
60+
prop.loc.start.offset,
61+
{
62+
...ctx.codeFeatures.all,
63+
verification: false,
64+
completion: {
65+
// fix https://github.com/vuejs/language-tools/issues/1905
66+
isAdditional: true,
67+
},
68+
navigation: {
69+
resolveRenameNewName: camelize,
70+
resolveRenameEditText: getPropRenameApply(prop.name),
71+
},
4072
}
73+
)
74+
);
75+
}
4176

42-
yield* wrapWith(
43-
prop.loc.start.offset,
44-
prop.loc.end.offset,
45-
ctx.codeFeatures.verification,
46-
`__VLS_directiveAsFunction(__VLS_directives.`,
47-
...generateCamelized(
48-
'v-' + prop.name,
49-
prop.loc.start.offset,
50-
{
51-
...ctx.codeFeatures.all,
52-
verification: false,
53-
completion: {
54-
// fix https://github.com/vuejs/language-tools/issues/1905
55-
isAdditional: true,
56-
},
57-
navigation: {
58-
resolveRenameNewName: camelize,
59-
resolveRenameEditText: getPropRenameApply(prop.name),
60-
},
61-
}
62-
),
63-
`)(null!, { ...__VLS_directiveBindingRestFields, `,
64-
...(
65-
prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION
66-
? [
67-
...wrapWith(
68-
prop.exp.loc.start.offset,
69-
prop.exp.loc.end.offset,
70-
ctx.codeFeatures.verification,
71-
'value'
72-
),
73-
': ',
74-
...wrapWith(
75-
prop.exp.loc.start.offset,
76-
prop.exp.loc.end.offset,
77-
ctx.codeFeatures.verification,
78-
...generateInterpolation(
79-
options,
80-
ctx,
81-
prop.exp.content,
82-
prop.exp.loc,
83-
prop.exp.loc.start.offset,
84-
ctx.codeFeatures.all,
85-
'(',
86-
')'
87-
)
88-
)
89-
]
90-
: [`undefined`]
91-
),
92-
`}, null!, null!)`
93-
);
94-
yield endOfLine;
95-
}
77+
function* generateArg(
78+
options: TemplateCodegenOptions,
79+
ctx: TemplateCodegenContext,
80+
prop: CompilerDOM.DirectiveNode
81+
): Generator<Code> {
82+
const { arg } = prop;
83+
if (arg?.type !== CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
84+
return;
85+
}
86+
87+
const startOffset = arg.loc.start.offset + arg.loc.source.indexOf(arg.content);
88+
89+
yield* wrapWith(
90+
startOffset,
91+
startOffset + arg.content.length,
92+
ctx.codeFeatures.verification,
93+
'arg'
94+
);
95+
yield ': ';
96+
if (arg.isStatic) {
97+
yield* generateStringLiteralKey(
98+
arg.content,
99+
startOffset,
100+
ctx.codeFeatures.withoutHighlight
101+
);
102+
}
103+
else {
104+
yield* generateInterpolation(
105+
options,
106+
ctx,
107+
arg.content,
108+
arg.loc,
109+
startOffset,
110+
ctx.codeFeatures.all,
111+
'(',
112+
')'
113+
);
96114
}
115+
yield ', ';
116+
}
117+
118+
function* generateModifiers(
119+
options: TemplateCodegenOptions,
120+
ctx: TemplateCodegenContext,
121+
prop: CompilerDOM.DirectiveNode
122+
): Generator<Code> {
123+
if (options.vueCompilerOptions.target < 3.5) {
124+
return;
125+
}
126+
127+
yield 'modifiers: { ';
128+
for (const mod of prop.modifiers) {
129+
yield* generateObjectProperty(
130+
options,
131+
ctx,
132+
mod.content,
133+
mod.loc.start.offset,
134+
ctx.codeFeatures.withoutHighlight
135+
);
136+
yield ': true, ';
137+
}
138+
yield '}, ';
139+
}
140+
141+
function* generateValue(
142+
options: TemplateCodegenOptions,
143+
ctx: TemplateCodegenContext,
144+
prop: CompilerDOM.DirectiveNode
145+
): Generator<Code> {
146+
if (prop.exp?.type !== CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
147+
return;
148+
}
149+
150+
yield* wrapWith(
151+
prop.exp.loc.start.offset,
152+
prop.exp.loc.end.offset,
153+
ctx.codeFeatures.verification,
154+
'value'
155+
);
156+
yield ': ';
157+
yield* wrapWith(
158+
prop.exp.loc.start.offset,
159+
prop.exp.loc.end.offset,
160+
ctx.codeFeatures.verification,
161+
...generateInterpolation(
162+
options,
163+
ctx,
164+
prop.exp.content,
165+
prop.exp.loc,
166+
prop.exp.loc.start.offset,
167+
ctx.codeFeatures.all,
168+
'(',
169+
')'
170+
)
171+
);
97172
}
98173

99174
function getPropRenameApply(oldName: string) {

packages/language-core/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
},
1515
"dependencies": {
1616
"@volar/language-core": "~2.4.1",
17-
"@vue/compiler-dom": "^3.5.2",
17+
"@vue/compiler-dom": "^3.5.0",
1818
"@vue/compiler-vue2": "^2.7.16",
19-
"@vue/shared": "^3.5.2",
19+
"@vue/shared": "^3.5.0",
2020
"alien-signals": "^0.2.0",
2121
"minimatch": "^9.0.3",
2222
"muggle-string": "^0.4.1",
@@ -27,7 +27,7 @@
2727
"@types/node": "latest",
2828
"@types/path-browserify": "^1.0.1",
2929
"@volar/typescript": "~2.4.1",
30-
"@vue/compiler-sfc": "^3.5.2"
30+
"@vue/compiler-sfc": "^3.5.0"
3131
},
3232
"peerDependencies": {
3333
"typescript": "*"

packages/language-server/tests/completions.spec.ts

+22
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,28 @@ describe('Completions', async () => {
200200
await requestCompletionItem('fixture.vue', 'vue', `<template><div v-p|></div></template>`, 'v-pre');
201201
});
202202

203+
// FIXME:
204+
it.skip('Directive Modifiers', async () => {
205+
expect(
206+
(await requestCompletionList('fixture.vue', 'vue', `
207+
<template>
208+
<div v-foo.|></div>
209+
</template>
210+
211+
<script setup lang="ts">
212+
import type { FunctionDirective } from 'vue';
213+
214+
let vFoo!: FunctionDirective<any, any, 'attr' | 'prop'>;
215+
</script>
216+
`)).items.map(item => item.label)
217+
).toMatchInlineSnapshot(`
218+
[
219+
"attr",
220+
"prop"
221+
]
222+
`);
223+
});
224+
203225
it('$event argument', async () => {
204226
await requestCompletionItem('fixture.vue', 'vue', `<template><div @click="console.log($eve|)"></div></template>`, 'event');
205227
});

packages/language-service/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
"@volar/language-core": "~2.4.1",
2121
"@volar/language-service": "~2.4.1",
2222
"@volar/typescript": "~2.4.1",
23-
"@vue/compiler-dom": "^3.4.0",
23+
"@vue/compiler-dom": "^3.5.0",
2424
"@vue/language-core": "2.1.6",
25-
"@vue/shared": "^3.4.0",
25+
"@vue/shared": "^3.5.0",
2626
"@vue/typescript-plugin": "2.1.6",
2727
"alien-signals": "^0.2.0",
2828
"path-browserify": "^1.0.1",

packages/typescript-plugin/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"dependencies": {
1616
"@volar/typescript": "~2.4.1",
1717
"@vue/language-core": "2.1.6",
18-
"@vue/shared": "^3.4.0"
18+
"@vue/shared": "^3.5.0"
1919
},
2020
"devDependencies": {
2121
"@types/node": "latest"

0 commit comments

Comments
 (0)