Skip to content

vue-perfectionist/define-macros-type-style ​

Enforce inline, local, or imported type arguments in Vue compiler macros.

Usage ​

Enable the rule alongside your existing vue-eslint-parser configuration with @typescript-eslint/parser as its script parser:

js
import { defineConfig } from 'eslint/config'
import vuePerfectionist from 'eslint-plugin-vue-perfectionist'

export default defineConfig([
  // Your existing Vue and TypeScript parser configurations go here.
  {
    files: ['**/*.vue'],
    plugins: { 'vue-perfectionist': vuePerfectionist },
    rules: {
      'vue-perfectionist/define-macros-type-style': 'error',
    },
  },
])

📖 Rule Details ​

By default, each macro must reference a local interface or type declaration. Names are unrestricted. The directly referenced declaration determines the style; local aliases and interfaces extending imported types still count as local.

vue
<script setup lang="ts">
interface ButtonProps {
  title: string
}

const props = defineProps<ButtonProps>()
</script>
correct
vue
<script setup lang="ts">
const props = defineProps<
{ title: string }
>()
</script>
incorrect

Imported types also produce a diagnostic under the default local policy:

vue
<script setup lang="ts">
import type { Props } from './types'

const props = defineProps<
Props
>()
</script>
incorrect

🔧 Options ​

The expanded defaults are:

json
{
  "vue-perfectionist/define-macros-type-style": [
    "error",
    {
      "macros": {
        "defineProps": "local",
        "defineEmits": "local",
        "defineSlots": "local"
      }
    }
  ]
}

Each macros value accepts 'inline', 'local', 'imported', a nonempty unique array of those styles, or false to disable that macro. Arrays replace the macro's default. Omitted keys retain their defaults, including with {} or { macros: {} }. Unknown fields and macro names are rejected.

To accept both local declarations and imports for props, while skipping the other macros:

json
{
  "macros": {
    "defineProps": ["local", "imported"],
    "defineEmits": false,
    "defineSlots": false
  }
}
Outer type formClassification
Direct type literal or function typeinline
Reference to a top-level, non-ambient local interface or type aliaslocal
Reference to a named or default ES import, including type-only importsimported
Qualified name rooted in an ES namespace importimported
Qualified import('./types').Props, including generic argumentsimported
Unresolved, ambient, built-in, generic parameter, bare namespace import, or ambiguous bindingUnknown source; reported
Union, intersection, mapped, conditional, indexed-access, keyword, or typeof typeUnsupported form; reported

Generic arguments do not affect the outer classification. Props<T> follows the binding of Props. Nested imported property types do not change an inline literal. Local namespaces and qualified named/default imports are unsupported. Compatible interface merging remains local, and same-named value bindings do not replace resolved type bindings. Declarations can appear after the call or in the ordinary script block. Alias chains are never followed and other files are never loaded.

Unknown sources are reported even when all three styles are allowed. Supported but disallowed styles report unexpectedTypeStyle; unsupported outer syntax reports unsupportedTypeForm; unresolved or ambiguous sources report unknownTypeSource. Each diagnostic covers the complete type argument.

Scope ​

Only defineProps, defineEmits, and defineSlots with exactly one explicit type argument and no runtime arguments are checked in .vue setup scripts. Calls must be top-level variable initializers (including destructuring) or expression statements. Parentheses and transparent TypeScript expression wrappers are accepted. An unshadowed withDefaults can wrap defineProps as its first argument in either position; the inner type is checked once.

Imported or shadowed macros, aliases, member/optional calls, arbitrary outer expressions, nested functions/blocks, ordinary script calls, template expressions, and standalone JS/TS files are skipped. Accessible types from either script block can establish a source. Missing parser services, runtime declarations, missing or extra type arguments, and calls with runtime arguments are skipped. defineModel, defineExpose, defineOptions, and custom macros are outside this rule's scope.

This rule needs no TypeScript project or type checker. It enforces a declaration convention and does not establish that Vue can compile the type. It neither requires a macro to exist nor requires a missing generic argument.

Combine this rule with require-macro-type-name to require specific call-site names. With both defaults, contracts must be local and named Props, Emits, and Slots. Disable the corresponding naming check when requiring inline types, since those policies conflict.

Automatic fixes and formatting ​

There are no automatic fixes or suggestions. Extracting or moving a type requires scope, comment, and file-boundary decisions. The examples show manual alternatives.

🔍 Implementation ​