From 1e7dd6fe060963d659e71344e49804f2aa9b5d3d Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Sat, 13 May 2023 13:04:52 +0900 Subject: [PATCH 1/4] Add `vue/valid-define-options` rule --- docs/rules/index.md | 1 + docs/rules/valid-define-emits.md | 1 + docs/rules/valid-define-options.md | 119 ++++++++++++++ docs/rules/valid-define-props.md | 1 + lib/index.js | 1 + lib/rules/valid-define-options.js | 127 +++++++++++++++ tests/lib/rules/valid-define-options.js | 202 ++++++++++++++++++++++++ 7 files changed, 452 insertions(+) create mode 100644 docs/rules/valid-define-options.md create mode 100644 lib/rules/valid-define-options.js create mode 100644 tests/lib/rules/valid-define-options.js diff --git a/docs/rules/index.md b/docs/rules/index.md index abc3419a4..2580ecaeb 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -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: | diff --git a/docs/rules/valid-define-emits.md b/docs/rules/valid-define-emits.md index f7de8454c..2f1d5e353 100644 --- a/docs/rules/valid-define-emits.md +++ b/docs/rules/valid-define-emits.md @@ -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 diff --git a/docs/rules/valid-define-options.md b/docs/rules/valid-define-options.md new file mode 100644 index 000000000..dadee96b6 --- /dev/null +++ b/docs/rules/valid-define-options.md @@ -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: ***This rule has not been released yet.*** + +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. + + + +```vue + +``` + + + + + +```vue + + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + +## :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) diff --git a/docs/rules/valid-define-props.md b/docs/rules/valid-define-props.md index 4345d2428..260ec39ad 100644 --- a/docs/rules/valid-define-props.md +++ b/docs/rules/valid-define-props.md @@ -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 diff --git a/lib/index.js b/lib/index.js index c7b580868..f8230212e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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'), diff --git a/lib/rules/valid-define-options.js b/lib/rules/valid-define-options.js new file mode 100644 index 000000000..80bb515f0 --- /dev/null +++ b/lib/rules/valid-define-options.js @@ -0,0 +1,127 @@ +/** + * @author Yosuke Ota + * 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} */ + 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' + }) + } + } + } + } + ) + } +} diff --git a/tests/lib/rules/valid-define-options.js b/tests/lib/rules/valid-define-options.js new file mode 100644 index 000000000..fe421b314 --- /dev/null +++ b/tests/lib/rules/valid-define-options.js @@ -0,0 +1,202 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/valid-define-options') + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { ecmaVersion: 2015, sourceType: 'module' } +}) + +tester.run('valid-define-options', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + code: ` + + ` + }, + { + filename: 'test.vue', + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions` are referencing locally declared variables.', + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: '`defineOptions` has been called multiple times.', + line: 3 + }, + { + message: '`defineOptions` has been called multiple times.', + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Options are not defined.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { parser: require.resolve('@typescript-eslint/parser') }, + errors: [ + { + message: 'Options are not defined.', + line: 3 + }, + { + message: '`defineOptions()` cannot accept type arguments.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `props`. Use `defineProps()` instead.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `emits`. Use `defineEmits()` instead.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `expose`. Use `defineExpose()` instead.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `slots`. Use `defineSlots()` instead.', + line: 3 + } + ] + } + ] +}) From 9d5e26cdbe8abd19a0c26360e33e6dbb041bcd3c Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Sat, 13 May 2023 22:46:20 +0900 Subject: [PATCH 2/4] Update lib/rules/valid-define-options.js Co-authored-by: Flo Edelmann --- lib/rules/valid-define-options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/valid-define-options.js b/lib/rules/valid-define-options.js index 80bb515f0..6b1e96185 100644 --- a/lib/rules/valid-define-options.js +++ b/lib/rules/valid-define-options.js @@ -21,7 +21,7 @@ module.exports = { schema: [], messages: { referencingLocally: - '`defineOptions` are referencing locally declared variables.', + '`defineOptions` is referencing locally declared variables.', multiple: '`defineOptions` has been called multiple times.', notDefined: 'Options are not defined.', disallowProp: From 5a70e7b147398e7cd78000ba4bf05ed4529707a0 Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Sat, 13 May 2023 22:49:27 +0900 Subject: [PATCH 3/4] fix sentence --- docs/rules/valid-define-emits.md | 2 +- docs/rules/valid-define-options.md | 2 +- docs/rules/valid-define-props.md | 2 +- lib/rules/valid-define-emits.js | 4 ++-- lib/rules/valid-define-options.js | 2 +- lib/rules/valid-define-props.js | 4 ++-- tests/lib/rules/valid-define-emits.js | 2 +- tests/lib/rules/valid-define-options.js | 2 +- tests/lib/rules/valid-define-props.js | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/rules/valid-define-emits.md b/docs/rules/valid-define-emits.md index 2f1d5e353..0f52775ff 100644 --- a/docs/rules/valid-define-emits.md +++ b/docs/rules/valid-define-emits.md @@ -17,7 +17,7 @@ This rule checks whether `defineEmits` compiler macro is valid. This rule reports `defineEmits` compiler macros in the following cases: -- `defineEmits` are referencing locally declared variables. +- `defineEmits` is referencing locally declared variables. - `defineEmits` has both a literal type and an argument. e.g. `defineEmits<(e: 'foo')=>void>(['bar'])` - `defineEmits` has been called multiple times. - Custom events are defined in both `defineEmits` and `export default {}`. diff --git a/docs/rules/valid-define-options.md b/docs/rules/valid-define-options.md index dadee96b6..2cba98137 100644 --- a/docs/rules/valid-define-options.md +++ b/docs/rules/valid-define-options.md @@ -16,7 +16,7 @@ This rule checks whether `defineOptions` compiler macro is valid. This rule reports `defineOptions` compiler macros in the following cases: -- `defineOptions` are referencing locally declared variables. +- `defineOptions` is referencing locally declared variables. - `defineOptions` has been called multiple times. - Options are not defined in `defineOptions`. - `defineOptions` has type arguments. diff --git a/docs/rules/valid-define-props.md b/docs/rules/valid-define-props.md index 260ec39ad..fc0f707fb 100644 --- a/docs/rules/valid-define-props.md +++ b/docs/rules/valid-define-props.md @@ -17,7 +17,7 @@ This rule checks whether `defineProps` compiler macro is valid. This rule reports `defineProps` compiler macros in the following cases: -- `defineProps` are referencing locally declared variables. +- `defineProps` is referencing locally declared variables. - `defineProps` has both a literal type and an argument. e.g. `defineProps<{/*props*/}>({/*props*/})` - `defineProps` has been called multiple times. - Props are defined in both `defineProps` and `export default {}`. diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js index 239da270d..64d3fd15a 100644 --- a/lib/rules/valid-define-emits.js +++ b/lib/rules/valid-define-emits.js @@ -20,7 +20,7 @@ module.exports = { messages: { hasTypeAndArg: '`defineEmits` has both a type-only emit and an argument.', referencingLocally: - '`defineEmits` are referencing locally declared variables.', + '`defineEmits` is referencing locally declared variables.', multiple: '`defineEmits` has been called multiple times.', notDefined: 'Custom events are not defined.', definedInBoth: @@ -85,7 +85,7 @@ module.exports = { if (utils.withinTypeNode(node)) { continue } - //`defineEmits` are referencing locally declared variables. + //`defineEmits` is referencing locally declared variables. context.report({ node, messageId: 'referencingLocally' diff --git a/lib/rules/valid-define-options.js b/lib/rules/valid-define-options.js index 6b1e96185..784ac3fbd 100644 --- a/lib/rules/valid-define-options.js +++ b/lib/rules/valid-define-options.js @@ -99,7 +99,7 @@ module.exports = { if (utils.withinTypeNode(node)) { continue } - //`defineOptions` are referencing locally declared variables. + //`defineOptions` is referencing locally declared variables. context.report({ node, messageId: 'referencingLocally' diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js index fe471dc15..43cce1d3c 100644 --- a/lib/rules/valid-define-props.js +++ b/lib/rules/valid-define-props.js @@ -21,7 +21,7 @@ module.exports = { hasTypeAndArg: '`defineProps` has both a type-only props and an argument.', referencingLocally: - '`defineProps` are referencing locally declared variables.', + '`defineProps` is referencing locally declared variables.', multiple: '`defineProps` has been called multiple times.', notDefined: 'Props are not defined.', definedInBoth: @@ -86,7 +86,7 @@ module.exports = { if (utils.withinTypeNode(node)) { continue } - //`defineProps` are referencing locally declared variables. + //`defineProps` is referencing locally declared variables. context.report({ node, messageId: 'referencingLocally' diff --git a/tests/lib/rules/valid-define-emits.js b/tests/lib/rules/valid-define-emits.js index 4dc70ca57..50c9047de 100644 --- a/tests/lib/rules/valid-define-emits.js +++ b/tests/lib/rules/valid-define-emits.js @@ -133,7 +133,7 @@ tester.run('valid-define-emits', rule, { `, errors: [ { - message: '`defineEmits` are referencing locally declared variables.', + message: '`defineEmits` is referencing locally declared variables.', line: 5 } ] diff --git a/tests/lib/rules/valid-define-options.js b/tests/lib/rules/valid-define-options.js index fe421b314..04fdc1e74 100644 --- a/tests/lib/rules/valid-define-options.js +++ b/tests/lib/rules/valid-define-options.js @@ -81,7 +81,7 @@ tester.run('valid-define-options', rule, { errors: [ { message: - '`defineOptions` are referencing locally declared variables.', + '`defineOptions` is referencing locally declared variables.', line: 4 } ] diff --git a/tests/lib/rules/valid-define-props.js b/tests/lib/rules/valid-define-props.js index a4c0d3ca2..511e46bbc 100644 --- a/tests/lib/rules/valid-define-props.js +++ b/tests/lib/rules/valid-define-props.js @@ -136,7 +136,7 @@ tester.run('valid-define-props', rule, { `, errors: [ { - message: '`defineProps` are referencing locally declared variables.', + message: '`defineProps` is referencing locally declared variables.', line: 5 } ] From 043a6194e09ad608c759aaffe23bbf3b5dc52126 Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Sat, 13 May 2023 22:52:13 +0900 Subject: [PATCH 4/4] format --- tests/lib/rules/valid-define-options.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/lib/rules/valid-define-options.js b/tests/lib/rules/valid-define-options.js index 04fdc1e74..58d31df68 100644 --- a/tests/lib/rules/valid-define-options.js +++ b/tests/lib/rules/valid-define-options.js @@ -80,8 +80,7 @@ tester.run('valid-define-options', rule, { `, errors: [ { - message: - '`defineOptions` is referencing locally declared variables.', + message: '`defineOptions` is referencing locally declared variables.', line: 4 } ]