Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ For example:
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: | :hammer: |
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | :lipstick: |
| [vue/v-on-handler-style](./v-on-handler-style.md) | enforce writing style for handlers in `v-on` directives | :wrench: | :hammer: |
| [vue/valid-define-options](./valid-define-options.md) | enforce valid `defineOptions` compiler macro | | :warning: |

</rules-table>

Expand Down
1 change: 1 addition & 0 deletions docs/rules/valid-define-emits.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Nothing.
## :couple: Related Rules

- [vue/define-emits-declaration](./define-emits-declaration.md)
- [vue/valid-define-options](./valid-define-options.md)
- [vue/valid-define-props](./valid-define-props.md)

## :rocket: Version
Expand Down
119 changes: 119 additions & 0 deletions docs/rules/valid-define-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/valid-define-options
description: enforce valid `defineOptions` compiler macro
---
# vue/valid-define-options

> enforce valid `defineOptions` compiler macro

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>

This rule checks whether `defineOptions` compiler macro is valid.

## :book: Rule Details

This rule reports `defineOptions` compiler macros in the following cases:

- `defineOptions` are referencing locally declared variables.
- `defineOptions` has been called multiple times.
- Options are not defined in `defineOptions`.
- `defineOptions` has type arguments.
- `defineOptions` has `props`, `emits`, `expose` or `slots` options.

<eslint-code-block :rules="{'vue/valid-define-options': ['error']}">

```vue
<script setup>
/* ✓ GOOD */
defineOptions({ name: 'foo' })
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/valid-define-options': ['error']}">

```vue
<script>
const def = { name: 'foo' }
</script>
<script setup>
/* ✓ GOOD */
defineOptions(def)
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/valid-define-options': ['error']}">

```vue
<script setup>
/* ✗ BAD */
const def = { name: 'foo' }
defineOptions(def)
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/valid-define-options': ['error']}">

```vue
<script setup>
/* ✗ BAD */
defineOptions({ name: 'foo' })
defineOptions({ inheritAttrs: false })
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/valid-define-options': ['error']}">

```vue
<script setup>
/* ✗ BAD */
defineOptions()
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/valid-define-options': ['error']}">

```vue
<script setup lang="ts">
/* ✗ BAD */
defineOptions<{ name: 'Foo' }>()
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/valid-define-options': ['error']}">

```vue
<script setup>
/* ✗ BAD */
defineOptions({ props: { msg: String } })
</script>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :couple: Related Rules

- [vue/valid-define-emits](./valid-define-emits.md)
- [vue/valid-define-props](./valid-define-props.md)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-define-options.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-define-options.js)
1 change: 1 addition & 0 deletions docs/rules/valid-define-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ Nothing.

- [vue/define-props-declaration](./define-props-declaration.md)
- [vue/valid-define-emits](./valid-define-emits.md)
- [vue/valid-define-options](./valid-define-options.md)

## :rocket: Version

Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ module.exports = {
'v-slot-style': require('./rules/v-slot-style'),
'valid-attribute-name': require('./rules/valid-attribute-name'),
'valid-define-emits': require('./rules/valid-define-emits'),
'valid-define-options': require('./rules/valid-define-options'),
'valid-define-props': require('./rules/valid-define-props'),
'valid-model-definition': require('./rules/valid-model-definition'),
'valid-next-tick': require('./rules/valid-next-tick'),
Expand Down
127 changes: 127 additions & 0 deletions lib/rules/valid-define-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* @author Yosuke Ota <https://github.com/ota-meshi>
* See LICENSE file in root directory for full license.
*/
'use strict'

const { findVariable } = require('@eslint-community/eslint-utils')
const utils = require('../utils')

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `defineOptions` compiler macro',
// TODO Switch in the next major version
// categories: ['vue3-essential', 'essential'],
categories: undefined,
url: 'https://eslint.vuejs.org/rules/valid-define-options.html'
},
fixable: null,
schema: [],
messages: {
referencingLocally:
'`defineOptions` are referencing locally declared variables.',
multiple: '`defineOptions` has been called multiple times.',
notDefined: 'Options are not defined.',
disallowProp:
'`defineOptions()` cannot be used to declare `{{propName}}`. Use `{{insteadMacro}}()` instead.',
typeArgs: '`defineOptions()` cannot accept type arguments.'
}
},
/** @param {RuleContext} context */
create(context) {
const scriptSetup = utils.getScriptSetupElement(context)
if (!scriptSetup) {
return {}
}

/** @type {Set<Expression | SpreadElement>} */
const optionsDefExpressions = new Set()
/** @type {CallExpression[]} */
const defineOptionsNodes = []

return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefineOptionsEnter(node) {
defineOptionsNodes.push(node)

if (node.arguments.length > 0) {
const define = node.arguments[0]
if (define.type === 'ObjectExpression') {
for (const [propName, insteadMacro] of [
['props', 'defineProps'],
['emits', 'defineEmits'],
['expose', 'defineExpose'],
['slots', 'defineSlots']
]) {
const prop = utils.findProperty(define, propName)
if (prop) {
context.report({
node,
messageId: 'disallowProp',
data: { propName, insteadMacro }
})
}
}
}

optionsDefExpressions.add(node.arguments[0])
} else {
context.report({
node,
messageId: 'notDefined'
})
}

if (node.typeParameters) {
context.report({
node: node.typeParameters,
messageId: 'typeArgs'
})
}
},
Identifier(node) {
for (const defineOptions of optionsDefExpressions) {
if (utils.inRange(defineOptions.range, node)) {
const variable = findVariable(context.getScope(), node)
if (
variable &&
variable.references.some((ref) => ref.identifier === node) &&
variable.defs.length > 0 &&
variable.defs.every(
(def) =>
def.type !== 'ImportBinding' &&
utils.inRange(scriptSetup.range, def.name) &&
!utils.inRange(defineOptions.range, def.name)
)
) {
if (utils.withinTypeNode(node)) {
continue
}
//`defineOptions` are referencing locally declared variables.
context.report({
node,
messageId: 'referencingLocally'
})
}
}
}
}
}),
{
'Program:exit'() {
if (defineOptionsNodes.length > 1) {
// `defineOptions` has been called multiple times.
for (const node of defineOptionsNodes) {
context.report({
node,
messageId: 'multiple'
})
}
}
}
}
)
}
}
Loading