From 3416cd2eb7b24e0cb0345b2bd044abfbf9332e5e Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Wed, 30 Apr 2025 12:35:09 +0200 Subject: [PATCH 1/2] feat: handle nested defineEmits --- .../__snapshots__/defineEmits.spec.ts.snap | 16 ++++++++ .../compileScript/defineEmits.spec.ts | 14 +++++++ .../__tests__/compileStyle.spec.ts | 36 ++++++++-------- packages/compiler-sfc/src/compileScript.ts | 10 ++--- .../compiler-sfc/src/script/defineEmits.ts | 41 +++++++++++++++++++ 5 files changed, 95 insertions(+), 22 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap index a25a384a979..b455ff6cf65 100644 --- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap @@ -15,6 +15,22 @@ return { myEmit } }" `; +exports[`defineEmits > nested call expression 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*@__PURE__*/_defineComponent({ + emits: ['foo', 'bar'], + setup(__props, { expose: __expose }) { + __expose(); + + const transformed = transform(__emit); + +return { transformed } +} + +})" +`; + exports[`defineEmits > w/ runtime options 1`] = ` "import { defineComponent as _defineComponent } from 'vue' diff --git a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts index 99cb78a594a..97d19a1b6b5 100644 --- a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts @@ -210,6 +210,20 @@ const emit = defineEmits(['a', 'b']) assertCode(content) }) + test('nested call expression', () => { + const { content } = compile(` + + `) + + // should remove defineEmits import and call + expect(content).not.toMatch('defineEmits') + + assertCode(content) + expect(content).toMatch(`const transformed = transform(__emit);`) + }) + describe('errors', () => { test('w/ both type and non-type args', () => { expect(() => { diff --git a/packages/compiler-sfc/__tests__/compileStyle.spec.ts b/packages/compiler-sfc/__tests__/compileStyle.spec.ts index b76414364dc..78fd52425e8 100644 --- a/packages/compiler-sfc/__tests__/compileStyle.spec.ts +++ b/packages/compiler-sfc/__tests__/compileStyle.spec.ts @@ -211,38 +211,42 @@ color: red expect( compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`), ).toMatchInlineSnapshot(` - ".div[data-v-test] { color: red; - } - .div[data-v-test]:where(:hover) { color: blue; - }"`) + ".div[data-v-test] { color: red; + } + .div[data-v-test]:where(:hover) { color: blue; + }" + `) expect( compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`), ).toMatchInlineSnapshot(` - ".div[data-v-test] { color: red; - } - .div[data-v-test]:is(:hover) { color: blue; - }"`) + ".div[data-v-test] { color: red; + } + .div[data-v-test]:is(:hover) { color: blue; + }" + `) expect( compileScoped( `.div { color: red; } .div:where(.foo:hover) { color: blue; }`, ), ).toMatchInlineSnapshot(` - ".div[data-v-test] { color: red; - } - .div[data-v-test]:where(.foo:hover) { color: blue; - }"`) + ".div[data-v-test] { color: red; + } + .div[data-v-test]:where(.foo:hover) { color: blue; + }" + `) expect( compileScoped( `.div { color: red; } .div:is(.foo:hover) { color: blue; }`, ), ).toMatchInlineSnapshot(` - ".div[data-v-test] { color: red; - } - .div[data-v-test]:is(.foo:hover) { color: blue; - }"`) + ".div[data-v-test] { color: red; + } + .div[data-v-test]:is(.foo:hover) { color: blue; + }" + `) }) test('media query', () => { diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 962b7bc7936..6c8810a6367 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -46,6 +46,8 @@ import { DEFINE_EMITS, genRuntimeEmits, processDefineEmits, + processNestedDefineEmits, + replaceDefineEmits, } from './script/defineEmits' import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose' import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions' @@ -544,7 +546,7 @@ export function compileScript( // defineEmits const isDefineEmits = - !isDefineProps && processDefineEmits(ctx, init, decl.id) + !isDefineProps && processNestedDefineEmits(ctx, init, decl.id) !isDefineEmits && (processDefineSlots(ctx, init, decl.id) || processDefineModel(ctx, init, decl.id)) @@ -572,11 +574,7 @@ export function compileScript( left-- } } else if (isDefineEmits) { - ctx.s.overwrite( - startOffset + init.start!, - startOffset + init.end!, - '__emit', - ) + replaceDefineEmits(ctx, init) } else { lastNonRemoved = i } diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index e4e6bdabf36..8c25ee45b43 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -16,6 +16,36 @@ import { export const DEFINE_EMITS = 'defineEmits' +/** + * Detects if this node recursively contains a call to `defineEmits()`. + * + * @returns true If the given node itself is a call to `defineEmits()` and should be removed, false otherwise. + */ +export function processNestedDefineEmits( + ctx: ScriptCompileContext, + node: Node, + declId?: LVal, +): boolean { + if (node.type !== 'CallExpression') { + return false + } + if (processDefineEmits(ctx, node, declId)) { + return true + } + for (const arg of node.arguments) { + if (processNestedDefineEmits(ctx, arg)) { + replaceDefineEmits(ctx, arg) + } + } + + return false +} + +/** + * Detects if the node is a call to `defineEmits()`. + * + * @returns true If the given node itself is a call to `defineEmits()` and should be removed, false otherwise. + */ export function processDefineEmits( ctx: ScriptCompileContext, node: Node, @@ -45,6 +75,17 @@ export function processDefineEmits( return true } +export function replaceDefineEmits( + ctx: ScriptCompileContext, + node: Node, +): void { + ctx.s.overwrite( + ctx.startOffset! + node.start!, + ctx.startOffset! + node.end!, + '__emit', + ) +} + export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined { let emitsDecl = '' if (ctx.emitsRuntimeDecl) { From 79f75d4bead04d0d3bb53969821997eb5952fbe9 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Wed, 30 Apr 2025 17:19:04 +0200 Subject: [PATCH 2/2] fix: nested emit --- .../compileScript/__snapshots__/defineEmits.spec.ts.snap | 9 ++++----- .../__tests__/compileScript/defineEmits.spec.ts | 3 +++ packages/compiler-sfc/src/script/defineEmits.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap index b455ff6cf65..1f59b8c936a 100644 --- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap @@ -16,11 +16,10 @@ return { myEmit } `; exports[`defineEmits > nested call expression 1`] = ` -"import { defineComponent as _defineComponent } from 'vue' - -export default /*@__PURE__*/_defineComponent({ +" +export default { emits: ['foo', 'bar'], - setup(__props, { expose: __expose }) { + setup(__props, { expose: __expose, emit: __emit }) { __expose(); const transformed = transform(__emit); @@ -28,7 +27,7 @@ export default /*@__PURE__*/_defineComponent({ return { transformed } } -})" +}" `; exports[`defineEmits > w/ runtime options 1`] = ` diff --git a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts index 97d19a1b6b5..15aa63d9e90 100644 --- a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts @@ -222,6 +222,9 @@ const emit = defineEmits(['a', 'b']) assertCode(content) expect(content).toMatch(`const transformed = transform(__emit);`) + expect(content).toMatch( + `setup(__props, { expose: __expose, emit: __emit })`, + ) }) describe('errors', () => { diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index 8c25ee45b43..2017a1f94ad 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -33,7 +33,7 @@ export function processNestedDefineEmits( return true } for (const arg of node.arguments) { - if (processNestedDefineEmits(ctx, arg)) { + if (processNestedDefineEmits(ctx, arg, declId)) { replaceDefineEmits(ctx, arg) } }