{"record":{"id":"a8a071cbf53c8482","repo":"oxc-project/oxc","slug":"defineoptions-cannot-be-used-to-declare-prop","errorCode":null,"errorMessage":"`defineOptions()` cannot be used to declare `{prop_name}`. Use `{instead_macro}()` instead.","messagePattern":"`defineOptions\\(\\)` cannot be used to declare `(.+?)`\\. Use `(.+?)\\(\\)` instead\\.","errorType":"validation","errorClass":"OxcDiagnostic","httpStatus":null,"severity":"error","filePath":"crates/oxc_linter/src/rules/vue/valid_define_options.rs","lineNumber":29,"sourceCode":"    AstNode, ast_util::variable_declaration_kind, context::LintContext,\n    frameworks::FrameworkOptions, rule::Rule,\n};\n\nfn referencing_locally_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"`defineOptions` is referencing locally declared variables.\")\n        .with_label(span)\n}\n\nfn multiple_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"`defineOptions` has been called multiple times.\").with_label(span)\n}\n\nfn not_defined_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Options are not defined.\").with_label(span)\n}\n\nfn disallow_prop_diagnostic(span: Span, prop_name: &str, instead_macro: &str) -> OxcDiagnostic {\n    OxcDiagnostic::warn(format!(\n        \"`defineOptions()` cannot be used to declare `{prop_name}`. Use `{instead_macro}()` instead.\"\n    ))\n    .with_label(span)\n}\n\nfn type_args_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"`defineOptions()` cannot accept type arguments.\").with_label(span)\n}\n\n#[derive(Debug, Default, Clone)]\npub struct ValidDefineOptions;\n\ndeclare_oxc_lint!(\n    /// ### What it does\n    ///\n    /// Enforce valid use of the `defineOptions` compiler macro.\n    ///\n    /// ### Why is this bad?","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/vue/valid_define_options.rs#L11-L47","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","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`."],"exampleFix":"// before\n<script setup>\ndefineOptions({\n  name: 'Foo',\n  props: { msg: String },\n  emits: ['click'],\n})\n</script>\n\n// after\n<script setup>\ndefineOptions({ name: 'Foo' })\ndefineProps({ msg: String })\ndefineEmits(['click'])\n</script>","handlingStrategy":"validation","validationCode":"const banned = ['props', 'emits', 'expose', 'slots'];\nconst m = setup.match(/defineOptions\\s*\\(\\s*\\{[\\s\\S]*?\\}\\s*\\)/);\nif (m) {\n  for (const key of banned) {\n    if (new RegExp(`\\\\b${key}\\\\s*:`).test(m[0])) {\n      throw new Error(`defineOptions cannot declare '${key}' — use define${key[0].toUpperCase()+key.slice(1)}()`);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["vue","script-setup","compiler-macro","defineoptions","defineprops","defineemits","lint"],"backgroundTag":"vue-script-setup-macro-misuse","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}