diff --git a/docs/rules/README.md b/docs/rules/README.md index de00e7e53..8ae175625 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -212,7 +212,9 @@ For example: | [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: | :hammer: | | [vue/component-options-name-casing](./component-options-name-casing.md) | enforce the casing of component name in `components` options | :wrench::bulb: | :hammer: | | [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name | | :hammer: | +| [vue/define-emits-declaration](./define-emits-declaration.md) | enforce declaration style of `defineEmits` | | :hammer: | | [vue/define-macros-order](./define-macros-order.md) | enforce order of `defineEmits` and `defineProps` compiler macros | :wrench: | :lipstick: | +| [vue/define-props-declaration](./define-props-declaration.md) | enforce declaration style of `defineProps` | | :hammer: | | [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | | :hammer: | | [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: | :lipstick: | | [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: | :lipstick: | diff --git a/docs/rules/define-emits-declaration.md b/docs/rules/define-emits-declaration.md new file mode 100644 index 000000000..50af9f951 --- /dev/null +++ b/docs/rules/define-emits-declaration.md @@ -0,0 +1,78 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/define-emits-declaration +description: enforce declaration style of `defineEmits` +--- +# vue/define-emits-declaration + +> enforce declaration style of `defineEmits` + +- :exclamation: ***This rule has not been released yet.*** + +## :book: Rule Details + +This rule enforces `defineEmits` typing style which you should use `type-based` or `runtime` declaration. + +This rule only works in setup script and `lang="ts"`. + + + +```vue + +``` + + + +## :wrench: Options + +```json + "vue/define-emits-declaration": ["error", "type-based" | "runtime"] +``` + +- `type-based` (default) enforces type-based declaration +- `runtime` enforces runtime declaration + +### `runtime` + + + +```vue + +``` + + + +## :couple: Related Rules + +- [vue/define-props-declaration](./define-props-declaration.md) +- [vue/valid-define-emits](./valid-define-emits.md) + +## :books: Further Reading + +- [`defineEmits`](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) +- [Typescript-only-features of `defineEmits`](https://vuejs.org/api/sfc-script-setup.html#typescript-only-features) +- [Guide - Typing-component-emits](https://vuejs.org/guide/typescript/composition-api.html#typing-component-emits) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-emits-declaration.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-emits-declaration.js) diff --git a/docs/rules/define-props-declaration.md b/docs/rules/define-props-declaration.md new file mode 100644 index 000000000..db68791fb --- /dev/null +++ b/docs/rules/define-props-declaration.md @@ -0,0 +1,79 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/define-props-declaration +description: enforce declaration style of `defineProps` +--- +# vue/define-props-declaration + +> enforce declaration style of `defineProps` + +- :exclamation: ***This rule has not been released yet.*** + +## :book: Rule Details + +This rule enforces `defineProps` typing style which you should use `type-based` or `runtime` declaration. + +This rule only works in setup script and `lang="ts"`. + + + +```vue + +``` + + + +## :wrench: Options + +```json + "vue/define-props-declaration": ["error", "type-based" | "runtime"] +``` + +- `type-based` (default) enforces type-based declaration +- `runtime` enforces runtime declaration + +### `"runtime"` + + + +```vue + +``` + + + +## :couple: Related Rules + +- [vue/define-emits-declaration](./define-emits-declaration.md) +- [vue/valid-define-props](./valid-define-props.md) + +## :books: Further Reading + +- [`defineProps`](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits) +- [Typescript-only-features of `defineProps`](https://vuejs.org/api/sfc-script-setup.html#typescript-only-features) +- [Guide - Typing-component-props](https://vuejs.org/guide/typescript/composition-api.html#typing-component-props) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-props-declaration.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-declaration.js) diff --git a/docs/rules/valid-define-emits.md b/docs/rules/valid-define-emits.md index 007a584e7..f7de8454c 100644 --- a/docs/rules/valid-define-emits.md +++ b/docs/rules/valid-define-emits.md @@ -27,8 +27,8 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -38,8 +38,8 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -49,8 +49,8 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -60,11 +60,11 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -74,9 +74,9 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -86,8 +86,8 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -97,9 +97,9 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -109,13 +109,13 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -125,8 +125,8 @@ This rule reports `defineEmits` compiler macros in the following cases: ```vue ``` @@ -136,6 +136,11 @@ This rule reports `defineEmits` compiler macros in the following cases: Nothing. +## :couple: Related Rules + +- [vue/define-emits-declaration](./define-emits-declaration.md) +- [vue/valid-define-props](./valid-define-props.md) + ## :rocket: Version This rule was introduced in eslint-plugin-vue v7.13.0 diff --git a/docs/rules/valid-define-props.md b/docs/rules/valid-define-props.md index 960573bb0..4345d2428 100644 --- a/docs/rules/valid-define-props.md +++ b/docs/rules/valid-define-props.md @@ -27,8 +27,8 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -38,8 +38,8 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -49,8 +49,8 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -60,11 +60,11 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -74,9 +74,9 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -86,8 +86,8 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -97,9 +97,9 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -109,13 +109,13 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -125,8 +125,8 @@ This rule reports `defineProps` compiler macros in the following cases: ```vue ``` @@ -136,6 +136,11 @@ This rule reports `defineProps` compiler macros in the following cases: Nothing. +## :couple: Related Rules + +- [vue/define-props-declaration](./define-props-declaration.md) +- [vue/valid-define-emits](./valid-define-emits.md) + ## :rocket: Version This rule was introduced in eslint-plugin-vue v7.13.0 diff --git a/lib/index.js b/lib/index.js index ca3a93084..04afd4af3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -27,7 +27,9 @@ module.exports = { 'component-options-name-casing': require('./rules/component-options-name-casing'), 'component-tags-order': require('./rules/component-tags-order'), 'custom-event-name-casing': require('./rules/custom-event-name-casing'), + 'define-emits-declaration': require('./rules/define-emits-declaration'), 'define-macros-order': require('./rules/define-macros-order'), + 'define-props-declaration': require('./rules/define-props-declaration'), 'dot-location': require('./rules/dot-location'), 'dot-notation': require('./rules/dot-notation'), eqeqeq: require('./rules/eqeqeq'), diff --git a/lib/rules/define-emits-declaration.js b/lib/rules/define-emits-declaration.js new file mode 100644 index 000000000..758097f9a --- /dev/null +++ b/lib/rules/define-emits-declaration.js @@ -0,0 +1,68 @@ +/** + * @author Amorites + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'enforce declaration style of `defineEmits`', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/define-emits-declaration.html' + }, + fixable: null, + messages: { + hasArg: 'Use type-based declaration instead of runtime declaration.', + hasTypeArg: 'Use runtime declaration instead of type-based declaration.' + }, + schema: [ + { + enum: ['type-based', 'runtime'] + } + ] + }, + /** @param {RuleContext} context */ + create(context) { + const scriptSetup = utils.getScriptSetupElement(context) + if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) { + return {} + } + + const defineType = context.options[0] || 'type-based' + return utils.defineScriptSetupVisitor(context, { + onDefineEmitsEnter(node) { + switch (defineType) { + case 'type-based': + if (node.arguments.length > 0) { + context.report({ + node, + messageId: 'hasArg' + }) + } + break + + case 'runtime': + if (node.typeParameters && node.typeParameters.params.length > 0) { + context.report({ + node, + messageId: 'hasTypeArg' + }) + } + break + } + } + }) + } +} diff --git a/lib/rules/define-props-declaration.js b/lib/rules/define-props-declaration.js new file mode 100644 index 000000000..2a73ed10a --- /dev/null +++ b/lib/rules/define-props-declaration.js @@ -0,0 +1,68 @@ +/** + * @author Amorites + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'enforce declaration style of `defineProps`', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/define-props-declaration.html' + }, + fixable: null, + messages: { + hasArg: 'Use type-based declaration instead of runtime declaration.', + hasTypeArg: 'Use runtime declaration instead of type-based declaration.' + }, + schema: [ + { + enum: ['type-based', 'runtime'] + } + ] + }, + /** @param {RuleContext} context */ + create(context) { + const scriptSetup = utils.getScriptSetupElement(context) + if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) { + return {} + } + + const defineType = context.options[0] || 'type-based' + return utils.defineScriptSetupVisitor(context, { + onDefinePropsEnter(node) { + switch (defineType) { + case 'type-based': + if (node.arguments.length > 0) { + context.report({ + node, + messageId: 'hasArg' + }) + } + break + + case 'runtime': + if (node.typeParameters && node.typeParameters.params.length > 0) { + context.report({ + node, + messageId: 'hasTypeArg' + }) + } + break + } + } + }) + } +} diff --git a/tests/lib/rules/define-emits-declaration.js b/tests/lib/rules/define-emits-declaration.js new file mode 100644 index 000000000..279d80497 --- /dev/null +++ b/tests/lib/rules/define-emits-declaration.js @@ -0,0 +1,151 @@ +/** + * @author Amorites + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/define-emits-declaration') + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('define-emits-declaration', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + options: ['type-based'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['runtime'] + }, + { + filename: 'test.vue', + // ignore code without defineEmits + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ], + options: ['type-based'] + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + errors: [ + { + message: 'Use runtime declaration instead of type-based declaration.', + line: 3 + } + ], + options: ['runtime'] + } + ] +}) diff --git a/tests/lib/rules/define-props-declaration.js b/tests/lib/rules/define-props-declaration.js new file mode 100644 index 000000000..67ea338ec --- /dev/null +++ b/tests/lib/rules/define-props-declaration.js @@ -0,0 +1,157 @@ +/** + * @author Amorites + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/define-props-declaration') + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('define-props-declaration', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + options: ['type-based'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['runtime'] + }, + { + filename: 'test.vue', + // ignore script without lang="ts" + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + }, + { + filename: 'test.vue', + // ignore non-setup script + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ], + options: ['type-based'] + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + options: ['runtime'], + errors: [ + { + message: 'Use runtime declaration instead of type-based declaration.', + line: 3 + } + ] + } + ] +})