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..1f59b8c936a 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,21 @@ return { myEmit } }" `; +exports[`defineEmits > nested call expression 1`] = ` +" +export default { + emits: ['foo', 'bar'], + setup(__props, { expose: __expose, emit: __emit }) { + __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..15aa63d9e90 100644 --- a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts @@ -210,6 +210,23 @@ 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);`) + expect(content).toMatch( + `setup(__props, { expose: __expose, emit: __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..2017a1f94ad 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, declId)) { + 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) {