mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2025-08-14 11:13:19 +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.push(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
node.arguments[1] = processProps(comp, options) || options;
|
let propsGenerics: BabelCore.types.TSType | undefined;
|
||||||
node.arguments[1] = processEmits(comp, node.arguments[1]) || options;
|
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) {
|
VariableDeclarator(path) {
|
||||||
inferComponentName(path);
|
inferComponentName(path);
|
||||||
@ -125,6 +134,7 @@ const plugin: (
|
|||||||
|
|
||||||
function processProps(
|
function processProps(
|
||||||
comp: BabelCore.types.Function,
|
comp: BabelCore.types.Function,
|
||||||
|
generics: BabelCore.types.TSType | undefined,
|
||||||
options:
|
options:
|
||||||
| BabelCore.types.ArgumentPlaceholder
|
| BabelCore.types.ArgumentPlaceholder
|
||||||
| BabelCore.types.SpreadElement
|
| BabelCore.types.SpreadElement
|
||||||
@ -134,10 +144,18 @@ const plugin: (
|
|||||||
if (!props) return;
|
if (!props) return;
|
||||||
|
|
||||||
if (props.type === 'AssignmentPattern') {
|
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;
|
ctx!.propsRuntimeDefaults = props.right;
|
||||||
} else {
|
} else {
|
||||||
ctx!.propsTypeDecl = getTypeAnnotation(props);
|
if (generics) {
|
||||||
|
ctx!.propsTypeDecl = resolveTypeReference(generics);
|
||||||
|
} else {
|
||||||
|
ctx!.propsTypeDecl = getTypeAnnotation(props);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx!.propsTypeDecl) return;
|
if (!ctx!.propsTypeDecl) return;
|
||||||
@ -157,20 +175,26 @@ const plugin: (
|
|||||||
|
|
||||||
function processEmits(
|
function processEmits(
|
||||||
comp: BabelCore.types.Function,
|
comp: BabelCore.types.Function,
|
||||||
|
generics: BabelCore.types.TSType | undefined,
|
||||||
options:
|
options:
|
||||||
| BabelCore.types.ArgumentPlaceholder
|
| BabelCore.types.ArgumentPlaceholder
|
||||||
| BabelCore.types.SpreadElement
|
| BabelCore.types.SpreadElement
|
||||||
| BabelCore.types.Expression
|
| BabelCore.types.Expression
|
||||||
) {
|
) {
|
||||||
|
let emitType: BabelCore.types.Node | undefined;
|
||||||
|
if (generics) {
|
||||||
|
emitType = resolveTypeReference(generics);
|
||||||
|
}
|
||||||
|
|
||||||
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
|
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
|
||||||
if (
|
if (
|
||||||
!setupCtx ||
|
!emitType &&
|
||||||
!t.isTSTypeReference(setupCtx) ||
|
setupCtx &&
|
||||||
!t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
|
t.isTSTypeReference(setupCtx) &&
|
||||||
)
|
t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
|
||||||
return;
|
) {
|
||||||
|
emitType = setupCtx.typeParameters?.params[0];
|
||||||
const emitType = setupCtx.typeParameters?.params[0];
|
}
|
||||||
if (!emitType) return;
|
if (!emitType) return;
|
||||||
|
|
||||||
ctx!.emitsTypeDecl = emitType;
|
ctx!.emitsTypeDecl = emitType;
|
||||||
@ -185,8 +209,84 @@ const plugin: (
|
|||||||
t.objectProperty(t.identifier('emits'), ast)
|
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;
|
export default plugin;
|
||||||
|
|
||||||
function getTypeAnnotation(node: BabelCore.types.Node) {
|
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`] = `
|
exports[`resolve type > runtime props > basic 1`] = `
|
||||||
"import { defineComponent, h } from 'vue';
|
"import { defineComponent, h } from 'vue';
|
||||||
interface Props {
|
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`] = `
|
exports[`resolve type > runtime props > with static default value 1`] = `
|
||||||
"import { defineComponent, h } from 'vue';
|
"import { defineComponent, h } from 'vue';
|
||||||
defineComponent((props: {
|
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`] = `
|
exports[`resolve type > w/ tsx 1`] = `
|
||||||
"import { type SetupContext, defineComponent } from 'vue';
|
"import { type SetupContext, defineComponent } from 'vue';
|
||||||
defineComponent(() => {
|
defineComponent(() => {
|
||||||
|
@ -31,6 +31,38 @@ describe('resolve type', () => {
|
|||||||
expect(result).toMatchSnapshot();
|
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 () => {
|
test('with static default value', async () => {
|
||||||
const result = await transform(
|
const result = await transform(
|
||||||
`
|
`
|
||||||
@ -75,6 +107,25 @@ describe('resolve type', () => {
|
|||||||
);
|
);
|
||||||
expect(result).toMatchSnapshot();
|
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 () => {
|
test('w/ tsx', async () => {
|
||||||
|
Reference in New Issue
Block a user