Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vue/valid-define-options rule #2165

Merged
merged 4 commits into from
May 13, 2023
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.',
ota-meshi marked this conversation as resolved.
Show resolved Hide resolved
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