Skip to content

Commit 6bd7c03

Browse files
authored
feat(language-core): infer prop JSDoc from defineModel's leading comments (#5211)
1 parent 07a8fce commit 6bd7c03

File tree

6 files changed

+87
-42
lines changed

6 files changed

+87
-42
lines changed

Diff for: packages/component-meta/tests/index.spec.ts

+20-26
Original file line numberDiff line numberDiff line change
@@ -32,47 +32,41 @@ const worker = (checker: ComponentMetaChecker, withTsconfig: boolean) => describ
3232
const onUpdateFoo = meta.events.find(event => event.name === 'update:foo');
3333

3434
const bar = meta.props.find(prop => prop.name === 'bar');
35-
const onUpdateBar = meta.events.find(event => event.name === 'update:bar');
36-
37-
const baz = meta.props.find(prop => prop.name === 'baz');
38-
const bazModifiers = meta.props.find(prop => prop.name === 'bazModifiers');
39-
const onUpdateBaz = meta.events.find(event => event.name === 'update:baz');
35+
const barModifiers = meta.props.find(prop => prop.name === 'barModifiers');
36+
const onUpdateBaz = meta.events.find(event => event.name === 'update:bar');
4037

4138
expect(modelValue).toBeDefined();
4239
expect(modelValue?.default).toBeUndefined();
43-
expect(modelValue?.required).toBeFalsy();
44-
expect(modelValue?.type).toEqual('number | undefined');
45-
expect(modelValue?.schema).toEqual({
46-
kind: 'enum',
47-
type: 'number | undefined',
48-
schema: ['undefined', 'number'],
49-
});
40+
expect(modelValue?.required).toBeTruthy();
41+
expect(modelValue?.type).toEqual('number');
42+
expect(modelValue?.description).toEqual('required number modelValue');
43+
expect(modelValue?.schema).toEqual('number');
5044
expect(onUpdateModelValue).toBeDefined();
5145

5246
expect(foo).toBeDefined();
53-
expect(foo?.default).toBeUndefined();
54-
expect(foo?.required).toBeTruthy();
55-
expect(foo?.type).toEqual('string[]');
47+
expect(foo?.default).toBe('false');
48+
expect(foo?.required).toBeFalsy();
49+
expect(foo?.type).toEqual('boolean | undefined');
50+
expect(foo?.description).toEqual('optional boolean foo with default false');
5651
expect(foo?.schema).toEqual({
57-
kind: 'array',
58-
type: 'string[]',
59-
schema: ['string'],
52+
kind: 'enum',
53+
type: 'boolean | undefined',
54+
schema: ['undefined', 'false', 'true'],
6055
});
6156
expect(onUpdateFoo).toBeDefined();
6257

6358
expect(bar).toBeDefined();
64-
expect(bar?.default).toBe('false');
59+
expect(bar?.default).toBeUndefined();
6560
expect(bar?.required).toBeFalsy();
66-
expect(bar?.type).toEqual('boolean | undefined');
61+
expect(bar?.type).toEqual('string | undefined');
62+
expect(bar?.description).toEqual('optional string bar with lazy and trim modifiers');
6763
expect(bar?.schema).toEqual({
6864
kind: 'enum',
69-
type: 'boolean | undefined',
70-
schema: ['undefined', 'false', 'true'],
65+
type: 'string | undefined',
66+
schema: ['undefined', 'string'],
7167
});
72-
expect(onUpdateBar).toBeDefined();
73-
74-
expect(baz).toBeDefined();
75-
expect(bazModifiers).toBeDefined();
68+
// TODO: The types of modifiers are inconsistent in the two running results
69+
expect(barModifiers).toBeDefined();
7670
expect(onUpdateBaz).toBeDefined();
7771
});
7872

Diff for: packages/language-core/lib/codegen/script/scriptSetup.ts

+13
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ function* generateSetupFunction(
103103
ctx.scriptSetupGeneratedOffset = options.getGeneratedLength() - scriptSetupRanges.importSectionEndOffset;
104104

105105
let setupCodeModifies: [Code[], number, number][] = [];
106+
for (const { comments } of scriptSetupRanges.defineProp) {
107+
if (comments) {
108+
setupCodeModifies.push([
109+
[``],
110+
comments.start,
111+
comments.end,
112+
]);
113+
}
114+
}
106115
if (scriptSetupRanges.defineProps) {
107116
const { name, statement, callExp, typeArg } = scriptSetupRanges.defineProps;
108117
setupCodeModifies.push(...generateDefineWithType(
@@ -491,6 +500,10 @@ function* generateComponentProps(
491500
for (const defineProp of scriptSetupRanges.defineProp) {
492501
const [propName, localName] = getPropAndLocalName(scriptSetup, defineProp);
493502

503+
if (defineProp.comments) {
504+
yield generateSfcBlockSection(scriptSetup, defineProp.comments.start, defineProp.comments.end, codeFeatures.all);
505+
yield newLine;
506+
}
494507
if (defineProp.isModel && !defineProp.name) {
495508
yield propName!;
496509
}

Diff for: packages/language-core/lib/codegen/template/elementProps.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export function* generateElementProps(
135135
: wrapWith(
136136
prop.loc.start.offset,
137137
prop.loc.start.offset + 'v-model'.length,
138-
ctx.codeFeatures.verification,
138+
ctx.codeFeatures.withoutHighlightAndCompletion,
139139
propName
140140
)
141141
),

Diff for: packages/language-core/lib/parsers/scriptSetupRanges.ts

+24
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type DefineProp = {
2020
defaultValue?: TextRange;
2121
required?: boolean;
2222
isModel?: boolean;
23+
comments?: TextRange;
2324
// used by component-meta
2425
argNode?: ts.Expression;
2526
};
@@ -206,6 +207,7 @@ export function parseScriptSetupRanges(
206207
defaultValue,
207208
required,
208209
isModel: true,
210+
comments: getCommentsRange(ts, node, parents, ast),
209211
argNode: options,
210212
});
211213
}
@@ -283,6 +285,7 @@ export function parseScriptSetupRanges(
283285
runtimeType,
284286
defaultValue,
285287
required,
288+
comments: getCommentsRange(ts, node, parents, ast),
286289
argNode: options,
287290
});
288291
}
@@ -589,3 +592,24 @@ function getStatementRange(
589592
}
590593
return statementRange;
591594
}
595+
596+
function getCommentsRange(
597+
ts: typeof import('typescript'),
598+
node: ts.Node,
599+
parents: ts.Node[],
600+
ast: ts.SourceFile
601+
) {
602+
for (let i = parents.length - 1; i >= 0; i--) {
603+
if (ts.isStatement(node)) {
604+
break;
605+
}
606+
node = parents[i];
607+
}
608+
const comments = ts.getLeadingCommentRanges(ast.text, node.pos);
609+
if (comments?.length) {
610+
return {
611+
start: comments[0].pos,
612+
end: comments.at(-1)!.end,
613+
};
614+
}
615+
}

Diff for: packages/tsc/tests/__snapshots__/dts.spec.ts.snap

+17-11
Original file line numberDiff line numberDiff line change
@@ -299,22 +299,28 @@ export default _default;
299299
300300
exports[`vue-tsc-dts > Input: reference-type-model/component.vue, Output: reference-type-model/component.vue.d.ts 1`] = `
301301
"type __VLS_PublicProps = {
302-
modelValue?: number;
303-
'foo': string[];
304-
'bar'?: boolean;
305-
'baz'?: string;
306-
'bazModifiers'?: Partial<Record<'lazy' | 'trim', true>>;
302+
/**
303+
* required number modelValue
304+
*/
305+
modelValue: number;
306+
/**
307+
* optional boolean foo with default false
308+
*/
309+
'foo'?: boolean;
310+
/**
311+
* optional string bar with lazy and trim modifiers
312+
*/
313+
'bar'?: string;
314+
'barModifiers'?: Partial<Record<'lazy' | 'trim', true>>;
307315
};
308316
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
309317
"update:modelValue": (value: number) => any;
310-
"update:foo": (value: string[]) => any;
311-
"update:bar": (value: boolean) => any;
312-
"update:baz": (value: string) => any;
318+
"update:foo": (value: boolean) => any;
319+
"update:bar": (value: string) => any;
313320
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
314321
"onUpdate:modelValue"?: (value: number) => any;
315-
"onUpdate:foo"?: (value: string[]) => any;
316-
"onUpdate:bar"?: (value: boolean) => any;
317-
"onUpdate:baz"?: (value: string) => any;
322+
"onUpdate:foo"?: (value: boolean) => any;
323+
"onUpdate:bar"?: (value: string) => any;
318324
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
319325
export default _default;
320326
"
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
<script setup lang="ts">
2-
defineModel<number>();
3-
defineModel<string[]>('foo', {
2+
/**
3+
* required number modelValue
4+
*/
5+
defineModel<number>({
46
required: true,
57
});
6-
defineModel<boolean>('bar', {
8+
/**
9+
* optional boolean foo with default false
10+
*/
11+
defineModel<boolean>('foo', {
712
default: false,
813
});
9-
defineModel<string, 'lazy' | 'trim'>('baz');
14+
/**
15+
* optional string bar with lazy and trim modifiers
16+
*/
17+
defineModel<string, 'lazy' | 'trim'>('bar');
1018
</script>

0 commit comments

Comments
 (0)