Skip to content

Commit 516685b

Browse files
committed
feat: infer component name
1 parent aa95451 commit 516685b

File tree

3 files changed

+283
-72
lines changed

3 files changed

+283
-72
lines changed

packages/babel-plugin-resolve-type/src/index.ts

Lines changed: 132 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,71 +11,12 @@ import {
1111
import { codeFrameColumns } from '@babel/code-frame';
1212
import { addNamed } from '@babel/helper-module-imports';
1313

14-
function getTypeAnnotation(node: BabelCore.types.Node) {
15-
if (
16-
'typeAnnotation' in node &&
17-
node.typeAnnotation &&
18-
node.typeAnnotation.type === 'TSTypeAnnotation'
19-
) {
20-
return node.typeAnnotation.typeAnnotation;
21-
}
22-
}
23-
2414
export default ({
2515
types: t,
2616
}: typeof BabelCore): BabelCore.PluginObj<SimpleTypeResolveOptions> => {
2717
let ctx: SimpleTypeResolveContext | undefined;
2818
let helpers: Set<string> | undefined;
2919

30-
function processProps(
31-
comp: BabelCore.types.Function,
32-
options: BabelCore.types.ObjectExpression
33-
) {
34-
const props = comp.params[0];
35-
if (!props) return;
36-
37-
if (props.type === 'AssignmentPattern') {
38-
ctx!.propsTypeDecl = getTypeAnnotation(props.left);
39-
ctx!.propsRuntimeDefaults = props.right;
40-
} else {
41-
ctx!.propsTypeDecl = getTypeAnnotation(props);
42-
}
43-
44-
if (!ctx!.propsTypeDecl) return;
45-
46-
const runtimeProps = extractRuntimeProps(ctx!);
47-
if (!runtimeProps) {
48-
return;
49-
}
50-
51-
const ast = parseExpression(runtimeProps);
52-
options.properties.push(t.objectProperty(t.identifier('props'), ast));
53-
}
54-
55-
function processEmits(
56-
comp: BabelCore.types.Function,
57-
options: BabelCore.types.ObjectExpression
58-
) {
59-
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
60-
if (
61-
!setupCtx ||
62-
!t.isTSTypeReference(setupCtx) ||
63-
!t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
64-
)
65-
return;
66-
67-
const emitType = setupCtx.typeParameters?.params[0];
68-
if (!emitType) return;
69-
70-
ctx!.emitsTypeDecl = emitType;
71-
const runtimeEmits = extractRuntimeEmits(ctx!);
72-
73-
const ast = t.arrayExpression(
74-
Array.from(runtimeEmits).map((e) => t.stringLiteral(e))
75-
);
76-
options.properties.push(t.objectProperty(t.identifier('emits'), ast));
77-
}
78-
7920
return {
8021
name: 'babel-plugin-resolve-type',
8122
inherits: typescript,
@@ -125,8 +66,10 @@ export default ({
12566
);
12667
}
12768

128-
const node = path.node;
69+
const { node } = path;
70+
12971
if (!t.isIdentifier(node.callee, { name: 'defineComponent' })) return;
72+
if (!checkDefineComponent(path)) return;
13073

13174
const comp = node.arguments[0];
13275
if (!comp || !t.isFunction(comp)) return;
@@ -137,14 +80,11 @@ export default ({
13780
node.arguments.push(options);
13881
}
13982

140-
if (!t.isObjectExpression(options)) {
141-
throw new Error(
142-
'[@vue/babel-plugin-resolve-type] Options inside of defineComponent should be an object expression.'
143-
);
144-
}
145-
146-
processProps(comp, options);
147-
processEmits(comp, options);
83+
node.arguments[1] = processProps(comp, options) || options;
84+
node.arguments[1] = processEmits(comp, node.arguments[1]) || options;
85+
},
86+
VariableDeclarator(path) {
87+
inferComponentName(path);
14888
},
14989
},
15090
post(file) {
@@ -153,4 +93,128 @@ export default ({
15393
}
15494
},
15595
};
96+
97+
function inferComponentName(
98+
path: BabelCore.NodePath<BabelCore.types.VariableDeclarator>
99+
) {
100+
const id = path.get('id');
101+
const init = path.get('init');
102+
if (!id || !id.isIdentifier() || !init || !init.isCallExpression()) return;
103+
104+
if (!init.get('callee')?.isIdentifier({ name: 'defineComponent' })) return;
105+
if (!checkDefineComponent(init)) return;
106+
107+
const nameProperty = t.objectProperty(
108+
t.identifier('name'),
109+
t.stringLiteral(id.node.name)
110+
);
111+
const { arguments: args } = init.node;
112+
if (args.length === 0) return;
113+
114+
if (args.length === 1) {
115+
init.node.arguments.push(t.objectExpression([]));
116+
}
117+
args[1] = addProperty(t, args[1], nameProperty);
118+
}
119+
120+
function processProps(
121+
comp: BabelCore.types.Function,
122+
options:
123+
| BabelCore.types.ArgumentPlaceholder
124+
| BabelCore.types.JSXNamespacedName
125+
| BabelCore.types.SpreadElement
126+
| BabelCore.types.Expression
127+
) {
128+
const props = comp.params[0];
129+
if (!props) return;
130+
131+
if (props.type === 'AssignmentPattern') {
132+
ctx!.propsTypeDecl = getTypeAnnotation(props.left);
133+
ctx!.propsRuntimeDefaults = props.right;
134+
} else {
135+
ctx!.propsTypeDecl = getTypeAnnotation(props);
136+
}
137+
138+
if (!ctx!.propsTypeDecl) return;
139+
140+
const runtimeProps = extractRuntimeProps(ctx!);
141+
if (!runtimeProps) {
142+
return;
143+
}
144+
145+
const ast = parseExpression(runtimeProps);
146+
return addProperty(
147+
t,
148+
options,
149+
t.objectProperty(t.identifier('props'), ast)
150+
);
151+
}
152+
153+
function processEmits(
154+
comp: BabelCore.types.Function,
155+
options:
156+
| BabelCore.types.ArgumentPlaceholder
157+
| BabelCore.types.JSXNamespacedName
158+
| BabelCore.types.SpreadElement
159+
| BabelCore.types.Expression
160+
) {
161+
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
162+
if (
163+
!setupCtx ||
164+
!t.isTSTypeReference(setupCtx) ||
165+
!t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
166+
)
167+
return;
168+
169+
const emitType = setupCtx.typeParameters?.params[0];
170+
if (!emitType) return;
171+
172+
ctx!.emitsTypeDecl = emitType;
173+
const runtimeEmits = extractRuntimeEmits(ctx!);
174+
175+
const ast = t.arrayExpression(
176+
Array.from(runtimeEmits).map((e) => t.stringLiteral(e))
177+
);
178+
return addProperty(
179+
t,
180+
options,
181+
t.objectProperty(t.identifier('emits'), ast)
182+
);
183+
}
156184
};
185+
186+
function getTypeAnnotation(node: BabelCore.types.Node) {
187+
if (
188+
'typeAnnotation' in node &&
189+
node.typeAnnotation &&
190+
node.typeAnnotation.type === 'TSTypeAnnotation'
191+
) {
192+
return node.typeAnnotation.typeAnnotation;
193+
}
194+
}
195+
196+
function checkDefineComponent(
197+
path: BabelCore.NodePath<BabelCore.types.CallExpression>
198+
) {
199+
const defineCompImport =
200+
path.scope.getBinding('defineComponent')?.path.parent;
201+
if (!defineCompImport) return true;
202+
203+
return (
204+
defineCompImport.type === 'ImportDeclaration' &&
205+
/^@?vue(\/|$)/.test(defineCompImport.source.value)
206+
);
207+
}
208+
209+
function addProperty<T extends BabelCore.types.Node>(
210+
t: (typeof BabelCore)['types'],
211+
object: T,
212+
property: BabelCore.types.ObjectProperty
213+
) {
214+
if (t.isObjectExpression(object)) {
215+
object.properties.unshift(property);
216+
} else if (t.isExpression(object)) {
217+
return t.objectExpression([property, t.spreadElement(object)]);
218+
}
219+
return object;
220+
}

packages/babel-plugin-resolve-type/test/__snapshots__/resolve-type.test.tsx.snap

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,76 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`resolve type > defineComponent scope > fake 1`] = `
4+
"const defineComponent = () => {};
5+
defineComponent((props: {
6+
msg?: string;
7+
}) => {
8+
return () => <div />;
9+
});"
10+
`;
11+
12+
exports[`resolve type > defineComponent scope > import sub-package 1`] = `
13+
"import { defineComponent } from 'vue/dist/vue.esm-bundler';
14+
defineComponent((props: {
15+
msg?: string;
16+
}) => {
17+
return () => <div />;
18+
}, {
19+
props: {
20+
msg: {
21+
type: String,
22+
required: false
23+
}
24+
}
25+
});"
26+
`;
27+
28+
exports[`resolve type > defineComponent scope > w/o import 1`] = `
29+
"defineComponent((props: {
30+
msg?: string;
31+
}) => {
32+
return () => <div />;
33+
}, {
34+
props: {
35+
msg: {
36+
type: String,
37+
required: false
38+
}
39+
}
40+
});"
41+
`;
42+
43+
exports[`resolve type > infer component name > identifier options 1`] = `
44+
"import { defineComponent } from 'vue';
45+
const Foo = defineComponent(() => {}, {
46+
name: \\"Foo\\",
47+
...opts
48+
});"
49+
`;
50+
51+
exports[`resolve type > infer component name > no options 1`] = `
52+
"import { defineComponent } from 'vue';
53+
const Foo = defineComponent(() => {}, {
54+
name: \\"Foo\\"
55+
});"
56+
`;
57+
58+
exports[`resolve type > infer component name > object options 1`] = `
59+
"import { defineComponent } from 'vue';
60+
const Foo = defineComponent(() => {}, {
61+
name: \\"Foo\\",
62+
foo: 'bar'
63+
});"
64+
`;
65+
66+
exports[`resolve type > infer component name > rest param 1`] = `
67+
"import { defineComponent } from 'vue';
68+
const Foo = defineComponent(() => {}, ...args);"
69+
`;
70+
371
exports[`resolve type > runtime emits > basic 1`] = `
472
"import { type SetupContext, defineComponent } from 'vue';
5-
const Comp = defineComponent((props, {
73+
defineComponent((props, {
674
emit
775
}: SetupContext<{
876
change(val: string): void;
@@ -83,7 +151,7 @@ defineComponent((props: {
83151
84152
exports[`resolve type > w/ tsx 1`] = `
85153
"import { type SetupContext, defineComponent } from 'vue';
86-
const Comp = defineComponent(() => {
154+
defineComponent(() => {
87155
return () => <div />;
88156
}, {});"
89157
`;

0 commit comments

Comments
 (0)