vue-perfectionist/prefer-function-result-pattern
Enforce naming patterns for function result bindings.
Usage
Add the rule to your flat config. Configure the usual parsers for TypeScript and Vue SFCs separately; JavaScript uses ESLint's default parser.
import { defineConfig } from 'eslint/config'
import vuePerfectionist from 'eslint-plugin-vue-perfectionist'
export default defineConfig([
{
files: ['**/*.{vue,js,jsx,ts,tsx}'],
plugins: { 'vue-perfectionist': vuePerfectionist },
rules: {
'vue-perfectionist/prefer-function-result-pattern': 'error',
},
},
])📖 Rule Details
Each target identifies functions by import source and original export names, selects a result binding, and checks its name against a regular expression. Methods in the same target share its options. Split them into separate targets when they need different patterns or result positions.
By default, boolean state names must match ^(is|has|should|can)[A-Z]. isOpen and hasPermission pass; is, island, is_open, and IsOpen do not. The default pattern does not constrain the rest of the identifier.
| Source | Exports | Checked binding |
|---|---|---|
vue | ref, shallowRef | Direct variable, for a boolean literal or explicit boolean type argument. |
vue | computed | Direct variable, for an explicit boolean type argument or a supported boolean getter. |
@vueuse/core | useToggle | First array element, for no arguments or one boolean literal argument. |
import { useToggle } from '@vueuse/core'
import { ref, shallowRef } from 'vue'
const isLoading = ref(false)
const hasPermission = shallowRef<boolean>()
const [shouldRender, toggle] = useToggle(true)
const count = ref(0)import { useToggle } from '@vueuse/core'
import { ref, shallowRef } from 'vue'
const loading = ref(false)
const permission = shallowRef<boolean>()
const [visible, toggle] = useToggle()Named import aliases and namespace imports are supported, including TypeScript wrappers around namespace receivers such as (Vue as typeof Vue).ref(false). These declarations are reported without automatic renaming:
import { ref as makeRef } from 'vue'
import * as VueUse from '@vueuse/core'
const ready = makeRef(false)
const [open] = VueUse.useToggle(false)🔧 Options
Omitting targets uses these defaults:
{
"targets": [
{
"source": "vue",
"names": ["ref", "shallowRef", "computed"],
"pattern": "^(is|has|should|can)[A-Z]",
"booleanOnly": true
},
{
"source": "@vueuse/core",
"names": ["useToggle"],
"pattern": "^(is|has|should|can)[A-Z]",
"returnIndex": 0,
"booleanOnly": true
}
]
}targets
- Type:
PreferFunctionResultPatternTarget[] - Default: the two targets above
An explicit array replaces all defaults. targets: [] disables checks. To keep default behavior while adding custom methods, include the default targets in your array.
| Property | Type | Meaning |
|---|---|---|
source | string | Required, nonempty, exact import module specifier. |
names | string[] | Required, nonempty array of unique, nonempty original export names. Use default for default imports. |
pattern | string | Required JavaScript regular expression source, without delimiters or flags. No implicit anchors. An empty pattern accepts every name. Invalid expressions are configuration errors. |
returnIndex | number | Optional nonnegative integer selecting an array destructuring element. Omit to check a direct variable. |
booleanOnly | boolean | Defaults to false. Enable syntactic boolean filtering described below. |
autoImport | boolean | Defaults to the shared settings['vue-perfectionist'].autoImport setting, which defaults to false. |
Patterns are compiled as new RegExp(pattern). Include ^ and $ explicitly when needed. For example, Ready also matches wasReadyBefore.
Grouping methods and overriding targets
This configuration gives Vue methods a shared pattern and overrides only ref:
{
"targets": [
{
"source": "vue",
"names": ["ref", "shallowRef"],
"pattern": "^is[A-Z]"
},
{
"source": "vue",
"names": ["ref"],
"pattern": "^has[A-Z]"
}
]
}With the configuration above:
import { ref, shallowRef } from 'vue'
const hasAccess = ref(false)
const isOpen = shallowRef(false)import { ref, shallowRef } from 'vue'
const isReady = ref(false)
const hasAccess = shallowRef(false)For the same method and binding position, the last matching target wins. The entire target is replaced, including booleanOnly and autoImport; fields are not merged. Selection happens before boolean filtering or the auto-import opt-in. If the winning target skips a call, an earlier target is not used as a fallback. Different returnIndex values remain independent. A direct binding and array element zero are different positions.
Custom functions and tuple results
Custom functions use the same target format. They need no boolean evidence unless booleanOnly: true is configured:
{
"targets": [
{
"source": "@vueuse/core",
"names": ["useDark", "useOnline"],
"pattern": "^is[A-Z]"
},
{
"source": "./state",
"names": ["useFlag"],
"pattern": "^(is|has|should|can)[A-Z]",
"returnIndex": 1
},
{
"source": "./default",
"names": ["default"],
"pattern": "^(is|has|should|can)[A-Z]"
}
]
}With the configuration above:
import { useDark, useOnline } from '@vueuse/core'
import makeFlag from './default'
import { useFlag } from './state'
const isDark = useDark()
const isOnline = useOnline()
const [toggle, hasAccess = false] = useFlag()
const isReady = makeFlag()import { useDark, useOnline } from '@vueuse/core'
import makeFlag from './default'
import { useFlag } from './state'
const dark = useDark()
const online = useOnline()
const [toggle, access = false] = useFlag()
const ready = makeFlag()Only the selected tuple position is checked. Holes, rest elements, object patterns, and nested patterns are skipped. A default value such as [toggle, access = false] still checks the identifier access.
Boolean filtering
booleanOnly: true uses syntax, not TypeScript type services or general type inference. For Vue ref / shallowRef and custom methods:
- An explicit first type argument takes precedence. It must be
boolean, a boolean literal type, or a union containing only boolean types. - Otherwise, the first argument must be
trueorfalse, including parenthesized or TypeScript-wrapped forms. - Unknown initial values, comparisons, negation, aliases, and mixed types such as
boolean | stringare skipped. Later arguments and type arguments are not used as evidence.
For useToggle from @vueuse/core, boolean filtering instead requires no arguments or one boolean literal argument, and no type arguments. Calls with options, custom truthy/falsy values, or unknown initial values are skipped. The default target checks only array element zero, so a direct function binding such as const toggle = useToggle(isOpen) is outside its scope. Setting booleanOnly: false removes this filter, including for useToggle. A custom export named useToggle from another source uses the generic filter.
Boolean computed values
Vue computed is included in the default targets. With booleanOnly: true, its first type argument takes precedence and must be a boolean type as described above. Otherwise, the rule checks an inline getter:
- An explicit getter return type must be
boolean, a boolean literal type, or a union containing only boolean types. It takes precedence over the body. - Without a return annotation, an expression body or a block containing only one
returnstatement must return a syntactically boolean expression:true/false, negation (!), equality or relational comparisons,in,instanceof, or a conditional/logical expression whose two result operands are both recognized as boolean. - Writable computed options support
get: () => ...andget() { ... }. Objects with spreads, dynamic keys, or duplicategetproperties are skipped. - Async and generator getters are skipped unless an explicit boolean type argument is provided.
import { computed } from 'vue'
const isReady = computed(() => count.value > 0)
const hasPermission = computed<boolean>(() => flag.value)
const canEdit = computed((): boolean => checkPermission())
const shouldRender = computed({
get: () => !hidden.value,
set(value) {
hidden.value = !value
},
})import { computed } from 'vue'
const ready = computed(() => count.value > 0)
const permission = computed<boolean>(() => flag.value)
const editable = computed((): boolean => checkPermission())
const visible = computed({
get: () => !hidden.value,
set(value) {
hidden.value = !value
},
})Unannotated getters returning identifiers, member access such as flag.value, or function calls are skipped, even when TypeScript could infer a boolean. Blocks with multiple statements or control flow are also skipped without a return annotation. Add computed<boolean>(...) or a (): boolean getter annotation to make the intended type explicit. Mixed types such as boolean | undefined are skipped. A custom export named computed from another source keeps the generic filter rather than inspecting its getter.
Auto-imports
For unplugin-auto-import, enable the shared setting:
import { defineConfig } from 'eslint/config'
export default defineConfig([
{
settings: {
'vue-perfectionist': {
autoImport: true,
},
},
rules: {
'vue-perfectionist/prefer-function-result-pattern': 'error',
},
},
])This enables unbound names from all configured targets, including custom methods and names declared in ESLint's languageOptions.globals. Each target can override the setting independently:
{
"targets": [
{
"source": "vue",
"names": ["ref", "shallowRef"],
"pattern": "^is[A-Z]",
"autoImport": false
},
{
"source": "@vueuse/core",
"names": ["useDark", "useOnline"],
"pattern": "^is[A-Z]",
"autoImport": true
}
]
}autoImport: false affects unbound calls only; explicit imports still match. Local declarations, parameters, imports from other sources, and type-only imports are never treated as auto-imports. Custom auto-import aliases are not inferred. The rule does not read Vite configuration, generated declarations, or auto-import manifests.
An unbound name has no source information. The target declares its identity; if multiple sources configure the same unbound name and result position, the last target wins. Use explicit imports to disambiguate those sources. ESLint globals alone do not opt into matching or configure no-undef; load your generated globals separately when using that rule.
Scope
Checks variable declarations in ordinary and setup Vue scripts, and standalone JavaScript/TypeScript files, including nested functions and exported bindings. The initializer must be a direct call, optionally wrapped in parentheses or TypeScript syntax. Diagnostics point to the selected binding identifier.
Imports match the exact module specifier and original export name, supporting named aliases, namespaces, and explicitly configured default exports. The default targets do not include default exports.
Assignments after declaration, awaited or indirect results, indirect function aliases, optional calls, computed member calls, require() imports, and Vue template expressions are skipped.
Automatic fixes and formatting
This rule reports without automatic fixes or suggestions. Choosing a meaningful name requires context, and renaming a binding can affect script references, template usage, and public exports.