oxc-project/oxc · error

Inline helpers are not supported yet

Error message

Inline helpers are not supported yet

What it means

Diagnostic from oxlint's vue/valid-define-emits rule (crates/oxc_linter/src/rules/vue/valid_define_emits.rs). It fires when `defineEmits()` is called with neither an argument nor a type parameter and the SFC's sibling plain `<script>` block does not provide `emits` on its default export — so no events are declared anywhere. Undeclared events break the component contract, devtools introspection, and Vue's attribute-fallthrough behavior.

Source

Thrown at crates/oxc_transformer/src/common/helper_loader.rs:321

            let existing = ctx.state.helper_loader.loaded_helpers.get(&helper).cloned();
            if let Some(binding) = existing {
                return binding.create_read_expression(ctx);
            }

            // Generate new binding
            let is_module = ctx.state.source_type.is_module();
            let flag =
                if is_module { SymbolFlags::Import } else { SymbolFlags::FunctionScopedVariable };
            let binding = ctx.generate_uid_in_root_scope(helper.name(), flag);

            ctx.state.module_imports.add_default_import(source, binding.clone(), false);
            ctx.state.helper_loader.loaded_helpers.insert(helper, binding.clone());

            binding.create_read_expression(ctx)
        }
        HelperLoaderMode::External => HelperLoaderStore::transform_for_external_helper(helper, ctx),
        HelperLoaderMode::Inline => {
            unreachable!("Inline helpers are not supported yet");
        }
    }
}

// Internal methods
impl<'a> HelperLoaderStore<'a> {
    // Construct string directly in arena without an intermediate temp allocation
    fn get_runtime_source(&self, helper: Helper, ctx: &TraverseCtx<'a>) -> Str<'a> {
        Str::from_strs_array_in([&self.module_name, "/helpers/", helper.name()], ctx)
    }

    fn transform_for_external_helper(helper: Helper, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
        let helper_var = static_ident!("babelHelpers");
        let symbol_id = ctx.scoping().find_binding(ctx.current_scope_id(), helper_var);
        let object = ctx.create_ident_expr(SPAN, helper_var, symbol_id, ReferenceFlags::Read);
        let property = IdentifierName::new(SPAN, helper.name(), ctx);
        Expression::new_static_member_expression(SPAN, object, property, false, ctx)
    }

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Declare the events: `defineEmits(['notify'])`, an object with validators, or a TS call-signature type.
  2. If the component emits nothing, remove the defineEmits call and any emit usages entirely.
  3. Alternatively keep events in the sibling `<script>`'s `export default { emits: [...] }` and leave `defineEmits()` bare — but declaring next to the macro is clearer.

Example fix

// before
const emit = defineEmits();

// after
const emit = defineEmits(['notify', 'submit']);
Defensive patterns

Strategy: validation

Validate before calling

// a bare defineEmits() must be accompanied by options emits or removed
const m = setupSource.match(/defineEmits\s*(<[^>]*>)?\s*\(([^)]*)\)/);
if (m && !m[1] && !m[2].trim() && !optionsBlockHasEmits) {
  throw new Error('defineEmits() declares no events');
}

Prevention

When it happens

Trigger: A bare `defineEmits()` in `<script setup>` when there is no `export default { emits: ... }` in another script block of the same SFC. Declaring events via array, object, type parameter, or the options block avoids it; `defineEmits(unresolvedVariable)` is not reported because the events cannot be statically known.

Common situations: Scaffolding `const emit = defineEmits()` as boilerplate before events are known; refactoring events out of the options API but leaving the macro call empty.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/5d5c67ba611c03ab. Report an issue: GitHub.