mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2025-08-14 03:03:20 +08:00
feat(resolve-type): support infer generics (#766)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
@ -84,8 +84,17 @@ const plugin: (
|
||||
node.arguments.push(options);
|
||||
}
|
||||
|
||||
node.arguments[1] = processProps(comp, options) || options;
|
||||
node.arguments[1] = processEmits(comp, node.arguments[1]) || options;
|
||||
let propsGenerics: BabelCore.types.TSType | undefined;
|
||||
let emitsGenerics: BabelCore.types.TSType | undefined;
|
||||
if (node.typeParameters && node.typeParameters.params.length > 0) {
|
||||
propsGenerics = node.typeParameters.params[0];
|
||||
emitsGenerics = node.typeParameters.params[1];
|
||||
}
|
||||
|
||||
node.arguments[1] =
|
||||
processProps(comp, propsGenerics, options) || options;
|
||||
node.arguments[1] =
|
||||
processEmits(comp, emitsGenerics, node.arguments[1]) || options;
|
||||
},
|
||||
VariableDeclarator(path) {
|
||||
inferComponentName(path);
|
||||
@ -125,6 +134,7 @@ const plugin: (
|
||||
|
||||
function processProps(
|
||||
comp: BabelCore.types.Function,
|
||||
generics: BabelCore.types.TSType | undefined,
|
||||
options:
|
||||
| BabelCore.types.ArgumentPlaceholder
|
||||
| BabelCore.types.SpreadElement
|
||||
@ -134,10 +144,18 @@ const plugin: (
|
||||
if (!props) return;
|
||||
|
||||
if (props.type === 'AssignmentPattern') {
|
||||
ctx!.propsTypeDecl = getTypeAnnotation(props.left);
|
||||
if (generics) {
|
||||
ctx!.propsTypeDecl = resolveTypeReference(generics);
|
||||
} else {
|
||||
ctx!.propsTypeDecl = getTypeAnnotation(props.left);
|
||||
}
|
||||
ctx!.propsRuntimeDefaults = props.right;
|
||||
} else {
|
||||
ctx!.propsTypeDecl = getTypeAnnotation(props);
|
||||
if (generics) {
|
||||
ctx!.propsTypeDecl = resolveTypeReference(generics);
|
||||
} else {
|
||||
ctx!.propsTypeDecl = getTypeAnnotation(props);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx!.propsTypeDecl) return;
|
||||
@ -157,20 +175,26 @@ const plugin: (
|
||||
|
||||
function processEmits(
|
||||
comp: BabelCore.types.Function,
|
||||
generics: BabelCore.types.TSType | undefined,
|
||||
options:
|
||||
| BabelCore.types.ArgumentPlaceholder
|
||||
| BabelCore.types.SpreadElement
|
||||
| BabelCore.types.Expression
|
||||
) {
|
||||
let emitType: BabelCore.types.Node | undefined;
|
||||
if (generics) {
|
||||
emitType = resolveTypeReference(generics);
|
||||
}
|
||||
|
||||
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
|
||||
if (
|
||||
!setupCtx ||
|
||||
!t.isTSTypeReference(setupCtx) ||
|
||||
!t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
|
||||
)
|
||||
return;
|
||||
|
||||
const emitType = setupCtx.typeParameters?.params[0];
|
||||
!emitType &&
|
||||
setupCtx &&
|
||||
t.isTSTypeReference(setupCtx) &&
|
||||
t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
|
||||
) {
|
||||
emitType = setupCtx.typeParameters?.params[0];
|
||||
}
|
||||
if (!emitType) return;
|
||||
|
||||
ctx!.emitsTypeDecl = emitType;
|
||||
@ -185,8 +209,84 @@ const plugin: (
|
||||
t.objectProperty(t.identifier('emits'), ast)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function resolveTypeReference(typeNode: BabelCore.types.TSType) {
|
||||
if (!ctx) return;
|
||||
|
||||
if (t.isTSTypeReference(typeNode)) {
|
||||
const typeName = getTypeReferenceName(typeNode);
|
||||
if (typeName) {
|
||||
const typeDeclaration = findTypeDeclaration(typeName);
|
||||
if (typeDeclaration) {
|
||||
return typeDeclaration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function getTypeReferenceName(typeRef: BabelCore.types.TSTypeReference) {
|
||||
if (t.isIdentifier(typeRef.typeName)) {
|
||||
return typeRef.typeName.name;
|
||||
} else if (t.isTSQualifiedName(typeRef.typeName)) {
|
||||
const parts: string[] = [];
|
||||
let current: BabelCore.types.TSEntityName = typeRef.typeName;
|
||||
|
||||
while (t.isTSQualifiedName(current)) {
|
||||
if (t.isIdentifier(current.right)) {
|
||||
parts.unshift(current.right.name);
|
||||
}
|
||||
current = current.left;
|
||||
}
|
||||
|
||||
if (t.isIdentifier(current)) {
|
||||
parts.unshift(current.name);
|
||||
}
|
||||
|
||||
return parts.join('.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findTypeDeclaration(typeName: string) {
|
||||
if (!ctx) return null;
|
||||
|
||||
for (const statement of ctx.ast) {
|
||||
if (
|
||||
t.isTSInterfaceDeclaration(statement) &&
|
||||
statement.id.name === typeName
|
||||
) {
|
||||
return t.tsTypeLiteral(statement.body.body);
|
||||
}
|
||||
|
||||
if (
|
||||
t.isTSTypeAliasDeclaration(statement) &&
|
||||
statement.id.name === typeName
|
||||
) {
|
||||
return statement.typeAnnotation;
|
||||
}
|
||||
|
||||
if (t.isExportNamedDeclaration(statement) && statement.declaration) {
|
||||
if (
|
||||
t.isTSInterfaceDeclaration(statement.declaration) &&
|
||||
statement.declaration.id.name === typeName
|
||||
) {
|
||||
return t.tsTypeLiteral(statement.declaration.body.body);
|
||||
}
|
||||
|
||||
if (
|
||||
t.isTSTypeAliasDeclaration(statement.declaration) &&
|
||||
statement.declaration.id.name === typeName
|
||||
) {
|
||||
return statement.declaration.typeAnnotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
export default plugin;
|
||||
|
||||
function getTypeAnnotation(node: BabelCore.types.Node) {
|
||||
|
@ -83,6 +83,22 @@ defineComponent((props, {
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`resolve type > runtime emits > with generic emit type 1`] = `
|
||||
"import { type SetupContext, defineComponent } from 'vue';
|
||||
type EmitEvents = {
|
||||
change(val: string): void;
|
||||
click(): void;
|
||||
};
|
||||
defineComponent<{}, EmitEvents>((props, {
|
||||
emit
|
||||
}) => {
|
||||
emit('change');
|
||||
return () => {};
|
||||
}, {
|
||||
emits: ["change", "click"]
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`resolve type > runtime props > basic 1`] = `
|
||||
"import { defineComponent, h } from 'vue';
|
||||
interface Props {
|
||||
@ -129,6 +145,28 @@ defineComponent((props: {
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`resolve type > runtime props > with generic 1`] = `
|
||||
"import { defineComponent, h } from 'vue';
|
||||
interface Props {
|
||||
msg: string;
|
||||
optional?: boolean;
|
||||
}
|
||||
defineComponent<Props>(props => {
|
||||
return () => h('div', props.msg);
|
||||
}, {
|
||||
props: {
|
||||
msg: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
optional: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
}
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`resolve type > runtime props > with static default value 1`] = `
|
||||
"import { defineComponent, h } from 'vue';
|
||||
defineComponent((props: {
|
||||
@ -148,6 +186,31 @@ defineComponent((props: {
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`resolve type > runtime props > with static default value and generic 1`] = `
|
||||
"import { defineComponent, h } from 'vue';
|
||||
type Props = {
|
||||
msg: string;
|
||||
optional?: boolean;
|
||||
};
|
||||
defineComponent<Props>((props = {
|
||||
msg: 'hello'
|
||||
}) => {
|
||||
return () => h('div', props.msg);
|
||||
}, {
|
||||
props: {
|
||||
msg: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'hello'
|
||||
},
|
||||
optional: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
}
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`resolve type > w/ tsx 1`] = `
|
||||
"import { type SetupContext, defineComponent } from 'vue';
|
||||
defineComponent(() => {
|
||||
|
@ -31,6 +31,38 @@ describe('resolve type', () => {
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('with generic', async () => {
|
||||
const result = await transform(
|
||||
`
|
||||
import { defineComponent, h } from 'vue';
|
||||
interface Props {
|
||||
msg: string;
|
||||
optional?: boolean;
|
||||
}
|
||||
defineComponent<Props>((props) => {
|
||||
return () => h('div', props.msg);
|
||||
})
|
||||
`
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('with static default value and generic', async () => {
|
||||
const result = await transform(
|
||||
`
|
||||
import { defineComponent, h } from 'vue';
|
||||
type Props = {
|
||||
msg: string;
|
||||
optional?: boolean;
|
||||
};
|
||||
defineComponent<Props>((props = { msg: 'hello' }) => {
|
||||
return () => h('div', props.msg);
|
||||
})
|
||||
`
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('with static default value', async () => {
|
||||
const result = await transform(
|
||||
`
|
||||
@ -75,6 +107,25 @@ describe('resolve type', () => {
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('with generic emit type', async () => {
|
||||
const result = await transform(
|
||||
`
|
||||
import { type SetupContext, defineComponent } from 'vue';
|
||||
type EmitEvents = {
|
||||
change(val: string): void;
|
||||
click(): void;
|
||||
};
|
||||
defineComponent<{}, EmitEvents>(
|
||||
(props, { emit }) => {
|
||||
emit('change');
|
||||
return () => {};
|
||||
}
|
||||
);
|
||||
`
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('w/ tsx', async () => {
|
||||
|
Reference in New Issue
Block a user