oxc-project/oxc · error · OxcDiagnostic
`defineOptions()` cannot be used to declare `{prop_name}`. U
Error message
`defineOptions()` cannot be used to declare `{prop_name}`. Use `{instead_macro}()` instead. What it means
Diagnostic from the oxlint rule `vue/valid-define-options`. The rule keeps a DISALLOWED_PROPS table — `props→defineProps`, `emits→defineEmits`, `expose→defineExpose`, `slots→defineSlots` — and when the object literal passed to `defineOptions` contains a static-named property matching one of these keys, it reports with the corresponding replacement macro name in the message. These four options have dedicated compiler macros in `<script setup>`; declaring them through `defineOptions` breaks compile-time handling (props/emits type inference, expose/slots registration).
Source
Thrown at crates/oxc_linter/src/rules/vue/valid_define_options.rs:29
AstNode, ast_util::variable_declaration_kind, context::LintContext,
frameworks::FrameworkOptions, rule::Rule,
};
fn referencing_locally_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("`defineOptions` is referencing locally declared variables.")
.with_label(span)
}
fn multiple_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("`defineOptions` has been called multiple times.").with_label(span)
}
fn not_defined_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Options are not defined.").with_label(span)
}
fn disallow_prop_diagnostic(span: Span, prop_name: &str, instead_macro: &str) -> OxcDiagnostic {
OxcDiagnostic::warn(format!(
"`defineOptions()` cannot be used to declare `{prop_name}`. Use `{instead_macro}()` instead."
))
.with_label(span)
}
fn type_args_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("`defineOptions()` cannot accept type arguments.").with_label(span)
}
#[derive(Debug, Default, Clone)]
pub struct ValidDefineOptions;
declare_oxc_lint!(
/// ### What it does
///
/// Enforce valid use of the `defineOptions` compiler macro.
///
/// ### Why is this bad?View on GitHub (pinned to e1e7af627c)
Solutions
- Move `props` out of `defineOptions` into `defineProps({ msg: String })` (or `defineProps<{...}>()` in TS).
- Move `emits` into `defineEmits(['click'])`.
- Use `defineExpose({...})` for expose and `defineSlots<{...}>()` for slots.
- Keep only compile-time options like `name`, `inheritAttrs`, `components`, `directives` in `defineOptions`.
Example fix
// before
<script setup>
defineOptions({
name: 'Foo',
props: { msg: String },
emits: ['click'],
})
</script>
// after
<script setup>
defineOptions({ name: 'Foo' })
defineProps({ msg: String })
defineEmits(['click'])
</script> Defensive patterns
Strategy: validation
Validate before calling
const banned = ['props', 'emits', 'expose', 'slots'];
const m = setup.match(/defineOptions\s*\(\s*\{[\s\S]*?\}\s*\)/);
if (m) {
for (const key of banned) {
if (new RegExp(`\\b${key}\\s*:`).test(m[0])) {
throw new Error(`defineOptions cannot declare '${key}' — use define${key[0].toUpperCase()+key.slice(1)}()`);
}
}
} Prevention
- Treat defineOptions as the home of name/inheritAttrs/components/directives only.
- Props/emits/expose/slots always go through their dedicated macros.
- Review Vue <script setup> migration PRs for options pasted wholesale from export default.
When it happens
Trigger: A .vue file (VueSetup) with `defineOptions({ ... })` whose object literal contains a statically-named key `props`, `emits`, `expose`, or `slots`. Examples from the rule's fail tests: `defineOptions({ props: { msg: String } })`, `defineOptions({ emits: ['click'] })`, `defineOptions({ expose: ['foo'] })`, `defineOptions({ slots: Object })`. Multiple disallowed keys in one call each produce their own diagnostic (`break` only exits the inner table loop per property).
Common situations: Migrating an Options API component by pasting `props`/`emits` from `export default { props, emits }` into `defineOptions` instead of using the dedicated macros; developers learning `<script setup>` assuming `defineOptions` is a general-purpose options bag.
Related errors
- [HIRBuilder] expected block {:?} to exist
- Inline helpers are not supported yet
- TS5042
- TS5081
- `defineOptions` is referencing locally declared variables.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/a8a071cbf53c8482.
Report an issue: GitHub.