Skip to content

Commit 9df70b9

Browse files
committed
feat(language-service): autocomplete for props with union type
close #3698
1 parent 6fd258d commit 9df70b9

File tree

2 files changed

+58
-46
lines changed

2 files changed

+58
-46
lines changed

packages/language-service/lib/plugins/vue-template.ts

+22-15
Original file line numberDiff line numberDiff line change
@@ -312,20 +312,23 @@ export function create(
312312
}
313313

314314
const { attrs, propInfos, events, directives } = tagInfo;
315-
const props = propInfos.map(prop =>
316-
hyphenateTag(prop.name).startsWith('on-vnode-')
317-
? 'onVue:' + prop.name.slice('onVnode'.length)
318-
: prop.name
319-
);
320-
const attributes: html.IAttributeData[] = [];
321315

322-
const propsSet = new Set(props);
316+
for (const prop of propInfos) {
317+
if (hyphenateTag(prop.name).startsWith('on-vnode-')) {
318+
prop.name = 'onVue:' + prop.name.slice('onVnode'.length);
319+
}
320+
}
323321

324-
for (const prop of [...props, ...attrs]) {
322+
const attributes: html.IAttributeData[] = [];
323+
const propsSet = new Set(propInfos.map(prop => prop.name));
325324

326-
const isGlobal = !propsSet.has(prop);
327-
const name = casing.attr === AttrNameCasing.Camel ? prop : hyphenateAttr(prop);
325+
for (const prop of [
326+
...propInfos,
327+
...attrs.map<ComponentPropInfo>(attr => ({ name: attr })),
328+
]) {
328329

330+
const isGlobal = !propsSet.has(prop.name);
331+
const name = casing.attr === AttrNameCasing.Camel ? prop.name : hyphenateAttr(prop.name);
329332
const isEvent = hyphenateAttr(name).startsWith('on-');
330333

331334
if (isEvent) {
@@ -363,6 +366,7 @@ export function create(
363366
{
364367
name: propName,
365368
description: propKey,
369+
valueSet: prop.values?.some(value => typeof value === 'string') ? '__deferred__' : undefined,
366370
},
367371
{
368372
name: ':' + propName,
@@ -371,7 +375,7 @@ export function create(
371375
{
372376
name: 'v-bind:' + propName,
373377
description: propKey,
374-
}
378+
},
375379
);
376380
}
377381
}
@@ -402,10 +406,13 @@ export function create(
402406

403407
const models: [boolean, string][] = [];
404408

405-
for (const prop of [...props, ...attrs]) {
406-
if (prop.startsWith('onUpdate:')) {
407-
const isGlobal = !propsSet.has(prop);
408-
models.push([isGlobal, prop.slice('onUpdate:'.length)]);
409+
for (const prop of [
410+
...propInfos,
411+
...attrs.map(attr => ({ name: attr })),
412+
]) {
413+
if (prop.name.startsWith('onUpdate:')) {
414+
const isGlobal = !propsSet.has(prop.name);
415+
models.push([isGlobal, prop.name.slice('onUpdate:'.length)]);
409416
}
410417
}
411418
for (const event of events) {

packages/typescript-plugin/lib/requests/getComponentProps.ts

+36-31
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ export function getComponentProps(
2222
return;
2323
}
2424
const vueCode = volarFile.generated.root;
25-
const program = languageService.getProgram()!;
26-
const checker = program.getTypeChecker();
2725
const components = getVariableType(ts, languageService, vueCode, '__VLS_components');
2826
if (!components) {
2927
return [];
@@ -35,26 +33,16 @@ export function getComponentProps(
3533
}
3634

3735
const result = new Map<string, ComponentPropInfo>();
36+
const program = languageService.getProgram()!;
37+
const checker = program.getTypeChecker();
3838

3939
for (const sig of componentType.getCallSignatures()) {
4040
const propParam = sig.parameters[0];
4141
if (propParam) {
4242
const propsType = checker.getTypeOfSymbolAtLocation(propParam, components.node);
4343
const props = propsType.getProperties();
4444
for (const prop of props) {
45-
const name = prop.name;
46-
const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined;
47-
const {
48-
content: commentMarkdown,
49-
deprecated
50-
} = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags());
51-
52-
result.set(name, {
53-
name,
54-
required,
55-
deprecated,
56-
commentMarkdown
57-
});
45+
handlePropSymbol(prop);
5846
}
5947
}
6048
}
@@ -66,27 +54,44 @@ export function getComponentProps(
6654
const propsType = checker.getTypeOfSymbolAtLocation(propsSymbol, components.node);
6755
const props = propsType.getProperties();
6856
for (const prop of props) {
69-
if (prop.flags & ts.SymbolFlags.Method) { // #2443
70-
continue;
71-
}
72-
const name = prop.name;
73-
const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined;
74-
const {
75-
content: commentMarkdown,
76-
deprecated
77-
} = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags());
78-
79-
result.set(name, {
80-
name,
81-
required,
82-
deprecated,
83-
commentMarkdown
84-
});
57+
handlePropSymbol(prop);
8558
}
8659
}
8760
}
8861

8962
return [...result.values()];
63+
64+
function handlePropSymbol(prop: ts.Symbol) {
65+
if (prop.flags & ts.SymbolFlags.Method) { // #2443
66+
return;
67+
}
68+
const name = prop.name;
69+
const required = !(prop.flags & ts.SymbolFlags.Optional) || undefined;
70+
const {
71+
content: commentMarkdown,
72+
deprecated,
73+
} = generateCommentMarkdown(prop.getDocumentationComment(checker), prop.getJsDocTags());
74+
const values: any[] = [];
75+
const type = checker.getTypeOfSymbol(prop);
76+
const subTypes: ts.Type[] | undefined = (type as any).types;
77+
78+
if (subTypes) {
79+
for (const subType of subTypes) {
80+
const value = (subType as any).value;
81+
if (value) {
82+
values.push(value);
83+
}
84+
}
85+
}
86+
87+
result.set(name, {
88+
name,
89+
required,
90+
deprecated,
91+
commentMarkdown,
92+
values,
93+
});
94+
}
9095
}
9196

9297
function generateCommentMarkdown(parts: ts.SymbolDisplayPart[], jsDocTags: ts.JSDocTagInfo[]) {

0 commit comments

Comments
 (0)