From 7907db1e1643a01e488a8f83ebf08118ced68aec Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Wed, 10 Jan 2024 14:40:35 +0900 Subject: [PATCH] Add support for defineModel --- docs/rules/define-macros-order.md | 13 ++-- docs/rules/no-unsupported-features.md | 1 + lib/rules/define-macros-order.js | 12 ++- lib/rules/no-undef-properties.js | 20 +++++ lib/rules/no-unsupported-features.js | 3 + lib/rules/no-unused-emit-declarations.js | 11 +++ lib/rules/no-unused-properties.js | 35 ++++++++- lib/rules/require-prop-types.js | 57 +++++++++----- lib/rules/syntaxes/define-model.js | 22 ++++++ lib/utils/index.js | 53 +++++++++++++ tests/lib/rules/define-macros-order.js | 34 ++++++++- tests/lib/rules/no-undef-properties.js | 27 +++++++ .../no-unsupported-features/define-model.js | 59 +++++++++++++++ .../lib/rules/no-unused-emit-declarations.js | 26 +++++++ tests/lib/rules/no-unused-properties.js | 35 +++++++++ tests/lib/rules/require-prop-types.js | 74 +++++++++++++++++++ typings/eslint-plugin-vue/util-types/utils.ts | 13 ++++ 17 files changed, 465 insertions(+), 30 deletions(-) create mode 100644 lib/rules/syntaxes/define-model.js create mode 100644 tests/lib/rules/no-unsupported-features/define-model.js diff --git a/docs/rules/define-macros-order.md b/docs/rules/define-macros-order.md index d46fc5108..7df8f42c7 100644 --- a/docs/rules/define-macros-order.md +++ b/docs/rules/define-macros-order.md @@ -27,7 +27,7 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they } ``` -- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"` and `"defineSlots"`. +- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"`, `"defineSlots"`, and `"defineModel"`. - `defineExposeLast` (`boolean`) ... Force `defineExpose` at the end. ### `{ "order": ["defineProps", "defineEmits"] }` (default) @@ -69,14 +69,15 @@ defineEmits(/* ... */) -### `{ "order": ["defineOptions", "defineProps", "defineEmits", "defineSlots"] }` +### `{ "order": ["defineOptions", "defineModel", "defineProps", "defineEmits", "defineSlots"] }` - + ```vue ``` - + ```vue `, output: ` @@ -618,6 +628,8 @@ tester.run('define-macros-order', rule, { import Foo from 'foo' /** options */ defineOptions({}) + /** model */ + const model = defineModel() /** emits */ defineEmits(['update:foo']) /** props */ @@ -629,7 +641,13 @@ tester.run('define-macros-order', rule, { `, options: [ { - order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots'] + order: [ + 'defineOptions', + 'defineModel', + 'defineEmits', + 'defineProps', + 'defineSlots' + ] } ], errors: [ @@ -651,12 +669,16 @@ tester.run('define-macros-order', rule, { defineEmits(['update:foo']) /** props */ const props = defineProps(['foo']) + /** model */ + const model = defineModel() `, output: ` + + `, + errors: [ + { + message: "'undef' is not defined.", + line: 14 + } + ] } ] }) diff --git a/tests/lib/rules/no-unsupported-features/define-model.js b/tests/lib/rules/no-unsupported-features/define-model.js new file mode 100644 index 000000000..0307d1f9f --- /dev/null +++ b/tests/lib/rules/no-unsupported-features/define-model.js @@ -0,0 +1,59 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../../lib/rules/no-unsupported-features') +const utils = require('./utils') + +const buildOptions = utils.optionsBuilder('define-model', '^3.3.0') +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 2019 + } +}) + +tester.run('no-unsupported-features/define-model', rule, { + valid: [ + { + code: ` + `, + options: buildOptions({ version: '^3.4.0' }) + }, + { + code: ` + `, + options: buildOptions() + }, + { + code: ` + `, + options: buildOptions({ version: '^3.3.0', ignores: ['define-model'] }) + } + ], + invalid: [ + { + code: ` + `, + options: buildOptions(), + errors: [ + { + message: + '`defineModel()` macros are not supported until Vue.js "3.4.0".', + line: 3 + } + ] + } + ] +}) diff --git a/tests/lib/rules/no-unused-emit-declarations.js b/tests/lib/rules/no-unused-emit-declarations.js index 10bf3b33a..f3189ea82 100644 --- a/tests/lib/rules/no-unused-emit-declarations.js +++ b/tests/lib/rules/no-unused-emit-declarations.js @@ -357,6 +357,16 @@ tester.run('no-unused-emit-declarations', rule, { const change = () => emit('foo'); `, ...getTypeScriptFixtureTestOptions() + }, + { + // defineModel + filename: 'test.vue', + code: ` + + ` } ], invalid: [ @@ -718,6 +728,22 @@ tester.run('no-unused-emit-declarations', rule, { } ], ...getTypeScriptFixtureTestOptions() + }, + { + // defineModel + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: '`update:foo` is defined as emit but never used.', + line: 3 + } + ] } ] }) diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js index f71a26519..e47f346aa 100644 --- a/tests/lib/rules/no-unused-properties.js +++ b/tests/lib/rules/no-unused-properties.js @@ -2152,6 +2152,19 @@ tester.run('no-unused-properties', rule, { }, }; ` + }, + { + // defineModel + filename: 'test.vue', + code: ` + + + ` } ], invalid: [ @@ -3811,6 +3824,28 @@ tester.run('no-unused-properties', rule, { } ], ...getTypeScriptFixtureTestOptions() + }, + + { + // defineModel + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'unused' of property found, but never used.", + line: 6 + } + ] } ] }) diff --git a/tests/lib/rules/require-prop-types.js b/tests/lib/rules/require-prop-types.js index fdb91dd30..f50b4b116 100644 --- a/tests/lib/rules/require-prop-types.js +++ b/tests/lib/rules/require-prop-types.js @@ -187,6 +187,40 @@ ruleTester.run('require-prop-types', rule, { defineProps() `, ...getTypeScriptFixtureTestOptions() + }, + { + // defineModel + code: ` + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + // defineModel + code: ` + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + parser: require.resolve('@typescript-eslint/parser') + } } ], @@ -368,6 +402,46 @@ ruleTester.run('require-prop-types', rule, { line: 3 } ] + }, + { + // defineModel + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + errors: [ + { + message: 'Prop "modelValue" should define at least its type.', + line: 3 + }, + { + message: 'Prop "foo" should define at least its type.', + line: 4 + } + ] + }, + { + // defineModel + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + errors: [ + { + message: 'Prop "modelValue" should define at least its type.', + line: 3 + }, + { + message: 'Prop "foo" should define at least its type.', + line: 4 + } + ] } ] }) diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts index 4d8384d66..3e9184262 100644 --- a/typings/eslint-plugin-vue/util-types/utils.ts +++ b/typings/eslint-plugin-vue/util-types/utils.ts @@ -46,10 +46,13 @@ export interface ScriptSetupVisitor extends ScriptSetupVisitorBase { onDefineSlotsExit?(node: CallExpression): void onDefineExposeEnter?(node: CallExpression): void onDefineExposeExit?(node: CallExpression): void + onDefineModelEnter?(node: CallExpression, model: ComponentModel): void + onDefineModelExit?(node: CallExpression, model: ComponentModel): void [query: string]: | ((node: VAST.ParamNode) => void) | ((node: CallExpression, props: ComponentProp[]) => void) | ((node: CallExpression, emits: ComponentEmit[]) => void) + | ((node: CallExpression, model: ComponentModel) => void) | undefined } @@ -187,3 +190,13 @@ export type ComponentEmit = | ComponentTypeEmit | ComponentInferTypeEmit | ComponentUnknownEmit + +export type ComponentModelName = { + modelName: string + node: Literal | null +} +export type ComponentModel = { + name: ComponentModelName + options: Expression | null + typeNode: TypeNode | null +}