Skip to content

Commit

Permalink
feat(volar/jsx-directive): support generic component (#658)
Browse files Browse the repository at this point in the history
* feat(volar/jsx-directive): support generic component

* chore: add changeset

* fix: format
  • Loading branch information
zhiyuanzmj committed Apr 7, 2024
1 parent d2513a5 commit 4ad70f5
Show file tree
Hide file tree
Showing 15 changed files with 208 additions and 207 deletions.
5 changes: 5 additions & 0 deletions .changeset/polite-paws-doubt.md
@@ -0,0 +1,5 @@
---
"@vue-macros/volar": patch
---

support generic component
28 changes: 20 additions & 8 deletions packages/jsx-directive/tests/__snapshots__/v-on.test.ts.snap
@@ -1,19 +1,23 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`jsx-vue-directive > vue 2.7 v-on > ./fixtures/v-on/child.vue 1`] = `
"<script setup lang="tsx">
"<script setup lang="tsx" generic="T">
const { bar } = defineProps<{
bar: T
}>()
const slots = defineSlots<{
default: () => any
bottom: (props: { foo: 1 }) => any
}>()
const emit = defineEmits<{
log: [foo: number]
log: [foo: T]
click: []
}>()
defineRender(() => (
<form v-on:submit_prevent={() => {}} onClick={() => emit('log', 1)}>
<form v-on:submit_prevent={() => {}} onClick={() => emit('log', bar)}>
<slots.default />
</form>
))
Expand All @@ -23,6 +27,7 @@ defineRender(() => (

exports[`jsx-vue-directive > vue 2.7 v-on > ./fixtures/v-on/index.vue 1`] = `
"<script setup lang="tsx">
import { expectTypeOf } from 'expect-type'
import Child from './child.vue'
let bar = $ref('')
Expand All @@ -31,7 +36,8 @@ defineRender(() => (
<legend>v-on</legend>
<Child
on={{ log: console.log }}
bar={bar}
on={{ log: (e) => expectTypeOf<string>(e) }}
v-on:click_capture_stop={() => console.log('stopped')}
>
<input
Expand All @@ -47,20 +53,24 @@ defineRender(() => (
`;

exports[`jsx-vue-directive > vue 3 v-on > ./fixtures/v-on/child.vue 1`] = `
"<script setup lang="tsx">
"<script setup lang="tsx" generic="T">
import { withModifiers as __MACROS_withModifiers } from "vue";
const { bar } = defineProps<{
bar: T
}>()
const slots = defineSlots<{
default: () => any
bottom: (props: { foo: 1 }) => any
}>()
const emit = defineEmits<{
log: [foo: number]
log: [foo: T]
click: []
}>()
defineRender(() => (
<form onSubmit={__MACROS_withModifiers(() => {}, ['prevent'])} onClick={() => emit('log', 1)}>
<form onSubmit={__MACROS_withModifiers(() => {}, ['prevent'])} onClick={() => emit('log', bar)}>
<slots.default />
</form>
))
Expand All @@ -71,6 +81,7 @@ defineRender(() => (
exports[`jsx-vue-directive > vue 3 v-on > ./fixtures/v-on/index.vue 1`] = `
"<script setup lang="tsx">
import { withModifiers as __MACROS_withModifiers } from "vue";const __MACROS_transformVOn = (obj) => Object.entries(obj).reduce((res, [key, value]) => (res['on' + key[0].toUpperCase() + key.slice(1)] = value, res), {});
import { expectTypeOf } from 'expect-type'
import Child from './child.vue'
let bar = $ref('')
Expand All @@ -79,7 +90,8 @@ defineRender(() => (
<legend>v-on</legend>
<Child
{...__MACROS_transformVOn({ log: console.log })}
bar={bar}
{...__MACROS_transformVOn({ log: (e) => expectTypeOf<string>(e) })}
onClickCapture={__MACROS_withModifiers(() => console.log('stopped'),['stop'])}
>
<input
Expand Down
14 changes: 10 additions & 4 deletions packages/jsx-directive/tests/__snapshots__/v-slot.test.ts.snap
Expand Up @@ -3,6 +3,7 @@
exports[`jsx-vue-directive > vue 2.7 v-slot > ./fixtures/v-slot/index.vue 1`] = `
"<script setup lang="tsx">
import { unref as __MACROS_unref } from "vue";
import { expectTypeOf } from 'expect-type'
import Child from './child.vue'
import type { FunctionalComponent } from 'vue'
Expand All @@ -22,6 +23,7 @@ const Comp: FunctionalComponent<
)
}
let baz = $ref('')
let show = $ref<boolean | undefined>()
defineRender(() => (
<div>
Expand All @@ -33,8 +35,9 @@ defineRender(() => (
<Comp scopedSlots={{'default': () => <span>default</span>,}}></Comp>
<Child scopedSlots={{'bottom': ({ foo }) => <span>{foo}</span>,}}></Child>
<Comp scopedSlots={{'default': () => <span> </span>,}} />
<Child scopedSlots={{'bottom': ({ foo }) => <span> </span>,}} />
<Child baz={baz} scopedSlots={{'title': ({ foo }) => <span>
{expectTypeOf<string>(foo)}
</span>,}}></Child>
<Child scopedSlots={{...(show) ? {'title': ({ foo }) => <span>
{show}
Expand All @@ -57,6 +60,7 @@ exports[`jsx-vue-directive > vue 3 v-slot > ./fixtures/v-slot/index.vue 1`] = `
"<script setup lang="tsx">
import { renderList as __MACROS_renderList } from "vue";
import { unref as __MACROS_unref } from "vue";
import { expectTypeOf } from 'expect-type'
import Child from './child.vue'
import type { FunctionalComponent } from 'vue'
Expand All @@ -76,6 +80,7 @@ const Comp: FunctionalComponent<
)
}
let baz = $ref('')
let show = $ref<boolean | undefined>()
defineRender(() => (
<div>
Expand All @@ -87,8 +92,9 @@ defineRender(() => (
<Comp v-slots={{'default': () => <>default</>,}}></Comp>
<Child v-slots={{'bottom': ({ foo }) => <>{foo}</>,}}></Child>
<Comp v-slots={{'default': () => <> </>,}} />
<Child v-slots={{'bottom': ({ foo }) => <> </>,}} />
<Child baz={baz} v-slots={{'title': ({ foo }) => <>
{expectTypeOf<string>(foo)}
</>,}}></Child>
<Child v-slots={{...(show) ? {'title': ({ foo }) => <>
{show}
Expand Down
10 changes: 7 additions & 3 deletions packages/jsx-directive/tests/fixtures/v-on/child.vue
@@ -1,16 +1,20 @@
<script setup lang="tsx">
<script setup lang="tsx" generic="T">
const { bar } = defineProps<{
bar: T
}>()
const slots = defineSlots<{
default: () => any
bottom: (props: { foo: 1 }) => any
}>()
const emit = defineEmits<{
log: [foo: number]
log: [foo: T]
click: []
}>()
defineRender(() => (
<form onSubmit_prevent onClick={() => emit('log', 1)}>
<form onSubmit_prevent onClick={() => emit('log', bar)}>
<slots.default />
</form>
))
Expand Down
4 changes: 3 additions & 1 deletion packages/jsx-directive/tests/fixtures/v-on/index.vue
@@ -1,4 +1,5 @@
<script setup lang="tsx">
import { expectTypeOf } from 'expect-type'
import Child from './child.vue'
let bar = $ref('')
Expand All @@ -7,7 +8,8 @@ defineRender(() => (
<legend>v-on</legend>
<Child
v-on={{ log: console.log }}
bar={bar}
v-on={{ log: (e) => expectTypeOf<string>(e) }}
onClick_capture_stop={() => console.log('stopped')}
>
<input
Expand Down
8 changes: 6 additions & 2 deletions packages/jsx-directive/tests/fixtures/v-slot/child.vue
@@ -1,7 +1,11 @@
<script setup lang="tsx">
<script setup lang="tsx" generic="T">
defineProps<{
baz?: T
}>()
defineSlots<{
default: () => any
title: (props: { foo: 1 }) => any
title: (props: { foo: T }) => any
bottom: (props: { foo: 1 }) => any
center: (props: { foo: 1 }) => any
'bot-tom': (props: { foo: 1 }) => any
Expand Down
7 changes: 5 additions & 2 deletions packages/jsx-directive/tests/fixtures/v-slot/index.vue
@@ -1,4 +1,5 @@
<script setup lang="tsx">
import { expectTypeOf } from 'expect-type'
import Child from './child.vue'
import type { FunctionalComponent } from 'vue'
Expand All @@ -18,6 +19,7 @@ const Comp: FunctionalComponent<
)
}
let baz = $ref('')
let show = $ref<boolean | undefined>()
defineRender(() => (
<div>
Expand All @@ -29,8 +31,9 @@ defineRender(() => (
<Comp v-slot>default</Comp>
<Child v-slot:bottom={{ foo }}>{foo}</Child>
<Comp v-slot />
<Child v-slot:bottom={{ foo }} />
<Child baz={baz} v-slot:title={{ foo }}>
{expectTypeOf<string>(foo)}
</Child>
<Child>
default
Expand Down
61 changes: 0 additions & 61 deletions packages/volar/src/common.ts
Expand Up @@ -3,7 +3,6 @@ import {
type Segment,
type Sfc,
type VueCompilerOptions,
getSlotsPropertyName,
replaceAll,
} from '@vue/language-core'
import type { VolarOptions } from '..'
Expand Down Expand Up @@ -85,63 +84,3 @@ export function getImportNames(

return names
}

export function getPropsType(codes: Segment<FileRangeCapabilities>[]) {
if (codes.toString().includes('type __VLS_getProps')) return

codes.push(`
type __VLS_getProps<T> = T extends new () => { $props: infer P }
? NonNullable<P>
: T extends (props: infer P, ctx: any) => any
? NonNullable<P>
: {};`)
}

export function getEmitsType(codes: Segment<FileRangeCapabilities>[]) {
if (codes.toString().includes('type __VLS_getEmits')) return

codes.push(`
type __VLS_getEmits<T> = T extends new () => { $emit: infer E }
? NonNullable<__VLS_NormalizeEmits<E>>
: T extends (
props: any,
ctx: { slots: any; attrs: any; emit: infer E },
) => any
? NonNullable<__VLS_NormalizeEmits<E>>
: {};`)
}

export function getModelsType(codes: Segment<FileRangeCapabilities>[]) {
if (codes.toString().includes('type __VLS_getModels')) return
getEmitsType(codes)
getPropsType(codes)

codes.push(`
type __VLS_CamelCase<S extends string> = S extends \`\${infer F}-\${infer RF}\${infer R}\`
? \`\${F}\${Uppercase<RF>}\${__VLS_CamelCase<R>}\`
: S;
type __VLS_RemoveUpdatePrefix<T> = T extends \`update:modelValue\`
? never
: T extends \`update:\${infer R}\`
? __VLS_CamelCase<R>
: T;
type __VLS_getModels<T> = T extends object
? {
[K in keyof __VLS_getEmits<T> as __VLS_RemoveUpdatePrefix<K>]: __VLS_getProps<T>[__VLS_RemoveUpdatePrefix<K>]
}
: {};`)
}

export function getSlotsType(
codes: Segment<FileRangeCapabilities>[],
vueVersion?: number,
) {
if (codes.toString().includes('type __VLS_getSlots')) return
codes.push(`
type __VLS_getSlots<T> = T extends new () => { '${getSlotsPropertyName(
vueVersion || 3,
)}': infer S } ? NonNullable<S>
: T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }) => any
? NonNullable<S>
: {};`)
}

0 comments on commit 4ad70f5

Please sign in to comment.