-
-
-
-
-
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
deleted file mode 100644
index fb7719ff3..000000000
--- a/docs/.vuepress/config.js
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * @author Toru Nagashima
- * See LICENSE file in root directory for full license.
- */
-'use strict'
-
-const rules = require('../../tools/lib/rules')
-const path = require('path')
-
-const uncategorizedRules = rules.filter(
- (rule) =>
- !rule.meta.docs.categories &&
- !rule.meta.docs.extensionRule &&
- !rule.meta.deprecated
-)
-const uncategorizedExtensionRule = rules.filter(
- (rule) =>
- !rule.meta.docs.categories &&
- rule.meta.docs.extensionRule &&
- !rule.meta.deprecated
-)
-const deprecatedRules = rules.filter((rule) => rule.meta.deprecated)
-
-const sidebarCategories = [
- { title: 'Base Rules', categoryIds: ['base'] },
- {
- title: 'Priority A: Essential',
- categoryIds: ['vue3-essential', 'essential']
- },
- {
- title: 'Priority A: Essential for Vue.js 3.x',
- categoryIds: ['vue3-essential']
- },
- { title: 'Priority A: Essential for Vue.js 2.x', categoryIds: ['essential'] },
- {
- title: 'Priority B: Strongly Recommended',
- categoryIds: ['vue3-strongly-recommended', 'strongly-recommended']
- },
- {
- title: 'Priority B: Strongly Recommended for Vue.js 3.x',
- categoryIds: ['vue3-strongly-recommended']
- },
- {
- title: 'Priority B: Strongly Recommended for Vue.js 2.x',
- categoryIds: ['strongly-recommended']
- },
- {
- title: 'Priority C: Recommended',
- categoryIds: ['vue3-recommended', 'recommended']
- },
- {
- title: 'Priority C: Recommended for Vue.js 3.x',
- categoryIds: ['vue3-recommended']
- },
- {
- title: 'Priority C: Recommended for Vue.js 2.x',
- categoryIds: ['recommended']
- }
-]
-
-const categorizedRules = []
-for (const { title, categoryIds } of sidebarCategories) {
- const categoryRules = rules
- .filter((rule) => rule.meta.docs.categories && !rule.meta.deprecated)
- .filter((rule) =>
- categoryIds.every((categoryId) =>
- rule.meta.docs.categories.includes(categoryId)
- )
- )
- const children = categoryRules
- .filter(({ ruleId }) => {
- const exists = categorizedRules.some(({ children }) =>
- children.some(([, alreadyRuleId]) => alreadyRuleId === ruleId)
- )
- return !exists
- })
- .map(({ ruleId, name }) => [`/rules/${name}`, ruleId])
-
- if (children.length === 0) {
- continue
- }
- categorizedRules.push({
- title,
- collapsable: false,
- children
- })
-}
-
-const extraCategories = []
-if (uncategorizedRules.length > 0) {
- extraCategories.push({
- title: 'Uncategorized',
- collapsable: false,
- children: uncategorizedRules.map(({ ruleId, name }) => [
- `/rules/${name}`,
- ruleId
- ])
- })
-}
-if (uncategorizedExtensionRule.length > 0) {
- extraCategories.push({
- title: 'Extension Rules',
- collapsable: false,
- children: uncategorizedExtensionRule.map(({ ruleId, name }) => [
- `/rules/${name}`,
- ruleId
- ])
- })
-}
-if (deprecatedRules.length > 0) {
- extraCategories.push({
- title: 'Deprecated',
- collapsable: false,
- children: deprecatedRules.map(({ ruleId, name }) => [
- `/rules/${name}`,
- ruleId
- ])
- })
-}
-
-module.exports = {
- configureWebpack(_config, _isServer) {
- return {
- resolve: {
- alias: {
- module: require.resolve('./shim/module'),
- eslint: path.resolve(__dirname, './shim/eslint')
- }
- }
- }
- },
-
- base: '/',
- title: 'eslint-plugin-vue',
- description: 'Official ESLint plugin for Vue.js',
- evergreen: true,
- head: [['link', { rel: 'icon', href: '/favicon.png' }]],
-
- plugins: {
- '@vuepress/pwa': {
- serviceWorker: true,
- updatePopup: true
- }
- },
-
- themeConfig: {
- repo: 'vuejs/eslint-plugin-vue',
- docsRepo: 'vuejs/eslint-plugin-vue',
- docsDir: 'docs',
- docsBranch: 'master',
- editLinks: true,
- lastUpdated: true,
-
- nav: [
- { text: 'User Guide', link: '/user-guide/' },
- { text: 'Developer Guide', link: '/developer-guide/' },
- { text: 'Rules', link: '/rules/' },
- { text: 'Demo', link: 'https://mysticatea.github.io/vue-eslint-demo' }
- ],
-
- sidebar: {
- '/rules/': [
- '/rules/',
-
- // Rules in each category.
- ...categorizedRules,
-
- // Rules in no category.
- ...extraCategories
- ],
-
- '/': ['/', '/user-guide/', '/developer-guide/', '/rules/']
- },
-
- algolia: {
- apiKey: 'b2b69365da747a9a9635cda391317c36',
- indexName: 'eslint-plugin-vue'
- }
- }
-}
diff --git a/docs/.vuepress/shim/eslint/index.js b/docs/.vuepress/shim/eslint/index.js
deleted file mode 100644
index e32cb598f..000000000
--- a/docs/.vuepress/shim/eslint/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-const Linter = require('eslint4b')
-module.exports = { Linter }
diff --git a/docs/.vuepress/shim/module.js b/docs/.vuepress/shim/module.js
deleted file mode 100644
index 66ab9785e..000000000
--- a/docs/.vuepress/shim/module.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- createRequire: () => () => null
-}
diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl
deleted file mode 100644
index 48680bbf3..000000000
--- a/docs/.vuepress/styles/index.styl
+++ /dev/null
@@ -1,20 +0,0 @@
-.theme-container.rule-details .theme-default-content > h1 {
- font-size: 1.8rem;
-
- + blockquote {
- margin-top: -15px;
- padding: 0;
- border: 0;
- font-weight: 500;
- font-size: 1.4rem;
- color: currentColor;
-
- ::first-letter {
- text-transform: uppercase;
- }
-
- p {
- line-height: 1.2;
- }
- }
-}
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index e19258040..000000000
--- a/docs/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-sidebarDepth: 0
----
-
-# Introduction
-
-Official ESLint plugin for Vue.js.
-
-This plugin allows us to check the `` and `
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+Specify the block name for the key of the option object.
+You can use the object as a value and use the following properties:
+
+- `lang` ... Specifies the available value for the `lang` attribute of the block. If multiple languages are available, specify them as an array. If you do not specify it, will disallow any language.
+- `allowNoLang` ... If `true`, allows the `lang` attribute not to be specified (allows the use of the default language of block).
+
+::: warning Note
+If the default language is specified for `lang` option of ``, `
+```
+
+
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+...
+```
+
+
+
+### `{ "order": ["template", "script", "style"] }`
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+
+
+```vue
+
+
+...
+
+```
+
+
+
+### `{ "order": ["docs", "template", "script", "style"] }`
+
+
+
+```vue
+
+ documentation
+...
+
+
+```
+
+
+
+
+
+```vue
+
+...
+
+ documentation
+
+```
+
+
+
+### `{ 'order': ['template', 'script:not([setup])', 'script[setup]'] }`
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+### `{ 'order': ['template', 'style:not([scoped])', 'style[scoped]'] }`
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+### `{ 'order': ['template', 'i18n:not([locale=en])', 'i18n[locale=en]'] }`
+
+
+
+```vue
+
+...
+/* ... */
+/* ... */
+```
+
+
+
+
+
+```vue
+
+...
+/* ... */
+/* ... */
+```
+
+
+
+## :books: Further Reading
+
+- [Style guide - Single-file component top-level element order](https://vuejs.org/style-guide/rules-recommended.html#single-file-component-top-level-element-order)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.16.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/block-order.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/block-order.js)
diff --git a/docs/rules/block-spacing.md b/docs/rules/block-spacing.md
index 72a4d495c..e9f16b8d0 100644
--- a/docs/rules/block-spacing.md
+++ b/docs/rules/block-spacing.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/block-spacing
-description: disallow or enforce spaces inside of blocks after opening block and before closing block
+description: Disallow or enforce spaces inside of blocks after opening block and before closing block in ``
since: v5.2.0
---
+
# vue/block-spacing
-> disallow or enforce spaces inside of blocks after opening block and before closing block
+> Disallow or enforce spaces inside of blocks after opening block and before closing block in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/block-spacing] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [block-spacing] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/block-spacing]
- [block-spacing]
+[@stylistic/block-spacing]: https://eslint.style/rules/default/block-spacing
[block-spacing]: https://eslint.org/docs/rules/block-spacing
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v5.2.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/block-spacing.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/block-spacing.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/block-spacing)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/block-spacing)
diff --git a/docs/rules/block-tag-newline.md b/docs/rules/block-tag-newline.md
index 59d43a14b..8e336b284 100644
--- a/docs/rules/block-tag-newline.md
+++ b/docs/rules/block-tag-newline.md
@@ -5,11 +5,12 @@ title: vue/block-tag-newline
description: enforce line breaks after opening and before closing block-level tags
since: v7.1.0
---
+
# vue/block-tag-newline
> enforce line breaks after opening and before closing block-level tags
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
diff --git a/docs/rules/brace-style.md b/docs/rules/brace-style.md
index 99bf8e0ce..1727f42fb 100644
--- a/docs/rules/brace-style.md
+++ b/docs/rules/brace-style.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/brace-style
-description: enforce consistent brace style for blocks
+description: Enforce consistent brace style for blocks in ``
since: v5.2.0
---
+
# vue/brace-style
-> enforce consistent brace style for blocks
+> Enforce consistent brace style for blocks in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/brace-style] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [brace-style] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/brace-style]
- [brace-style]
+[@stylistic/brace-style]: https://eslint.style/rules/default/brace-style
[brace-style]: https://eslint.org/docs/rules/brace-style
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v5.2.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/brace-style.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/brace-style.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/brace-style)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/brace-style)
diff --git a/docs/rules/camelcase.md b/docs/rules/camelcase.md
index 2ee39bdcc..6ed97876a 100644
--- a/docs/rules/camelcase.md
+++ b/docs/rules/camelcase.md
@@ -2,12 +2,13 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/camelcase
-description: enforce camelcase naming convention
+description: Enforce camelcase naming convention in ``
since: v5.2.0
---
+
# vue/camelcase
-> enforce camelcase naming convention
+> Enforce camelcase naming convention in ``
This rule is the same rule as core [camelcase] rule but it applies to the expressions in ``.
@@ -26,4 +27,4 @@ This rule was introduced in eslint-plugin-vue v5.2.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/camelcase.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/camelcase.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/camelcase)
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/camelcase)
diff --git a/docs/rules/comma-dangle.md b/docs/rules/comma-dangle.md
index 1ef275f42..aceaa6dc1 100644
--- a/docs/rules/comma-dangle.md
+++ b/docs/rules/comma-dangle.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/comma-dangle
-description: require or disallow trailing commas
+description: Require or disallow trailing commas in ``
since: v5.2.0
---
+
# vue/comma-dangle
-> require or disallow trailing commas
+> Require or disallow trailing commas in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/comma-dangle] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [comma-dangle] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/comma-dangle]
- [comma-dangle]
+[@stylistic/comma-dangle]: https://eslint.style/rules/default/comma-dangle
[comma-dangle]: https://eslint.org/docs/rules/comma-dangle
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v5.2.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comma-dangle.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comma-dangle.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/comma-dangle)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/comma-dangle)
diff --git a/docs/rules/comma-spacing.md b/docs/rules/comma-spacing.md
index f3531c8cf..b585fadf7 100644
--- a/docs/rules/comma-spacing.md
+++ b/docs/rules/comma-spacing.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/comma-spacing
-description: enforce consistent spacing before and after commas
+description: Enforce consistent spacing before and after commas in ``
since: v7.0.0
---
+
# vue/comma-spacing
-> enforce consistent spacing before and after commas
+> Enforce consistent spacing before and after commas in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/comma-spacing] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [comma-spacing] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/comma-spacing]
- [comma-spacing]
+[@stylistic/comma-spacing]: https://eslint.style/rules/default/comma-spacing
[comma-spacing]: https://eslint.org/docs/rules/comma-spacing
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comma-spacing.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comma-spacing.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/comma-spacing)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/comma-spacing)
diff --git a/docs/rules/comma-style.md b/docs/rules/comma-style.md
index 2dfb24660..9e08fdaaf 100644
--- a/docs/rules/comma-style.md
+++ b/docs/rules/comma-style.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/comma-style
-description: enforce consistent comma style
+description: Enforce consistent comma style in ``
since: v7.0.0
---
+
# vue/comma-style
-> enforce consistent comma style
+> Enforce consistent comma style in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/comma-style] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [comma-style] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/comma-style]
- [comma-style]
+[@stylistic/comma-style]: https://eslint.style/rules/default/comma-style
[comma-style]: https://eslint.org/docs/rules/comma-style
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comma-style.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comma-style.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/comma-style)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/js/comma-style)
diff --git a/docs/rules/comment-directive.md b/docs/rules/comment-directive.md
index 77c3986c5..294dcc15c 100644
--- a/docs/rules/comment-directive.md
+++ b/docs/rules/comment-directive.md
@@ -5,11 +5,12 @@ title: vue/comment-directive
description: support comment-directives in ``
since: v4.1.0
---
+
# vue/comment-directive
> support comment-directives in ``
-- :gear: This rule is included in all of `"plugin:vue/base"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-essential"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/base"`, `*.configs["flat/base"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-recommended"`, `*.configs["flat/vue2-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
Sole purpose of this rule is to provide `eslint-disable` functionality in the `` and in the block level.
It supports usage of the following comments:
diff --git a/docs/rules/component-api-style.md b/docs/rules/component-api-style.md
new file mode 100644
index 000000000..7a81176c9
--- /dev/null
+++ b/docs/rules/component-api-style.md
@@ -0,0 +1,149 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/component-api-style
+description: enforce component API style
+since: v7.18.0
+---
+
+# vue/component-api-style
+
+> enforce component API style
+
+## :book: Rule Details
+
+This rule aims to make the API style you use to define Vue components consistent in your project.
+
+For example, if you want to allow only `
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/component-api-style": ["error",
+ ["script-setup", "composition"] // "script-setup", "composition", "composition-vue2", or "options"
+ ]
+}
+```
+
+- Array options ... Defines the API styles you want to allow. Default is `["script-setup", "composition"]`. You can use the following values.
+ - `"script-setup"` ... If set, allows [`
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.18.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-api-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-api-style.js)
diff --git a/docs/rules/component-definition-name-casing.md b/docs/rules/component-definition-name-casing.md
index 5e89ace75..18c625970 100644
--- a/docs/rules/component-definition-name-casing.md
+++ b/docs/rules/component-definition-name-casing.md
@@ -5,12 +5,13 @@ title: vue/component-definition-name-casing
description: enforce specific casing for component definition name
since: v7.0.0
---
+
# vue/component-definition-name-casing
> enforce specific casing for component definition name
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
Define a style for component definition name casing for consistency purposes.
@@ -31,7 +32,7 @@ Default casing is set to `PascalCase`.
- `"PascalCase"` (default) ... enforce component definition names to pascal case.
- `"kebab-case"` ... enforce component definition names to kebab case.
-### `"PascalCase" (default)
+### `"PascalCase"` (default)
@@ -63,19 +64,15 @@ export default {
```js
/* ✓ GOOD */
-Vue.component('MyComponent', {
-
-})
+Vue.component('MyComponent', {})
/* ✗ BAD */
-Vue.component('my-component', {
-
-})
+Vue.component('my-component', {})
```
-### `"kebab-case"
+### `"kebab-case"`
@@ -107,21 +104,17 @@ export default {
```js
/* ✓ GOOD */
-Vue.component('my-component', {
-
-})
+Vue.component('my-component', {})
/* ✗ BAD */
-Vue.component('MyComponent', {
-
-})
+Vue.component('MyComponent', {})
```
## :books: Further Reading
-- [Style guide - Component name casing in JS/JSX](https://v3.vuejs.org/style-guide/#component-name-casing-in-js-jsx-strongly-recommended)
+- [Style guide - Component name casing in JS/JSX](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-name-casing-in-js-jsx)
## :rocket: Version
diff --git a/docs/rules/component-name-in-template-casing.md b/docs/rules/component-name-in-template-casing.md
index a8bbc7745..dd59d40e4 100644
--- a/docs/rules/component-name-in-template-casing.md
+++ b/docs/rules/component-name-in-template-casing.md
@@ -5,11 +5,12 @@ title: vue/component-name-in-template-casing
description: enforce specific casing for the component naming style in template
since: v5.0.0
---
+
# vue/component-name-in-template-casing
> enforce specific casing for the component naming style in template
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
Define a style for the component name in template casing for consistency purposes.
@@ -33,6 +34,7 @@ This rule aims to warn the tag names other than the configured casing in Vue.js
- `registeredComponentsOnly` ... If `true`, only registered components (in PascalCase) are checked. If `false`, check all.
default `true`
- `ignores` (`string[]`) ... The element names to ignore. Sets the element name to allow. For example, custom elements or Vue components with special name. You can set the regexp by writing it like `"/^name/"`.
+- `globals` (`string[]`) ... Globally registered component names to check. For example, `RouterView` and `RouterLink` are globally registered by `vue-router` and can't be detected as registered in a SFC file.
### `"PascalCase", { registeredComponentsOnly: true }` (default)
@@ -42,7 +44,7 @@ This rule aims to warn the tag names other than the configured casing in Vue.js
-
+
@@ -60,7 +62,7 @@ export default {
components: {
CoolComponent,
'registered-in-kebab-case': VueComponent1,
- 'registeredInCamelCase': VueComponent2
+ registeredInCamelCase: VueComponent2
}
}
@@ -106,7 +108,7 @@ export default {
-
+
@@ -129,11 +131,11 @@ export default {
```vue
-
+
-
+
@@ -141,9 +143,25 @@ export default {
+### `"PascalCase", { globals: ["RouterView"] }`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
## :books: Further Reading
-- [Style guide - Component name casing in templates](https://v3.vuejs.org/style-guide/#component-name-casing-in-templates-strongly-recommended)
+- [Style guide - Component name casing in templates](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-name-casing-in-templates)
## :rocket: Version
diff --git a/docs/rules/component-options-name-casing.md b/docs/rules/component-options-name-casing.md
new file mode 100644
index 000000000..469865478
--- /dev/null
+++ b/docs/rules/component-options-name-casing.md
@@ -0,0 +1,170 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/component-options-name-casing
+description: enforce the casing of component name in `components` options
+since: v8.2.0
+---
+
+# vue/component-options-name-casing
+
+> enforce the casing of component name in `components` options
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule aims to enforce casing of the component names in `components` options.
+
+## :wrench: Options
+
+```json
+{
+ "vue/component-options-name-casing": ["error", "PascalCase" | "kebab-case" | "camelCase"]
+}
+```
+
+This rule has an option which can be one of these values:
+
+- `"PascalCase"` (default) ... enforce component names to pascal case.
+- `"kebab-case"` ... enforce component names to kebab case.
+- `"camelCase"` ... enforce component names to camel case.
+
+Please note that if you use kebab case in `components` options,
+you can **only** use kebab case in template;
+and if you use camel case in `components` options,
+you **can't** use pascal case in template.
+
+For demonstration, the code example is invalid:
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+### `"PascalCase"` (default)
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+### `"kebab-case"`
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+### `"camelCase"`
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-options-name-casing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-options-name-casing.js)
diff --git a/docs/rules/component-tags-order.md b/docs/rules/component-tags-order.md
index e0c0c99f3..a5037cdee 100644
--- a/docs/rules/component-tags-order.md
+++ b/docs/rules/component-tags-order.md
@@ -5,15 +5,16 @@ title: vue/component-tags-order
description: enforce order of component top-level elements
since: v6.1.0
---
+
# vue/component-tags-order
> enforce order of component top-level elements
-- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :no_entry: This rule was **removed** in eslint-plugin-vue v10.0.0 and replaced by [vue/block-order](block-order.md) rule.
## :book: Rule Details
-This rule warns about the order of the `
+
+```
+
+
+
+
+
+```vue
+
...
+ documentation
```
-
+### `{ 'order': ['template', 'script:not([setup])', 'script[setup]'] }`
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+
```vue
...
+
- documents
+```
+
+
+
+### `{ 'order': ['template', 'style:not([scoped])', 'style[scoped]'] }`
+
+
+
+```vue
+
+...
+
+
+```
+
+
+
+
+
+```vue
+
+...
+
```
+### `{ 'order': ['template', 'i18n:not([locale=en])', 'i18n[locale=en]'] }`
+
+
+
+```vue
+
+...
+/* ... */
+/* ... */
+```
+
+
+
+
+
+```vue
+
+...
+/* ... */
+/* ... */
+```
+
+
+
## :books: Further Reading
-- [Style guide - Single-file component top-level element order](https://v3.vuejs.org/style-guide/#single-file-component-top-level-element-order-recommended)
+- [Style guide - Single-file component top-level element order](https://vuejs.org/style-guide/rules-recommended.html#single-file-component-top-level-element-order)
## :rocket: Version
diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md
index ae08fc25d..a57e0eb80 100644
--- a/docs/rules/custom-event-name-casing.md
+++ b/docs/rules/custom-event-name-casing.md
@@ -5,6 +5,7 @@ title: vue/custom-event-name-casing
description: enforce specific casing for custom event name
since: v7.0.0
---
+
# vue/custom-event-name-casing
> enforce specific casing for custom event name
@@ -13,7 +14,7 @@ Define a style for custom event name casing for consistency purposes.
## :book: Rule Details
-This rule aims to warn the custom event names other than the configured casing.
+This rule aims to warn the custom event names other than the configured casing. (Default is **camelCase**.)
Vue 2 recommends using kebab-case for custom event names.
@@ -27,28 +28,28 @@ In Vue 3, using either camelCase or kebab-case for your custom event name does n
See [Guide - Custom Events] for more details.
-This rule enforces kebab-case by default.
+This rule enforces camelCase by default.
```vue
-
+
-
+
+```
+
+
+
+## :wrench: Options
+
+```json
+ "vue/define-emits-declaration": ["error", "type-based" | "type-literal" | "runtime"]
+```
+
+- `type-based` (default) enforces type based declaration
+- `type-literal` enforces strict "type literal" type based declaration
+- `runtime` enforces runtime declaration
+
+### `runtime`
+
+
+
+```vue
+
+```
+
+
+
+### `type-literal`
+
+
+
+```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)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.5.0
+
+## :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-macros-order.md b/docs/rules/define-macros-order.md
new file mode 100644
index 000000000..14729f991
--- /dev/null
+++ b/docs/rules/define-macros-order.md
@@ -0,0 +1,191 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/define-macros-order
+description: enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)
+since: v8.7.0
+---
+
+# vue/define-macros-order
+
+> enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule reports compiler macros (like `defineProps` or `defineEmits` but also custom ones) when they are not the first statements in `
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+### `{ "order": ["defineOptions", "defineModel", "defineProps", "defineEmits", "defineSlots"] }`
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+### `{ "order": ["definePage", "defineModel", "defineCustom", "defineEmits", "defineSlots"] }`
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+### `{ "defineExposeLast": true }`
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.7.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-macros-order.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-macros-order.js)
diff --git a/docs/rules/define-props-declaration.md b/docs/rules/define-props-declaration.md
new file mode 100644
index 000000000..40c5ea0b0
--- /dev/null
+++ b/docs/rules/define-props-declaration.md
@@ -0,0 +1,88 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/define-props-declaration
+description: enforce declaration style of `defineProps`
+since: v9.5.0
+---
+
+# vue/define-props-declaration
+
+> enforce declaration style of `defineProps`
+
+## :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)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.5.0
+
+## :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/define-props-destructuring.md b/docs/rules/define-props-destructuring.md
new file mode 100644
index 000000000..e3c2b2745
--- /dev/null
+++ b/docs/rules/define-props-destructuring.md
@@ -0,0 +1,98 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/define-props-destructuring
+description: enforce consistent style for props destructuring
+since: v10.1.0
+---
+
+# vue/define-props-destructuring
+
+> enforce consistent style for props destructuring
+
+## :book: Rule Details
+
+This rule enforces a consistent style for handling Vue 3 Composition API props, allowing you to choose between requiring destructuring or prohibiting it.
+
+By default, the rule requires you to use destructuring syntax when using `defineProps` instead of storing props in a variable and warns against combining `withDefaults` with destructuring.
+
+
+
+```vue
+
+```
+
+
+
+The rule applies to both JavaScript and TypeScript props:
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```js
+{
+ "vue/define-props-destructuring": ["error", {
+ "destructure": "always" | "never"
+ }]
+}
+```
+
+- `destructure` - Sets the destructuring preference for props
+ - `"always"` (default) - Requires destructuring when using `defineProps` and warns against using `withDefaults` with destructuring
+ - `"never"` - Requires using a variable to store props and prohibits destructuring
+
+### `"destructure": "never"`
+
+
+
+```vue
+
+```
+
+
+
+## :books: Further Reading
+
+- [Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v10.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-props-destructuring.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-destructuring.js)
diff --git a/docs/rules/dot-location.md b/docs/rules/dot-location.md
index aafef433a..edb785080 100644
--- a/docs/rules/dot-location.md
+++ b/docs/rules/dot-location.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/dot-location
-description: enforce consistent newlines before and after dots
+description: Enforce consistent newlines before and after dots in ``
since: v6.0.0
---
+
# vue/dot-location
-> enforce consistent newlines before and after dots
+> Enforce consistent newlines before and after dots in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/dot-location] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [dot-location] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/dot-location]
- [dot-location]
+[@stylistic/dot-location]: https://eslint.style/rules/default/dot-location
[dot-location]: https://eslint.org/docs/rules/dot-location
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v6.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/dot-location.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/dot-location.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/dot-location)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/js/dot-location)
diff --git a/docs/rules/dot-notation.md b/docs/rules/dot-notation.md
index d94fac957..101caf5cf 100644
--- a/docs/rules/dot-notation.md
+++ b/docs/rules/dot-notation.md
@@ -2,14 +2,15 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/dot-notation
-description: enforce dot notation whenever possible
+description: Enforce dot notation whenever possible in ``
since: v7.0.0
---
+
# vue/dot-notation
-> enforce dot notation whenever possible
+> Enforce dot notation whenever possible in ``
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
This rule is the same rule as core [dot-notation] rule but it applies to the expressions in ``.
@@ -28,4 +29,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/dot-notation.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/dot-notation.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/dot-notation)
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/dot-notation)
diff --git a/docs/rules/enforce-style-attribute.md b/docs/rules/enforce-style-attribute.md
new file mode 100644
index 000000000..fcffefbae
--- /dev/null
+++ b/docs/rules/enforce-style-attribute.md
@@ -0,0 +1,89 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/enforce-style-attribute
+description: enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags
+since: v9.20.0
+---
+
+# vue/enforce-style-attribute
+
+> enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags
+
+## :book: Rule Details
+
+This rule allows you to explicitly allow the use of the `scoped` and `module` attributes on your top level style tags.
+
+### `"scoped"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"module"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"plain"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/enforce-style-attribute": [
+ "error",
+ { "allow": ["scoped", "module", "plain"] }
+ ]
+}
+```
+
+- `"allow"` (`["scoped" | "module" | "plain"]`) Array of attributes to allow on a top level style tag. The option `plain` is used to allow style tags that have neither the `scoped` nor `module` attributes. Default: `["scoped"]`
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.20.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/enforce-style-attribute.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/enforce-style-attribute.js)
diff --git a/docs/rules/eqeqeq.md b/docs/rules/eqeqeq.md
index 08fdaacea..392deff7f 100644
--- a/docs/rules/eqeqeq.md
+++ b/docs/rules/eqeqeq.md
@@ -2,14 +2,15 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/eqeqeq
-description: require the use of `===` and `!==`
+description: Require the use of `===` and `!==` in ``
since: v5.2.0
---
+
# vue/eqeqeq
-> require the use of `===` and `!==`
+> Require the use of `===` and `!==` in ``
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
This rule is the same rule as core [eqeqeq] rule but it applies to the expressions in ``.
@@ -28,4 +29,4 @@ This rule was introduced in eslint-plugin-vue v5.2.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/eqeqeq.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/eqeqeq.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/eqeqeq)
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/eqeqeq)
diff --git a/docs/rules/experimental-script-setup-vars.md b/docs/rules/experimental-script-setup-vars.md
index 9608384a1..82e7de884 100644
--- a/docs/rules/experimental-script-setup-vars.md
+++ b/docs/rules/experimental-script-setup-vars.md
@@ -5,20 +5,21 @@ title: vue/experimental-script-setup-vars
description: prevent variables defined in `
```
@@ -128,11 +131,11 @@ export default {
```vue
```
@@ -143,11 +146,11 @@ export default {
```vue
```
@@ -158,10 +161,10 @@ export default {
```vue
```
@@ -308,7 +311,7 @@ export default {
## :books: Further Reading
-- [Style guide - Single-file component filename casing](https://v3.vuejs.org/style-guide/#single-file-component-filename-casing-strongly-recommended)
+- [Style guide - Single-file component filename casing](https://vuejs.org/style-guide/rules-strongly-recommended.html#single-file-component-filename-casing)
## :rocket: Version
diff --git a/docs/rules/match-component-import-name.md b/docs/rules/match-component-import-name.md
new file mode 100644
index 000000000..522b90a02
--- /dev/null
+++ b/docs/rules/match-component-import-name.md
@@ -0,0 +1,49 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/match-component-import-name
+description: require the registered component name to match the imported component name
+since: v8.7.0
+---
+
+# vue/match-component-import-name
+
+> require the registered component name to match the imported component name
+
+## :book: Rule Details
+
+By default, this rule will validate that the imported name matches the name of the components object property identifer. Note that "matches" means that the imported name matches either the PascalCase or kebab-case version of the components object property identifer. If you would like to enforce that it must match only one of PascalCase or kebab-case, use this rule in conjunction with the rule [vue/component-definition-name-casing](./component-definition-name-casing.md).
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/component-definition-name-casing](./component-definition-name-casing.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.7.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/match-component-import-name.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/match-component-import-name.js)
diff --git a/docs/rules/max-attributes-per-line.md b/docs/rules/max-attributes-per-line.md
index 69bd77949..141b136e0 100644
--- a/docs/rules/max-attributes-per-line.md
+++ b/docs/rules/max-attributes-per-line.md
@@ -5,12 +5,13 @@ title: vue/max-attributes-per-line
description: enforce the maximum number of attributes per line
since: v3.12.0
---
+
# vue/max-attributes-per-line
> enforce the maximum number of attributes per line
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
Limits the maximum number of attributes/properties per line to improve readability.
@@ -57,18 +58,18 @@ There is a configurable number of attributes that are acceptable in one-line cas
```json
{
"vue/max-attributes-per-line": ["error", {
- "singleline": 1,
+ "singleline": {
+ "max": 1
+ },
"multiline": {
- "max": 1,
- "allowFirstLine": false
+ "max": 1
}
}]
}
```
-- `singleline` (`number`) ... The number of maximum attributes per line when the opening tag is in a single line. Default is `1`.
-- `multiline.max` (`number`) ... The max number of attributes per line when the opening tag is in multiple lines. Default is `1`. This can be `{ multiline: 1 }` instead of `{ multiline: { max: 1 }}` if you don't configure `allowFirstLine` property.
-- `multiline.allowFirstLine` (`boolean`) ... If `true`, it allows attributes on the same line as that tag name. Default is `false`.
+- `singleline.max` (`number`) ... The number of maximum attributes per line when the opening tag is in a single line. Default is `1`. This can be `{ singleline: 1 }` instead of `{ singleline: { max: 1 }}`.
+- `multiline.max` (`number`) ... The max number of attributes per line when the opening tag is in multiple lines. Default is `1`. This can be `{ multiline: 1 }` instead of `{ multiline: { max: 1 }}`.
### `"singleline": 3`
@@ -108,25 +109,13 @@ There is a configurable number of attributes that are acceptable in one-line cas
-### `"multiline": 1, "allowFirstLine": true`
-
-
-
-```vue
-
-
-
-
-```
+## :couple: Related Rules
-
+- [vue/first-attribute-linebreak](./first-attribute-linebreak.md)
## :books: Further Reading
-- [Style guide - Multi attribute elements](https://v3.vuejs.org/style-guide/#multi-attribute-elements-strongly-recommended)
+- [Style guide - Multi attribute elements](https://vuejs.org/style-guide/rules-strongly-recommended.html#multi-attribute-elements)
## :rocket: Version
diff --git a/docs/rules/max-len.md b/docs/rules/max-len.md
index 8d7b32eb7..0816000ee 100644
--- a/docs/rules/max-len.md
+++ b/docs/rules/max-len.md
@@ -2,12 +2,13 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/max-len
-description: enforce a maximum line length
+description: enforce a maximum line length in `.vue` files
since: v6.1.0
---
+
# vue/max-len
-> enforce a maximum line length
+> enforce a maximum line length in `.vue` files
## :book: Rule Details
@@ -112,7 +113,6 @@ var foo = ['line', 'length', 'is', '50', '......']
-
### `"template": 120`
diff --git a/docs/rules/max-lines-per-block.md b/docs/rules/max-lines-per-block.md
new file mode 100644
index 000000000..a65be3bb9
--- /dev/null
+++ b/docs/rules/max-lines-per-block.md
@@ -0,0 +1,63 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/max-lines-per-block
+description: enforce maximum number of lines in Vue SFC blocks
+since: v9.15.0
+---
+
+# vue/max-lines-per-block
+
+> enforce maximum number of lines in Vue SFC blocks
+
+## :book: Rule Details
+
+This rule enforces a maximum number of lines per block, in order to aid in maintainability and reduce complexity.
+
+## :wrench: Options
+
+This rule takes an object, where you can specify the maximum number of lines in each type of SFC block and customize the line counting behavior.
+The following properties can be specified for the object.
+
+- `script` ... Specify the maximum number of lines in `
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.15.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-lines-per-block.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-lines-per-block.js)
diff --git a/docs/rules/max-props.md b/docs/rules/max-props.md
new file mode 100644
index 000000000..918c67294
--- /dev/null
+++ b/docs/rules/max-props.md
@@ -0,0 +1,65 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/max-props
+description: enforce maximum number of props in Vue component
+since: v9.28.0
+---
+
+# vue/max-props
+
+> enforce maximum number of props in Vue component
+
+## :book: Rule Details
+
+This rule enforces a maximum number of props in a Vue SFC, in order to aid in maintainability and reduce complexity.
+
+## :wrench: Options
+
+This rule takes an object, where you can specify the maximum number of props allowed in a Vue SFC.
+There is one property that can be specified for the object.
+
+- `maxProps` ... Specify the maximum number of props in the `script` block.
+
+### `{ maxProps: 1 }`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+### `{ maxProps: 5 }`
+
+
+
+```vue
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.28.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-props.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-props.js)
diff --git a/docs/rules/max-template-depth.md b/docs/rules/max-template-depth.md
new file mode 100644
index 000000000..42ee4da88
--- /dev/null
+++ b/docs/rules/max-template-depth.md
@@ -0,0 +1,70 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/max-template-depth
+description: enforce maximum depth of template
+since: v9.28.0
+---
+
+# vue/max-template-depth
+
+> enforce maximum depth of template
+
+## :book: Rule Details
+
+This rule enforces a maximum depth of the template in a Vue SFC, in order to aid in maintainability and reduce complexity.
+
+## :wrench: Options
+
+This rule takes an object, where you can specify the maximum depth allowed in a Vue SFC template block.
+There is one property that can be specified for the object.
+
+- `maxDepth` ... Specify the maximum template depth `template` block.
+
+### `{ maxDepth: 3 }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.28.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-template-depth.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-template-depth.js)
diff --git a/docs/rules/multi-word-component-names.md b/docs/rules/multi-word-component-names.md
new file mode 100644
index 000000000..08539dddb
--- /dev/null
+++ b/docs/rules/multi-word-component-names.md
@@ -0,0 +1,188 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/multi-word-component-names
+description: require component names to be always multi-word
+since: v7.20.0
+---
+
+# vue/multi-word-component-names
+
+> require component names to be always multi-word
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+
+## :book: Rule Details
+
+This rule require component names to be always multi-word, except for root `App`
+components, and built-in components provided by Vue, such as `` or
+``. This prevents conflicts with existing and future HTML elements,
+since all HTML elements are single words.
+
+
+
+```js
+/* ✓ GOOD */
+Vue.component('todo-item', {
+ // ...
+})
+
+/* ✗ BAD */
+Vue.component('Todo', {
+ // ...
+})
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/multi-word-component-names": ["error", {
+ "ignores": []
+ }]
+}
+```
+
+- `ignores` (`string[]`) ... The component names to ignore. Sets the component name to allow.
+
+### `ignores: ["Todo"]`
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/no-reserved-component-names](./no-reserved-component-names.md)
+
+## :books: Further Reading
+
+- [Style guide - Multi-word component names](https://vuejs.org/style-guide/rules-essential.html#use-multi-word-component-names)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.20.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/multi-word-component-names.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/multi-word-component-names.js)
diff --git a/docs/rules/multiline-html-element-content-newline.md b/docs/rules/multiline-html-element-content-newline.md
index 9813113ee..bf654aafe 100644
--- a/docs/rules/multiline-html-element-content-newline.md
+++ b/docs/rules/multiline-html-element-content-newline.md
@@ -5,12 +5,13 @@ title: vue/multiline-html-element-content-newline
description: require a line break before and after the contents of a multiline element
since: v5.0.0
---
+
# vue/multiline-html-element-content-newline
> require a line break before and after the contents of a multiline element
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
diff --git a/docs/rules/multiline-ternary.md b/docs/rules/multiline-ternary.md
new file mode 100644
index 000000000..1fe4d84fb
--- /dev/null
+++ b/docs/rules/multiline-ternary.md
@@ -0,0 +1,71 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/multiline-ternary
+description: Enforce newlines between operands of ternary expressions in ``
+since: v9.7.0
+---
+
+# vue/multiline-ternary
+
+> Enforce newlines between operands of ternary expressions in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/multiline-ternary] rule but it applies to the expressions in `` and `
+```
+
+
+
+## :books: Further Reading
+
+- [@stylistic/multiline-ternary]
+- [multiline-ternary]
+
+[@stylistic/multiline-ternary]: https://eslint.style/rules/default/multiline-ternary
+[multiline-ternary]: https://eslint.org/docs/rules/multiline-ternary
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.7.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/multiline-ternary.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/multiline-ternary.js)
+
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/js/multiline-ternary)
diff --git a/docs/rules/mustache-interpolation-spacing.md b/docs/rules/mustache-interpolation-spacing.md
index f331d968b..1e66fc942 100644
--- a/docs/rules/mustache-interpolation-spacing.md
+++ b/docs/rules/mustache-interpolation-spacing.md
@@ -5,12 +5,13 @@ title: vue/mustache-interpolation-spacing
description: enforce unified spacing in mustache interpolations
since: v3.13.0
---
+
# vue/mustache-interpolation-spacing
> enforce unified spacing in mustache interpolations
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
diff --git a/docs/rules/name-property-casing.md b/docs/rules/name-property-casing.md
index 7a912d56b..e1840987b 100644
--- a/docs/rules/name-property-casing.md
+++ b/docs/rules/name-property-casing.md
@@ -5,12 +5,12 @@ title: vue/name-property-casing
description: enforce specific casing for the name property in Vue components
since: v3.8.0
---
+
# vue/name-property-casing
> enforce specific casing for the name property in Vue components
-- :warning: This rule was **deprecated** and replaced by [vue/component-definition-name-casing](component-definition-name-casing.md) rule.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :no_entry: This rule was **removed** in eslint-plugin-vue v9.0.0 and replaced by [vue/component-definition-name-casing](component-definition-name-casing.md) rule.
## :book: Rule Details
@@ -20,10 +20,10 @@ This rule aims at enforcing the style for the `name` property casing for consist
```vue
```
@@ -33,10 +33,10 @@ This rule aims at enforcing the style for the `name` property casing for consist
```vue
```
@@ -59,10 +59,10 @@ This rule aims at enforcing the style for the `name` property casing for consist
```vue
```
@@ -72,10 +72,10 @@ This rule aims at enforcing the style for the `name` property casing for consist
```vue
```
@@ -83,7 +83,7 @@ This rule aims at enforcing the style for the `name` property casing for consist
## :books: Further Reading
-- [Style guide - Component name casing in JS/JSX](https://v3.vuejs.org/style-guide/#component-name-casing-in-js-jsx-strongly-recommended)
+- [Style guide - Component name casing in JS/JSX](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-name-casing-in-js-jsx)
## :rocket: Version
diff --git a/docs/rules/new-line-between-multi-line-property.md b/docs/rules/new-line-between-multi-line-property.md
index 7af127bea..1191a1567 100644
--- a/docs/rules/new-line-between-multi-line-property.md
+++ b/docs/rules/new-line-between-multi-line-property.md
@@ -5,11 +5,12 @@ title: vue/new-line-between-multi-line-property
description: enforce new lines between multi-line properties in Vue components
since: v7.3.0
---
+
# vue/new-line-between-multi-line-property
> enforce new lines between multi-line properties in Vue components
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -75,7 +76,7 @@ export default {
-## :wrench: Option
+## :wrench: Options
```json
{
@@ -89,7 +90,7 @@ export default {
## :books: Further Reading
-- [Style guide - Empty lines in component/instance options](https://v3.vuejs.org/style-guide/#empty-lines-in-component-instance-options-recommended)
+- [Style guide - Empty lines in component/instance options](https://vuejs.org/style-guide/rules-recommended.html#empty-lines-in-component-instance-options)
## :rocket: Version
diff --git a/docs/rules/next-tick-style.md b/docs/rules/next-tick-style.md
index 2f17f2dd4..0e9d7812f 100644
--- a/docs/rules/next-tick-style.md
+++ b/docs/rules/next-tick-style.md
@@ -5,11 +5,12 @@ title: vue/next-tick-style
description: enforce Promise or callback style in `nextTick`
since: v7.5.0
---
+
# vue/next-tick-style
> enforce Promise or callback style in `nextTick`
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -46,6 +47,7 @@ export default {
## :wrench: Options
+
Default is set to `promise`.
```json
@@ -91,11 +93,11 @@ export default {
## :books: Further Reading
-- [`Vue.nextTick` API in Vue 2](https://vuejs.org/v2/api/#Vue-nextTick)
-- [`vm.$nextTick` API in Vue 2](https://vuejs.org/v2/api/#vm-nextTick)
-- [Global API Treeshaking](https://v3.vuejs.org/guide/migration/global-api-treeshaking.html)
-- [Global `nextTick` API in Vue 3](https://v3.vuejs.org/api/global-api.html#nexttick)
-- [Instance `$nextTick` API in Vue 3](https://v3.vuejs.org/api/instance-methods.html#nexttick)
+- [`Vue.nextTick` API in Vue 2](https://v2.vuejs.org/v2/api/#Vue-nextTick)
+- [`vm.$nextTick` API in Vue 2](https://v2.vuejs.org/v2/api/#vm-nextTick)
+- [Global API Treeshaking](https://v3-migration.vuejs.org/breaking-changes/global-api-treeshaking.html)
+- [Global `nextTick` API in Vue 3](https://vuejs.org/api/general.html#nexttick)
+- [Instance `$nextTick` API in Vue 3](https://vuejs.org/api/component-instance.html#nexttick)
## :rocket: Version
diff --git a/docs/rules/no-arrow-functions-in-watch.md b/docs/rules/no-arrow-functions-in-watch.md
index 2ad7bdf70..fb1f55215 100644
--- a/docs/rules/no-arrow-functions-in-watch.md
+++ b/docs/rules/no-arrow-functions-in-watch.md
@@ -5,15 +5,16 @@ title: vue/no-arrow-functions-in-watch
description: disallow using arrow functions to define watcher
since: v7.0.0
---
+
# vue/no-arrow-functions-in-watch
> disallow using arrow functions to define watcher
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
-This rules disallows using arrow functions to defined watcher.The reason is arrow functions bind the parent context, so `this` will not be the Vue instance as you expect.([see here for more details](https://v3.vuejs.org/api/options-data.html#watch))
+This rule disallows using arrow functions when defining a watcher. Arrow functions bind to their parent context, which means they will not have access to the Vue component instance via `this`. [See here for more details](https://vuejs.org/api/options-state.html#watch).
diff --git a/docs/rules/no-async-in-computed-properties.md b/docs/rules/no-async-in-computed-properties.md
index ac7ac50f2..814a53238 100644
--- a/docs/rules/no-async-in-computed-properties.md
+++ b/docs/rules/no-async-in-computed-properties.md
@@ -5,11 +5,12 @@ title: vue/no-async-in-computed-properties
description: disallow asynchronous actions in computed properties
since: v3.8.0
---
+
# vue/no-async-in-computed-properties
> disallow asynchronous actions in computed properties
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
Computed properties and functions should be synchronous. Asynchronous actions inside them may not work as expected and can lead to an unexpected behaviour, that's why you should avoid them.
If you need async computed properties you might want to consider using additional plugin [vue-async-computed]
@@ -25,7 +26,7 @@ This rule is aimed at preventing asynchronous methods from being called in compu
export default {
computed: {
/* ✓ GOOD */
- foo () {
+ foo() {
var bar = 0
try {
bar = bar / this.a
@@ -37,22 +38,22 @@ export default {
},
/* ✗ BAD */
- pro () {
+ pro() {
return Promise.all([new Promise((resolve, reject) => {})])
},
foo1: async function () {
return await someFunc()
},
- bar () {
- return fetch(url).then(response => {})
+ bar() {
+ return fetch(url).then((response) => {})
},
- tim () {
- setTimeout(() => { }, 0)
+ tim() {
+ setTimeout(() => {}, 0)
},
- inter () {
- setInterval(() => { }, 0)
+ inter() {
+ setInterval(() => {}, 0)
},
- anim () {
+ anim() {
requestAnimationFrame(() => {})
}
}
@@ -66,7 +67,7 @@ export default {
```vue
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.20.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-computed-properties-in-data.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-computed-properties-in-data.js)
diff --git a/docs/rules/no-confusing-v-for-v-if.md b/docs/rules/no-confusing-v-for-v-if.md
index 8c5d799da..f463c883e 100644
--- a/docs/rules/no-confusing-v-for-v-if.md
+++ b/docs/rules/no-confusing-v-for-v-if.md
@@ -5,11 +5,12 @@ title: vue/no-confusing-v-for-v-if
description: disallow confusing `v-for` and `v-if` on the same element
since: v3.0.0
---
+
# vue/no-confusing-v-for-v-if
> disallow confusing `v-for` and `v-if` on the same element
-- :warning: This rule was **deprecated** and replaced by [vue/no-use-v-if-with-v-for](no-use-v-if-with-v-for.md) rule.
+- :no_entry: This rule was **removed** in eslint-plugin-vue v9.0.0 and replaced by [vue/no-use-v-if-with-v-for](no-use-v-if-with-v-for.md) rule.
## :book: Rule Details
@@ -50,7 +51,7 @@ In that case, the `v-if` should be written on the wrapper element.
::: warning Note
When they exist on the same node, `v-for` has a higher priority than `v-if`. That means the `v-if` will be run on each iteration of the loop separately.
-[https://v3.vuejs.org/guide/list.html#v-for-with-v-if](https://v3.vuejs.org/guide/list.html#v-for-with-v-if)
+[https://vuejs.org/guide/essentials/list.html#v-for-with-v-if](https://vuejs.org/guide/essentials/list.html#v-for-with-v-if)
:::
## :wrench: Options
@@ -59,9 +60,9 @@ Nothing.
## :books: Further Reading
-- [Style guide - Avoid v-if with v-for](https://v3.vuejs.org/style-guide/#avoid-v-if-with-v-for-essential)
-- [Guide - Conditional Rendering / v-if with v-for](https://v3.vuejs.org/guide/conditional.html#v-if-with-v-for)
-- [Guide - List Rendering / v-for with v-if](https://v3.vuejs.org/guide/list.html#v-for-with-v-if)
+- [Style guide - Avoid v-if with v-for](https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for)
+- [Guide - Conditional Rendering / v-if with v-for](https://vuejs.org/guide/essentials/conditional.html#v-if-with-v-for)
+- [Guide - List Rendering / v-for with v-if](https://vuejs.org/guide/essentials/list.html#v-for-with-v-if)
## :rocket: Version
diff --git a/docs/rules/no-console.md b/docs/rules/no-console.md
new file mode 100644
index 000000000..64ef0278e
--- /dev/null
+++ b/docs/rules/no-console.md
@@ -0,0 +1,34 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-console
+description: Disallow the use of `console` in ``
+since: v9.15.0
+---
+
+# vue/no-console
+
+> Disallow the use of `console` in ``
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule is the same rule as core [no-console] rule but it applies to the expressions in ``.
+
+## :books: Further Reading
+
+- [no-console]
+
+[no-console]: https://eslint.org/docs/latest/rules/no-console
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.15.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-console.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-console.js)
+
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-console)
diff --git a/docs/rules/no-constant-condition.md b/docs/rules/no-constant-condition.md
index 54f6df9d0..36520e25b 100644
--- a/docs/rules/no-constant-condition.md
+++ b/docs/rules/no-constant-condition.md
@@ -2,12 +2,13 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-constant-condition
-description: disallow constant expressions in conditions
+description: Disallow constant expressions in conditions in ``
since: v7.5.0
---
+
# vue/no-constant-condition
-> disallow constant expressions in conditions
+> Disallow constant expressions in conditions in ``
This rule is the same rule as core [no-constant-condition] rule but it applies to the expressions in ``.
@@ -26,4 +27,4 @@ This rule was introduced in eslint-plugin-vue v7.5.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-constant-condition.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-constant-condition.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/no-constant-condition)
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-constant-condition)
diff --git a/docs/rules/no-custom-modifiers-on-v-model.md b/docs/rules/no-custom-modifiers-on-v-model.md
index 092ee325c..f657f9cf7 100644
--- a/docs/rules/no-custom-modifiers-on-v-model.md
+++ b/docs/rules/no-custom-modifiers-on-v-model.md
@@ -5,13 +5,14 @@ title: vue/no-custom-modifiers-on-v-model
description: disallow custom modifiers on v-model used on the component
since: v7.0.0
---
+
# vue/no-custom-modifiers-on-v-model
> disallow custom modifiers on v-model used on the component
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
-This rule checks whether `v-model `used on the component do not have custom modifiers.
+This rule checks whether `v-model` used on the component do not have custom modifiers.
## :book: Rule Details
@@ -29,11 +30,9 @@ This rule reports `v-model` directives in the following cases:
-
-
```
diff --git a/docs/rules/no-deprecated-data-object-declaration.md b/docs/rules/no-deprecated-data-object-declaration.md
index faedcfab7..214aacc30 100644
--- a/docs/rules/no-deprecated-data-object-declaration.md
+++ b/docs/rules/no-deprecated-data-object-declaration.md
@@ -5,19 +5,20 @@ title: vue/no-deprecated-data-object-declaration
description: disallow using deprecated object declaration on data (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-data-object-declaration
> disallow using deprecated object declaration on data (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
This rule reports use of deprecated object declaration on `data` property (in Vue.js 3.0.0+).
The different from `vue/no-shared-component-data` is the root instance being also disallowed.
-See [Migration Guide - Data Option](https://v3.vuejs.org/guide/migration/data-option.html) for more details.
+See [Migration Guide - Data Option](https://v3-migration.vuejs.org/breaking-changes/data-option.html) for more details.
@@ -31,7 +32,7 @@ createApp({
createApp({
/* ✓ GOOD */
- data () {
+ data() {
return {
foo: null
}
@@ -62,7 +63,7 @@ export default {
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Removed APIs](https://v3-migration.vuejs.org/breaking-changes/#removed-apis)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.29.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-delete-set.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-delete-set.js)
diff --git a/docs/rules/no-deprecated-destroyed-lifecycle.md b/docs/rules/no-deprecated-destroyed-lifecycle.md
index 37f8c0fe1..9c681e0ea 100644
--- a/docs/rules/no-deprecated-destroyed-lifecycle.md
+++ b/docs/rules/no-deprecated-destroyed-lifecycle.md
@@ -5,30 +5,32 @@ title: vue/no-deprecated-destroyed-lifecycle
description: disallow using deprecated `destroyed` and `beforeDestroy` lifecycle hooks (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-destroyed-lifecycle
> disallow using deprecated `destroyed` and `beforeDestroy` lifecycle hooks (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
This rule reports use of deprecated `destroyed` and `beforeDestroy` lifecycle hooks. (in Vue.js 3.0.0+).
-
+
```vue
```
@@ -39,6 +41,10 @@ export default {
Nothing.
+## :books: Further Reading
+
+- [Migration Guide - VNode Lifecycle Events](https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events.html#migration-strategy)
+
## :rocket: Version
This rule was introduced in eslint-plugin-vue v7.0.0
diff --git a/docs/rules/no-deprecated-dollar-listeners-api.md b/docs/rules/no-deprecated-dollar-listeners-api.md
index f0f1616cc..91854806b 100644
--- a/docs/rules/no-deprecated-dollar-listeners-api.md
+++ b/docs/rules/no-deprecated-dollar-listeners-api.md
@@ -5,11 +5,12 @@ title: vue/no-deprecated-dollar-listeners-api
description: disallow using deprecated `$listeners` (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-dollar-listeners-api
> disallow using deprecated `$listeners` (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
@@ -46,6 +47,7 @@ Nothing.
## :books: Further Reading
- [Vue RFCs - 0031-attr-fallthrough](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0031-attr-fallthrough.md)
+- [Migration Guide - `$listeners` removed](https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-dollar-scopedslots-api.md b/docs/rules/no-deprecated-dollar-scopedslots-api.md
index 3183ea472..45e67d480 100644
--- a/docs/rules/no-deprecated-dollar-scopedslots-api.md
+++ b/docs/rules/no-deprecated-dollar-scopedslots-api.md
@@ -5,18 +5,19 @@ title: vue/no-deprecated-dollar-scopedslots-api
description: disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-dollar-scopedslots-api
> disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
This rule reports use of deprecated `$scopedSlots`. (in Vue.js 3.0.0+).
-See [Migration Guide - Slots Unification](https://v3.vuejs.org/guide/migration/slots-unification.html) for more details.
+See [Migration Guide - Slots Unification](https://v3-migration.vuejs.org/breaking-changes/slots-unification.html) for more details.
@@ -43,7 +44,7 @@ Nothing.
## :books: Further Reading
-- [Migration Guide - Slots Unification](https://v3.vuejs.org/guide/migration/slots-unification.html)
+- [Migration Guide - Slots Unification](https://v3-migration.vuejs.org/breaking-changes/slots-unification.html)
- [Vue RFCs - 0006-slots-unification](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0006-slots-unification.md)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-events-api.md b/docs/rules/no-deprecated-events-api.md
index f78ec8c71..a3539d7cf 100644
--- a/docs/rules/no-deprecated-events-api.md
+++ b/docs/rules/no-deprecated-events-api.md
@@ -5,17 +5,18 @@ title: vue/no-deprecated-events-api
description: disallow using deprecated events api (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-events-api
> disallow using deprecated events api (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
This rule reports use of deprecated `$on`, `$off` `$once` api. (in Vue.js 3.0.0+).
-See [Migration Guide - Events API](https://v3.vuejs.org/guide/migration/events-api.html) for more details.
+See [Migration Guide - Events API](https://v3-migration.vuejs.org/breaking-changes/events-api.html) for more details.
@@ -23,8 +24,8 @@ See [Migration Guide - Events API](https://v3.vuejs.org/guide/migration/events-a
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-deprecated-model-definition": ["error", {
+ "allowVue3Compat": true
+ }]
+}
+```
+
+### `"allowVue3Compat": true`
+
+Allow `model` definitions with prop/event names that match the Vue.js 3.0.0+ `v-model` syntax, i.e. `modelValue`/`update:modelValue` or `model-value`/`update:model-value`.
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/valid-model-definition](./valid-model-definition.md) (for Vue.js 2.x)
+- [vue/no-v-model-argument](./no-v-model-argument.md) (for Vue.js 2.x)
+
+## :books: Further Reading
+
+- [Migration Guide – `v-model`](https://v3-migration.vuejs.org/breaking-changes/v-model.html)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.16.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-model-definition.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-model-definition.js)
diff --git a/docs/rules/no-deprecated-props-default-this.md b/docs/rules/no-deprecated-props-default-this.md
index aa84ea3c9..af8136fa2 100644
--- a/docs/rules/no-deprecated-props-default-this.md
+++ b/docs/rules/no-deprecated-props-default-this.md
@@ -2,21 +2,22 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-deprecated-props-default-this
-description: disallow props default function `this` access
+description: disallow deprecated `this` access in props default function (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-props-default-this
-> disallow props default function `this` access
+> disallow deprecated `this` access in props default function (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
This rule reports the use of `this` within the props default value factory functions.
In Vue.js 3.0.0+, props default value factory functions no longer have access to `this`.
-See [Migration Guide - Props Default Function `this` Access](https://v3.vuejs.org/guide/migration/props-default-this.html) for more details.
+See [Migration Guide - Props Default Function `this` Access](https://v3-migration.vuejs.org/breaking-changes/props-default-this.html) for more details.
@@ -26,7 +27,7 @@ export default {
props: {
a: String,
b: {
- default () {
+ default() {
/* ✗ BAD */
return this.a
}
@@ -46,7 +47,7 @@ export default {
props: {
a: String,
b: {
- default (props) {
+ default(props) {
/* ✓ GOOD */
return props.a
}
@@ -64,7 +65,7 @@ Nothing.
## :books: Further Reading
-- [Migration Guide - Props Default Function `this` Access](https://v3.vuejs.org/guide/migration/props-default-this.html)
+- [Migration Guide - Props Default Function `this` Access](https://v3-migration.vuejs.org/breaking-changes/props-default-this.html)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-router-link-tag-prop.md b/docs/rules/no-deprecated-router-link-tag-prop.md
new file mode 100644
index 000000000..1ec049fd0
--- /dev/null
+++ b/docs/rules/no-deprecated-router-link-tag-prop.md
@@ -0,0 +1,97 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-router-link-tag-prop
+description: disallow using deprecated `tag` property on `RouterLink` (in Vue.js 3.0.0+)
+since: v7.20.0
+---
+
+# vue/no-deprecated-router-link-tag-prop
+
+> disallow using deprecated `tag` property on `RouterLink` (in Vue.js 3.0.0+)
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+
+## :book: Rule Details
+
+This rule reports deprecated the `tag` attribute on `RouterLink` elements (removed in Vue.js v3.0.0+).
+
+
+
+```vue
+
+
+ Home
+ Home
+
+
+
Home
+
+
+
+
Home
+
+
+ Home
+ Home
+
+
+ Home
+ Home
+ Home
+ Home
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-deprecated-router-link-tag-prop": ["error", {
+ "components": ['RouterLink']
+ }]
+}
+```
+
+- `components` (`string[]`) ... Component names which will be checked with the `tag` attribute. default `['RouterLink']`.
+
+Note: this rule will check both `kebab-case` and `PascalCase` versions of the
+given component names.
+
+### `{ "components": ['RouterLink', 'NuxtLink'] }`
+
+
+
+```vue
+
+
+ Home
+ Home
+
+ Home
+ Home
+
+ Home
+ Home
+
+ Home
+ Home
+
+```
+
+
+
+## :books: Further Reading
+
+- [Vue RFCs - 0021-router-link-scoped-slot](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0021-router-link-scoped-slot.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.20.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-router-link-tag-prop.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-router-link-tag-prop.js)
diff --git a/docs/rules/no-deprecated-scope-attribute.md b/docs/rules/no-deprecated-scope-attribute.md
index cae02132a..d19b172a8 100644
--- a/docs/rules/no-deprecated-scope-attribute.md
+++ b/docs/rules/no-deprecated-scope-attribute.md
@@ -5,12 +5,13 @@ title: vue/no-deprecated-scope-attribute
description: disallow deprecated `scope` attribute (in Vue.js 2.5.0+)
since: v6.0.0
---
+
# vue/no-deprecated-scope-attribute
> disallow deprecated `scope` attribute (in Vue.js 2.5.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -42,7 +43,7 @@ This rule reports deprecated `scope` attribute in Vue.js v2.5.0+.
## :books: Further Reading
-- [API - scope](https://vuejs.org/v2/api/#scope-removed)
+- [API - scope](https://v2.vuejs.org/v2/api/#scope-removed)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-slot-attribute.md b/docs/rules/no-deprecated-slot-attribute.md
index d8a45cff5..64f2c5e00 100644
--- a/docs/rules/no-deprecated-slot-attribute.md
+++ b/docs/rules/no-deprecated-slot-attribute.md
@@ -5,12 +5,13 @@ title: vue/no-deprecated-slot-attribute
description: disallow deprecated `slot` attribute (in Vue.js 2.6.0+)
since: v6.1.0
---
+
# vue/no-deprecated-slot-attribute
> disallow deprecated `slot` attribute (in Vue.js 2.6.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -37,9 +38,52 @@ This rule reports deprecated `slot` attribute in Vue.js v2.6.0+.
+## :wrench: Options
+
+```json
+{
+ "vue/no-deprecated-slot-attribute": ["error", {
+ "ignore": ["my-component"]
+ }]
+}
+```
+
+- `"ignore"` (`string[]`) An array of tags that ignore this rules. This option will check both kebab-case and PascalCase versions of the given tag names. Default is empty.
+
+### `"ignore": ["my-component"]`
+
+
+
+```vue
+
+
+
+
+ {{ props.title }}
+
+
+
+
+
+
+ {{ props.title }}
+
+
+
+
+
+
+ {{ props.title }}
+
+
+
+```
+
+
+
## :books: Further Reading
-- [API - slot](https://vuejs.org/v2/api/#slot-deprecated)
+- [API - slot](https://v2.vuejs.org/v2/api/#slot-deprecated)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-slot-scope-attribute.md b/docs/rules/no-deprecated-slot-scope-attribute.md
index f1fe017e6..86212de0f 100644
--- a/docs/rules/no-deprecated-slot-scope-attribute.md
+++ b/docs/rules/no-deprecated-slot-scope-attribute.md
@@ -5,12 +5,13 @@ title: vue/no-deprecated-slot-scope-attribute
description: disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+)
since: v6.1.0
---
+
# vue/no-deprecated-slot-scope-attribute
> disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -39,7 +40,7 @@ This rule reports deprecated `slot-scope` attribute in Vue.js v2.6.0+.
## :books: Further Reading
-- [API - slot-scope](https://vuejs.org/v2/api/#slot-scope-deprecated)
+- [API - slot-scope](https://v2.vuejs.org/v2/api/#slot-scope-deprecated)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-v-bind-sync.md b/docs/rules/no-deprecated-v-bind-sync.md
index dfc652890..77a79f16f 100644
--- a/docs/rules/no-deprecated-v-bind-sync.md
+++ b/docs/rules/no-deprecated-v-bind-sync.md
@@ -5,33 +5,33 @@ title: vue/no-deprecated-v-bind-sync
description: disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-v-bind-sync
> disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
This rule reports use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+).
-See [Migration Guide - `v-model`](https://v3.vuejs.org/guide/migration/v-model.html) for more details.
+See [Migration Guide - `v-model`](https://v3-migration.vuejs.org/breaking-changes/v-model.html) for more details.
```vue
-
-
-
+
+
-
-
-
-
+
+
+
+
```
@@ -49,7 +49,7 @@ Nothing.
## :books: Further Reading
-- [Migration Guide - `v-model`](https://v3.vuejs.org/guide/migration/v-model.html)
+- [Migration Guide - `v-model`](https://v3-migration.vuejs.org/breaking-changes/v-model.html)
- [Vue RFCs - 0005-replace-v-bind-sync-with-v-model-argument](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0005-replace-v-bind-sync-with-v-model-argument.md)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-v-is.md b/docs/rules/no-deprecated-v-is.md
new file mode 100644
index 000000000..9fd1590e4
--- /dev/null
+++ b/docs/rules/no-deprecated-v-is.md
@@ -0,0 +1,55 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-v-is
+description: disallow deprecated `v-is` directive (in Vue.js 3.1.0+)
+since: v7.11.0
+---
+
+# vue/no-deprecated-v-is
+
+> disallow deprecated `v-is` directive (in Vue.js 3.1.0+)
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+
+## :book: Rule Details
+
+This rule reports deprecated `v-is` directive in Vue.js v3.1.0+.
+
+Use [`is` attribute with `vue:` prefix](https://vuejs.org/api/built-in-special-attributes.html#is) instead.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/valid-v-is]
+
+[vue/valid-v-is]: ./valid-v-is.md
+
+## :books: Further Reading
+
+- [Migration Guide - Custom Elements Interop](https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html#vue-prefix-for-in-dom-template-parsing-workarounds)
+- [API - v-is](https://vuejs.org/api/built-in-special-attributes.html#is)
+- [API - v-is (Old)](https://github.com/vuejs/docs-next/blob/008613756c3d781128d96b64a2d27f7598f8f548/src/api/directives.md#v-is)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.11.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-is.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-is.js)
diff --git a/docs/rules/no-deprecated-v-on-native-modifier.md b/docs/rules/no-deprecated-v-on-native-modifier.md
index 394db36bc..8b8312235 100644
--- a/docs/rules/no-deprecated-v-on-native-modifier.md
+++ b/docs/rules/no-deprecated-v-on-native-modifier.md
@@ -5,11 +5,12 @@ title: vue/no-deprecated-v-on-native-modifier
description: disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-v-on-native-modifier
> disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
@@ -44,6 +45,7 @@ Nothing.
## :books: Further Reading
- [Vue RFCs - 0031-attr-fallthrough](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0031-attr-fallthrough.md)
+- [Migration Guide - `v-on.native` modifier removed](https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-v-on-number-modifiers.md b/docs/rules/no-deprecated-v-on-number-modifiers.md
index 74e711f9c..54ea7d431 100644
--- a/docs/rules/no-deprecated-v-on-number-modifiers.md
+++ b/docs/rules/no-deprecated-v-on-number-modifiers.md
@@ -5,18 +5,19 @@ title: vue/no-deprecated-v-on-number-modifiers
description: disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-v-on-number-modifiers
> disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
This rule reports use of deprecated `KeyboardEvent.keyCode` modifier on `v-on` directive (in Vue.js 3.0.0+).
-See [Migration Guide - KeyCode Modifiers](https://v3.vuejs.org/guide/migration/keycode-modifiers.html) for more details.
+See [Migration Guide - KeyCode Modifiers](https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html) for more details.
@@ -48,7 +49,7 @@ Nothing.
## :books: Further Reading
-- [Migration Guide - KeyCode Modifiers](https://v3.vuejs.org/guide/migration/keycode-modifiers.html)
+- [Migration Guide - KeyCode Modifiers](https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html)
- [Vue RFCs - 0014-drop-keycode-support](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0014-drop-keycode-support.md)
## :rocket: Version
diff --git a/docs/rules/no-deprecated-vue-config-keycodes.md b/docs/rules/no-deprecated-vue-config-keycodes.md
index b271741e0..697604b8c 100644
--- a/docs/rules/no-deprecated-vue-config-keycodes.md
+++ b/docs/rules/no-deprecated-vue-config-keycodes.md
@@ -5,17 +5,18 @@ title: vue/no-deprecated-vue-config-keycodes
description: disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+)
since: v7.0.0
---
+
# vue/no-deprecated-vue-config-keycodes
> disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+)
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
This rule reports use of deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+).
-See [Migration Guide - KeyCode Modifiers](https://v3.vuejs.org/guide/migration/keycode-modifiers.html) for more details.
+See [Migration Guide - KeyCode Modifiers](https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html) for more details.
@@ -44,9 +45,9 @@ Nothing.
- [Vue RFCs - 0014-drop-keycode-support]
- [API - Global Config - keyCodes]
-[Migration Guide - KeyCode Modifiers]: https://v3.vuejs.org/guide/migration/keycode-modifiers.html
+[Migration Guide - KeyCode Modifiers]: https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers.html
[Vue RFCs - 0014-drop-keycode-support]: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0014-drop-keycode-support.md
-[API - Global Config - keyCodes]: https://vuejs.org/v2/api/#keyCodes
+[API - Global Config - keyCodes]: https://v2.vuejs.org/v2/api/#keyCodes
## :rocket: Version
diff --git a/docs/rules/no-dupe-keys.md b/docs/rules/no-dupe-keys.md
index 41f4a962f..3372a8e26 100644
--- a/docs/rules/no-dupe-keys.md
+++ b/docs/rules/no-dupe-keys.md
@@ -5,17 +5,19 @@ title: vue/no-dupe-keys
description: disallow duplication of field names
since: v3.9.0
---
+
# vue/no-dupe-keys
> disallow duplication of field names
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
-This rule prevents to use duplicated names.
+This rule prevents using duplicate key names.
## :book: Rule Details
-This rule is aimed at preventing duplicated property names.
+This rule prevents duplicate `props`/`data`/`methods`/etc. key names defined on a component.
+Even if a key name does not conflict in the `
@@ -64,10 +66,10 @@ export default {
/* ✗ BAD */
export default {
computed: {
- foo () {}
+ foo() {}
},
firebase: {
- foo () {}
+ foo() {}
}
}
diff --git a/docs/rules/no-dupe-v-else-if.md b/docs/rules/no-dupe-v-else-if.md
index 4fbdffcbc..d5bf83797 100644
--- a/docs/rules/no-dupe-v-else-if.md
+++ b/docs/rules/no-dupe-v-else-if.md
@@ -5,11 +5,12 @@ title: vue/no-dupe-v-else-if
description: disallow duplicate conditions in `v-if` / `v-else-if` chains
since: v7.0.0
---
+
# vue/no-dupe-v-else-if
> disallow duplicate conditions in `v-if` / `v-else-if` chains
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
diff --git a/docs/rules/no-duplicate-attr-inheritance.md b/docs/rules/no-duplicate-attr-inheritance.md
index 908b49b19..fe3cd37bf 100644
--- a/docs/rules/no-duplicate-attr-inheritance.md
+++ b/docs/rules/no-duplicate-attr-inheritance.md
@@ -5,16 +5,17 @@ title: vue/no-duplicate-attr-inheritance
description: enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"`
since: v7.0.0
---
+
# vue/no-duplicate-attr-inheritance
> enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"`
## :book: Rule Details
-This rule aims to prevent duplicated attribute inheritance.
-This rule to warn to apply `inheritAttrs: false` when it detects `v-bind="$attrs"` being used.
+This rule aims to prevent duplicate attribute inheritance.
+This rule suggests applying `inheritAttrs: false` when it detects `v-bind="$attrs"` being used.
-
+
```vue
@@ -25,11 +26,12 @@ export default {
/* ✓ GOOD */
inheritAttrs: false
}
+
```
-
+
```vue
@@ -40,17 +42,46 @@ export default {
/* ✗ BAD */
// inheritAttrs: true (default)
}
+
```
## :wrench: Options
-Nothing.
+```json
+{
+ "vue/no-duplicate-attr-inheritance": ["error", {
+ "checkMultiRootNodes": false,
+ }]
+}
+```
+
+- `"checkMultiRootNodes"`: If set to `true`, also suggest applying `inheritAttrs: false` to components with multiple root nodes (where `inheritAttrs: false` is the implicit default, see [attribute inheritance on multiple root nodes](https://vuejs.org/guide/components/attrs.html#attribute-inheritance-on-multiple-root-nodes)), whenever it detects `v-bind="$attrs"` being used. Default is `false`, which will ignore components with multiple root nodes.
+
+### `"checkMultiRootNodes": true`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
## :books: Further Reading
-- [API - inheritAttrs](https://v3.vuejs.org/api/options-misc.html#inheritattrs)
+- [API - inheritAttrs](https://vuejs.org/api/options-misc.html#inheritattrs)
+- [Fallthrough Attributes](https://vuejs.org/guide/components/attrs.html#attribute-inheritance-on-multiple-root-nodes)
## :rocket: Version
diff --git a/docs/rules/no-duplicate-attributes.md b/docs/rules/no-duplicate-attributes.md
index 000b67414..9ee2d2be6 100644
--- a/docs/rules/no-duplicate-attributes.md
+++ b/docs/rules/no-duplicate-attributes.md
@@ -5,19 +5,19 @@ title: vue/no-duplicate-attributes
description: disallow duplication of attributes
since: v3.0.0
---
+
# vue/no-duplicate-attributes
> disallow duplication of attributes
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
-When duplicate arguments exist, only the last one is valid.
-It's possibly mistakes.
+When there are multiple attributes with the same name on a component, only the last one is used and the rest are ignored, so this is usually a mistake.
## :book: Rule Details
This rule reports duplicate attributes.
-`v-bind:foo` directives are handled as the attributes `foo`.
+`v-bind:foo` directives are handled as the attribute `foo`.
@@ -53,8 +53,8 @@ This rule reports duplicate attributes.
- `allowCoexistClass` (`boolean`) ... Enables [`v-bind:class`] directive can coexist with the plain `class` attribute. Default is `true`.
- `allowCoexistStyle` (`boolean`) ... Enables [`v-bind:style`] directive can coexist with the plain `style` attribute. Default is `true`.
-[`v-bind:class`]: https://v3.vuejs.org/guide/class-and-style.html
-[`v-bind:style`]: https://v3.vuejs.org/guide/class-and-style.html
+[`v-bind:class`]: https://vuejs.org/guide/essentials/class-and-style.html
+[`v-bind:style`]: https://vuejs.org/guide/essentials/class-and-style.html
### `"allowCoexistClass": false, "allowCoexistStyle": false`
diff --git a/docs/rules/no-empty-component-block.md b/docs/rules/no-empty-component-block.md
index 5a15b6d7d..344bd0cba 100644
--- a/docs/rules/no-empty-component-block.md
+++ b/docs/rules/no-empty-component-block.md
@@ -5,18 +5,21 @@ title: vue/no-empty-component-block
description: disallow the `` `
@@ -43,7 +46,6 @@ See: https://vue-loader.vuejs.org/spec.html#src-imports
-
diff --git a/docs/rules/no-empty-pattern.md b/docs/rules/no-empty-pattern.md
index 6b017a8b0..6434a7bff 100644
--- a/docs/rules/no-empty-pattern.md
+++ b/docs/rules/no-empty-pattern.md
@@ -2,12 +2,13 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-empty-pattern
-description: disallow empty destructuring patterns
+description: Disallow empty destructuring patterns in ``
since: v6.0.0
---
+
# vue/no-empty-pattern
-> disallow empty destructuring patterns
+> Disallow empty destructuring patterns in ``
This rule is the same rule as core [no-empty-pattern] rule but it applies to the expressions in ``.
@@ -26,4 +27,4 @@ This rule was introduced in eslint-plugin-vue v6.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-empty-pattern.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-empty-pattern.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/no-empty-pattern)
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-empty-pattern)
diff --git a/docs/rules/no-export-in-script-setup.md b/docs/rules/no-export-in-script-setup.md
new file mode 100644
index 000000000..08166596b
--- /dev/null
+++ b/docs/rules/no-export-in-script-setup.md
@@ -0,0 +1,61 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-export-in-script-setup
+description: disallow `export` in `
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Vue RFCs - 0040-script-setup]
+
+[Vue RFCs - 0040-script-setup]: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.13.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-export-in-script-setup.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-export-in-script-setup.js)
diff --git a/docs/rules/no-expose-after-await.md b/docs/rules/no-expose-after-await.md
new file mode 100644
index 000000000..7f8fe9137
--- /dev/null
+++ b/docs/rules/no-expose-after-await.md
@@ -0,0 +1,73 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-expose-after-await
+description: disallow asynchronously registered `expose`
+since: v8.1.0
+---
+
+# vue/no-expose-after-await
+
+> disallow asynchronously registered `expose`
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+
+## :book: Rule Details
+
+This rule reports usages of `expose()` and `defineExpose()` after an `await` expression.
+In the `setup()` function, `expose()` should be registered synchronously.
+In the `
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Vue RFCs - 0042-expose-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0042-expose-api.md)
+- [Vue RFCs - 0013-composition-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0013-composition-api.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-expose-after-await.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-expose-after-await.js)
diff --git a/docs/rules/no-extra-parens.md b/docs/rules/no-extra-parens.md
index 49359d948..442a965ba 100644
--- a/docs/rules/no-extra-parens.md
+++ b/docs/rules/no-extra-parens.md
@@ -2,21 +2,27 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-extra-parens
-description: disallow unnecessary parentheses
+description: Disallow unnecessary parentheses in ``
since: v7.0.0
---
+
# vue/no-extra-parens
-> disallow unnecessary parentheses
+> Disallow unnecessary parentheses in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/no-extra-parens] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [no-extra-parens] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :book: Rule Details
This rule restricts the use of parentheses to only where they are necessary.
-This rule extends the core [no-extra-parens] rule and applies it to the ``. This rule also checks some Vue.js syntax.
+This rule extends the [@stylistic/no-extra-parens] rule and applies it to the ``. This rule also checks some Vue.js syntax.
@@ -37,8 +43,10 @@ This rule extends the core [no-extra-parens] rule and applies it to the `Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/no-extra-parens)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/no-extra-parens)
diff --git a/docs/rules/no-implicit-coercion.md b/docs/rules/no-implicit-coercion.md
new file mode 100644
index 000000000..22698320c
--- /dev/null
+++ b/docs/rules/no-implicit-coercion.md
@@ -0,0 +1,32 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-implicit-coercion
+description: Disallow shorthand type conversions in ``
+since: v9.33.0
+---
+
+# vue/no-implicit-coercion
+
+> Disallow shorthand type conversions in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as core [no-implicit-coercion] rule but it applies to the expressions in ``.
+
+## :books: Further Reading
+
+- [no-implicit-coercion]
+
+[no-implicit-coercion]: https://eslint.org/docs/rules/no-implicit-coercion
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.33.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-implicit-coercion.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-implicit-coercion.js)
+
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-implicit-coercion)
diff --git a/docs/rules/no-import-compiler-macros.md b/docs/rules/no-import-compiler-macros.md
new file mode 100644
index 000000000..7ceadb336
--- /dev/null
+++ b/docs/rules/no-import-compiler-macros.md
@@ -0,0 +1,58 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-import-compiler-macros
+description: disallow importing Vue compiler macros
+since: v10.0.0
+---
+
+# vue/no-import-compiler-macros
+
+> disallow importing Vue compiler macros
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule disallow importing vue compiler macros.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [defineProps() & defineEmits()]
+
+[defineProps() & defineEmits()]: https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v10.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-import-compiler-macros.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-import-compiler-macros.js)
diff --git a/docs/rules/no-invalid-model-keys.md b/docs/rules/no-invalid-model-keys.md
new file mode 100644
index 000000000..e93c79db4
--- /dev/null
+++ b/docs/rules/no-invalid-model-keys.md
@@ -0,0 +1,121 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-invalid-model-keys
+description: require valid keys in model option
+since: v7.9.0
+---
+
+# vue/no-invalid-model-keys
+
+> require valid keys in model option
+
+- :no_entry: This rule was **removed** in eslint-plugin-vue v10.0.0 and replaced by [vue/valid-model-definition](valid-model-definition.md) rule.
+
+## :book: Rule Details
+
+This rule is aimed at preventing invalid keys in model option.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.9.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-invalid-model-keys.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-invalid-model-keys.js)
diff --git a/docs/rules/no-irregular-whitespace.md b/docs/rules/no-irregular-whitespace.md
index 401b0feef..615f2294a 100644
--- a/docs/rules/no-irregular-whitespace.md
+++ b/docs/rules/no-irregular-whitespace.md
@@ -2,12 +2,13 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-irregular-whitespace
-description: disallow irregular whitespace
+description: disallow irregular whitespace in `.vue` files
since: v6.1.0
---
+
# vue/no-irregular-whitespace
-> disallow irregular whitespace
+> disallow irregular whitespace in `.vue` files
## :book: Rule Details
diff --git a/docs/rules/no-lifecycle-after-await.md b/docs/rules/no-lifecycle-after-await.md
index dbcef6b80..78d54c526 100644
--- a/docs/rules/no-lifecycle-after-await.md
+++ b/docs/rules/no-lifecycle-after-await.md
@@ -5,11 +5,12 @@ title: vue/no-lifecycle-after-await
description: disallow asynchronously registered lifecycle hooks
since: v7.0.0
---
+
# vue/no-lifecycle-after-await
> disallow asynchronously registered lifecycle hooks
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
@@ -43,7 +44,7 @@ Nothing.
## :books: Further Reading
-- [Guide - Composition API - Lifecycle Hooks](https://v3.vuejs.org/guide/composition-api-lifecycle-hooks.html)
+- [Guide - Composition API - Lifecycle Hooks](https://vuejs.org/api/composition-api-lifecycle.html)
- [Vue RFCs - 0013-composition-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0013-composition-api.md)
## :rocket: Version
diff --git a/docs/rules/no-lone-template.md b/docs/rules/no-lone-template.md
index 0c3904e3c..170b63ce3 100644
--- a/docs/rules/no-lone-template.md
+++ b/docs/rules/no-lone-template.md
@@ -5,11 +5,12 @@ title: vue/no-lone-template
description: disallow unnecessary ``
since: v7.0.0
---
+
# vue/no-lone-template
> disallow unnecessary ``
-- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
diff --git a/docs/rules/no-loss-of-precision.md b/docs/rules/no-loss-of-precision.md
new file mode 100644
index 000000000..c9b88ce63
--- /dev/null
+++ b/docs/rules/no-loss-of-precision.md
@@ -0,0 +1,34 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-loss-of-precision
+description: Disallow literal numbers that lose precision in ``
+since: v8.0.0
+---
+
+# vue/no-loss-of-precision
+
+> Disallow literal numbers that lose precision in ``
+
+This rule is the same rule as core [no-loss-of-precision] rule but it applies to the expressions in ``.
+
+:::warning
+You must be using ESLint v7.1.0 or later to use this rule.
+:::
+
+## :books: Further Reading
+
+- [no-loss-of-precision]
+
+[no-loss-of-precision]: https://eslint.org/docs/rules/no-loss-of-precision
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-loss-of-precision.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-loss-of-precision.js)
+
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-loss-of-precision)
diff --git a/docs/rules/no-multi-spaces.md b/docs/rules/no-multi-spaces.md
index 065b799b7..66c6a81d7 100644
--- a/docs/rules/no-multi-spaces.md
+++ b/docs/rules/no-multi-spaces.md
@@ -5,12 +5,13 @@ title: vue/no-multi-spaces
description: disallow multiple spaces
since: v3.12.0
---
+
# vue/no-multi-spaces
> disallow multiple spaces
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
diff --git a/docs/rules/no-multiple-objects-in-class.md b/docs/rules/no-multiple-objects-in-class.md
index 9e1508b8b..13f1f591b 100644
--- a/docs/rules/no-multiple-objects-in-class.md
+++ b/docs/rules/no-multiple-objects-in-class.md
@@ -2,16 +2,17 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-multiple-objects-in-class
-description: disallow to pass multiple objects into array to class
+description: disallow passing multiple objects in an array to class
since: v7.0.0
---
+
# vue/no-multiple-objects-in-class
-> disallow to pass multiple objects into array to class
+> disallow passing multiple objects in an array to class
## :book: Rule Details
-This rule disallows to pass multiple objects into array to class.
+This rule disallows to pass multiple objects into array to class.
@@ -19,10 +20,10 @@ This rule disallows to pass multiple objects into array to class.
-
+
-
+
```
diff --git a/docs/rules/no-multiple-slot-args.md b/docs/rules/no-multiple-slot-args.md
index c73be9007..74d6c2931 100644
--- a/docs/rules/no-multiple-slot-args.md
+++ b/docs/rules/no-multiple-slot-args.md
@@ -2,14 +2,15 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-multiple-slot-args
-description: disallow to pass multiple arguments to scoped slots
+description: disallow passing multiple arguments to scoped slots
since: v7.0.0
---
+
# vue/no-multiple-slot-args
-> disallow to pass multiple arguments to scoped slots
+> disallow passing multiple arguments to scoped slots
-- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
diff --git a/docs/rules/no-multiple-template-root.md b/docs/rules/no-multiple-template-root.md
index e5130faab..8a85c9201 100644
--- a/docs/rules/no-multiple-template-root.md
+++ b/docs/rules/no-multiple-template-root.md
@@ -5,11 +5,12 @@ title: vue/no-multiple-template-root
description: disallow adding multiple root nodes to the template
since: v7.0.0
---
+
# vue/no-multiple-template-root
> disallow adding multiple root nodes to the template
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -41,7 +42,7 @@ This rule checks whether template contains single root element valid for Vue 2.
```vue
-
+
```
@@ -60,7 +61,32 @@ This rule checks whether template contains single root element valid for Vue 2.
## :wrench: Options
-Nothing.
+```json
+{
+ "vue/no-multiple-template-root": ["error", {
+ "disallowComments": false
+ }]
+}
+```
+
+- "disallowComments" (`boolean`) Enables there should not be any comments in the template root. Default is `false`.
+
+### "disallowComments": true
+
+
+
+```vue
+/* ✗ BAD */
+
+
+
+ vue eslint plugin
+
+
+
+```
+
+
## :rocket: Version
diff --git a/docs/rules/no-mutating-props.md b/docs/rules/no-mutating-props.md
index 1b58da7d4..3ddee4cc2 100644
--- a/docs/rules/no-mutating-props.md
+++ b/docs/rules/no-mutating-props.md
@@ -5,11 +5,12 @@ title: vue/no-mutating-props
description: disallow mutation of component props
since: v7.0.0
---
+
# vue/no-mutating-props
> disallow mutation of component props
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -22,22 +23,38 @@ This rule reports mutation of component props.
+
+
```
@@ -50,22 +67,38 @@ This rule reports mutation of component props.
+
+
```
@@ -75,12 +108,12 @@ This rule reports mutation of component props.
```vue
```
@@ -88,12 +121,50 @@ This rule reports mutation of component props.
## :wrench: Options
-Nothing.
+```json
+{
+ "vue/no-mutating-props": ["error", {
+ "shallowOnly": false
+ }]
+}
+```
+
+- "shallowOnly" (`boolean`) Enables mutating the value of a prop but leaving the reference the same. Default is `false`.
+
+### "shallowOnly": true
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
## :books: Further Reading
-- [Style guide - Implicit parent-child communication](https://v3.vuejs.org/style-guide/#implicit-parent-child-communication-use-with-caution)
-- [Vue - Prop Mutation - deprecated](https://vuejs.org/v2/guide/migration.html#Prop-Mutation-deprecated)
+- [Style guide - Implicit parent-child communication](https://vuejs.org/style-guide/rules-use-with-caution.html#implicit-parent-child-communication)
+- [Vue - Prop Mutation - deprecated](https://v2.vuejs.org/v2/guide/migration.html#Prop-Mutation-deprecated)
## :rocket: Version
diff --git a/docs/rules/no-parsing-error.md b/docs/rules/no-parsing-error.md
index 811fd06af..a6fcf2908 100644
--- a/docs/rules/no-parsing-error.md
+++ b/docs/rules/no-parsing-error.md
@@ -5,21 +5,22 @@ title: vue/no-parsing-error
description: disallow parsing errors in ``
since: v3.0.0
---
+
# vue/no-parsing-error
> disallow parsing errors in ``
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule reports syntax errors in ``. For example:
- Syntax errors of scripts in directives.
- Syntax errors of scripts in mustaches.
- Syntax errors of HTML.
- - Invalid end tags.
- - Attributes in end tags.
- - ...
- - See also: [WHATWG HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors)
+ - Invalid end tags.
+ - Attributes in end tags.
+ - ...
+ - See also: [WHATWG HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors)
## :book: Rule Details
diff --git a/docs/rules/no-potential-component-option-typo.md b/docs/rules/no-potential-component-option-typo.md
index 0a4696af8..17e21c818 100644
--- a/docs/rules/no-potential-component-option-typo.md
+++ b/docs/rules/no-potential-component-option-typo.md
@@ -5,15 +5,18 @@ title: vue/no-potential-component-option-typo
description: disallow a potential typo in your component property
since: v7.0.0
---
+
# vue/no-potential-component-option-typo
> disallow a potential typo in your component property
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
## :book: Rule Details
This rule disallow a potential typo in your component options
-**Here is the config**
+### Here is the config
```json
{
@@ -57,7 +60,7 @@ export default {
> we use edit distance to compare two string similarity, threshold is an option to control upper bound of edit distance to report
-**Here is the another example about config option `threshold`**
+### Here is the another example about config option `threshold`
```json
{
diff --git a/docs/rules/no-ref-as-operand.md b/docs/rules/no-ref-as-operand.md
index 2610d4624..af10e0b7d 100644
--- a/docs/rules/no-ref-as-operand.md
+++ b/docs/rules/no-ref-as-operand.md
@@ -5,12 +5,13 @@ title: vue/no-ref-as-operand
description: disallow use of value wrapped by `ref()` (Composition API) as an operand
since: v7.0.0
---
+
# vue/no-ref-as-operand
> disallow use of value wrapped by `ref()` (Composition API) as an operand
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -24,7 +25,7 @@ You must use `.value` to access the `Ref` value.
import { ref } from 'vue'
export default {
- setup () {
+ setup(_props, { emit }) {
const count = ref(0)
const ok = ref(true)
@@ -33,12 +34,14 @@ export default {
count.value + 1
1 + count.value
var msg = ok.value ? 'yes' : 'no'
+ emit('increment', count.value)
/* ✗ BAD */
count++
count + 1
1 + count
var msg = ok ? 'yes' : 'no'
+ emit('increment', count)
return {
count
diff --git a/docs/rules/no-ref-object-destructure.md b/docs/rules/no-ref-object-destructure.md
new file mode 100644
index 000000000..8ea5247a1
--- /dev/null
+++ b/docs/rules/no-ref-object-destructure.md
@@ -0,0 +1,61 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-ref-object-destructure
+description: disallow usages of ref objects that can lead to loss of reactivity
+since: v9.5.0
+---
+
+# vue/no-ref-object-destructure
+
+> disallow usages of ref objects that can lead to loss of reactivity
+
+- :no_entry: This rule was **removed** in eslint-plugin-vue v10.0.0 and replaced by [vue/no-ref-object-reactivity-loss](no-ref-object-reactivity-loss.md) rule.
+
+## :book: Rule Details
+
+This rule reports the destructuring of ref objects causing the value to lose reactivity.
+
+
+
+```js
+import { ref } from 'vue'
+const count = ref(0)
+const v1 = count.value /* ✗ BAD */
+const { value: v2 } = count /* ✗ BAD */
+const v3 = computed(() => count.value /* ✓ GOOD */)
+const v4 = fn(count.value) /* ✗ BAD */
+const v5 = fn(count) /* ✓ GOOD */
+const v6 = computed(() => fn(count.value) /* ✓ GOOD */)
+```
+
+
+
+This rule also supports Reactivity Transform, but Reactivity Transform is an experimental feature and may have false positives due to future Vue changes.
+See the [RFC](https://github.com/vuejs/rfcs/pull/420) for more information on Reactivity Transform.
+
+
+
+```js
+const count = $ref(0)
+const v1 = count /* ✗ BAD */
+const v2 = $computed(() => count /* ✓ GOOD */)
+const v3 = fn(count) /* ✗ BAD */
+const v4 = fn($$(count)) /* ✓ GOOD */
+const v5 = $computed(() => fn(count) /* ✓ GOOD */)
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.5.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-ref-object-destructure.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-ref-object-destructure.js)
diff --git a/docs/rules/no-ref-object-reactivity-loss.md b/docs/rules/no-ref-object-reactivity-loss.md
new file mode 100644
index 000000000..5df8ed52b
--- /dev/null
+++ b/docs/rules/no-ref-object-reactivity-loss.md
@@ -0,0 +1,59 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-ref-object-reactivity-loss
+description: disallow usages of ref objects that can lead to loss of reactivity
+since: v9.17.0
+---
+
+# vue/no-ref-object-reactivity-loss
+
+> disallow usages of ref objects that can lead to loss of reactivity
+
+## :book: Rule Details
+
+This rule reports the usages of ref objects causing the value to lose reactivity.
+
+
+
+```js
+import { ref } from 'vue'
+const count = ref(0)
+const v1 = count.value /* ✗ BAD */
+const { value: v2 } = count /* ✗ BAD */
+const v3 = computed(() => count.value /* ✓ GOOD */)
+const v4 = fn(count.value) /* ✗ BAD */
+const v5 = fn(count) /* ✓ GOOD */
+const v6 = computed(() => fn(count.value) /* ✓ GOOD */)
+```
+
+
+
+This rule also supports Reactivity Transform, but Reactivity Transform is an experimental feature and may have false positives due to future Vue changes.
+See the [RFC](https://github.com/vuejs/rfcs/pull/420) for more information on Reactivity Transform.
+
+
+
+```js
+const count = $ref(0)
+const v1 = count /* ✗ BAD */
+const v2 = $computed(() => count /* ✓ GOOD */)
+const v3 = fn(count) /* ✗ BAD */
+const v4 = fn($$(count)) /* ✓ GOOD */
+const v5 = $computed(() => fn(count) /* ✓ GOOD */)
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.17.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-ref-object-reactivity-loss.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-ref-object-reactivity-loss.js)
diff --git a/docs/rules/no-required-prop-with-default.md b/docs/rules/no-required-prop-with-default.md
new file mode 100644
index 000000000..8308e4d8c
--- /dev/null
+++ b/docs/rules/no-required-prop-with-default.md
@@ -0,0 +1,102 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-required-prop-with-default
+description: enforce props with default values to be optional
+since: v9.6.0
+---
+
+# vue/no-required-prop-with-default
+
+> enforce props with default values to be optional
+
+- :gear: This rule is included in all of `"plugin:vue/vue2-recommended"`, `*.configs["flat/vue2-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+If a prop is declared with a default value, whether it is required or not, we can always skip it in actual use. In that situation, the default value would be applied.
+So, a required prop with a default value is essentially the same as an optional prop.
+This rule enforces all props with default values to be optional.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-required-prop-with-default": ["error", {
+ "autofix": false,
+ }]
+}
+```
+
+- `"autofix"` ... If `true`, enable autofix. (Default: `false`)
+
+## :couple: Related Rules
+
+- [vue/require-default-prop](./require-default-prop.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.6.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-required-prop-with-default.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-required-prop-with-default.js)
diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md
index d424fb8f1..c99191872 100644
--- a/docs/rules/no-reserved-component-names.md
+++ b/docs/rules/no-reserved-component-names.md
@@ -5,10 +5,13 @@ title: vue/no-reserved-component-names
description: disallow the use of reserved names in component definitions
since: v6.1.0
---
+
# vue/no-reserved-component-names
> disallow the use of reserved names in component definitions
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+
## :book: Rule Details
This rule prevents name collisions between Vue components and standard HTML elements and built-in components.
@@ -32,13 +35,15 @@ export default {
{
"vue/no-reserved-component-names": ["error", {
"disallowVueBuiltInComponents": false,
- "disallowVue3BuiltInComponents": false
+ "disallowVue3BuiltInComponents": false,
+ "htmlElementCaseSensitive": false,
}]
}
```
- `disallowVueBuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 2.x built-in component names. Default is `false`.
- `disallowVue3BuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 3.x built-in component names. Default is `false`.
+- `htmlElementCaseSensitive` (`boolean`) ... If `true`, component names must exactly match the case of an HTML element to be considered conflicting. Default is `false` (i.e. case-insensitve comparison).
### `"disallowVueBuiltInComponents": true`
@@ -70,14 +75,46 @@ export default {
+### `"htmlElementCaseSensitive": true`
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/multi-word-component-names](./multi-word-component-names.md)
+
## :books: Further Reading
- [List of html elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element)
- [List of SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Element)
- [Kebab case elements](https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name/22545622#22545622)
-- [Valid custom element name](https://w3c.github.io/webcomponents/spec/custom/#valid-custom-element-name)
-- [API - Built-In Components](https://v3.vuejs.org/api/built-in-components.html)
-- [API (for v2) - Built-In Components](https://vuejs.org/v2/api/index.html#Built-In-Components)
+- [Valid custom element name](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name)
+- [API - Built-In Components](https://vuejs.org/api/built-in-components.html)
+- [API (for v2) - Built-In Components](https://v2.vuejs.org/v2/api/index.html#Built-In-Components)
## :rocket: Version
diff --git a/docs/rules/no-reserved-keys.md b/docs/rules/no-reserved-keys.md
index 36535c5b5..9244ee0b7 100644
--- a/docs/rules/no-reserved-keys.md
+++ b/docs/rules/no-reserved-keys.md
@@ -5,11 +5,12 @@ title: vue/no-reserved-keys
description: disallow overwriting reserved keys
since: v3.9.0
---
+
# vue/no-reserved-keys
> disallow overwriting reserved keys
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -26,14 +27,14 @@ export default {
},
computed: {
$on: {
- get () {}
+ get() {}
}
},
data: {
_foo: null
},
methods: {
- $nextTick () {}
+ $nextTick() {}
}
}
@@ -64,10 +65,10 @@ export default {
/* ✗ BAD */
export default {
computed: {
- foo () {}
+ foo() {}
},
firebase: {
- foo2 () {}
+ foo2() {}
}
}
diff --git a/docs/rules/no-reserved-props.md b/docs/rules/no-reserved-props.md
new file mode 100644
index 000000000..982ddb947
--- /dev/null
+++ b/docs/rules/no-reserved-props.md
@@ -0,0 +1,57 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-reserved-props
+description: disallow reserved names in props
+since: v8.0.0
+---
+
+# vue/no-reserved-props
+
+> disallow reserved names in props
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+
+## :book: Rule Details
+
+This rule disallow reserved names to be used in props.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-reserved-props": ["error", {
+ "vueVersion": 3, // or 2
+ }]
+}
+```
+
+- `vueVersion` (`2 | 3`) ... Specify the version of Vue you are using. Default is `3`.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-reserved-props.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-reserved-props.js)
diff --git a/docs/rules/no-restricted-block.md b/docs/rules/no-restricted-block.md
index cb6ab788b..0a96a64a4 100644
--- a/docs/rules/no-restricted-block.md
+++ b/docs/rules/no-restricted-block.md
@@ -5,6 +5,7 @@ title: vue/no-restricted-block
description: disallow specific block
since: v7.4.0
---
+
# vue/no-restricted-block
> disallow specific block
diff --git a/docs/rules/no-restricted-call-after-await.md b/docs/rules/no-restricted-call-after-await.md
index f880f8f12..0082bb881 100644
--- a/docs/rules/no-restricted-call-after-await.md
+++ b/docs/rules/no-restricted-call-after-await.md
@@ -5,6 +5,7 @@ title: vue/no-restricted-call-after-await
description: disallow asynchronously called restricted methods
since: v7.4.0
---
+
# vue/no-restricted-call-after-await
> disallow asynchronously called restricted methods
diff --git a/docs/rules/no-restricted-class.md b/docs/rules/no-restricted-class.md
new file mode 100644
index 000000000..a69f28d55
--- /dev/null
+++ b/docs/rules/no-restricted-class.md
@@ -0,0 +1,84 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-class
+description: disallow specific classes in Vue components
+since: v7.19.0
+---
+
+# vue/no-restricted-class
+
+> disallow specific classes in Vue components
+
+## :book: Rule Details
+
+This rule lets you specify a list of classes that you don't want to allow in your templates.
+
+## :wrench: Options
+
+The simplest way to specify a list of forbidden classes is to pass it directly
+in the rule configuration.
+
+```json
+{
+ "vue/no-restricted-class": ["error", "forbidden", "forbidden-two", "forbidden-three", "/^for(bidden|gotten)/"]
+}
+```
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Note
+This rule will only detect classes that are used as strings in your templates. Passing classes via
+variables, like below, will not be detected by this rule.
+
+```vue
+
+
+
+
+
+```
+
+:::
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.19.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-class.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-class.js)
diff --git a/docs/rules/no-restricted-component-names.md b/docs/rules/no-restricted-component-names.md
new file mode 100644
index 000000000..689c73aa1
--- /dev/null
+++ b/docs/rules/no-restricted-component-names.md
@@ -0,0 +1,98 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-component-names
+description: disallow specific component names
+since: v9.15.0
+---
+
+# vue/no-restricted-component-names
+
+> disallow specific component names
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule allows you to specify component names that you don't want to use in your application.
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :wrench: Options
+
+This rule takes a list of strings, where each string is a component name or pattern to be restricted:
+
+```json
+{
+ "vue/no-restricted-component-names": ["error", "foo", "/^Disallow/"]
+}
+```
+
+Alternatively, you can specify an object with a `name` property and an optional `message` and `suggest` property:
+
+```json
+{
+ "vue/no-restricted-component-names": [
+ "error",
+ {
+ "name": "Disallow",
+ "message": "Please do not use `Disallow` as a component name",
+ "suggest": "allow"
+ },
+ {
+ "name": "/^custom/",
+ "message": "Please do not use component names starting with 'custom'"
+ }
+ ]
+}
+```
+
+
+
+```vue
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/restricted-component-names](./restricted-component-names.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.15.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-component-names.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-component-names.js)
diff --git a/docs/rules/no-restricted-component-options.md b/docs/rules/no-restricted-component-options.md
index a11570945..57d736ae3 100644
--- a/docs/rules/no-restricted-component-options.md
+++ b/docs/rules/no-restricted-component-options.md
@@ -5,6 +5,7 @@ title: vue/no-restricted-component-options
description: disallow specific component option
since: v7.0.0
---
+
# vue/no-restricted-component-options
> disallow specific component option
diff --git a/docs/rules/no-restricted-custom-event.md b/docs/rules/no-restricted-custom-event.md
index 231ad0607..88a3eb424 100644
--- a/docs/rules/no-restricted-custom-event.md
+++ b/docs/rules/no-restricted-custom-event.md
@@ -5,10 +5,13 @@ title: vue/no-restricted-custom-event
description: disallow specific custom event
since: v7.3.0
---
+
# vue/no-restricted-custom-event
> disallow specific custom event
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
## :book: Rule Details
This rule allows you to specify custom event that you don't want to use in your application.
@@ -29,7 +32,7 @@ This rule takes a list of strings, where each string is a custom event name or p
-
+
+```
+
+
+
+Destructuring the `props` passed to `setup` will cause the value to lose reactivity.
+
+
+
+```vue
+
+```
+
+
+
+Also, destructuring in root scope of `setup()` should error, but ok inside nested callbacks or returned render functions:
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Guide - Composition API - Setup](https://vuejs.org/api/composition-api-setup.html)
+- [Vue RFCs - 0013-composition-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0013-composition-api.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.17.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-setup-props-reactivity-loss.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-setup-props-reactivity-loss.js)
diff --git a/docs/rules/no-shared-component-data.md b/docs/rules/no-shared-component-data.md
index 258828a47..1a96cf89f 100644
--- a/docs/rules/no-shared-component-data.md
+++ b/docs/rules/no-shared-component-data.md
@@ -5,12 +5,13 @@ title: vue/no-shared-component-data
description: enforce component's data property to be a function
since: v3.8.0
---
+
# vue/no-shared-component-data
> enforce component's data property to be a function
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
When using the data property on a component (i.e. anywhere except on `new Vue`), the value must be a function that returns an object.
@@ -32,7 +33,7 @@ Vue.component('some-comp', {
})
export default {
- data () {
+ data() {
return {
foo: 'bar'
}
@@ -70,7 +71,7 @@ Nothing.
## :books: Further Reading
-- [Style guide (for v2) - Component data](https://vuejs.org/v2/style-guide/#Component-data-essential)
+- [Style guide (for v2) - Component data](https://v2.vuejs.org/v2/style-guide/#Component-data-essential)
- [API - data](https://v3.vuejs.org/api/options-data.html#data-2)
- [API (for v2) - data](https://v3.vuejs.org/api/options-data.html#data-2)
diff --git a/docs/rules/no-side-effects-in-computed-properties.md b/docs/rules/no-side-effects-in-computed-properties.md
index b2d0490b7..970299537 100644
--- a/docs/rules/no-side-effects-in-computed-properties.md
+++ b/docs/rules/no-side-effects-in-computed-properties.md
@@ -5,11 +5,12 @@ title: vue/no-side-effects-in-computed-properties
description: disallow side effects in computed properties
since: v3.6.0
---
+
# vue/no-side-effects-in-computed-properties
> disallow side effects in computed properties
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -24,10 +25,10 @@ It is considered a very bad practice to introduce side effects inside computed p
/* ✓ GOOD */
export default {
computed: {
- fullName () {
+ fullName() {
return `${this.firstName} ${this.lastName}`
},
- reversedArray () {
+ reversedArray() {
return this.array.slice(0).reverse() // .slice makes a copy of the array, instead of mutating the orginal
}
}
@@ -44,11 +45,11 @@ export default {
/* ✗ BAD */
export default {
computed: {
- fullName () {
+ fullName() {
this.firstName = 'lorem' // <- side effect
return `${this.firstName} ${this.lastName}`
},
- reversedArray () {
+ reversedArray() {
return this.array.reverse() // <- side effect - orginal array is being mutated
}
}
@@ -62,7 +63,7 @@ export default {
```vue
```
@@ -50,7 +51,37 @@ This rule aims to eliminate shadowed variable declarations of v-for directives o
## :wrench: Options
-Nothing.
+This rule takes one optional object option, with the property `"allow"`.
+
+```json
+{
+ "vue/no-template-shadow": ["error", { "allow": [] }]
+}
+```
+
+- `"allow"` (`[string]`) Array of identifier names for which shadowing is allowed.
+
+Examples of correct code for the `{ "allow": ["i"] }` option:
+
+
+
+```vue
+
+
+
+
+
+```
+
+
## :rocket: Version
diff --git a/docs/rules/no-template-target-blank.md b/docs/rules/no-template-target-blank.md
index 411b1c9c2..d2a8fc006 100644
--- a/docs/rules/no-template-target-blank.md
+++ b/docs/rules/no-template-target-blank.md
@@ -5,9 +5,12 @@ title: vue/no-template-target-blank
description: disallow target="_blank" attribute without rel="noopener noreferrer"
since: v7.0.0
---
+
# vue/no-template-target-blank
-> disallow target="_blank" attribute without rel="noopener noreferrer"
+> disallow target="\_blank" attribute without rel="noopener noreferrer"
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
## :book: Rule Details
@@ -22,7 +25,7 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
link
-
+
```
@@ -52,7 +55,7 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
link
-
+
```
@@ -68,7 +71,7 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
link
-
+
```
@@ -84,7 +87,7 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
link
-
+
```
@@ -100,7 +103,7 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
link
-
+
```
diff --git a/docs/rules/no-textarea-mustache.md b/docs/rules/no-textarea-mustache.md
index 4c26b6bf0..0db4aff4f 100644
--- a/docs/rules/no-textarea-mustache.md
+++ b/docs/rules/no-textarea-mustache.md
@@ -5,11 +5,12 @@ title: vue/no-textarea-mustache
description: disallow mustaches in `
```
diff --git a/docs/rules/no-unsupported-features.md b/docs/rules/no-unsupported-features.md
index dfb73e851..d77fdbdaa 100644
--- a/docs/rules/no-unsupported-features.md
+++ b/docs/rules/no-unsupported-features.md
@@ -5,11 +5,12 @@ title: vue/no-unsupported-features
description: disallow unsupported Vue.js syntax on the specified version
since: v6.1.0
---
+
# vue/no-unsupported-features
> disallow unsupported Vue.js syntax on the specified version
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -28,18 +29,31 @@ This rule reports unsupported Vue.js syntax on the specified version.
- `version` ... The `version` option accepts [the valid version range of `node-semver`](https://github.com/npm/node-semver#range-grammar). Set the version of Vue.js you are using. This option is required.
- `ignores` ... You can use this `ignores` option to ignore the given features.
-The `"ignores"` option accepts an array of the following strings.
+ The `"ignores"` option accepts an array of the following strings.
+ - Vue.js 3.4.0+
+ - `"define-model"` ... `defineModel()` macro.
+ - `"v-bind-same-name-shorthand"` ... `v-bind` same-name shorthand.
+ - Vue.js 3.3.0+
+ - `"define-slots"` ... `defineSlots()` macro.
+ - `"define-options"` ... `defineOptions()` macro.
+ - Vue.js 3.2.0+
+ - `"v-memo"` ... [v-memo](https://vuejs.org/api/built-in-directives.html#v-memo) directive.
+ - `"v-bind-prop-modifier-shorthand"` ... `v-bind` with `.prop` modifier shorthand.
+ - `"v-bind-attr-modifier"` ... `.attr` modifier on `v-bind` directive.
+ - Vue.js 3.1.0+
+ - `"is-attribute-with-vue-prefix"` ... [`is` attribute with `vue:` prefix](https://vuejs.org/api/built-in-special-attributes.html#is)
- Vue.js 3.0.0+
- `"v-model-argument"` ... [argument on `v-model`][Vue RFCs - 0005-replace-v-bind-sync-with-v-model-argument]
- `"v-model-custom-modifiers"` ... [custom modifiers on `v-model`][Vue RFCs - 0011-v-model-api-change]
- `"v-is"` ... [v-is](https://v3.vuejs.org/api/directives.html#v-is) directive.
+ - Vue.js 2.7.0+
+ - `"style-css-vars-injection"` ... [SFC CSS variable injection][Vue RFCs - 0043-sfc-style-variables]
+ - `"script-setup"` ... [`
```
@@ -61,15 +62,15 @@ This rule reports components that haven't been used in the template.
```
@@ -108,17 +109,17 @@ Components registered under `PascalCase` or `camelCase` names have may be called
```
@@ -136,21 +137,20 @@ Components registered under `PascalCase` or `camelCase` names have may be called
```
diff --git a/docs/rules/no-unused-emit-declarations.md b/docs/rules/no-unused-emit-declarations.md
new file mode 100644
index 000000000..54abc81d1
--- /dev/null
+++ b/docs/rules/no-unused-emit-declarations.md
@@ -0,0 +1,63 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-unused-emit-declarations
+description: disallow unused emit declarations
+since: v9.19.0
+---
+
+# vue/no-unused-emit-declarations
+
+> disallow unused emit declarations
+
+## :book: Rule Details
+
+This rule is aimed at eliminating unused emit declarations.
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/require-explicit-emits](./require-explicit-emits.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.19.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unused-emit-declarations.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unused-emit-declarations.js)
diff --git a/docs/rules/no-unused-properties.md b/docs/rules/no-unused-properties.md
index da8fa7d87..e0e14416a 100644
--- a/docs/rules/no-unused-properties.md
+++ b/docs/rules/no-unused-properties.md
@@ -5,6 +5,7 @@ title: vue/no-unused-properties
description: disallow unused properties
since: v7.0.0
---
+
# vue/no-unused-properties
> disallow unused properties
@@ -14,7 +15,7 @@ since: v7.0.0
This rule is aimed at eliminating unused properties.
::: warning Note
-This rule cannot be checked for use in other components (e.g. `mixins`, Property access via `$refs`) and use in places where the scope cannot be determined.
+This rule cannot check for use of properties by other components (e.g. `mixins`, property access via `$refs`) and use in places where the scope cannot be determined. Some access to properties might be implied, for example accessing data or computed via a variable such as `this[varName]`. In this case, the default is to assume all properties, methods, etc. are 'used'. See the `unreferencedOptions` for a more strict interpretation of 'use' in these cases.
:::
@@ -25,9 +26,9 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
{{ count }}
```
@@ -41,9 +42,9 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
{{ cnt }}
```
@@ -56,7 +57,8 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
"vue/no-unused-properties": ["error", {
"groups": ["props"],
"deepData": false,
- "ignorePublicMembers": false
+ "ignorePublicMembers": false,
+ "unreferencedOptions": []
}]
}
```
@@ -69,6 +71,7 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
- `"setup"`
- `deepData` (`boolean`) If `true`, the object of the property defined in `data` will be searched deeply. Default is `false`. Include `"data"` in `groups` to use this option.
- `ignorePublicMembers` (`boolean`) If `true`, members marked with a [JSDoc `/** @public */` tag](https://jsdoc.app/tags-public.html) will be ignored. Default is `false`.
+- `unreferencedOptions` (`string[]`) Array of access methods that should be interpreted as leaving properties unreferenced. Currently, two such methods are available: `unknownMemberAsUnreferenced`, and `returnAsUnreferenced`. See examples below.
### `"groups": ["props", "data"]`
@@ -77,16 +80,16 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
```vue
```
@@ -97,16 +100,16 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
```vue
```
@@ -148,18 +151,18 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
{{ reversedMessage }}
```
@@ -173,18 +176,18 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
{{ message }}
```
@@ -200,19 +203,80 @@ This rule cannot be checked for use in other components (e.g. `mixins`, Property
+```
+
+
+
+### `{ "groups": ["computed"], "unreferencedOptions": ["unknownMemberAsUnreferenced"] }`
+
+
+
+```vue
+
+
+```
+
+
+
+### `{ "groups": ["computed"], "unreferencedOptions": ["returnAsUnreferenced"] }`
+
+
+
+```vue
+
+
```
diff --git a/docs/rules/no-unused-refs.md b/docs/rules/no-unused-refs.md
new file mode 100644
index 000000000..06383d93e
--- /dev/null
+++ b/docs/rules/no-unused-refs.md
@@ -0,0 +1,54 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-unused-refs
+description: disallow unused refs
+since: v7.9.0
+---
+
+# vue/no-unused-refs
+
+> disallow unused refs
+
+## :book: Rule Details
+
+This rule is aimed at eliminating unused refs.
+This rule reports refs that are defined using the `ref` attribute in `` but are not used via `$refs`.
+
+::: warning Note
+This rule cannot be checked for use in other components (e.g. `mixins`, Access via `$refs.x.$refs`).
+:::
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.9.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unused-refs.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unused-refs.js)
diff --git a/docs/rules/no-unused-vars.md b/docs/rules/no-unused-vars.md
index a7486bad5..eedfc7250 100644
--- a/docs/rules/no-unused-vars.md
+++ b/docs/rules/no-unused-vars.md
@@ -5,11 +5,13 @@ title: vue/no-unused-vars
description: disallow unused variable definitions of v-for directives or scope attributes
since: v3.14.0
---
+
# vue/no-unused-vars
> disallow unused variable definitions of v-for directives or scope attributes
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
## :book: Rule Details
diff --git a/docs/rules/no-use-computed-property-like-method.md b/docs/rules/no-use-computed-property-like-method.md
new file mode 100644
index 000000000..3dc4dbc54
--- /dev/null
+++ b/docs/rules/no-use-computed-property-like-method.md
@@ -0,0 +1,346 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-use-computed-property-like-method
+description: disallow use computed property like method
+since: v7.15.0
+---
+
+# vue/no-use-computed-property-like-method
+
+> disallow use computed property like method
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+
+## :book: Rule Details
+
+This rule disallows to use computed property like method.
+
+
+
+```vue
+
+```
+
+
+
+This rule can't check if props is used as array:
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.15.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-use-computed-property-like-method.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-use-computed-property-like-method.js)
diff --git a/docs/rules/no-use-v-else-with-v-for.md b/docs/rules/no-use-v-else-with-v-for.md
new file mode 100644
index 000000000..203775ad8
--- /dev/null
+++ b/docs/rules/no-use-v-else-with-v-for.md
@@ -0,0 +1,58 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-use-v-else-with-v-for
+description: disallow using `v-else-if`/`v-else` on the same element as `v-for`
+since: v9.16.0
+---
+
+# vue/no-use-v-else-with-v-for
+
+> disallow using `v-else-if`/`v-else` on the same element as `v-for`
+
+## :book: Rule Details
+
+This rule reports elements that have both `v-else-if`/`v-else` and `v-for` directives. That is valid in Vue (`v-else-if`/`v-else` will take precedence), but is confusing to read.
+
+
+
+```vue
+
+
+
foo
+
+
{{ x }}
+
+
+
{{ x }}
+
+
+
+
foo
+
{{ x }}
+
{{ x }}
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :mute: When Not To Use It
+
+If you don't find using `v-else-if`/`v-else` together with `v-for` confusing to read, you can safely disable this rule.
+
+## :couple: Related Rules
+
+- [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.16.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-use-v-else-with-v-for.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-use-v-else-with-v-for.js)
diff --git a/docs/rules/no-use-v-if-with-v-for.md b/docs/rules/no-use-v-if-with-v-for.md
index 78b11aa4c..faf1c62b2 100644
--- a/docs/rules/no-use-v-if-with-v-for.md
+++ b/docs/rules/no-use-v-if-with-v-for.md
@@ -2,22 +2,24 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-use-v-if-with-v-for
-description: disallow use v-if on the same element as v-for
+description: disallow using `v-if` on the same element as `v-for`
since: v4.6.0
---
+
# vue/no-use-v-if-with-v-for
-> disallow use v-if on the same element as v-for
+> disallow using `v-if` on the same element as `v-for`
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
This rule is aimed at preventing the use of `v-for` directives together with `v-if` directives on the same element.
There are two common cases where this can be tempting:
- * To filter items in a list (e.g. `v-for="user in users" v-if="user.isActive"`). In these cases, replace `users` with a new computed property that returns your filtered list (e.g. `activeUsers`).
- * To avoid rendering a list if it should be hidden (e.g. `v-for="user in users" v-if="shouldShowUsers"`). In these cases, move the `v-if` to a container element (e.g. `ul`, `ol`).
+
+- To filter items in a list (e.g. `v-for="user in users" v-if="user.isActive"`). In these cases, replace `users` with a new computed property that returns your filtered list (e.g. `activeUsers`).
+- To avoid rendering a list if it should be hidden (e.g. `v-for="user in users" v-if="shouldShowUsers"`). In these cases, move the `v-if` to a container element (e.g. `ul`, `ol`).
@@ -87,11 +89,15 @@ There are two common cases where this can be tempting:
+## :couple: Related Rules
+
+- [vue/no-use-v-else-with-v-for](./no-use-v-else-with-v-for.md)
+
## :books: Further Reading
-- [Style guide - Avoid v-if with v-for](https://v3.vuejs.org/style-guide/#avoid-v-if-with-v-for-essential)
-- [Guide - Conditional Rendering / v-if with v-for](https://v3.vuejs.org/guide/conditional.html#v-if-with-v-for)
-- [Guide - List Rendering / v-for with v-if](https://v3.vuejs.org/guide/list.html#v-for-with-v-if)
+- [Style guide - Avoid v-if with v-for](https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for)
+- [Guide - Conditional Rendering / v-if with v-for](https://vuejs.org/guide/essentials/conditional.html#v-if-with-v-for)
+- [Guide - List Rendering / v-for with v-if](https://vuejs.org/guide/essentials/list.html#v-for-with-v-if)
## :rocket: Version
diff --git a/docs/rules/no-useless-concat.md b/docs/rules/no-useless-concat.md
index 0deda262e..bd74f475d 100644
--- a/docs/rules/no-useless-concat.md
+++ b/docs/rules/no-useless-concat.md
@@ -2,12 +2,13 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-useless-concat
-description: disallow unnecessary concatenation of literals or template literals
+description: Disallow unnecessary concatenation of literals or template literals in ``
since: v7.0.0
---
+
# vue/no-useless-concat
-> disallow unnecessary concatenation of literals or template literals
+> Disallow unnecessary concatenation of literals or template literals in ``
This rule is the same rule as core [no-useless-concat] rule but it applies to the expressions in ``.
@@ -26,4 +27,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-useless-concat.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-useless-concat.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/no-useless-concat)
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-useless-concat)
diff --git a/docs/rules/no-useless-mustaches.md b/docs/rules/no-useless-mustaches.md
index 205371a86..aed50cb1c 100644
--- a/docs/rules/no-useless-mustaches.md
+++ b/docs/rules/no-useless-mustaches.md
@@ -5,11 +5,12 @@ title: vue/no-useless-mustaches
description: disallow unnecessary mustache interpolations
since: v7.0.0
---
+
# vue/no-useless-mustaches
> disallow unnecessary mustache interpolations
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
diff --git a/docs/rules/no-useless-template-attributes.md b/docs/rules/no-useless-template-attributes.md
new file mode 100644
index 000000000..d8eb20a98
--- /dev/null
+++ b/docs/rules/no-useless-template-attributes.md
@@ -0,0 +1,70 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-useless-template-attributes
+description: disallow useless attribute on ``
+since: v7.19.0
+---
+
+# vue/no-useless-template-attributes
+
+> disallow useless attribute on ``
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+
+## :book: Rule Details
+
+This rule to prevent any useless attribute on `` tags.
+
+
+
+```vue
+
+
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+
+ ...
+ ...
+ ...
+
+ ...
+
+
+ ...
+ ...
+ ...
+ ...
+
+
+ ...
+ ...
+ ...
+ ...
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/no-lone-template]
+
+[vue/no-lone-template]: ./no-lone-template.md
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.19.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-useless-template-attributes.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-useless-template-attributes.js)
diff --git a/docs/rules/no-useless-v-bind.md b/docs/rules/no-useless-v-bind.md
index 303f15871..4c5b11ba5 100644
--- a/docs/rules/no-useless-v-bind.md
+++ b/docs/rules/no-useless-v-bind.md
@@ -5,11 +5,12 @@ title: vue/no-useless-v-bind
description: disallow unnecessary `v-bind` directives
since: v7.0.0
---
+
# vue/no-useless-v-bind
> disallow unnecessary `v-bind` directives
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -21,12 +22,12 @@ The `v-bind` with a string literal value can be changed to a static attribute de
```vue
-
-
+
+
-
-
+
+
```
@@ -53,10 +54,10 @@ The `v-bind` with a string literal value can be changed to a static attribute de
```vue
-
+
-
+
```
@@ -69,7 +70,7 @@ The `v-bind` with a string literal value can be changed to a static attribute de
```vue
-
+
```
diff --git a/docs/rules/no-v-for-template-key-on-child.md b/docs/rules/no-v-for-template-key-on-child.md
index f43aa307f..4e6ddd0c3 100644
--- a/docs/rules/no-v-for-template-key-on-child.md
+++ b/docs/rules/no-v-for-template-key-on-child.md
@@ -5,23 +5,24 @@ title: vue/no-v-for-template-key-on-child
description: disallow key of `` placed on child elements
since: v7.0.0
---
+
# vue/no-v-for-template-key-on-child
> disallow key of `` placed on child elements
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
This rule reports the key of the `` placed on the child elements.
-In Vue.js 3.x, with the support for fragments, the `` key can be placed on the `` tag.
+In Vue.js 3.x, with the support for fragments, the `` key can be placed on the `` tag.
-See [Migration Guide - `key` attribute > With ``](https://v3.vuejs.org/guide/migration/key-attribute.html#with-template-v-for) for more details.
+See [Migration Guide - `key` attribute > With ``](https://v3-migration.vuejs.org/breaking-changes/key-attribute.html#with-template-v-for) for more details.
::: warning Note
-Do not use with the [vue/no-v-for-template-key] rule for Vue.js 2.x.
-This rule conflicts with the [vue/no-v-for-template-key] rule.
+This rule is targeted at Vue.js 3.x.
+If you are using Vue.js 2.x, enable the [vue/no-v-for-template-key] rule instead. Don't enable both rules together; they are conflicting.
:::
@@ -54,7 +55,7 @@ Nothing.
## :books: Further Reading
-- [Migration Guide - `key` attribute > With ``](https://v3.vuejs.org/guide/migration/key-attribute.html#with-template-v-for)
+- [Migration Guide - `key` attribute > With ``](https://v3-migration.vuejs.org/breaking-changes/key-attribute.html#with-template-v-for)
## :rocket: Version
diff --git a/docs/rules/no-v-for-template-key.md b/docs/rules/no-v-for-template-key.md
index 128a5c07d..5dbe15471 100644
--- a/docs/rules/no-v-for-template-key.md
+++ b/docs/rules/no-v-for-template-key.md
@@ -5,11 +5,13 @@ title: vue/no-v-for-template-key
description: disallow `key` attribute on ``
since: v7.0.0
---
+
# vue/no-v-for-template-key
> disallow `key` attribute on ``
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+- :no_entry_sign: This rule was **deprecated**.
+- :gear: This rule is included in all of `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -18,8 +20,8 @@ This rule reports the `` elements which have `key` attribute.
In Vue.js 2.x, disallows `key` attribute on `` elements.
::: warning Note
-Do not use with the [vue/no-v-for-template-key-on-child] rule for Vue.js 3.x.
-This rule conflicts with the [vue/no-v-for-template-key-on-child] rule.
+This rule is targeted at Vue.js 2.x.
+If you are using Vue.js 3.x, enable the [vue/no-v-for-template-key-on-child] rule instead. Don't enable both rules together; they are conflicting.
:::
@@ -58,8 +60,8 @@ Nothing.
## :books: Further Reading
-- [API - Special Attributes - key](https://v3.vuejs.org/api/special-attributes.html#key)
-- [API (for v2) - Special Attributes - key](https://vuejs.org/v2/api/#key)
+- [API - Special Attributes - key](https://vuejs.org/api/built-in-special-attributes.html#key)
+- [API (for v2) - Special Attributes - key](https://v2.vuejs.org/v2/api/#key)
## :rocket: Version
diff --git a/docs/rules/no-v-html.md b/docs/rules/no-v-html.md
index cb390eca1..3098683e0 100644
--- a/docs/rules/no-v-html.md
+++ b/docs/rules/no-v-html.md
@@ -5,11 +5,12 @@ title: vue/no-v-html
description: disallow use of v-html to prevent XSS attack
since: v4.7.0
---
+
# vue/no-v-html
> disallow use of v-html to prevent XSS attack
-- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -37,10 +38,6 @@ Nothing.
If you are certain the content passed to `v-html` is sanitized HTML you can disable this rule.
-## :books: Further Reading
-
-- [XSS in Vue.js](https://blog.sqreen.io/xss-in-vue-js/)
-
## :rocket: Version
This rule was introduced in eslint-plugin-vue v4.7.0
diff --git a/docs/rules/no-v-model-argument.md b/docs/rules/no-v-model-argument.md
index 4571893c6..6856c5e6c 100644
--- a/docs/rules/no-v-model-argument.md
+++ b/docs/rules/no-v-model-argument.md
@@ -5,11 +5,13 @@ title: vue/no-v-model-argument
description: disallow adding an argument to `v-model` used in custom component
since: v7.0.0
---
+
# vue/no-v-model-argument
> disallow adding an argument to `v-model` used in custom component
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+- :no_entry_sign: This rule was **deprecated**.
+- :gear: This rule is included in all of `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether `v-model` used on custom component do not have an argument.
@@ -26,7 +28,6 @@ This rule reports `v-model` directives in the following cases:
-
@@ -34,7 +35,6 @@ This rule reports `v-model` directives in the following cases:
-
## :wrench: Options
Nothing.
diff --git a/docs/rules/no-v-text-v-html-on-component.md b/docs/rules/no-v-text-v-html-on-component.md
new file mode 100644
index 000000000..9bbbcf7ab
--- /dev/null
+++ b/docs/rules/no-v-text-v-html-on-component.md
@@ -0,0 +1,94 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-v-text-v-html-on-component
+description: disallow v-text / v-html on component
+since: v8.4.0
+---
+
+# vue/no-v-text-v-html-on-component
+
+> disallow v-text / v-html on component
+
+- :gear: This rule is included in all of `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-recommended"`, `*.configs["flat/vue2-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+
+## :book: Rule Details
+
+This rule disallows the use of v-text / v-html on component.
+
+If you use v-text / v-html on a component, it will overwrite the component's content and may break the component.
+
+
+
+```vue
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-v-text-v-html-on-component": ["error", {
+ "allow": ["router-link", "nuxt-link"],
+ "ignoreElementNamespaces": false
+ }]
+}
+```
+
+- `allow` (`string[]`) ... Specify a list of custom components for which the rule should not apply.
+- `ignoreElementNamespaces` (`boolean`) ... If `true`, always treat SVG and MathML tag names as HTML elements, even if they are not used inside a SVG/MathML root element. Default is `false`.
+
+### `{ "allow": ["router-link", "nuxt-link"] }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+### `{ "ignoreElementNamespaces": true }`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.4.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-text-v-html-on-component.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-text-v-html-on-component.js)
diff --git a/docs/rules/no-v-text.md b/docs/rules/no-v-text.md
new file mode 100644
index 000000000..89bedbfbb
--- /dev/null
+++ b/docs/rules/no-v-text.md
@@ -0,0 +1,42 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-v-text
+description: disallow use of v-text
+since: v7.17.0
+---
+
+# vue/no-v-text
+
+> disallow use of v-text
+
+## :book: Rule Details
+
+This rule reports all uses of `v-text` directive.
+
+
+
+```vue
+
+
+
{{ foobar }}
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.17.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-text.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-text.js)
diff --git a/docs/rules/no-watch-after-await.md b/docs/rules/no-watch-after-await.md
index a5c2a5e9f..32b5dab0c 100644
--- a/docs/rules/no-watch-after-await.md
+++ b/docs/rules/no-watch-after-await.md
@@ -5,11 +5,12 @@ title: vue/no-watch-after-await
description: disallow asynchronously registered `watch`
since: v7.0.0
---
+
# vue/no-watch-after-await
> disallow asynchronously registered `watch`
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
## :book: Rule Details
diff --git a/docs/rules/object-curly-newline.md b/docs/rules/object-curly-newline.md
index cc4831c12..8f636a1da 100644
--- a/docs/rules/object-curly-newline.md
+++ b/docs/rules/object-curly-newline.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/object-curly-newline
-description: enforce consistent line breaks inside braces
+description: Enforce consistent line breaks after opening and before closing braces in ``
since: v7.0.0
---
+
# vue/object-curly-newline
-> enforce consistent line breaks inside braces
+> Enforce consistent line breaks after opening and before closing braces in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/object-curly-newline] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [object-curly-newline] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/object-curly-newline]
- [object-curly-newline]
+[@stylistic/object-curly-newline]: https://eslint.style/rules/default/object-curly-newline
[object-curly-newline]: https://eslint.org/docs/rules/object-curly-newline
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/object-curly-newline.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/object-curly-newline.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/object-curly-newline)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/object-curly-newline)
diff --git a/docs/rules/object-curly-spacing.md b/docs/rules/object-curly-spacing.md
index f63716c24..e7a3fa0a6 100644
--- a/docs/rules/object-curly-spacing.md
+++ b/docs/rules/object-curly-spacing.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/object-curly-spacing
-description: enforce consistent spacing inside braces
+description: Enforce consistent spacing inside braces in ``
since: v5.2.0
---
+
# vue/object-curly-spacing
-> enforce consistent spacing inside braces
+> Enforce consistent spacing inside braces in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/object-curly-spacing] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [object-curly-spacing] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/object-curly-spacing]
- [object-curly-spacing]
+[@stylistic/object-curly-spacing]: https://eslint.style/rules/default/object-curly-spacing
[object-curly-spacing]: https://eslint.org/docs/rules/object-curly-spacing
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v5.2.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/object-curly-spacing.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/object-curly-spacing.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/object-curly-spacing)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/object-curly-spacing)
diff --git a/docs/rules/object-property-newline.md b/docs/rules/object-property-newline.md
index 62ec559f6..6b6434ced 100644
--- a/docs/rules/object-property-newline.md
+++ b/docs/rules/object-property-newline.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/object-property-newline
-description: enforce placing object properties on separate lines
+description: Enforce placing object properties on separate lines in ``
since: v7.0.0
---
+
# vue/object-property-newline
-> enforce placing object properties on separate lines
+> Enforce placing object properties on separate lines in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/object-property-newline] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [object-property-newline] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/object-property-newline]
- [object-property-newline]
+[@stylistic/object-property-newline]: https://eslint.style/rules/default/object-property-newline
[object-property-newline]: https://eslint.org/docs/rules/object-property-newline
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/object-property-newline.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/object-property-newline.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/object-property-newline)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/object-property-newline)
diff --git a/docs/rules/object-shorthand.md b/docs/rules/object-shorthand.md
new file mode 100644
index 000000000..8b4365b19
--- /dev/null
+++ b/docs/rules/object-shorthand.md
@@ -0,0 +1,32 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/object-shorthand
+description: Require or disallow method and property shorthand syntax for object literals in ``
+since: v8.4.0
+---
+
+# vue/object-shorthand
+
+> Require or disallow method and property shorthand syntax for object literals in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as core [object-shorthand] rule but it applies to the expressions in ``.
+
+## :books: Further Reading
+
+- [object-shorthand]
+
+[object-shorthand]: https://eslint.org/docs/rules/object-shorthand
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.4.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/object-shorthand.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/object-shorthand.js)
+
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/object-shorthand)
diff --git a/docs/rules/one-component-per-file.md b/docs/rules/one-component-per-file.md
index 006a13b74..8d31a0875 100644
--- a/docs/rules/one-component-per-file.md
+++ b/docs/rules/one-component-per-file.md
@@ -5,11 +5,12 @@ title: vue/one-component-per-file
description: enforce that each component should be in its own file
since: v7.0.0
---
+
# vue/one-component-per-file
> enforce that each component should be in its own file
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -48,9 +49,13 @@ export default {
Nothing.
+## :couple: Related Rules
+
+- [vue/require-default-export](./require-default-export.md)
+
## :books: Further Reading
-- [Style guide - Component files](https://v3.vuejs.org/style-guide/#component-files-strongly-recommended)
+- [Style guide - Component files](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files)
## :rocket: Version
diff --git a/docs/rules/operator-linebreak.md b/docs/rules/operator-linebreak.md
index a982b999a..b994e7622 100644
--- a/docs/rules/operator-linebreak.md
+++ b/docs/rules/operator-linebreak.md
@@ -2,21 +2,29 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/operator-linebreak
-description: enforce consistent linebreak style for operators
+description: Enforce consistent linebreak style for operators in ``
since: v7.0.0
---
+
# vue/operator-linebreak
-> enforce consistent linebreak style for operators
+> Enforce consistent linebreak style for operators in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/operator-linebreak] rule but it applies to the expressions in ``.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
-This rule is the same rule as core [operator-linebreak] rule but it applies to the expressions in ``.
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
## :books: Further Reading
+- [@stylistic/operator-linebreak]
- [operator-linebreak]
+[@stylistic/operator-linebreak]: https://eslint.style/rules/default/operator-linebreak
[operator-linebreak]: https://eslint.org/docs/rules/operator-linebreak
## :rocket: Version
@@ -28,4 +36,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/operator-linebreak.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/operator-linebreak.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/operator-linebreak)
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/js/operator-linebreak)
diff --git a/docs/rules/order-in-components.md b/docs/rules/order-in-components.md
index bcbbe77d0..29d3eda1c 100644
--- a/docs/rules/order-in-components.md
+++ b/docs/rules/order-in-components.md
@@ -5,17 +5,19 @@ title: vue/order-in-components
description: enforce order of properties in components
since: v3.2.0
---
+
# vue/order-in-components
> enforce order of properties in components
-- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
## :book: Rule Details
This rule makes sure you keep declared order of properties in components.
-Recommended order of properties can be [found here](https://v3.vuejs.org/style-guide/#component-instance-options-order-recommended).
+Recommended order of properties can be [found here](https://vuejs.org/style-guide/rules-recommended.html#component-instance-options-order).
@@ -27,7 +29,7 @@ export default {
props: {
propA: Number
},
- data () {
+ data() {
return {
msg: 'Welcome to Your Vue.js App'
}
@@ -45,7 +47,7 @@ export default {
/* ✗ BAD */
export default {
name: 'app',
- data () {
+ data() {
return {
msg: 'Welcome to Your Vue.js App'
}
@@ -86,6 +88,8 @@ export default {
"model",
["props", "propsData"],
"emits",
+ "slots",
+ "expose",
"setup",
"asyncData",
"data",
@@ -105,16 +109,15 @@ export default {
- `order` (`(string | string[])[]`) ... The order of properties. Elements are the property names or one of the following groups:
- - `LIFECYCLE_HOOKS`: [Vue Lifecycle Events](https://v3.vuejs.org/guide/instance.html#lifecycle-diagram), in the order they are called
+ - `LIFECYCLE_HOOKS`: [Vue Lifecycle Events](https://vuejs.org/guide/essentials/lifecycle.html#lifecycle-diagram), in the order they are called
- `ROUTER_GUARDS`: [Vue Router Navigation Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards), in the order they are called
If an element is an array of strings, it means any of those can be placed there unordered. Default is above.
-
## :books: Further Reading
-- [Style guide - Component/instance options order](https://v3.vuejs.org/style-guide/#component-instance-options-order-recommended)
-- [Style guide (for v2) - Component/instance options order](https://vuejs.org/v2/style-guide/#Component-instance-options-order-recommended)
+- [Style guide - Component/instance options order](https://vuejs.org/style-guide/rules-recommended.html#component-instance-options-order)
+- [Style guide (for v2) - Component/instance options order](https://v2.vuejs.org/v2/style-guide/#Component-instance-options-order-recommended)
## :rocket: Version
diff --git a/docs/rules/padding-line-between-blocks.md b/docs/rules/padding-line-between-blocks.md
index 7e1e058e1..4f3ee0290 100644
--- a/docs/rules/padding-line-between-blocks.md
+++ b/docs/rules/padding-line-between-blocks.md
@@ -5,11 +5,12 @@ title: vue/padding-line-between-blocks
description: require or disallow padding lines between blocks
since: v6.2.0
---
+
# vue/padding-line-between-blocks
> require or disallow padding lines between blocks
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -134,7 +135,6 @@ export default {}
[padding-line-between-statements]: https://eslint.org/docs/rules/padding-line-between-statements
[lines-between-class-members]: https://eslint.org/docs/rules/lines-between-class-members
-
## :rocket: Version
This rule was introduced in eslint-plugin-vue v6.2.0
diff --git a/docs/rules/padding-line-between-tags.md b/docs/rules/padding-line-between-tags.md
new file mode 100644
index 000000000..e5b5c4b41
--- /dev/null
+++ b/docs/rules/padding-line-between-tags.md
@@ -0,0 +1,170 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/padding-line-between-tags
+description: require or disallow newlines between sibling tags in template
+since: v9.5.0
+---
+
+# vue/padding-line-between-tags
+
+> require or disallow newlines between sibling tags in template
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule requires or disallows newlines between sibling HTML tags.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/padding-line-between-tags": ["error", [
+ { "blankLine": "always", "prev": "*", "next": "*" }
+ ]]
+}
+```
+
+This rule requires blank lines between each sibling HTML tag by default.
+
+A configuration is an object which has 3 properties; `blankLine`, `prev` and `next`. For example, `{ blankLine: "always", prev: "br", next: "div" }` means “one or more blank lines are required between a `br` tag and a `div` tag.” You can supply any number of configurations. If a tag pair matches multiple configurations, the last matched configuration will be used.
+
+- `blankLine` is one of the following:
+ - `always` requires one or more blank lines.
+ - `never` disallows blank lines.
+ - `consistent` requires or disallows a blank line based on the first sibling element.
+- `prev` any tag name without brackets.
+- `next` any tag name without brackets.
+
+### Disallow blank lines between all tags
+
+`{ blankLine: 'never', prev: '*', next: '*' }`
+
+
+
+```vue
+
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.5.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-tags.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-line-between-tags.js)
diff --git a/docs/rules/padding-lines-in-component-definition.md b/docs/rules/padding-lines-in-component-definition.md
new file mode 100644
index 000000000..eef694e14
--- /dev/null
+++ b/docs/rules/padding-lines-in-component-definition.md
@@ -0,0 +1,168 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/padding-lines-in-component-definition
+description: require or disallow padding lines in component definition
+since: v9.9.0
+---
+
+# vue/padding-lines-in-component-definition
+
+> require or disallow padding lines in component definition
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule requires or disallows blank lines in the component definition. Properly blank lines help developers improve code readability and code style flexibility.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/padding-lines-in-component-definition": ["error", {
+ "betweenOptions": "always" | "never",
+
+ "withinOption": {
+ "props": {
+ "betweenItems": "always" | "never" | "ignore",
+ "withinEach": "always" | "never" | "ignore",
+ } | "always" | "never" | "ignore", // shortcut to set both
+
+ "data": {
+ "betweenItems": "always" | "never" | "ignore",
+ "withinEach": "always" | "never" | "ignore",
+ } | "always" | "never" | "ignore" // shortcut to set both
+
+ // ... all options
+ } | "always" | "never" | "ignore",
+
+ "groupSingleLineProperties": true | false
+ }]
+}
+```
+
+- `betweenOptions` ... Setting padding lines between options. default `always`
+- `withinOption` ... Setting padding lines within option
+ - `emits` ... Setting padding between lines between `emits` and `defineEmits`. default `always`
+ - `props` ... Setting padding between lines between `props` and `defineProps`. default `always`
+ - ...
+- `groupSingleLineProperties` ... Setting groupings of multiple consecutive single-line properties (e.g. `name`, `inheritAttrs`), default `true`
+
+### Group single-line properties
+
+
+
+```vue
+
+```
+
+
+
+### With custom options
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.9.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-lines-in-component-definition.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-lines-in-component-definition.js)
diff --git a/docs/rules/prefer-define-options.md b/docs/rules/prefer-define-options.md
new file mode 100644
index 000000000..110c1426a
--- /dev/null
+++ b/docs/rules/prefer-define-options.md
@@ -0,0 +1,61 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-define-options
+description: enforce use of `defineOptions` instead of default export
+since: v9.13.0
+---
+
+# vue/prefer-define-options
+
+> enforce use of `defineOptions` instead of default export
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule aims to enforce use of `defineOptions` instead of default export in `
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [API - defineOptions()](https://vuejs.org/api/sfc-script-setup.html#defineoptions)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.13.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-define-options.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-define-options.js)
diff --git a/docs/rules/prefer-import-from-vue.md b/docs/rules/prefer-import-from-vue.md
new file mode 100644
index 000000000..2da6700f9
--- /dev/null
+++ b/docs/rules/prefer-import-from-vue.md
@@ -0,0 +1,58 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-import-from-vue
+description: enforce import from 'vue' instead of import from '@vue/*'
+since: v8.5.0
+---
+
+# vue/prefer-import-from-vue
+
+> enforce import from 'vue' instead of import from '@vue/\*'
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule aims to use imports from `'vue'` instead of imports from `'@vue/*'`.
+
+Imports from the following modules are almost always wrong. You should import from `vue` instead.
+
+- `@vue/runtime-dom`
+- `@vue/runtime-core`
+- `@vue/reactivity`
+- `@vue/shared`
+
+
+
+```js
+/* ✓ GOOD */
+import { createApp, ref, Component } from 'vue'
+```
+
+
+
+
+
+```js
+/* ✗ BAD */
+import { createApp } from '@vue/runtime-dom'
+import { Component } from '@vue/runtime-core'
+import { ref } from '@vue/reactivity'
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.5.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-import-from-vue.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-import-from-vue.js)
diff --git a/docs/rules/prefer-prop-type-boolean-first.md b/docs/rules/prefer-prop-type-boolean-first.md
new file mode 100644
index 000000000..8282e32ba
--- /dev/null
+++ b/docs/rules/prefer-prop-type-boolean-first.md
@@ -0,0 +1,65 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-prop-type-boolean-first
+description: enforce `Boolean` comes first in component prop types
+since: v8.6.0
+---
+
+# vue/prefer-prop-type-boolean-first
+
+> enforce `Boolean` comes first in component prop types
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+When declaring types of a property in component, we can use array style to accept multiple types.
+
+When using components in template,
+we can use shorthand-style property if its value is `true`.
+
+However, if a property allows `Boolean` or `String` and we use it with shorthand form in somewhere else,
+different types order can introduce different behaviors:
+If `Boolean` comes first, it will be `true`; if `String` comes first, it will be `""` (empty string).
+
+See [this demo](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCBNeUNvbXBvbmVudCBmcm9tICcuL015Q29tcG9uZW50LnZ1ZSdcbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIFNob3J0aGFuZCBmb3JtOlxuICA8TXlDb21wb25lbnQgYm9vbCBib29sLW9yLXN0cmluZyBzdHJpbmctb3ItYm9vbCAvPlxuICBcbiAgTG9uZ2hhbmQgZm9ybTpcbiAgPE15Q29tcG9uZW50IDpib29sPVwidHJ1ZVwiIDpib29sLW9yLXN0cmluZz1cInRydWVcIiA6c3RyaW5nLW9yLWJvb2w9XCJ0cnVlXCIgLz5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIk15Q29tcG9uZW50LnZ1ZSI6IjxzY3JpcHQ+XG5leHBvcnQgZGVmYXVsdCB7XG4gIHByb3BzOiB7XG4gICAgYm9vbDogQm9vbGVhbixcbiAgICBib29sT3JTdHJpbmc6IFtCb29sZWFuLCBTdHJpbmddLFxuICAgIHN0cmluZ09yQm9vbDogW1N0cmluZywgQm9vbGVhbl0sXG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxwcmU+XG5ib29sOiB7e2Jvb2x9fSAoe3sgdHlwZW9mIGJvb2wgfX0pXG5ib29sT3JTdHJpbmc6IHt7Ym9vbE9yU3RyaW5nfX0gKHt7IHR5cGVvZiBib29sT3JTdHJpbmcgfX0pXG5zdHJpbmdPckJvb2w6IHt7c3RyaW5nT3JCb29sfX0gKHt7IHR5cGVvZiBzdHJpbmdPckJvb2wgfX0pXG4gIDwvcHJlPlxuPC90ZW1wbGF0ZT4ifQ==).
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.6.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-prop-type-boolean-first.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-prop-type-boolean-first.js)
diff --git a/docs/rules/prefer-separate-static-class.md b/docs/rules/prefer-separate-static-class.md
new file mode 100644
index 000000000..610e3f2b8
--- /dev/null
+++ b/docs/rules/prefer-separate-static-class.md
@@ -0,0 +1,48 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-separate-static-class
+description: require static class names in template to be in a separate `class` attribute
+since: v8.2.0
+---
+
+# vue/prefer-separate-static-class
+
+> require static class names in template to be in a separate `class` attribute
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule reports static class names in dynamic class attributes.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-separate-static-class.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-separate-static-class.js)
diff --git a/docs/rules/prefer-template.md b/docs/rules/prefer-template.md
index 09a41769e..ae4d3fc29 100644
--- a/docs/rules/prefer-template.md
+++ b/docs/rules/prefer-template.md
@@ -2,14 +2,15 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/prefer-template
-description: require template literals instead of string concatenation
+description: Require template literals instead of string concatenation in ``
since: v7.0.0
---
+
# vue/prefer-template
-> require template literals instead of string concatenation
+> Require template literals instead of string concatenation in ``
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
This rule is the same rule as core [prefer-template] rule but it applies to the expressions in ``.
@@ -28,4 +29,4 @@ This rule was introduced in eslint-plugin-vue v7.0.0
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-template.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-template.js)
-Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/prefer-template)
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/prefer-template)
diff --git a/docs/rules/prefer-true-attribute-shorthand.md b/docs/rules/prefer-true-attribute-shorthand.md
new file mode 100644
index 000000000..700921ce1
--- /dev/null
+++ b/docs/rules/prefer-true-attribute-shorthand.md
@@ -0,0 +1,146 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-true-attribute-shorthand
+description: require shorthand form attribute when `v-bind` value is `true`
+since: v8.5.0
+---
+
+# vue/prefer-true-attribute-shorthand
+
+> require shorthand form attribute when `v-bind` value is `true`
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+`v-bind` attribute with `true` value usually can be written in shorthand form. This can reduce verbosity.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Warning
+The shorthand form is not always equivalent! If a prop accepts multiple types, but Boolean is not the first one, a shorthand prop won't pass `true`.
+:::
+
+```vue
+
+```
+
+**Shorthand form:**
+
+```vue
+
+```
+
+```txt
+bool: true (boolean)
+boolOrString: true (boolean)
+stringOrBool: "" (string)
+```
+
+**Longhand form:**
+
+```vue
+
+```
+
+```txt
+bool: true (boolean)
+boolOrString: true (boolean)
+stringOrBool: true (boolean)
+```
+
+Those two calls will introduce different render result. See [this demo](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCBNeUNvbXBvbmVudCBmcm9tICcuL015Q29tcG9uZW50LnZ1ZSdcbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIFNob3J0aGFuZCBmb3JtOlxuICA8TXlDb21wb25lbnQgYm9vbCBib29sLW9yLXN0cmluZyBzdHJpbmctb3ItYm9vbCAvPlxuICBcbiAgTG9uZ2hhbmQgZm9ybTpcbiAgPE15Q29tcG9uZW50IDpib29sPVwidHJ1ZVwiIDpib29sLW9yLXN0cmluZz1cInRydWVcIiA6c3RyaW5nLW9yLWJvb2w9XCJ0cnVlXCIgLz5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIk15Q29tcG9uZW50LnZ1ZSI6IjxzY3JpcHQ+XG5leHBvcnQgZGVmYXVsdCB7XG4gIHByb3BzOiB7XG4gICAgYm9vbDogQm9vbGVhbixcbiAgICBib29sT3JTdHJpbmc6IFtCb29sZWFuLCBTdHJpbmddLFxuICAgIHN0cmluZ09yQm9vbDogW1N0cmluZywgQm9vbGVhbl0sXG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxwcmU+XG5ib29sOiB7e2Jvb2x9fSAoe3sgdHlwZW9mIGJvb2wgfX0pXG5ib29sT3JTdHJpbmc6IHt7Ym9vbE9yU3RyaW5nfX0gKHt7IHR5cGVvZiBib29sT3JTdHJpbmcgfX0pXG5zdHJpbmdPckJvb2w6IHt7c3RyaW5nT3JCb29sfX0gKHt7IHR5cGVvZiBzdHJpbmdPckJvb2wgfX0pXG4gIDwvcHJlPlxuPC90ZW1wbGF0ZT4ifQ==).
+
+## :wrench: Options
+
+Default options is `"always"`.
+
+```json
+{
+ "vue/prefer-true-attribute-shorthand": ["error",
+ "always" | "never",
+ {
+ except: []
+ }
+ ]
+}
+```
+
+- `"always"` (default) ... requires shorthand form.
+- `"never"` ... requires long form.
+- `except` (`string[]`) ... specifies a list of attribute names that should be treated differently.
+
+### `"never"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"never", { 'except': ['value', '/^foo-/'] }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/no-boolean-default](./no-boolean-default.md)
+- [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.5.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-true-attribute-shorthand.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-true-attribute-shorthand.js)
diff --git a/docs/rules/prefer-use-template-ref.md b/docs/rules/prefer-use-template-ref.md
new file mode 100644
index 000000000..1b1b40385
--- /dev/null
+++ b/docs/rules/prefer-use-template-ref.md
@@ -0,0 +1,78 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-use-template-ref
+description: require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs
+since: v9.31.0
+---
+
+# vue/prefer-use-template-ref
+
+> require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs
+
+## :book: Rule Details
+
+Vue 3.5 introduced a new way of obtaining template refs via
+the [`useTemplateRef()`](https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs) API.
+
+This rule enforces using the new `useTemplateRef` function instead of `ref`/`shallowRef` for template refs.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+This rule skips `ref` template function refs as these should be used to allow custom implementation of storing `ref`. If you prefer
+`useTemplateRef`, you have to change the value of the template `ref` to a string.
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.31.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-use-template-ref.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-use-template-ref.js)
diff --git a/docs/rules/prop-name-casing.md b/docs/rules/prop-name-casing.md
index 81f19518f..32681643c 100644
--- a/docs/rules/prop-name-casing.md
+++ b/docs/rules/prop-name-casing.md
@@ -5,11 +5,12 @@ title: vue/prop-name-casing
description: enforce specific casing for the Prop name in Vue components
since: v4.3.0
---
+
# vue/prop-name-casing
> enforce specific casing for the Prop name in Vue components
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -38,12 +39,18 @@ export default {
```json
{
- "vue/prop-name-casing": ["error", "camelCase" | "snake_case"]
+ "vue/prop-name-casing": ["error",
+ "camelCase" | "snake_case",
+ {
+ "ignoreProps": []
+ }
+ ]
}
```
- `"camelCase"` (default) ... Enforce property names in `props` to camel case.
- `"snake_case"` ... Enforce property names in `props` to snake case.
+- `ignoreProps` (`string[]`) ... An array of prop names (or patterns) that don't need to follow the specified casing.
### `"snake_case"`
@@ -66,15 +73,40 @@ export default {
-## :books: Further Reading
+### `"ignoreProps": ["foo-bar", "/^_[a-z]+/u"]`
+
+
+
+```vue
+
+```
-- [Style guide - Prop name casing](https://v3.vuejs.org/style-guide/#prop-name-casing-strongly-recommended)
+
## :couple: Related Rules
- [vue/attribute-hyphenation](./attribute-hyphenation.md)
- [vue/custom-event-name-casing](./custom-event-name-casing.md)
+## :books: Further Reading
+
+- [Style guide - Prop name casing](https://vuejs.org/style-guide/rules-strongly-recommended.html#prop-name-casing)
+
## :rocket: Version
This rule was introduced in eslint-plugin-vue v4.3.0
diff --git a/docs/rules/quote-props.md b/docs/rules/quote-props.md
new file mode 100644
index 000000000..f5fe59248
--- /dev/null
+++ b/docs/rules/quote-props.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/quote-props
+description: Require quotes around object literal, type literal, interfaces and enums property names in ``
+since: v8.4.0
+---
+
+# vue/quote-props
+
+> Require quotes around object literal, type literal, interfaces and enums property names in ``
+
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+This rule is the same rule as [@stylistic/quote-props] rule but it applies to the expressions in ``.
+
+This rule extends the rule that [@stylistic/eslint-plugin] has, but if [@stylistic/eslint-plugin] is not installed, this rule extracts and extends the same rule from ESLint core.
+However, if neither is found, the rule cannot be used.
+
+[@stylistic/eslint-plugin]: https://eslint.style/packages/default
+
+## :books: Further Reading
+
+- [@stylistic/quote-props]
+- [quote-props]
+
+[@stylistic/quote-props]: https://eslint.style/rules/default/quote-props
+[quote-props]: https://eslint.org/docs/rules/quote-props
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v8.4.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/quote-props.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/quote-props.js)
+
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/quote-props)
diff --git a/docs/rules/require-component-is.md b/docs/rules/require-component-is.md
index 767e9a930..40280a975 100644
--- a/docs/rules/require-component-is.md
+++ b/docs/rules/require-component-is.md
@@ -5,28 +5,28 @@ title: vue/require-component-is
description: require `v-bind:is` of `` elements
since: v3.0.0
---
+
# vue/require-component-is
> require `v-bind:is` of `` elements
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
This rule reports the `` elements which do not have `v-bind:is` attributes.
-
```vue
-
-
+
+
-
-
+
+
```
@@ -36,14 +36,13 @@ This rule reports the `` elements which do not have `v-bind:is` attri
You can use the same mount point and dynamically switch between multiple components using the reserved `` element and dynamically bind to its `is` attribute.
:::
-
## :wrench: Options
Nothing.
## :books: Further Reading
-- [Guide - Components Basics / Dynamic Components](https://v3.vuejs.org/guide/component-basics.html#dynamic-components)
+- [Guide - Components Basics / Dynamic Components](https://vuejs.org/guide/essentials/component-basics.html#dynamic-components)
## :rocket: Version
diff --git a/docs/rules/require-default-export.md b/docs/rules/require-default-export.md
new file mode 100644
index 000000000..9266eee89
--- /dev/null
+++ b/docs/rules/require-default-export.md
@@ -0,0 +1,60 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-default-export
+description: require components to be the default export
+since: v9.28.0
+---
+
+# vue/require-default-export
+
+> require components to be the default export
+
+## :book: Rule Details
+
+This rule reports when a Vue component does not have a default export, if the component is not defined as `
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/one-component-per-file](./one-component-per-file.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.28.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-default-export.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-default-export.js)
diff --git a/docs/rules/require-default-prop.md b/docs/rules/require-default-prop.md
index 2f43c88a1..c96edc24e 100644
--- a/docs/rules/require-default-prop.md
+++ b/docs/rules/require-default-prop.md
@@ -5,11 +5,12 @@ title: vue/require-default-prop
description: require default value for props
since: v3.13.0
---
+
# vue/require-default-prop
> require default value for props
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -61,9 +62,13 @@ export default {
Nothing.
+## :couple: Related Rules
+
+- [vue/no-boolean-default](./no-boolean-default.md)
+
## :books: Further Reading
-- [Style guide - Prop definitions](https://v3.vuejs.org/style-guide/#prop-definitions-essential)
+- [Style guide - Prop definitions](https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions)
## :rocket: Version
diff --git a/docs/rules/require-direct-export.md b/docs/rules/require-direct-export.md
index 52661b7c1..4f2e37650 100644
--- a/docs/rules/require-direct-export.md
+++ b/docs/rules/require-direct-export.md
@@ -5,6 +5,7 @@ title: vue/require-direct-export
description: require the component to be directly exported
since: v5.2.0
---
+
# vue/require-direct-export
> require the component to be directly exported
@@ -70,7 +71,7 @@ export default ComponentA
```vue
```
@@ -83,7 +84,7 @@ export default props => h('div', props.msg)
```vue
```
diff --git a/docs/rules/require-emit-validator.md b/docs/rules/require-emit-validator.md
new file mode 100644
index 000000000..34b674bf5
--- /dev/null
+++ b/docs/rules/require-emit-validator.md
@@ -0,0 +1,66 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-emit-validator
+description: require type definitions in emits
+since: v7.10.0
+---
+
+# vue/require-emit-validator
+
+> require type definitions in emits
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule enforces that a `emits` statement contains type definition.
+
+Declaring `emits` with types can bring better maintenance.
+Even if using with TypeScript, this can provide better type inference when annotating parameters with types.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [API Reference](https://vuejs.org/api/options-state.html#emits)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.10.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-emit-validator.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-emit-validator.js)
diff --git a/docs/rules/require-explicit-emits.md b/docs/rules/require-explicit-emits.md
index b698b488f..5c5d3f8a9 100644
--- a/docs/rules/require-explicit-emits.md
+++ b/docs/rules/require-explicit-emits.md
@@ -5,27 +5,29 @@ title: vue/require-explicit-emits
description: require `emits` option with name triggered by `$emit()`
since: v7.0.0
---
+
# vue/require-explicit-emits
> require `emits` option with name triggered by `$emit()`
-- :gear: This rule is included in `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
## :book: Rule Details
This rule reports event triggers not declared with the `emits` option. (The `emits` option is a new in Vue.js 3.0.0+)
Explicit `emits` declaration serves as self-documenting code. This can be useful for other developers to instantly understand what events the component is supposed to emit.
-Also, with attribute fallthrough changes in Vue.js 3.0.0+, `v-on` listeners on components will fallthrough as native listeners by default. Declare it as a component-only event in `emits` to avoid unnecessary registration of native listeners.
+Also, with attribute fallthrough changes in Vue.js 3.0.0+, `v-on` listeners on components will fallthrough as native listeners by default. Declare it as a component-only event in `emits` to avoid unnecessary registration of native listeners.
```vue
-
+
-
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.21.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-explicit-slots.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-explicit-slots.js)
diff --git a/docs/rules/require-expose.md b/docs/rules/require-expose.md
new file mode 100644
index 000000000..98762a8c9
--- /dev/null
+++ b/docs/rules/require-expose.md
@@ -0,0 +1,126 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-expose
+description: require declare public properties using `expose`
+since: v7.14.0
+---
+
+# vue/require-expose
+
+> require declare public properties using `expose`
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule enforces the component to explicitly declare the exposed properties to the component using `expose`. You can use `expose` to control the internal properties of a component so that they cannot be referenced externally.
+
+The `expose` API was officially introduced in Vue 3.2.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Vue RFCs - 0042-expose-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0042-expose-api.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.14.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-expose.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-expose.js)
diff --git a/docs/rules/require-macro-variable-name.md b/docs/rules/require-macro-variable-name.md
new file mode 100644
index 000000000..af6db1733
--- /dev/null
+++ b/docs/rules/require-macro-variable-name.md
@@ -0,0 +1,89 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-macro-variable-name
+description: require a certain macro variable name
+since: v9.15.0
+---
+
+# vue/require-macro-variable-name
+
+> require a certain macro variable name
+
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule reports macro variables not corresponding to the specified name.
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/require-macro-variable-name": ["error", {
+ "defineProps": "props",
+ "defineEmits": "emit",
+ "defineSlots": "slots",
+ "useSlots": "slots",
+ "useAttrs": "attrs"
+ }]
+}
+```
+
+- `defineProps` - The name of the macro variable for `defineProps`. default: `props`
+- `defineEmits` - The name of the macro variable for `defineEmits`. default: `emit`
+- `defineSlots` - The name of the macro variable for `defineSlots`. default: `slots`
+- `useSlots` - The name of the macro variable for `useSlots`. default: `slots`
+- `useAttrs` - The name of the macro variable for `useAttrs`. default: `attrs`
+
+### With custom macro variable names
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.15.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-macro-variable-name.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-macro-variable-name.js)
diff --git a/docs/rules/require-name-property.md b/docs/rules/require-name-property.md
index d741e07c3..6d9974fc4 100644
--- a/docs/rules/require-name-property.md
+++ b/docs/rules/require-name-property.md
@@ -5,10 +5,13 @@ title: vue/require-name-property
description: require a name property in Vue components
since: v6.1.0
---
+
# vue/require-name-property
> require a name property in Vue components
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
## :book: Rule Details
This rule requires a `name` property to be set on components.
@@ -31,8 +34,7 @@ export default {
```vue
```
diff --git a/docs/rules/require-prop-comment.md b/docs/rules/require-prop-comment.md
new file mode 100644
index 000000000..25b78c346
--- /dev/null
+++ b/docs/rules/require-prop-comment.md
@@ -0,0 +1,148 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-prop-comment
+description: require props to have a comment
+since: v9.8.0
+---
+
+# vue/require-prop-comment
+
+> require props to have a comment
+
+## :book: Rule Details
+
+This rule enforces that every prop has a comment that documents it.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/require-prop-comment": ["error", {
+ "type": "JSDoc"
+ }]
+}
+```
+
+- `type` ... Type of comment. Default is `"JSDoc"`
+ - `"JSDoc"` ... Only JSDoc comment are allowed.
+ - `"line"` ... Only line comment are allowed.
+ - `"block"` ... Only block comment are allowed.
+ - `"any"` ... All comment types are allowed.
+
+### `"type": "block"`
+
+
+
+```vue
+
+```
+
+
+
+### `"type": "line"`
+
+
+
+```vue
+
+```
+
+
+
+### `"type": "any"`
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.8.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-prop-comment.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-prop-comment.js)
diff --git a/docs/rules/require-prop-type-constructor.md b/docs/rules/require-prop-type-constructor.md
index 97bce84f3..e872fc5d4 100644
--- a/docs/rules/require-prop-type-constructor.md
+++ b/docs/rules/require-prop-type-constructor.md
@@ -5,12 +5,13 @@ title: vue/require-prop-type-constructor
description: require prop type to be a constructor
since: v5.0.0
---
+
# vue/require-prop-type-constructor
> require prop type to be a constructor
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
@@ -38,7 +39,7 @@ export default {
anotherProp: [Number, String],
myFieldWithBadType: {
type: Object,
- default: function() {
+ default: function () {
return {}
},
},
@@ -47,16 +48,16 @@ export default {
default: 1,
},
/* ✗ BAD */
- myProp: "Number",
- anotherProp: ["Number", "String"],
+ myProp: 'Number',
+ anotherProp: ['Number', 'String'],
myFieldWithBadType: {
- type: "Object",
- default: function() {
+ type: 'Object',
+ default: function () {
return {}
},
},
myOtherFieldWithBadType: {
- type: "Number",
+ type: 'Number',
default: 1,
},
}
@@ -72,7 +73,7 @@ Nothing.
## :books: Further Reading
-- [Guide - Prop Validation](https://v3.vuejs.org/guide/component-props.html#prop-validation)
+- [Guide - Prop Validation](https://vuejs.org/guide/components/props.html#prop-validation)
## :rocket: Version
diff --git a/docs/rules/require-prop-types.md b/docs/rules/require-prop-types.md
index ae3ffe47c..20e46453f 100644
--- a/docs/rules/require-prop-types.md
+++ b/docs/rules/require-prop-types.md
@@ -5,11 +5,12 @@ title: vue/require-prop-types
description: require type definitions in props
since: v3.9.0
---
+
# vue/require-prop-types
> require type definitions in props
-- :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -65,7 +66,7 @@ Nothing.
## :books: Further Reading
-- [Style guide - Prop definitions](https://v3.vuejs.org/style-guide/#prop-definitions-essential)
+- [Style guide - Prop definitions](https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions)
## :rocket: Version
diff --git a/docs/rules/require-render-return.md b/docs/rules/require-render-return.md
index 0e5c89434..88cbf1efb 100644
--- a/docs/rules/require-render-return.md
+++ b/docs/rules/require-render-return.md
@@ -5,11 +5,12 @@ title: vue/require-render-return
description: enforce render function to always return value
since: v3.10.0
---
+
# vue/require-render-return
> enforce render function to always return value
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -21,7 +22,7 @@ This rule aims to enforce render function to always return value
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :mute: When Not To Use It
+
+When you're not using TypeScript in the project.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.16.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-typed-object-prop.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-typed-object-prop.js)
diff --git a/docs/rules/require-typed-ref.md b/docs/rules/require-typed-ref.md
new file mode 100644
index 000000000..85b58b213
--- /dev/null
+++ b/docs/rules/require-typed-ref.md
@@ -0,0 +1,51 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-typed-ref
+description: require `ref` and `shallowRef` functions to be strongly typed
+since: v9.15.0
+---
+
+# vue/require-typed-ref
+
+> require `ref` and `shallowRef` functions to be strongly typed
+
+## :book: Rule Details
+
+This rule disallows calling `ref()` or `shallowRef()` functions without generic type parameter or an argument when using TypeScript.
+
+With TypeScript it is easy to prevent usage of `any` by using [`noImplicitAny`](https://www.typescriptlang.org/tsconfig#noImplicitAny). Unfortunately this rule is easily bypassed with Vue `ref()` function. Calling `ref()` function without a generic parameter or an initial value leads to ref having `Ref` type.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.15.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-typed-ref.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-typed-ref.js)
diff --git a/docs/rules/require-v-for-key.md b/docs/rules/require-v-for-key.md
index 1e2b8a5f0..6811b9419 100644
--- a/docs/rules/require-v-for-key.md
+++ b/docs/rules/require-v-for-key.md
@@ -5,11 +5,12 @@ title: vue/require-v-for-key
description: require `v-bind:key` with `v-for` directives
since: v3.0.0
---
+
# vue/require-v-for-key
> require `v-bind:key` with `v-for` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -20,12 +21,9 @@ This rule reports the elements which have `v-for` and do not have `v-bind:key` w
```vue
-
+
-
+
```
@@ -43,13 +41,15 @@ Nothing.
## :couple: Related Rules
- [vue/valid-v-for]
+- [vue/v-if-else-key]
[vue/valid-v-for]: ./valid-v-for.md
+[vue/v-if-else-key]: ./v-if-else-key.md
## :books: Further Reading
-- [Style guide - Keyed v-for](https://v3.vuejs.org/style-guide/#keyed-v-for-essential)
-- [Guide (for v2) - v-for with a Component](https://vuejs.org/v2/guide/list.html#v-for-with-a-Component)
+- [Style guide - Keyed v-for](https://vuejs.org/style-guide/rules-essential.html#use-keyed-v-for)
+- [Guide (for v2) - v-for with a Component](https://v2.vuejs.org/v2/guide/list.html#v-for-with-a-Component)
## :rocket: Version
diff --git a/docs/rules/require-valid-default-prop.md b/docs/rules/require-valid-default-prop.md
index c53af81e1..bea185798 100644
--- a/docs/rules/require-valid-default-prop.md
+++ b/docs/rules/require-valid-default-prop.md
@@ -5,11 +5,12 @@ title: vue/require-valid-default-prop
description: enforce props default values to be valid
since: v3.13.0
---
+
# vue/require-valid-default-prop
> enforce props default values to be valid
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -78,7 +79,7 @@ Nothing.
## :books: Further Reading
-- [Guide - Prop Validation](https://v3.vuejs.org/guide/component-props.html#prop-validation)
+- [Guide - Prop Validation](https://vuejs.org/guide/components/props.html#prop-validation)
## :rocket: Version
diff --git a/docs/rules/restricted-component-names.md b/docs/rules/restricted-component-names.md
new file mode 100644
index 000000000..1d707baf3
--- /dev/null
+++ b/docs/rules/restricted-component-names.md
@@ -0,0 +1,69 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/restricted-component-names
+description: enforce using only specific component names
+since: v9.32.0
+---
+
+# vue/restricted-component-names
+
+> enforce using only specific component names
+
+## :book: Rule Details
+
+This rule enforces consistency in component names.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/restricted-component-names": ["error", {
+ "allow": []
+ }]
+}
+```
+
+### `"allow: ['/^custom-/']"`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/no-restricted-component-names](./no-restricted-component-names.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.32.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/restricted-component-names.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/restricted-component-names.js)
diff --git a/docs/rules/return-in-computed-property.md b/docs/rules/return-in-computed-property.md
index d18a1b274..5a1906060 100644
--- a/docs/rules/return-in-computed-property.md
+++ b/docs/rules/return-in-computed-property.md
@@ -5,11 +5,12 @@ title: vue/return-in-computed-property
description: enforce that a return statement is present in computed property
since: v3.7.0
---
+
# vue/return-in-computed-property
> enforce that a return statement is present in computed property
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
## :book: Rule Details
@@ -22,7 +23,7 @@ This rule enforces that a `return` statement is present in `computed` properties
export default {
computed: {
/* ✓ GOOD */
- foo () {
+ foo() {
if (this.bar) {
return this.baz
} else {
@@ -33,7 +34,7 @@ export default {
return false
},
/* ✗ BAD */
- baz () {
+ baz() {
if (this.baf) {
return this.baf
}
@@ -50,7 +51,7 @@ export default {
```vue
+
+
+
+
+```
+
+
+
+After turning on, `Foo` is being marked as used and `no-unused-vars` rule doesn't report an issue.
+
+## :mute: When Not To Use It
+
+You can disable this rule in any of the following cases:
+
+- You are using `vue-eslint-parser` v9.0.0 or later.
+- You are not using `
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+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
+
+This rule was introduced in eslint-plugin-vue v7.13.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-define-emits.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-define-emits.js)
diff --git a/docs/rules/valid-define-options.md b/docs/rules/valid-define-options.md
new file mode 100644
index 000000000..a81d5ad96
--- /dev/null
+++ b/docs/rules/valid-define-options.md
@@ -0,0 +1,125 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-define-options
+description: enforce valid `defineOptions` compiler macro
+since: v9.13.0
+---
+
+# vue/valid-define-options
+
+> enforce valid `defineOptions` compiler macro
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+
+This rule checks whether `defineOptions` compiler macro is valid.
+
+## :book: Rule Details
+
+This rule reports `defineOptions` compiler macros in the following cases:
+
+- `defineOptions` is 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)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.13.0
+
+## :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
new file mode 100644
index 000000000..1af111c9a
--- /dev/null
+++ b/docs/rules/valid-define-props.md
@@ -0,0 +1,153 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-define-props
+description: enforce valid `defineProps` compiler macro
+since: v7.13.0
+---
+
+# vue/valid-define-props
+
+> enforce valid `defineProps` compiler macro
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+
+This rule checks whether `defineProps` compiler macro is valid.
+
+## :book: Rule Details
+
+This rule reports `defineProps` compiler macros in the following cases:
+
+- `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 {}`.
+- Props are not defined in either `defineProps` or `export default {}`.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v7.13.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-define-props.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-define-props.js)
diff --git a/docs/rules/valid-model-definition.md b/docs/rules/valid-model-definition.md
new file mode 100644
index 000000000..201fc1201
--- /dev/null
+++ b/docs/rules/valid-model-definition.md
@@ -0,0 +1,122 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-model-definition
+description: require valid keys in model option
+since: v9.0.0
+---
+
+# vue/valid-model-definition
+
+> require valid keys in model option
+
+- :no_entry_sign: This rule was **deprecated**.
+- :gear: This rule is included in all of `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+
+## :book: Rule Details
+
+This rule is aimed at preventing invalid keys in model option.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-model-definition.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-model-definition.js)
diff --git a/docs/rules/valid-next-tick.md b/docs/rules/valid-next-tick.md
index 43be62caa..b2c95980b 100644
--- a/docs/rules/valid-next-tick.md
+++ b/docs/rules/valid-next-tick.md
@@ -5,11 +5,14 @@ title: vue/valid-next-tick
description: enforce valid `nextTick` function calls
since: v7.5.0
---
+
# vue/valid-next-tick
> enforce valid `nextTick` function calls
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
## :book: Rule Details
@@ -76,11 +79,11 @@ Nothing.
## :books: Further Reading
-- [`Vue.nextTick` API in Vue 2](https://vuejs.org/v2/api/#Vue-nextTick)
-- [`vm.$nextTick` API in Vue 2](https://vuejs.org/v2/api/#vm-nextTick)
-- [Global API Treeshaking](https://v3.vuejs.org/guide/migration/global-api-treeshaking.html)
-- [Global `nextTick` API in Vue 3](https://v3.vuejs.org/api/global-api.html#nexttick)
-- [Instance `$nextTick` API in Vue 3](https://v3.vuejs.org/api/instance-methods.html#nexttick)
+- [`Vue.nextTick` API in Vue 2](https://v2.vuejs.org/v2/api/#Vue-nextTick)
+- [`vm.$nextTick` API in Vue 2](https://v2.vuejs.org/v2/api/#vm-nextTick)
+- [Global API Treeshaking](https://v3-migration.vuejs.org/breaking-changes/global-api-treeshaking.html)
+- [Global `nextTick` API in Vue 3](https://vuejs.org/api/general.html#nexttick)
+- [Instance `$nextTick` API in Vue 3](https://vuejs.org/api/component-instance.html#nexttick)
## :rocket: Version
diff --git a/docs/rules/valid-template-root.md b/docs/rules/valid-template-root.md
index 14a56edc0..9dd89a287 100644
--- a/docs/rules/valid-template-root.md
+++ b/docs/rules/valid-template-root.md
@@ -5,11 +5,12 @@ title: vue/valid-template-root
description: enforce valid template root
since: v3.11.0
---
+
# vue/valid-template-root
> enforce valid template root
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every template root is valid.
diff --git a/docs/rules/valid-v-bind-sync.md b/docs/rules/valid-v-bind-sync.md
index 8b847659c..19129fb3d 100644
--- a/docs/rules/valid-v-bind-sync.md
+++ b/docs/rules/valid-v-bind-sync.md
@@ -5,11 +5,13 @@ title: vue/valid-v-bind-sync
description: enforce valid `.sync` modifier on `v-bind` directives
since: v7.0.0
---
+
# vue/valid-v-bind-sync
> enforce valid `.sync` modifier on `v-bind` directives
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+- :no_entry_sign: This rule was **deprecated**.
+- :gear: This rule is included in all of `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `.sync` modifier on `v-bind` directives is valid.
@@ -27,12 +29,12 @@ This rule reports `.sync` modifier on `v-bind` directives in the following cases
```vue
-
-
+
+
-
-
+
+
@@ -42,8 +44,8 @@ This rule reports `.sync` modifier on `v-bind` directives in the following cases
-
-
+
+
@@ -70,7 +72,7 @@ Nothing.
## :books: Further Reading
-- [Guide (for v2) - `.sync` Modifier](https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)
+- [Guide (for v2) - `.sync` Modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)
## :rocket: Version
diff --git a/docs/rules/valid-v-bind.md b/docs/rules/valid-v-bind.md
index 32a07aa60..64badbb6a 100644
--- a/docs/rules/valid-v-bind.md
+++ b/docs/rules/valid-v-bind.md
@@ -5,11 +5,12 @@ title: vue/valid-v-bind
description: enforce valid `v-bind` directives
since: v3.11.0
---
+
# vue/valid-v-bind
> enforce valid `v-bind` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-bind` directive is valid.
@@ -27,15 +28,14 @@ This rule does not report `v-bind` directives which do not have their argument (
```vue
-
-
-
-
+
+
+
+
-
-
-
+
+
```
diff --git a/docs/rules/valid-v-cloak.md b/docs/rules/valid-v-cloak.md
index c992edd6e..57c60edcf 100644
--- a/docs/rules/valid-v-cloak.md
+++ b/docs/rules/valid-v-cloak.md
@@ -5,11 +5,12 @@ title: vue/valid-v-cloak
description: enforce valid `v-cloak` directives
since: v3.11.0
---
+
# vue/valid-v-cloak
> enforce valid `v-cloak` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-cloak` directive is valid.
@@ -26,12 +27,12 @@ This rule reports `v-cloak` directives in the following cases:
```vue
-
+
-
-
-
+
+
+
```
diff --git a/docs/rules/valid-v-else-if.md b/docs/rules/valid-v-else-if.md
index 85c33e55a..13959825b 100644
--- a/docs/rules/valid-v-else-if.md
+++ b/docs/rules/valid-v-else-if.md
@@ -5,11 +5,12 @@ title: vue/valid-v-else-if
description: enforce valid `v-else-if` directives
since: v3.11.0
---
+
# vue/valid-v-else-if
> enforce valid `v-else-if` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-else-if` directive is valid.
@@ -28,13 +29,14 @@ This rule reports `v-else-if` directives in the following cases:
```vue
-
-
+
+
-
-
-
+
+
+
+
```
diff --git a/docs/rules/valid-v-else.md b/docs/rules/valid-v-else.md
index 35b26ceea..23df5787b 100644
--- a/docs/rules/valid-v-else.md
+++ b/docs/rules/valid-v-else.md
@@ -5,11 +5,12 @@ title: vue/valid-v-else
description: enforce valid `v-else` directives
since: v3.11.0
---
+
# vue/valid-v-else
> enforce valid `v-else` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-else` directive is valid.
@@ -28,13 +29,14 @@ This rule reports `v-else` directives in the following cases:
```vue
-
-
+
+
-
-
-
+
+
+
+
```
diff --git a/docs/rules/valid-v-for.md b/docs/rules/valid-v-for.md
index 843682626..f0e49c5e1 100644
--- a/docs/rules/valid-v-for.md
+++ b/docs/rules/valid-v-for.md
@@ -5,11 +5,12 @@ title: vue/valid-v-for
description: enforce valid `v-for` directives
since: v3.11.0
---
+
# vue/valid-v-for
> enforce valid `v-for` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-for` directive is valid.
diff --git a/docs/rules/valid-v-html.md b/docs/rules/valid-v-html.md
index 8c1932290..75e3c233d 100644
--- a/docs/rules/valid-v-html.md
+++ b/docs/rules/valid-v-html.md
@@ -5,11 +5,12 @@ title: vue/valid-v-html
description: enforce valid `v-html` directives
since: v3.11.0
---
+
# vue/valid-v-html
> enforce valid `v-html` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-html` directive is valid.
@@ -26,12 +27,12 @@ This rule reports `v-html` directives in the following cases:
```vue
-
+
-
-
-
+
+
+
```
diff --git a/docs/rules/valid-v-if.md b/docs/rules/valid-v-if.md
index f059aab30..8e45793f8 100644
--- a/docs/rules/valid-v-if.md
+++ b/docs/rules/valid-v-if.md
@@ -5,11 +5,12 @@ title: vue/valid-v-if
description: enforce valid `v-if` directives
since: v3.11.0
---
+
# vue/valid-v-if
> enforce valid `v-if` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-if` directive is valid.
@@ -27,14 +28,14 @@ This rule reports `v-if` directives in the following cases:
```vue
-
-
-
+
+
+
-
-
-
+
+
+
enforce valid `v-is` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
This rule checks whether every `v-is` directive is valid.
@@ -51,13 +52,16 @@ Nothing.
## :couple: Related Rules
+- [vue/no-deprecated-v-is]
- [vue/no-parsing-error]
+[vue/no-deprecated-v-is]: ./no-deprecated-v-is.md
[vue/no-parsing-error]: ./no-parsing-error.md
## :books: Further Reading
-- [API - v-is](https://v3.vuejs.org/api/directives.html#v-is)
+- [API - v-is (Recent)](https://github.com/vuejs/docs/blob/8b4f11a4e94d01c7f1c91a60ceaa5b89d6b6de9f/src/api/built-in-directives.md#v-is-)
+- [API - v-is (Old)](https://github.com/vuejs/docs-next/blob/008613756c3d781128d96b64a2d27f7598f8f548/src/api/directives.md#v-is)
## :rocket: Version
diff --git a/docs/rules/valid-v-memo.md b/docs/rules/valid-v-memo.md
new file mode 100644
index 000000000..c276eb8b4
--- /dev/null
+++ b/docs/rules/valid-v-memo.md
@@ -0,0 +1,72 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-memo
+description: enforce valid `v-memo` directives
+since: v7.16.0
+---
+
+# vue/valid-v-memo
+
+> enforce valid `v-memo` directives
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
+
+This rule checks whether every `v-memo` directive is valid.
+
+## :book: Rule Details
+
+This rule reports `v-memo` directives in the following cases:
+
+- The directive has that argument. E.g. ``
+- The directive has that modifier. E.g. ``
+- The directive does not have that attribute value. E.g. ``
+- The attribute value of the directive is definitely not array. E.g. ``
+- The directive was used inside v-for. E.g. `
`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Note
+This rule does not check syntax errors in directives because it's checked by [vue/no-parsing-error] rule.
+:::
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :books: Further Reading
+
+- [API - v-memo](https://vuejs.org/api/built-in-directives.html#v-memo)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.16.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-memo.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-memo.js)
diff --git a/docs/rules/valid-v-model.md b/docs/rules/valid-v-model.md
index 25c9ddb1f..0bc38d1b0 100644
--- a/docs/rules/valid-v-model.md
+++ b/docs/rules/valid-v-model.md
@@ -5,11 +5,12 @@ title: vue/valid-v-model
description: enforce valid `v-model` directives
since: v3.11.0
---
+
# vue/valid-v-model
> enforce valid `v-model` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-model` directive is valid.
@@ -31,27 +32,27 @@ This rule reports `v-model` directives in the following cases:
```vue
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
```
diff --git a/docs/rules/valid-v-on.md b/docs/rules/valid-v-on.md
index b3a157c2d..acfb630b7 100644
--- a/docs/rules/valid-v-on.md
+++ b/docs/rules/valid-v-on.md
@@ -5,11 +5,12 @@ title: vue/valid-v-on
description: enforce valid `v-on` directives
since: v3.11.0
---
+
# vue/valid-v-on
> enforce valid `v-on` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-on` directive is valid.
@@ -26,18 +27,18 @@ This rule reports `v-on` directives in the following cases:
```vue
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
```
@@ -67,8 +68,8 @@ This rule has an object option:
```vue
-
-
+
+
```
diff --git a/docs/rules/valid-v-once.md b/docs/rules/valid-v-once.md
index 4ab4ed8da..daa711b8d 100644
--- a/docs/rules/valid-v-once.md
+++ b/docs/rules/valid-v-once.md
@@ -5,11 +5,12 @@ title: vue/valid-v-once
description: enforce valid `v-once` directives
since: v3.11.0
---
+
# vue/valid-v-once
> enforce valid `v-once` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-once` directive is valid.
@@ -26,12 +27,12 @@ This rule reports `v-once` directives in the following cases:
```vue
-
+
-
-
-
+
+
+
```
diff --git a/docs/rules/valid-v-pre.md b/docs/rules/valid-v-pre.md
index 63095aab7..0464ed3d7 100644
--- a/docs/rules/valid-v-pre.md
+++ b/docs/rules/valid-v-pre.md
@@ -5,11 +5,12 @@ title: vue/valid-v-pre
description: enforce valid `v-pre` directives
since: v3.11.0
---
+
# vue/valid-v-pre
> enforce valid `v-pre` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-pre` directive is valid.
@@ -26,12 +27,12 @@ This rule reports `v-pre` directives in the following cases:
```vue
-
+
-
-
-
+
+
+
```
diff --git a/docs/rules/valid-v-show.md b/docs/rules/valid-v-show.md
index 710489946..5be8a50a5 100644
--- a/docs/rules/valid-v-show.md
+++ b/docs/rules/valid-v-show.md
@@ -5,11 +5,12 @@ title: vue/valid-v-show
description: enforce valid `v-show` directives
since: v3.11.0
---
+
# vue/valid-v-show
> enforce valid `v-show` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-show` directive is valid.
@@ -27,12 +28,12 @@ This rule reports `v-show` directives in the following cases:
```vue
-
+
-
-
-
+
+
+
```
diff --git a/docs/rules/valid-v-slot.md b/docs/rules/valid-v-slot.md
index 9c7c6621d..0c0c9e564 100644
--- a/docs/rules/valid-v-slot.md
+++ b/docs/rules/valid-v-slot.md
@@ -5,11 +5,12 @@ title: vue/valid-v-slot
description: enforce valid `v-slot` directives
since: v7.0.0
---
+
# vue/valid-v-slot
> enforce valid `v-slot` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-slot` directive is valid.
@@ -32,7 +33,7 @@ This rule reports `v-slot` directives in the following cases:
- {{data}}
+ {{ data }}
@@ -48,7 +49,7 @@ This rule reports `v-slot` directives in the following cases:
- {{data}}
+ {{ data }}
@@ -57,10 +58,10 @@ This rule reports `v-slot` directives in the following cases:
- {{data}}
+ {{ data }}
- {{data}}
+ {{ data }}
one
@@ -85,7 +86,7 @@ This rule reports `v-slot` directives in the following cases:
- {{data}}
+ {{ data }}
@@ -127,7 +128,7 @@ This rule does not check syntax errors in directives because it's checked by [vu
-
+
{{ data }}
diff --git a/docs/rules/valid-v-text.md b/docs/rules/valid-v-text.md
index c9d2a569e..093939d01 100644
--- a/docs/rules/valid-v-text.md
+++ b/docs/rules/valid-v-text.md
@@ -5,11 +5,12 @@ title: vue/valid-v-text
description: enforce valid `v-text` directives
since: v3.11.0
---
+
# vue/valid-v-text
> enforce valid `v-text` directives
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue2-essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue2-strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/vue2-recommended"` and `*.configs["flat/vue2-recommended"]`.
This rule checks whether every `v-text` directive is valid.
@@ -26,12 +27,12 @@ This rule reports `v-text` directives in the following cases:
```vue
-
+
-
-
-
+
+
+
```
diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md
deleted file mode 100644
index 45a3b72ac..000000000
--- a/docs/user-guide/README.md
+++ /dev/null
@@ -1,335 +0,0 @@
-# User Guide
-
-## :cd: Installation
-
-Via `vue-cli` (**Recommended**):
-
-```bash
-vue add @vue/cli-plugin-eslint
-```
-
-Via [npm](https://www.npmjs.com/):
-
-```bash
-npm install --save-dev eslint eslint-plugin-vue
-```
-
-Via [yarn](https://yarnpkg.com/):
-
-```bash
-yarn add -D eslint eslint-plugin-vue
-```
-
-::: tip Requirements
-
-- ESLint v6.2.0 and above
-- Node.js v8.10.0 and above
-
-:::
-
-## :book: Usage
-
-### Configuration
-
-Use `.eslintrc.*` file to configure rules. See also: [https://eslint.org/docs/user-guide/configuring](https://eslint.org/docs/user-guide/configuring).
-
-Example **.eslintrc.js**:
-
-```js
-module.exports = {
- extends: [
- // add more generic rulesets here, such as:
- // 'eslint:recommended',
- 'plugin:vue/vue3-recommended',
- // 'plugin:vue/recommended' // Use this if you are using Vue.js 2.x.
- ],
- rules: {
- // override/add rules settings here, such as:
- // 'vue/no-unused-vars': 'error'
- }
-}
-```
-
-See [the rule list](../rules/README.md) to get the `extends` & `rules` that this plugin provides.
-
-#### Bundle Configurations
-
-This plugin provides some predefined configs.
-You can use the following configs by adding them to `extends`.
-
-- `"plugin:vue/base"` ... Settings and rules to enable correct ESLint parsing.
-- Configurations for using Vue.js 3.x.
- - `"plugin:vue/vue3-essential"` ... `base`, plus rules to prevent errors or unintended behavior.
- - `"plugin:vue/vue3-strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience.
- - `"plugin:vue/vue3-recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency.
-- Configurations for using Vue.js 2.x.
- - `"plugin:vue/essential"` ... `base`, plus rules to prevent errors or unintended behavior.
- - `"plugin:vue/strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience.
- - `"plugin:vue/recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency
-
-:::warning Reporting rules
-By default all rules from **base** and **essential** categories report ESLint errors. Other rules - because they're not covering potential bugs in the application - report warnings. What does it mean? By default - nothing, but if you want - you can set up a threshold and break the build after a certain amount of warnings, instead of any. More information [here](https://eslint.org/docs/user-guide/command-line-interface#handling-warnings).
-:::
-
-:::warning Status of Vue.js 3.x supports
-This plugin supports the basic syntax of Vue.js 3.0, but the Vue.js 3.0 experimental features `
+ *
+ * @type {Record}
+ */
+const DEFAULT_LANGUAGES = {
+ template: ['html'],
+ style: ['css'],
+ script: ['js', 'javascript']
+}
+
+/**
+ * @param {NonNullable} lang
+ */
+function getAllowsLangPhrase(lang) {
+ const langs = [...lang].map((s) => `"${s}"`)
+ switch (langs.length) {
+ case 1: {
+ return langs[0]
+ }
+ default: {
+ return `${langs.slice(0, -1).join(', ')}, and ${langs[langs.length - 1]}`
+ }
+ }
+}
+
+/**
+ * Normalizes a given option.
+ * @param {string} blockName The block name.
+ * @param {UserBlockOptions} option An option to parse.
+ * @returns {BlockOptions} Normalized option.
+ */
+function normalizeOption(blockName, option) {
+ /** @type {Set} */
+ let lang
+
+ if (Array.isArray(option.lang)) {
+ lang = new Set(option.lang)
+ } else if (typeof option.lang === 'string') {
+ lang = new Set([option.lang])
+ } else {
+ lang = new Set()
+ }
+
+ let hasDefault = false
+ for (const def of DEFAULT_LANGUAGES[blockName] || []) {
+ if (lang.has(def)) {
+ lang.delete(def)
+ hasDefault = true
+ }
+ }
+ if (lang.size === 0) {
+ return {
+ lang,
+ allowNoLang: true
+ }
+ }
+ return {
+ lang,
+ allowNoLang: hasDefault || Boolean(option.allowNoLang)
+ }
+}
+/**
+ * Normalizes a given options.
+ * @param { UserOptions } options An option to parse.
+ * @returns {Options} Normalized option.
+ */
+function normalizeOptions(options) {
+ if (!options) {
+ return {}
+ }
+
+ /** @type {Options} */
+ const normalized = {}
+
+ for (const blockName of Object.keys(options)) {
+ const value = options[blockName]
+ if (value) {
+ normalized[blockName] = normalizeOption(blockName, value)
+ }
+ }
+
+ return normalized
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow use other than available `lang`',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/block-lang.html'
+ },
+ schema: [
+ {
+ type: 'object',
+ patternProperties: {
+ '^(?:\\S+)$': {
+ oneOf: [
+ {
+ type: 'object',
+ properties: {
+ lang: {
+ oneOf: [
+ { type: 'string' },
+ {
+ type: 'array',
+ items: {
+ type: 'string'
+ },
+ uniqueItems: true,
+ additionalItems: false
+ }
+ ]
+ },
+ allowNoLang: { type: 'boolean' }
+ },
+ additionalProperties: false
+ }
+ ]
+ }
+ },
+ minProperties: 1,
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ expected:
+ "Only {{allows}} can be used for the 'lang' attribute of '<{{tag}}>'.",
+ missing: "The 'lang' attribute of '<{{tag}}>' is missing.",
+ unexpected: "Do not specify the 'lang' attribute of '<{{tag}}>'.",
+ useOrNot:
+ "Only {{allows}} can be used for the 'lang' attribute of '<{{tag}}>'. Or, not specifying the `lang` attribute is allowed.",
+ unexpectedDefault:
+ "Do not explicitly specify the default language for the 'lang' attribute of '<{{tag}}>'."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = normalizeOptions(
+ context.options[0] || {
+ script: { allowNoLang: true },
+ template: { allowNoLang: true },
+ style: { allowNoLang: true }
+ }
+ )
+ if (Object.keys(options).length === 0) {
+ return {}
+ }
+
+ /**
+ * @param {VElement} element
+ * @returns {void}
+ */
+ function verify(element) {
+ const tag = element.name
+ const option = options[tag]
+ if (!option) {
+ return
+ }
+ const lang = utils.getAttribute(element, 'lang')
+ if (lang == null || lang.value == null) {
+ if (!option.allowNoLang) {
+ context.report({
+ node: element.startTag,
+ messageId: 'missing',
+ data: {
+ tag
+ }
+ })
+ }
+ return
+ }
+ if (!option.lang.has(lang.value.value)) {
+ let messageId
+ if (!option.allowNoLang) {
+ messageId = 'expected'
+ } else if (option.lang.size === 0) {
+ messageId = (DEFAULT_LANGUAGES[tag] || []).includes(lang.value.value)
+ ? 'unexpectedDefault'
+ : 'unexpected'
+ } else {
+ messageId = 'useOrNot'
+ }
+ context.report({
+ node: lang,
+ messageId,
+ data: {
+ tag,
+ allows: getAllowsLangPhrase(option.lang)
+ }
+ })
+ }
+ }
+
+ return utils.defineDocumentVisitor(context, {
+ 'VDocumentFragment > VElement': verify
+ })
+ }
+}
diff --git a/lib/rules/block-order.js b/lib/rules/block-order.js
new file mode 100644
index 000000000..d4fdb62ea
--- /dev/null
+++ b/lib/rules/block-order.js
@@ -0,0 +1,185 @@
+/**
+ * @author Yosuke Ota
+ * issue https://github.com/vuejs/eslint-plugin-vue/issues/140
+ */
+'use strict'
+
+const utils = require('../utils')
+const { parseSelector } = require('../utils/selector')
+
+/**
+ * @typedef {import('../utils/selector').VElementSelector} VElementSelector
+ */
+
+const DEFAULT_ORDER = Object.freeze([['script', 'template'], 'style'])
+
+/**
+ * @param {VElement} element
+ * @return {string}
+ */
+function getAttributeString(element) {
+ return element.startTag.attributes
+ .map((attribute) => {
+ if (attribute.value && attribute.value.type !== 'VLiteral') {
+ return ''
+ }
+
+ return `${attribute.key.name}${
+ attribute.value && attribute.value.value
+ ? `=${attribute.value.value}`
+ : ''
+ }`
+ })
+ .join(' ')
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce order of component top-level elements',
+ categories: ['vue3-recommended', 'vue2-recommended'],
+ url: 'https://eslint.vuejs.org/rules/block-order.html'
+ },
+ fixable: 'code',
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ order: {
+ type: 'array',
+ items: {
+ oneOf: [
+ { type: 'string' },
+ { type: 'array', items: { type: 'string' }, uniqueItems: true }
+ ]
+ },
+ uniqueItems: true,
+ additionalItems: false
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ unexpected:
+ "'<{{elementName}}{{elementAttributes}}>' should be above '<{{firstUnorderedName}}{{firstUnorderedAttributes}}>' on line {{line}}."
+ }
+ },
+ /**
+ * @param {RuleContext} context - The rule context.
+ * @returns {RuleListener} AST event handlers.
+ */
+ create(context) {
+ /**
+ * @typedef {object} OrderElement
+ * @property {string} selectorText
+ * @property {VElementSelector} selector
+ * @property {number} index
+ */
+ /** @type {OrderElement[]} */
+ const orders = []
+ /** @type {(string|string[])[]} */
+ const orderOptions =
+ (context.options[0] && context.options[0].order) || DEFAULT_ORDER
+ for (const [index, selectorOrSelectors] of orderOptions.entries()) {
+ if (Array.isArray(selectorOrSelectors)) {
+ for (const selector of selectorOrSelectors) {
+ orders.push({
+ selectorText: selector,
+ selector: parseSelector(selector, context),
+ index
+ })
+ }
+ } else {
+ orders.push({
+ selectorText: selectorOrSelectors,
+ selector: parseSelector(selectorOrSelectors, context),
+ index
+ })
+ }
+ }
+
+ /**
+ * @param {VElement} element
+ */
+ function getOrderElement(element) {
+ return orders.find((o) => o.selector.test(element))
+ }
+ const sourceCode = context.getSourceCode()
+ const documentFragment =
+ sourceCode.parserServices.getDocumentFragment &&
+ sourceCode.parserServices.getDocumentFragment()
+
+ function getTopLevelHTMLElements() {
+ if (documentFragment) {
+ return documentFragment.children.filter(utils.isVElement)
+ }
+ return []
+ }
+
+ return {
+ Program(node) {
+ if (utils.hasInvalidEOF(node)) {
+ return
+ }
+ const elements = getTopLevelHTMLElements()
+
+ const elementsWithOrder = elements.flatMap((element) => {
+ const order = getOrderElement(element)
+ return order ? [{ order, element }] : []
+ })
+ const sourceCode = context.getSourceCode()
+ for (const [index, elementWithOrders] of elementsWithOrder.entries()) {
+ const { order: expected, element } = elementWithOrders
+ const firstUnordered = elementsWithOrder
+ .slice(0, index)
+ .filter(({ order }) => expected.index < order.index)
+ .sort((e1, e2) => e1.order.index - e2.order.index)[0]
+ if (firstUnordered) {
+ const firstUnorderedAttributes = getAttributeString(
+ firstUnordered.element
+ )
+ const elementAttributes = getAttributeString(element)
+
+ context.report({
+ node: element,
+ loc: element.loc,
+ messageId: 'unexpected',
+ data: {
+ elementName: element.name,
+ elementAttributes: elementAttributes
+ ? ` ${elementAttributes}`
+ : '',
+ firstUnorderedName: firstUnordered.element.name,
+ firstUnorderedAttributes: firstUnorderedAttributes
+ ? ` ${firstUnorderedAttributes}`
+ : '',
+ line: firstUnordered.element.loc.start.line
+ },
+ *fix(fixer) {
+ // insert element before firstUnordered
+ const fixedElements = elements.flatMap((it) => {
+ if (it === firstUnordered.element) {
+ return [element, it]
+ } else if (it === element) {
+ return []
+ }
+ return [it]
+ })
+ for (let i = elements.length - 1; i >= 0; i--) {
+ if (elements[i] !== fixedElements[i]) {
+ yield fixer.replaceTextRange(
+ elements[i].range,
+ sourceCode.text.slice(...fixedElements[i].range)
+ )
+ }
+ }
+ }
+ })
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/block-spacing.js b/lib/rules/block-spacing.js
index 0634f2b6a..37c526e91 100644
--- a/lib/rules/block-spacing.js
+++ b/lib/rules/block-spacing.js
@@ -3,9 +3,9 @@
*/
'use strict'
-const { wrapCoreRule } = require('../utils')
+const { wrapStylisticOrCoreRule } = require('../utils')
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('block-spacing', {
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('block-spacing', {
skipDynamicArguments: true
})
diff --git a/lib/rules/block-tag-newline.js b/lib/rules/block-tag-newline.js
index c88a36c27..22fb12870 100644
--- a/lib/rules/block-tag-newline.js
+++ b/lib/rules/block-tag-newline.js
@@ -25,17 +25,15 @@ function getLinebreakCount(text) {
*/
function getPhrase(lineBreaks) {
switch (lineBreaks) {
- case 1:
+ case 1: {
return '1 line break'
- default:
+ }
+ default: {
return `${lineBreaks} line breaks`
+ }
}
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
const ENUM_OPTIONS = { enum: ['always', 'never', 'consistent', 'ignore'] }
module.exports = {
meta: {
@@ -76,8 +74,6 @@ module.exports = {
messages: {
unexpectedOpeningLinebreak:
"There should be no line break after '<{{tag}}>'.",
- unexpectedClosingLinebreak:
- "There should be no line break before '{{tag}}>'.",
expectedOpeningLinebreak:
"Expected {{expected}} after '<{{tag}}>', but {{actual}} found.",
expectedClosingLinebreak:
@@ -88,15 +84,14 @@ module.exports = {
},
/** @param {RuleContext} context */
create(context) {
+ const sourceCode = context.getSourceCode()
const df =
- context.parserServices.getDocumentFragment &&
- context.parserServices.getDocumentFragment()
+ sourceCode.parserServices.getDocumentFragment &&
+ sourceCode.parserServices.getDocumentFragment()
if (!df) {
return {}
}
- const sourceCode = context.getSourceCode()
-
/**
* @param {VStartTag} startTag
* @param {string} beforeText
@@ -257,9 +252,8 @@ module.exports = {
}
const option =
- options.multiline === options.singleline
- ? options.singleline
- : /[\n\r\u2028\u2029]/u.test(text.trim())
+ options.multiline !== options.singleline &&
+ /[\n\r\u2028\u2029]/u.test(text.trim())
? options.multiline
: options.singleline
if (option === 'ignore') {
@@ -329,17 +323,17 @@ module.exports = {
return (element) => {
const { name } = element
const elementsOptions = blocks[name]
- if (!elementsOptions) {
- verifyElement(element, options)
- } else {
+ if (elementsOptions) {
normalizeOptionValue({
singleline: elementsOptions.singleline || options.singleline,
multiline: elementsOptions.multiline || options.multiline,
maxEmptyLines:
- elementsOptions.maxEmptyLines != null
- ? elementsOptions.maxEmptyLines
- : options.maxEmptyLines
+ elementsOptions.maxEmptyLines == null
+ ? options.maxEmptyLines
+ : elementsOptions.maxEmptyLines
})(element)
+ } else {
+ verifyElement(element, options)
}
}
}
@@ -348,13 +342,6 @@ module.exports = {
const verify = normalizeOptionValue(context.options[0])
- /**
- * @returns {VElement[]}
- */
- function getTopLevelHTMLElements() {
- return documentFragment.children.filter(utils.isVElement)
- }
-
return utils.defineTemplateBodyVisitor(
context,
{},
@@ -365,8 +352,10 @@ module.exports = {
return
}
- for (const element of getTopLevelHTMLElements()) {
- verify(element)
+ for (const element of documentFragment.children) {
+ if (utils.isVElement(element)) {
+ verify(element)
+ }
}
}
}
diff --git a/lib/rules/brace-style.js b/lib/rules/brace-style.js
index 5dad2c29d..507f5e101 100644
--- a/lib/rules/brace-style.js
+++ b/lib/rules/brace-style.js
@@ -3,9 +3,9 @@
*/
'use strict'
-const { wrapCoreRule } = require('../utils')
+const { wrapStylisticOrCoreRule } = require('../utils')
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('brace-style', {
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('brace-style', {
skipDynamicArguments: true
})
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index 4b0898fe6..8aa43e19b 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -5,5 +5,5 @@
const { wrapCoreRule } = require('../utils')
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapCoreRule('camelcase')
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index 6fb48b79d..760611cfa 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -3,7 +3,7 @@
*/
'use strict'
-const { wrapCoreRule } = require('../utils')
+const { wrapStylisticOrCoreRule } = require('../utils')
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('comma-dangle')
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('comma-dangle')
diff --git a/lib/rules/comma-spacing.js b/lib/rules/comma-spacing.js
index 7ac0245e8..4be3bc85a 100644
--- a/lib/rules/comma-spacing.js
+++ b/lib/rules/comma-spacing.js
@@ -3,10 +3,11 @@
*/
'use strict'
-const { wrapCoreRule } = require('../utils')
+const { wrapStylisticOrCoreRule } = require('../utils')
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('comma-spacing', {
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('comma-spacing', {
skipDynamicArguments: true,
- skipDynamicArgumentsReport: true
+ skipDynamicArgumentsReport: true,
+ applyDocument: true
})
diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js
index 960e6d6df..815bd23be 100644
--- a/lib/rules/comma-style.js
+++ b/lib/rules/comma-style.js
@@ -3,16 +3,16 @@
*/
'use strict'
-const { wrapCoreRule } = require('../utils')
+const { wrapStylisticOrCoreRule } = require('../utils')
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('comma-style', {
- create(_context, { coreHandlers }) {
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('comma-style', {
+ create(_context, { baseHandlers }) {
return {
VSlotScopeExpression(node) {
- if (coreHandlers.FunctionExpression) {
+ if (baseHandlers.FunctionExpression) {
// @ts-expect-error -- Process params of VSlotScopeExpression as FunctionExpression.
- coreHandlers.FunctionExpression(node)
+ baseHandlers.FunctionExpression(node)
}
}
}
diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js
index 9ff9d2fdf..655e222bd 100644
--- a/lib/rules/comment-directive.js
+++ b/lib/rules/comment-directive.js
@@ -1,14 +1,10 @@
/**
* @author Toru Nagashima
*/
-/* eslint-disable eslint-plugin/report-message-format, consistent-docs-description */
+/* eslint-disable eslint-plugin/report-message-format */
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
/**
@@ -17,9 +13,6 @@ const utils = require('../utils')
* @property {number} RuleAndLocation.index
* @property {string} [RuleAndLocation.key]
*/
-// -----------------------------------------------------------------------------
-// Helpers
-// -----------------------------------------------------------------------------
const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+|$)/
const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\s+|$)/
@@ -51,7 +44,7 @@ function parse(pattern, comment) {
/** @type {RuleAndLocation[]} */
const rules = []
- const rulesRe = /([^,\s]+)[,\s]*/g
+ const rulesRe = /([^\s,]+)[\s,]*/g
let startIndex = match[0].length
rulesRe.lastIndex = startIndex
@@ -77,16 +70,16 @@ function parse(pattern, comment) {
* @returns {void}
*/
function enable(context, loc, group, rule) {
- if (!rule) {
+ if (rule) {
context.report({
loc,
- messageId: group === 'block' ? 'enableBlock' : 'enableLine'
+ messageId: group === 'block' ? 'enableBlockRule' : 'enableLineRule',
+ data: { rule }
})
} else {
context.report({
loc,
- messageId: group === 'block' ? 'enableBlockRule' : 'enableLineRule',
- data: { rule }
+ messageId: group === 'block' ? 'enableBlock' : 'enableLine'
})
}
}
@@ -101,17 +94,17 @@ function enable(context, loc, group, rule) {
* @returns {void}
*/
function disable(context, loc, group, rule, key) {
- if (!rule) {
+ if (rule) {
context.report({
loc,
- messageId: group === 'block' ? 'disableBlock' : 'disableLine',
- data: { key }
+ messageId: group === 'block' ? 'disableBlockRule' : 'disableLineRule',
+ data: { rule, key }
})
} else {
context.report({
loc,
- messageId: group === 'block' ? 'disableBlockRule' : 'disableLineRule',
- data: { rule, key }
+ messageId: group === 'block' ? 'disableBlock' : 'disableLine',
+ data: { key }
})
}
}
@@ -126,35 +119,35 @@ function disable(context, loc, group, rule, key) {
*/
function processBlock(context, comment, reportUnusedDisableDirectives) {
const parsed = parse(COMMENT_DIRECTIVE_B, comment.value)
- if (parsed != null) {
- if (parsed.type === 'eslint-disable') {
- if (parsed.rules.length) {
- const rules = reportUnusedDisableDirectives
- ? reportUnusedRules(context, comment, parsed.type, parsed.rules)
- : parsed.rules
- for (const rule of rules) {
- disable(
- context,
- comment.loc.start,
- 'block',
- rule.ruleId,
- rule.key || '*'
- )
- }
- } else {
- const key = reportUnusedDisableDirectives
- ? reportUnused(context, comment, parsed.type)
- : ''
- disable(context, comment.loc.start, 'block', null, key)
+ if (parsed === null) return
+
+ if (parsed.type === 'eslint-disable') {
+ if (parsed.rules.length > 0) {
+ const rules = reportUnusedDisableDirectives
+ ? reportUnusedRules(context, comment, parsed.type, parsed.rules)
+ : parsed.rules
+ for (const rule of rules) {
+ disable(
+ context,
+ comment.loc.start,
+ 'block',
+ rule.ruleId,
+ rule.key || '*'
+ )
}
} else {
- if (parsed.rules.length) {
- for (const rule of parsed.rules) {
- enable(context, comment.loc.start, 'block', rule.ruleId)
- }
- } else {
- enable(context, comment.loc.start, 'block', null)
+ const key = reportUnusedDisableDirectives
+ ? reportUnused(context, comment, parsed.type)
+ : ''
+ disable(context, comment.loc.start, 'block', null, key)
+ }
+ } else {
+ if (parsed.rules.length > 0) {
+ for (const rule of parsed.rules) {
+ enable(context, comment.loc.start, 'block', rule.ruleId)
}
+ } else {
+ enable(context, comment.loc.start, 'block', null)
}
}
}
@@ -173,7 +166,7 @@ function processLine(context, comment, reportUnusedDisableDirectives) {
const line =
comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1)
const column = -1
- if (parsed.rules.length) {
+ if (parsed.rules.length > 0) {
const rules = reportUnusedDisableDirectives
? reportUnusedRules(context, comment, parsed.type, parsed.rules)
: parsed.rules
@@ -277,15 +270,11 @@ function extractTopLevelDocumentFragmentComments(documentFragment) {
)
}
-// -----------------------------------------------------------------------------
-// Rule Definition
-// -----------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
- description: 'support comment-directives in ``',
+ description: 'support comment-directives in ``', // eslint-disable-line eslint-plugin/require-meta-docs-description
categories: ['base'],
url: 'https://eslint.vuejs.org/rules/comment-directive.html'
},
@@ -324,9 +313,10 @@ module.exports = {
const options = context.options[0] || {}
/** @type {boolean} */
const reportUnusedDisableDirectives = options.reportUnusedDisableDirectives
+ const sourceCode = context.getSourceCode()
const documentFragment =
- context.parserServices.getDocumentFragment &&
- context.parserServices.getDocumentFragment()
+ sourceCode.parserServices.getDocumentFragment &&
+ sourceCode.parserServices.getDocumentFragment()
return {
Program(node) {
diff --git a/lib/rules/component-api-style.js b/lib/rules/component-api-style.js
new file mode 100644
index 000000000..550eebfed
--- /dev/null
+++ b/lib/rules/component-api-style.js
@@ -0,0 +1,308 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef { 'script-setup' | 'composition' | 'composition-vue2' | 'options' } PreferOption
+ *
+ * @typedef {PreferOption[]} UserPreferOption
+ *
+ * @typedef {object} NormalizeOptions
+ * @property {object} allowsSFC
+ * @property {boolean} [allowsSFC.scriptSetup]
+ * @property {boolean} [allowsSFC.composition]
+ * @property {boolean} [allowsSFC.compositionVue2]
+ * @property {boolean} [allowsSFC.options]
+ * @property {object} allowsOther
+ * @property {boolean} [allowsOther.composition]
+ * @property {boolean} [allowsOther.compositionVue2]
+ * @property {boolean} [allowsOther.options]
+ */
+
+/** @type {PreferOption[]} */
+const STYLE_OPTIONS = [
+ 'script-setup',
+ 'composition',
+ 'composition-vue2',
+ 'options'
+]
+
+/**
+ * Normalize options.
+ * @param {any[]} options The options user configured.
+ * @returns {NormalizeOptions} The normalized options.
+ */
+function parseOptions(options) {
+ /** @type {NormalizeOptions} */
+ const opts = { allowsSFC: {}, allowsOther: {} }
+
+ /** @type {UserPreferOption} */
+ const preferOptions = options[0] || ['script-setup', 'composition']
+ for (const prefer of preferOptions) {
+ switch (prefer) {
+ case 'script-setup': {
+ opts.allowsSFC.scriptSetup = true
+ break
+ }
+ case 'composition': {
+ opts.allowsSFC.composition = true
+ opts.allowsOther.composition = true
+ break
+ }
+ case 'composition-vue2': {
+ opts.allowsSFC.compositionVue2 = true
+ opts.allowsOther.compositionVue2 = true
+ break
+ }
+ case 'options': {
+ opts.allowsSFC.options = true
+ opts.allowsOther.options = true
+ break
+ }
+ }
+ }
+
+ if (
+ !opts.allowsOther.composition &&
+ !opts.allowsOther.compositionVue2 &&
+ !opts.allowsOther.options
+ ) {
+ opts.allowsOther.composition = true
+ opts.allowsOther.compositionVue2 = true
+ opts.allowsOther.options = true
+ }
+
+ return opts
+}
+
+const OPTIONS_API_OPTIONS = new Set([
+ 'mixins',
+ 'extends',
+ // state
+ 'data',
+ 'computed',
+ 'methods',
+ 'watch',
+ 'provide',
+ 'inject',
+ // lifecycle
+ 'beforeCreate',
+ 'created',
+ 'beforeMount',
+ 'mounted',
+ 'beforeUpdate',
+ 'updated',
+ 'activated',
+ 'deactivated',
+ 'beforeDestroy',
+ 'beforeUnmount',
+ 'destroyed',
+ 'unmounted',
+ 'render',
+ 'renderTracked',
+ 'renderTriggered',
+ 'errorCaptured',
+ // public API
+ 'expose'
+])
+const COMPOSITION_API_OPTIONS = new Set(['setup'])
+
+const COMPOSITION_API_VUE2_OPTIONS = new Set([
+ 'setup',
+ 'render', // https://github.com/vuejs/composition-api#template-refs
+ 'renderTracked', // https://github.com/vuejs/composition-api#missing-apis
+ 'renderTriggered' // https://github.com/vuejs/composition-api#missing-apis
+])
+
+const LIFECYCLE_HOOK_OPTIONS = new Set([
+ 'beforeCreate',
+ 'created',
+ 'beforeMount',
+ 'mounted',
+ 'beforeUpdate',
+ 'updated',
+ 'activated',
+ 'deactivated',
+ 'beforeDestroy',
+ 'beforeUnmount',
+ 'destroyed',
+ 'unmounted',
+ 'renderTracked',
+ 'renderTriggered',
+ 'errorCaptured'
+])
+
+/**
+ * @typedef { 'script-setup' | 'composition' | 'options' } ApiStyle
+ */
+
+/**
+ * @param {object} allowsOpt
+ * @param {boolean} [allowsOpt.scriptSetup]
+ * @param {boolean} [allowsOpt.composition]
+ * @param {boolean} [allowsOpt.compositionVue2]
+ * @param {boolean} [allowsOpt.options]
+ */
+function buildAllowedPhrase(allowsOpt) {
+ const phrases = []
+ if (allowsOpt.scriptSetup) {
+ phrases.push('`\n`
+ ),
+ fixer.removeRange(removeRange)
+ ]
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/define-slots.js b/lib/rules/syntaxes/define-slots.js
new file mode 100644
index 000000000..450ec3e89
--- /dev/null
+++ b/lib/rules/syntaxes/define-slots.js
@@ -0,0 +1,22 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../../utils/index')
+
+module.exports = {
+ supported: '>=3.3.0',
+ /** @param {RuleContext} context @returns {RuleListener} */
+ createScriptVisitor(context) {
+ return utils.defineScriptSetupVisitor(context, {
+ onDefineSlotsEnter(node) {
+ context.report({
+ node,
+ messageId: 'forbiddenDefineSlots'
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/syntaxes/dynamic-directive-arguments.js b/lib/rules/syntaxes/dynamic-directive-arguments.js
index 9a790e9f7..e670fa3fc 100644
--- a/lib/rules/syntaxes/dynamic-directive-arguments.js
+++ b/lib/rules/syntaxes/dynamic-directive-arguments.js
@@ -20,7 +20,8 @@ module.exports = {
}
return {
- 'VAttribute[directive=true] > VDirectiveKey > VExpressionContainer': reportDynamicArgument
+ 'VAttribute[directive=true] > VDirectiveKey > VExpressionContainer':
+ reportDynamicArgument
}
}
}
diff --git a/lib/rules/syntaxes/is-attribute-with-vue-prefix.js b/lib/rules/syntaxes/is-attribute-with-vue-prefix.js
new file mode 100644
index 000000000..9fd2afdd0
--- /dev/null
+++ b/lib/rules/syntaxes/is-attribute-with-vue-prefix.js
@@ -0,0 +1,25 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+module.exports = {
+ supported: '>=3.1.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ return {
+ /** @param {VAttribute} node */
+ "VAttribute[directive=false][key.name='is']"(node) {
+ if (!node.value) {
+ return
+ }
+ if (node.value.value.startsWith('vue:')) {
+ context.report({
+ node: node.value,
+ messageId: 'forbiddenIsAttributeWithVuePrefix'
+ })
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/scope-attribute.js b/lib/rules/syntaxes/scope-attribute.js
index c356a6750..af7dbcd2f 100644
--- a/lib/rules/syntaxes/scope-attribute.js
+++ b/lib/rules/syntaxes/scope-attribute.js
@@ -23,7 +23,8 @@ module.exports = {
}
return {
- "VAttribute[directive=true] > VDirectiveKey[name.name='scope']": reportScope
+ "VAttribute[directive=true] > VDirectiveKey[name.name='scope']":
+ reportScope
}
}
}
diff --git a/lib/rules/syntaxes/script-setup.js b/lib/rules/syntaxes/script-setup.js
new file mode 100644
index 000000000..7c0538b3d
--- /dev/null
+++ b/lib/rules/syntaxes/script-setup.js
@@ -0,0 +1,28 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../../utils')
+
+module.exports = {
+ supported: '>=2.7.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createScriptVisitor(context) {
+ const scriptSetup = utils.getScriptSetupElement(context)
+ if (!scriptSetup) {
+ return {}
+ }
+ const reportNode =
+ utils.getAttribute(scriptSetup, 'setup') || scriptSetup.startTag
+ return {
+ Program() {
+ context.report({
+ node: reportNode,
+ messageId: 'forbiddenScriptSetup'
+ })
+ }
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/slot-attribute.js b/lib/rules/syntaxes/slot-attribute.js
index 8b4392746..27087cb37 100644
--- a/lib/rules/syntaxes/slot-attribute.js
+++ b/lib/rules/syntaxes/slot-attribute.js
@@ -3,12 +3,23 @@
* See LICENSE file in root directory for full license.
*/
'use strict'
+
+const canConvertToVSlot = require('./utils/can-convert-to-v-slot')
+const casing = require('../../utils/casing')
+
module.exports = {
deprecated: '2.6.0',
supported: '<3.0.0',
/** @param {RuleContext} context @returns {TemplateListener} */
createTemplateBodyVisitor(context) {
+ const options = context.options[0] || {}
+ /** @type {Set} */
+ const ignore = new Set(options.ignore)
+
const sourceCode = context.getSourceCode()
+ const tokenStore =
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
/**
* Checks whether the given node can convert to the `v-slot`.
@@ -16,7 +27,7 @@ module.exports = {
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertFromSlotToVSlot(slotAttr) {
- if (slotAttr.parent.parent.name !== 'template') {
+ if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
return false
}
if (!slotAttr.value) {
@@ -33,7 +44,7 @@ module.exports = {
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertFromVBindSlotToVSlot(slotAttr) {
- if (slotAttr.parent.parent.name !== 'template') {
+ if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
return false
}
@@ -45,10 +56,8 @@ module.exports = {
// parse error or empty expression
return false
}
- const slotName = sourceCode.getText(slotAttr.value.expression).trim()
- // If non-Latin characters are included it can not be converted.
- // It does not check the space only because `a>b?c:d` should be rejected.
- return !/[^a-z]/i.test(slotName)
+
+ return slotAttr.value.expression.type === 'Identifier'
}
/**
@@ -60,28 +69,50 @@ module.exports = {
* @returns {IterableIterator} fix data
*/
function* fixSlotToVSlot(fixer, slotAttr, slotName, vBind) {
- const element = slotAttr.parent
- const scopeAttr = element.attributes.find(
+ const startTag = slotAttr.parent
+ const scopeAttr = startTag.attributes.find(
(attr) =>
attr.directive === true &&
attr.key.name &&
(attr.key.name.name === 'slot-scope' ||
attr.key.name.name === 'scope')
)
- const nameArgument = slotName
- ? vBind
- ? `:[${slotName}]`
- : `:${slotName}`
- : ''
+ let nameArgument = ''
+ if (slotName) {
+ nameArgument = vBind ? `:[${slotName}]` : `:${slotName}`
+ }
const scopeValue =
scopeAttr && scopeAttr.value
? `=${sourceCode.getText(scopeAttr.value)}`
: ''
const replaceText = `v-slot${nameArgument}${scopeValue}`
- yield fixer.replaceText(slotAttr || scopeAttr, replaceText)
- if (slotAttr && scopeAttr) {
- yield fixer.remove(scopeAttr)
+
+ const element = startTag.parent
+ if (element.name === 'template') {
+ yield fixer.replaceText(slotAttr || scopeAttr, replaceText)
+ if (slotAttr && scopeAttr) {
+ yield fixer.remove(scopeAttr)
+ }
+ } else {
+ yield fixer.remove(slotAttr || scopeAttr)
+ if (slotAttr && scopeAttr) {
+ yield fixer.remove(scopeAttr)
+ }
+
+ const vFor = startTag.attributes.find(
+ (attr) => attr.directive && attr.key.name.name === 'for'
+ )
+ const vForText = vFor ? `${sourceCode.getText(vFor)} ` : ''
+ if (vFor) {
+ yield fixer.remove(vFor)
+ }
+
+ yield fixer.insertTextBefore(
+ element,
+ `\n`
+ )
+ yield fixer.insertTextAfter(element, `\n`)
}
}
/**
@@ -90,6 +121,15 @@ module.exports = {
* @returns {void}
*/
function reportSlot(slotAttr) {
+ const componentName = slotAttr.parent.parent.rawName
+ if (
+ ignore.has(componentName) ||
+ ignore.has(casing.pascalCase(componentName)) ||
+ ignore.has(casing.kebabCase(componentName))
+ ) {
+ return
+ }
+
context.report({
node: slotAttr.key,
messageId: 'forbiddenSlotAttribute',
@@ -128,7 +168,8 @@ module.exports = {
return {
"VAttribute[directive=false][key.name='slot']": reportSlot,
- "VAttribute[directive=true][key.name.name='bind'][key.argument.name='slot']": reportVBindSlot
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='slot']":
+ reportVBindSlot
}
}
}
diff --git a/lib/rules/syntaxes/slot-scope-attribute.js b/lib/rules/syntaxes/slot-scope-attribute.js
index 902b5ded8..5f8070498 100644
--- a/lib/rules/syntaxes/slot-scope-attribute.js
+++ b/lib/rules/syntaxes/slot-scope-attribute.js
@@ -3,6 +3,9 @@
* See LICENSE file in root directory for full license.
*/
'use strict'
+
+const canConvertToVSlotForElement = require('./utils/can-convert-to-v-slot')
+
module.exports = {
deprecated: '2.6.0',
supported: '>=2.5.0 <3.0.0',
@@ -14,6 +17,9 @@ module.exports = {
*/
createTemplateBodyVisitor(context, { fixToUpgrade } = {}) {
const sourceCode = context.getSourceCode()
+ const tokenStore =
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
/**
* Checks whether the given node can convert to the `v-slot`.
@@ -21,7 +27,9 @@ module.exports = {
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertToVSlot(startTag) {
- if (startTag.parent.name !== 'template') {
+ if (
+ !canConvertToVSlotForElement(startTag.parent, sourceCode, tokenStore)
+ ) {
return false
}
@@ -54,16 +62,26 @@ module.exports = {
* Convert to `v-slot`.
* @param {RuleFixer} fixer fixer
* @param {VDirective} scopeAttr node of `slot-scope`
- * @returns {Fix} fix data
+ * @returns {Fix[]} fix data
*/
function fixSlotScopeToVSlot(fixer, scopeAttr) {
+ const element = scopeAttr.parent.parent
const scopeValue =
scopeAttr && scopeAttr.value
? `=${sourceCode.getText(scopeAttr.value)}`
: ''
const replaceText = `v-slot${scopeValue}`
- return fixer.replaceText(scopeAttr, replaceText)
+ if (element.name === 'template') {
+ return [fixer.replaceText(scopeAttr, replaceText)]
+ } else {
+ const tokenBefore = tokenStore.getTokenBefore(scopeAttr)
+ return [
+ fixer.removeRange([tokenBefore.range[1], scopeAttr.range[1]]),
+ fixer.insertTextBefore(element, `\n`),
+ fixer.insertTextAfter(element, `\n`)
+ ]
+ }
}
/**
* Reports `slot-scope` node
diff --git a/lib/rules/syntaxes/style-css-vars-injection.js b/lib/rules/syntaxes/style-css-vars-injection.js
new file mode 100644
index 000000000..03608b5e1
--- /dev/null
+++ b/lib/rules/syntaxes/style-css-vars-injection.js
@@ -0,0 +1,28 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const { getStyleVariablesContext } = require('../../utils/style-variables')
+
+module.exports = {
+ supported: '>=3.0.3 || >=2.7.0 <3.0.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createScriptVisitor(context) {
+ const styleVars = getStyleVariablesContext(context)
+ if (!styleVars) {
+ return {}
+ }
+ return {
+ Program() {
+ for (const vBind of styleVars.vBinds) {
+ context.report({
+ node: vBind,
+ messageId: 'forbiddenStyleCssVarsInjection'
+ })
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/utils/can-convert-to-v-slot.js b/lib/rules/syntaxes/utils/can-convert-to-v-slot.js
new file mode 100644
index 000000000..e0e51053d
--- /dev/null
+++ b/lib/rules/syntaxes/utils/can-convert-to-v-slot.js
@@ -0,0 +1,223 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../../../utils')
+/**
+ * @typedef {object} SlotVForVariables
+ * @property {VForExpression} expr
+ * @property {VVariable[]} variables
+ */
+/**
+ * @typedef {object} SlotContext
+ * @property {VElement} element
+ * @property {VAttribute | VDirective | null} slot
+ * @property {VDirective | null} vFor
+ * @property {SlotVForVariables | null} slotVForVars
+ * @property {string} normalizedName
+ */
+/**
+ * Checks whether the given element can use v-slot.
+ * @param {VElement} element
+ * @param {SourceCode} sourceCode
+ * @param {ParserServices.TokenStore} tokenStore
+ */
+module.exports = function canConvertToVSlot(element, sourceCode, tokenStore) {
+ const ownerElement = element.parent
+ if (
+ ownerElement.type === 'VDocumentFragment' ||
+ !utils.isCustomComponent(ownerElement) ||
+ ownerElement.name === 'component'
+ ) {
+ return false
+ }
+ const slot = getSlotContext(element, sourceCode)
+ if (slot.vFor && !slot.slotVForVars) {
+ // E.g.,
+ return false
+ }
+ if (hasSameSlotDirective(ownerElement, slot, sourceCode, tokenStore)) {
+ return false
+ }
+ return true
+}
+/**
+ * @param {VElement} element
+ * @param {SourceCode} sourceCode
+ * @returns {SlotContext}
+ */
+function getSlotContext(element, sourceCode) {
+ const slot =
+ utils.getAttribute(element, 'slot') ||
+ utils.getDirective(element, 'bind', 'slot')
+ const vFor = utils.getDirective(element, 'for')
+ const slotVForVars = getSlotVForVariableIfUsingIterationVars(slot, vFor)
+
+ return {
+ element,
+ slot,
+ vFor,
+ slotVForVars,
+ normalizedName: getNormalizedName(slot, sourceCode)
+ }
+}
+
+/**
+ * Gets the `v-for` directive and variable that provide the variables used by the given `slot` attribute.
+ * @param {VAttribute | VDirective | null} slot The current `slot` attribute node.
+ * @param {VDirective | null} [vFor] The current `v-for` directive node.
+ * @returns { SlotVForVariables | null } The SlotVForVariables.
+ */
+function getSlotVForVariableIfUsingIterationVars(slot, vFor) {
+ if (!slot || !slot.directive) {
+ return null
+ }
+ const expr =
+ vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
+ const variables =
+ expr && getUsingIterationVars(slot.value, slot.parent.parent)
+ return expr && variables && variables.length > 0 ? { expr, variables } : null
+}
+
+/**
+ * Gets iterative variables if a given expression node is using iterative variables that the element defined.
+ * @param {VExpressionContainer|null} expression The expression node to check.
+ * @param {VElement} element The element node which has the expression.
+ * @returns {VVariable[]} The expression node is using iteration variables.
+ */
+function getUsingIterationVars(expression, element) {
+ const vars = []
+ if (expression && expression.type === 'VExpressionContainer') {
+ for (const { variable } of expression.references) {
+ if (
+ variable != null &&
+ variable.kind === 'v-for' &&
+ variable.id.range[0] > element.startTag.range[0] &&
+ variable.id.range[1] < element.startTag.range[1]
+ ) {
+ vars.push(variable)
+ }
+ }
+ }
+ return vars
+}
+
+/**
+ * Get the normalized name of a given `slot` attribute node.
+ * @param {VAttribute | VDirective | null} slotAttr node of `slot`
+ * @param {SourceCode} sourceCode The source code.
+ * @returns {string} The normalized name.
+ */
+function getNormalizedName(slotAttr, sourceCode) {
+ if (!slotAttr) {
+ return 'default'
+ }
+ if (!slotAttr.directive) {
+ return slotAttr.value ? slotAttr.value.value : 'default'
+ }
+ return slotAttr.value ? `[${sourceCode.getText(slotAttr.value)}]` : '[null]'
+}
+
+/**
+ * Checks whether parent element has the same slot as the given slot.
+ * @param {VElement} ownerElement The parent element.
+ * @param {SlotContext} targetSlot The SlotContext with a slot to check if they are the same.
+ * @param {SourceCode} sourceCode
+ * @param {ParserServices.TokenStore} tokenStore
+ */
+function hasSameSlotDirective(
+ ownerElement,
+ targetSlot,
+ sourceCode,
+ tokenStore
+) {
+ for (const group of utils.iterateChildElementsChains(ownerElement)) {
+ if (group.includes(targetSlot.element)) {
+ continue
+ }
+ for (const childElement of group) {
+ const slot = getSlotContext(childElement, sourceCode)
+ if (!targetSlot.slotVForVars || !slot.slotVForVars) {
+ if (
+ !targetSlot.slotVForVars &&
+ !slot.slotVForVars &&
+ targetSlot.normalizedName === slot.normalizedName
+ ) {
+ return true
+ }
+ continue
+ }
+ if (
+ equalSlotVForVariables(
+ targetSlot.slotVForVars,
+ slot.slotVForVars,
+ tokenStore
+ )
+ ) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+/**
+ * Determines whether the two given `v-slot` variables are considered to be equal.
+ * @param {SlotVForVariables} a First element.
+ * @param {SlotVForVariables} b Second element.
+ * @param {ParserServices.TokenStore} tokenStore The token store.
+ * @returns {boolean} `true` if the elements are considered to be equal.
+ */
+function equalSlotVForVariables(a, b, tokenStore) {
+ if (a.variables.length !== b.variables.length) {
+ return false
+ }
+ if (!equal(a.expr.right, b.expr.right)) {
+ return false
+ }
+
+ const checkedVarNames = new Set()
+ const len = Math.min(a.expr.left.length, b.expr.left.length)
+ for (let index = 0; index < len; index++) {
+ const aPtn = a.expr.left[index]
+ const bPtn = b.expr.left[index]
+
+ const aVar = a.variables.find(
+ (v) => aPtn.range[0] <= v.id.range[0] && v.id.range[1] <= aPtn.range[1]
+ )
+ const bVar = b.variables.find(
+ (v) => bPtn.range[0] <= v.id.range[0] && v.id.range[1] <= bPtn.range[1]
+ )
+ if (aVar && bVar) {
+ if (aVar.id.name !== bVar.id.name) {
+ return false
+ }
+ if (!equal(aPtn, bPtn)) {
+ return false
+ }
+ checkedVarNames.add(aVar.id.name)
+ } else if (aVar || bVar) {
+ return false
+ }
+ }
+ return a.variables.every(
+ (v) =>
+ checkedVarNames.has(v.id.name) ||
+ b.variables.some((bv) => v.id.name === bv.id.name)
+ )
+
+ /**
+ * Determines whether the two given nodes are considered to be equal.
+ * @param {ASTNode} a First node.
+ * @param {ASTNode} b Second node.
+ * @returns {boolean} `true` if the nodes are considered to be equal.
+ */
+ function equal(a, b) {
+ if (a.type !== b.type) {
+ return false
+ }
+ return utils.equalTokens(a, b, tokenStore)
+ }
+}
diff --git a/lib/rules/syntaxes/v-bind-attr-modifier.js b/lib/rules/syntaxes/v-bind-attr-modifier.js
new file mode 100644
index 000000000..82a0922aa
--- /dev/null
+++ b/lib/rules/syntaxes/v-bind-attr-modifier.js
@@ -0,0 +1,32 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+module.exports = {
+ supported: '>=3.2.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ /**
+ * Reports `v-bind.attr` node
+ * @param { VIdentifier } mod node of `v-bind.attr`
+ * @returns {void}
+ */
+ function report(mod) {
+ context.report({
+ node: mod,
+ messageId: 'forbiddenVBindAttrModifier'
+ })
+ }
+
+ return {
+ "VAttribute[directive=true][key.name.name='bind']"(node) {
+ const attrMod = node.key.modifiers.find((m) => m.name === 'attr')
+ if (attrMod) {
+ report(attrMod)
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js b/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js
index 219d2b3c9..645ed375d 100644
--- a/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js
+++ b/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js
@@ -5,7 +5,7 @@
'use strict'
module.exports = {
- supported: '>=2.6.0-beta.1 <=2.6.0-beta.3',
+ supported: '>=3.2.0 || >=2.6.0-beta.1 <=2.6.0-beta.3',
/** @param {RuleContext} context @returns {TemplateListener} */
createTemplateBodyVisitor(context) {
/**
@@ -27,7 +27,8 @@ module.exports = {
}
return {
- "VAttribute[directive=true] > VDirectiveKey[name.name='bind'][name.rawName='.']": reportPropModifierShorthand
+ "VAttribute[directive=true] > VDirectiveKey[name.name='bind'][name.rawName='.']":
+ reportPropModifierShorthand
}
}
}
diff --git a/lib/rules/syntaxes/v-bind-same-name-shorthand.js b/lib/rules/syntaxes/v-bind-same-name-shorthand.js
new file mode 100644
index 000000000..d9e7a388c
--- /dev/null
+++ b/lib/rules/syntaxes/v-bind-same-name-shorthand.js
@@ -0,0 +1,34 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../../utils')
+
+module.exports = {
+ supported: '>=3.4.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ /**
+ * Verify the directive node
+ * @param {VDirective} node The directive node to check
+ * @returns {void}
+ */
+ function checkDirective(node) {
+ if (utils.isVBindSameNameShorthand(node)) {
+ context.report({
+ node,
+ messageId: 'forbiddenVBindSameNameShorthand',
+ // fix to use `:x="x"` (downgrade)
+ fix: (fixer) =>
+ fixer.insertTextAfter(node, `="${node.value.expression.name}"`)
+ })
+ }
+ }
+
+ return {
+ "VAttribute[directive=true][key.name.name='bind']": checkDirective
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/v-is.js b/lib/rules/syntaxes/v-is.js
index 9aeeb8d7e..7fb1f862e 100644
--- a/lib/rules/syntaxes/v-is.js
+++ b/lib/rules/syntaxes/v-is.js
@@ -4,23 +4,24 @@
*/
'use strict'
module.exports = {
+ deprecated: '3.1.0',
supported: '>=3.0.0',
/** @param {RuleContext} context @returns {TemplateListener} */
createTemplateBodyVisitor(context) {
/**
* Reports `v-is` node
- * @param {VDirective} vSlotAttr node of `v-is`
+ * @param {VDirective} vIsAttr node of `v-is`
* @returns {void}
*/
- function reportVSlot(vSlotAttr) {
+ function reportVIs(vIsAttr) {
context.report({
- node: vSlotAttr.key,
+ node: vIsAttr.key,
messageId: 'forbiddenVIs'
})
}
return {
- "VAttribute[directive=true][key.name.name='is']": reportVSlot
+ "VAttribute[directive=true][key.name.name='is']": reportVIs
}
}
}
diff --git a/lib/rules/syntaxes/v-memo.js b/lib/rules/syntaxes/v-memo.js
new file mode 100644
index 000000000..958b51cf3
--- /dev/null
+++ b/lib/rules/syntaxes/v-memo.js
@@ -0,0 +1,26 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+module.exports = {
+ supported: '>=3.2.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ /**
+ * Reports `v-is` node
+ * @param {VDirective} vMemoAttr node of `v-is`
+ * @returns {void}
+ */
+ function reportVMemo(vMemoAttr) {
+ context.report({
+ node: vMemoAttr.key,
+ messageId: 'forbiddenVMemo'
+ })
+ }
+
+ return {
+ "VAttribute[directive=true][key.name.name='memo']": reportVMemo
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/v-model-custom-modifiers.js b/lib/rules/syntaxes/v-model-custom-modifiers.js
index d11116b2d..4b17b47d6 100644
--- a/lib/rules/syntaxes/v-model-custom-modifiers.js
+++ b/lib/rules/syntaxes/v-model-custom-modifiers.js
@@ -4,10 +4,6 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
const BUILTIN_MODIFIERS = new Set(['lazy', 'number', 'trim'])
module.exports = {
diff --git a/lib/rules/syntaxes/v-slot.js b/lib/rules/syntaxes/v-slot.js
index 9a9ba1fba..a6a4b4ebd 100644
--- a/lib/rules/syntaxes/v-slot.js
+++ b/lib/rules/syntaxes/v-slot.js
@@ -3,23 +3,22 @@
* See LICENSE file in root directory for full license.
*/
'use strict'
+
+/**
+ * Checks whether the given node can convert to the `slot`.
+ * @param {VDirective} vSlotAttr node of `v-slot`
+ * @returns {boolean} `true` if the given node can convert to the `slot`
+ */
+function canConvertToSlot(vSlotAttr) {
+ return vSlotAttr.parent.parent.name === 'template'
+}
+
module.exports = {
supported: '>=2.6.0',
/** @param {RuleContext} context @returns {TemplateListener} */
createTemplateBodyVisitor(context) {
const sourceCode = context.getSourceCode()
- /**
- * Checks whether the given node can convert to the `slot`.
- * @param {VDirective} vSlotAttr node of `v-slot`
- * @returns {boolean} `true` if the given node can convert to the `slot`
- */
- function canConvertToSlot(vSlotAttr) {
- if (vSlotAttr.parent.parent.name !== 'template') {
- return false
- }
- return true
- }
/**
* Convert to `slot` and `slot-scope`.
* @param {RuleFixer} fixer fixer
@@ -28,7 +27,7 @@ module.exports = {
*/
function fixVSlotToSlot(fixer, vSlotAttr) {
const key = vSlotAttr.key
- if (key.modifiers.length) {
+ if (key.modifiers.length > 0) {
// unknown modifiers
return null
}
@@ -54,7 +53,7 @@ module.exports = {
if (scopedValueNode) {
attrs.push(`slot-scope=${sourceCode.getText(scopedValueNode)}`)
}
- if (!attrs.length) {
+ if (attrs.length === 0) {
attrs.push('slot') // useless
}
return fixer.replaceText(vSlotAttr, attrs.join(' '))
diff --git a/lib/rules/template-curly-spacing.js b/lib/rules/template-curly-spacing.js
index acefea6b5..e215108e9 100644
--- a/lib/rules/template-curly-spacing.js
+++ b/lib/rules/template-curly-spacing.js
@@ -3,9 +3,10 @@
*/
'use strict'
-const { wrapCoreRule } = require('../utils')
+const { wrapStylisticOrCoreRule } = require('../utils')
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('template-curly-spacing', {
- skipDynamicArguments: true
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('template-curly-spacing', {
+ skipDynamicArguments: true,
+ applyDocument: true
})
diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js
index 8ed42345c..a664a8edf 100644
--- a/lib/rules/this-in-template.js
+++ b/lib/rules/this-in-template.js
@@ -4,31 +4,27 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
const RESERVED_NAMES = new Set(require('../utils/js-reserved.json'))
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow usage of `this` in template',
- categories: ['vue3-recommended', 'recommended'],
+ categories: ['vue3-recommended', 'vue2-recommended'],
url: 'https://eslint.vuejs.org/rules/this-in-template.html'
},
- fixable: null,
+ fixable: 'code',
schema: [
{
enum: ['always', 'never']
}
- ]
+ ],
+ messages: {
+ unexpected: "Unexpected usage of 'this'.",
+ expected: "Expected 'this'."
+ }
},
/**
@@ -38,7 +34,7 @@ module.exports = {
* @returns {Object} AST event handlers.
*/
create(context) {
- const options = context.options[0] !== 'always' ? 'never' : 'always'
+ const options = context.options[0] === 'always' ? 'always' : 'never'
/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} parent
@@ -54,7 +50,7 @@ module.exports = {
scopeStack = {
parent: scopeStack,
nodes: scopeStack
- ? scopeStack.nodes.slice() // make copy
+ ? [...scopeStack.nodes] // make copy
: []
}
if (node.variables) {
@@ -83,7 +79,7 @@ module.exports = {
!propertyName ||
scopeStack.nodes.some((el) => el.name === propertyName) ||
RESERVED_NAMES.has(propertyName) || // this.class | this['class']
- /^[0-9].*$|[^a-zA-Z0-9_$]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas']
+ /^\d.*$|[^\w$]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas']
) {
return
}
@@ -91,7 +87,11 @@ module.exports = {
context.report({
node,
loc: node.loc,
- message: "Unexpected usage of 'this'."
+ fix(fixer) {
+ // node.parent should be some code like `this.test`, `this?.['result']`
+ return fixer.replaceText(node.parent, propertyName)
+ },
+ messageId: 'unexpected'
})
}
}
@@ -116,7 +116,10 @@ module.exports = {
context.report({
node: reference.id,
loc: reference.id.loc,
- message: "Expected 'this'."
+ messageId: 'expected',
+ fix(fixer) {
+ return fixer.insertTextBefore(reference.id, 'this.')
+ }
})
}
}
diff --git a/lib/rules/use-v-on-exact.js b/lib/rules/use-v-on-exact.js
index 0a1790658..f048b70ef 100644
--- a/lib/rules/use-v-on-exact.js
+++ b/lib/rules/use-v-on-exact.js
@@ -4,10 +4,6 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
/**
* @typedef { {name: string, node: VDirectiveKey, modifiers: string[] } } EventDirective
*/
@@ -25,10 +21,6 @@ const GLOBAL_MODIFIERS = new Set([
'native'
])
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
/**
* Finds and returns all keys for event directives
*
@@ -82,17 +74,18 @@ function hasSystemModifier(modifiers) {
* with keys represinting each event name
*
* @param {EventDirective[]} events
- * @returns { { [key: string]: EventDirective[] } } { click: [], keypress: [] }
+ * @returns { { [key: string]: EventDirective[] } } { click: [], keypress: [] }
*/
function groupEvents(events) {
- return events.reduce((acc, event) => {
- if (acc[event.name]) {
- acc[event.name].push(event)
- } else {
- acc[event.name] = [event]
+ /** @type { { [key: string]: EventDirective[] } } */
+ const grouped = {}
+ for (const event of events) {
+ if (!grouped[event.name]) {
+ grouped[event.name] = []
}
- return acc
- }, /** @type { { [key: string]: EventDirective[] } }*/ ({}))
+ grouped[event.name].push(event)
+ }
+ return grouped
}
/**
@@ -141,9 +134,9 @@ function hasConflictedModifiers(baseEvent, event) {
const baseEventSystemModifiers = getSystemModifiersString(baseEvent.modifiers)
return (
- baseEvent.modifiers.length >= 1 &&
+ baseEvent.modifiers.length > 0 &&
baseEventSystemModifiers !== eventSystemModifiers &&
- baseEventSystemModifiers.indexOf(eventSystemModifiers) > -1
+ baseEventSystemModifiers.includes(eventSystemModifiers)
)
}
@@ -154,30 +147,31 @@ function hasConflictedModifiers(baseEvent, event) {
* @returns {EventDirective[]} conflicted events, without duplicates
*/
function findConflictedEvents(events) {
- return events.reduce((acc, event) => {
- return [
- ...acc,
+ /** @type {EventDirective[]} */
+ const conflictedEvents = []
+ for (const event of events) {
+ conflictedEvents.push(
...events
- .filter((evt) => !acc.find((e) => evt === e)) // No duplicates
+ .filter((evt) => !conflictedEvents.includes(evt)) // No duplicates
.filter(hasConflictedModifiers.bind(null, event))
- ]
- }, /** @type {EventDirective[]} */ ([]))
+ )
+ }
+ return conflictedEvents
}
-// ------------------------------------------------------------------------------
-// Rule details
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce usage of `exact` modifier on `v-on`',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/use-v-on-exact.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ considerExact: "Consider to use '.exact' modifier."
+ }
},
/**
@@ -204,24 +198,24 @@ module.exports = {
const grouppedEvents = groupEvents(events)
- Object.keys(grouppedEvents).forEach((eventName) => {
+ for (const eventName of Object.keys(grouppedEvents)) {
const eventsInGroup = grouppedEvents[eventName]
const hasEventWithKeyModifier = eventsInGroup.some((event) =>
hasSystemModifier(event.modifiers)
)
- if (!hasEventWithKeyModifier) return
+ if (!hasEventWithKeyModifier) continue
const conflictedEvents = findConflictedEvents(eventsInGroup)
- conflictedEvents.forEach((e) => {
+ for (const e of conflictedEvents) {
context.report({
node: e.node,
loc: e.node.loc,
- message: "Consider to use '.exact' modifier."
+ messageId: 'considerExact'
})
- })
- })
+ }
+ }
}
})
}
diff --git a/lib/rules/v-bind-style.js b/lib/rules/v-bind-style.js
index e6878a349..752a8fdf0 100644
--- a/lib/rules/v-bind-style.js
+++ b/lib/rules/v-bind-style.js
@@ -5,71 +5,166 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
+const casing = require('../utils/casing')
+
+/**
+ * @typedef { VDirectiveKey & { name: VIdentifier & { name: 'bind' }, argument: VExpressionContainer | VIdentifier } } VBindDirectiveKey
+ * @typedef { VDirective & { key: VBindDirectiveKey } } VBindDirective
+ */
+
+/**
+ * @param {string} name
+ * @returns {string}
+ */
+function kebabCaseToCamelCase(name) {
+ return casing.isKebabCase(name) ? casing.camelCase(name) : name
+}
+
+/**
+ * @param {VBindDirective} node
+ * @returns {boolean}
+ */
+function isSameName(node) {
+ const attrName =
+ node.key.argument.type === 'VIdentifier' ? node.key.argument.rawName : null
+ const valueName =
+ node.value?.expression?.type === 'Identifier'
+ ? node.value.expression.name
+ : null
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
+ if (!attrName || !valueName) return false
+
+ return kebabCaseToCamelCase(attrName) === kebabCaseToCamelCase(valueName)
+}
+
+/**
+ * @param {VBindDirectiveKey} key
+ * @returns {number}
+ */
+function getCutStart(key) {
+ const modifiers = key.modifiers
+ return modifiers.length > 0
+ ? modifiers[modifiers.length - 1].range[1]
+ : key.argument.range[1]
+}
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce `v-bind` directive style',
- categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
url: 'https://eslint.vuejs.org/rules/v-bind-style.html'
},
fixable: 'code',
- schema: [{ enum: ['shorthand', 'longform'] }]
+ schema: [
+ { enum: ['shorthand', 'longform'] },
+ {
+ type: 'object',
+ properties: {
+ sameNameShorthand: { enum: ['always', 'never', 'ignore'] }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ expectedLonghand: "Expected 'v-bind' before ':'.",
+ unexpectedLonghand: "Unexpected 'v-bind' before ':'.",
+ expectedLonghandForProp: "Expected 'v-bind:' instead of '.'.",
+ expectedShorthand: 'Expected same-name shorthand.',
+ unexpectedShorthand: 'Unexpected same-name shorthand.'
+ }
},
/** @param {RuleContext} context */
create(context) {
const preferShorthand = context.options[0] !== 'longform'
+ /** @type {"always" | "never" | "ignore"} */
+ const sameNameShorthand = context.options[1]?.sameNameShorthand || 'ignore'
- return utils.defineTemplateBodyVisitor(context, {
- /** @param {VDirective} node */
- "VAttribute[directive=true][key.name.name='bind'][key.argument!=null]"(
- node
- ) {
- const shorthandProp = node.key.name.rawName === '.'
- const shorthand = node.key.name.rawName === ':' || shorthandProp
- if (shorthand === preferShorthand) {
- return
- }
+ /** @param {VBindDirective} node */
+ function checkAttributeStyle(node) {
+ const shorthandProp = node.key.name.rawName === '.'
+ const shorthand = node.key.name.rawName === ':' || shorthandProp
+ if (shorthand === preferShorthand) {
+ return
+ }
+
+ let messageId = 'expectedLonghand'
+ if (preferShorthand) {
+ messageId = 'unexpectedLonghand'
+ } else if (shorthandProp) {
+ messageId = 'expectedLonghandForProp'
+ }
+
+ context.report({
+ node,
+ loc: node.loc,
+ messageId,
+ *fix(fixer) {
+ if (preferShorthand) {
+ yield fixer.remove(node.key.name)
+ } else {
+ yield fixer.insertTextBefore(node, 'v-bind')
- context.report({
- node,
- loc: node.loc,
- message: preferShorthand
- ? "Unexpected 'v-bind' before ':'."
- : shorthandProp
- ? "Expected 'v-bind:' instead of '.'."
- : /* otherwise */ "Expected 'v-bind' before ':'.",
- *fix(fixer) {
- if (preferShorthand) {
- yield fixer.remove(node.key.name)
- } else {
- yield fixer.insertTextBefore(node, 'v-bind')
-
- if (shorthandProp) {
- // Replace `.` by `:`.
- yield fixer.replaceText(node.key.name, ':')
-
- // Insert `.prop` modifier if it doesn't exist.
- const modifier = node.key.modifiers[0]
- const isAutoGeneratedPropModifier =
- modifier.name === 'prop' && modifier.rawName === ''
- if (isAutoGeneratedPropModifier) {
- yield fixer.insertTextBefore(modifier, '.prop')
- }
+ if (shorthandProp) {
+ // Replace `.` by `:`.
+ yield fixer.replaceText(node.key.name, ':')
+
+ // Insert `.prop` modifier if it doesn't exist.
+ const modifier = node.key.modifiers[0]
+ const isAutoGeneratedPropModifier =
+ modifier.name === 'prop' && modifier.rawName === ''
+ if (isAutoGeneratedPropModifier) {
+ yield fixer.insertTextBefore(modifier, '.prop')
}
}
}
- })
+ }
+ })
+ }
+
+ /** @param {VBindDirective} node */
+ function checkAttributeSameName(node) {
+ if (sameNameShorthand === 'ignore' || !isSameName(node)) return
+
+ const preferShorthand = sameNameShorthand === 'always'
+ const isShorthand = utils.isVBindSameNameShorthand(node)
+ if (isShorthand === preferShorthand) {
+ return
+ }
+
+ const messageId = preferShorthand
+ ? 'expectedShorthand'
+ : 'unexpectedShorthand'
+
+ context.report({
+ node,
+ loc: node.loc,
+ messageId,
+ *fix(fixer) {
+ if (preferShorthand) {
+ /** @type {Range} */
+ const valueRange = [getCutStart(node.key), node.range[1]]
+
+ yield fixer.removeRange(valueRange)
+ } else if (node.key.argument.type === 'VIdentifier') {
+ yield fixer.insertTextAfter(
+ node,
+ `="${kebabCaseToCamelCase(node.key.argument.rawName)}"`
+ )
+ }
+ }
+ })
+ }
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VBindDirective} node */
+ "VAttribute[directive=true][key.name.name='bind'][key.argument!=null]"(
+ node
+ ) {
+ checkAttributeSameName(node)
+ checkAttributeStyle(node)
}
})
}
diff --git a/lib/rules/v-for-delimiter-style.js b/lib/rules/v-for-delimiter-style.js
index ef0086cfe..2b20cfd0a 100644
--- a/lib/rules/v-for-delimiter-style.js
+++ b/lib/rules/v-for-delimiter-style.js
@@ -6,27 +6,22 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'layout',
docs: {
description: "enforce `v-for` directive's delimiter style",
categories: undefined,
- recommended: false,
url: 'https://eslint.vuejs.org/rules/v-for-delimiter-style.html'
},
fixable: 'code',
- schema: [{ enum: ['in', 'of'] }]
+ schema: [{ enum: ['in', 'of'] }],
+ messages: {
+ expected:
+ "Expected '{{preferredDelimiter}}' instead of '{{usedDelimiter}}' in 'v-for'."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -36,16 +31,19 @@ module.exports = {
return utils.defineTemplateBodyVisitor(context, {
/** @param {VForExpression} node */
VForExpression(node) {
+ const sourceCode = context.getSourceCode()
const tokenStore =
- context.parserServices.getTemplateBodyTokenStore &&
- context.parserServices.getTemplateBodyTokenStore()
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
- const delimiterToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.left.length
- ? node.left[node.left.length - 1]
- : tokenStore.getFirstToken(node),
- (token) => token.type !== 'Punctuator' || token.value !== ')'
- ))
+ const delimiterToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(
+ node.left.length > 0
+ ? node.left[node.left.length - 1]
+ : tokenStore.getFirstToken(node),
+ (token) => token.type !== 'Punctuator'
+ )
+ )
if (delimiterToken.value === preferredDelimiter) {
return
@@ -54,7 +52,7 @@ module.exports = {
context.report({
node,
loc: node.loc,
- message: `Expected '{{preferredDelimiter}}' instead of '{{usedDelimiter}}' in 'v-for'.`,
+ messageId: 'expected',
data: {
preferredDelimiter,
usedDelimiter: delimiterToken.value
diff --git a/lib/rules/v-if-else-key.js b/lib/rules/v-if-else-key.js
new file mode 100644
index 000000000..d8e913670
--- /dev/null
+++ b/lib/rules/v-if-else-key.js
@@ -0,0 +1,317 @@
+/**
+ * @author Felipe Melendez
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// =============================================================================
+// Requirements
+// =============================================================================
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+
+// =============================================================================
+// Rule Helpers
+// =============================================================================
+
+/**
+ * A conditional family is made up of a group of repeated components that are conditionally rendered
+ * using v-if, v-else-if, and v-else.
+ *
+ * @typedef {Object} ConditionalFamily
+ * @property {VElement} if - The node associated with the 'v-if' directive.
+ * @property {VElement[]} elseIf - An array of nodes associated with 'v-else-if' directives.
+ * @property {VElement | null} else - The node associated with the 'v-else' directive, or null if there isn't one.
+ */
+
+/**
+ * Checks if a given node has sibling nodes of the same type that are also conditionally rendered.
+ * This is used to determine if multiple instances of the same component are being conditionally
+ * rendered within the same parent scope.
+ *
+ * @param {VElement} node - The Vue component node to check for conditional rendering siblings.
+ * @param {string} componentName - The name of the component to check for sibling instances.
+ * @returns {boolean} True if there are sibling nodes of the same type and conditionally rendered, false otherwise.
+ */
+const hasConditionalRenderedSiblings = (node, componentName) => {
+ if (!node.parent || node.parent.type !== 'VElement') {
+ return false
+ }
+ return node.parent.children.some(
+ (sibling) =>
+ sibling !== node &&
+ sibling.type === 'VElement' &&
+ sibling.rawName === componentName &&
+ hasConditionalDirective(sibling)
+ )
+}
+
+/**
+ * Checks for the presence of a 'key' attribute in the given node. If the 'key' attribute is missing
+ * and the node is part of a conditional family a report is generated.
+ * The fix proposed adds a unique key based on the component's name and count,
+ * following the format '${kebabCase(componentName)}-${componentCount}', e.g., 'some-component-2'.
+ *
+ * @param {VElement} node - The Vue component node to check for a 'key' attribute.
+ * @param {RuleContext} context - The rule's context object, used for reporting.
+ * @param {string} componentName - Name of the component.
+ * @param {string} uniqueKey - A unique key for the repeated component, used for the fix.
+ * @param {Map} conditionalFamilies - Map of conditionally rendered components and their respective conditional directives.
+ */
+const checkForKey = (
+ node,
+ context,
+ componentName,
+ uniqueKey,
+ conditionalFamilies
+) => {
+ if (
+ !node.parent ||
+ node.parent.type !== 'VElement' ||
+ !hasConditionalRenderedSiblings(node, componentName)
+ ) {
+ return
+ }
+
+ const conditionalFamily = conditionalFamilies.get(node.parent)
+
+ if (!conditionalFamily || utils.hasAttribute(node, 'key')) {
+ return
+ }
+
+ const needsKey =
+ conditionalFamily.if === node ||
+ conditionalFamily.else === node ||
+ conditionalFamily.elseIf.includes(node)
+
+ if (needsKey) {
+ context.report({
+ node: node.startTag,
+ loc: node.startTag.loc,
+ messageId: 'requireKey',
+ data: { componentName },
+ fix(fixer) {
+ const afterComponentNamePosition =
+ node.startTag.range[0] + componentName.length + 1
+ return fixer.insertTextBeforeRange(
+ [afterComponentNamePosition, afterComponentNamePosition],
+ ` key="${uniqueKey}"`
+ )
+ }
+ })
+ }
+}
+
+/**
+ * Checks for the presence of conditional directives in the given node.
+ *
+ * @param {VElement} node - The node to check for conditional directives.
+ * @returns {boolean} Returns true if a conditional directive is found in the node or its parents,
+ * false otherwise.
+ */
+const hasConditionalDirective = (node) =>
+ utils.hasDirective(node, 'if') ||
+ utils.hasDirective(node, 'else-if') ||
+ utils.hasDirective(node, 'else')
+
+// =============================================================================
+// Rule Definition
+// =============================================================================
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'require key attribute for conditionally rendered repeated components',
+ categories: null,
+ url: 'https://eslint.vuejs.org/rules/v-if-else-key.html'
+ },
+ // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
+ fixable: 'code',
+ schema: [],
+ messages: {
+ requireKey:
+ "Conditionally rendered repeated component '{{componentName}}' expected to have a 'key' attribute."
+ }
+ },
+ /**
+ * Creates and returns a rule object which checks usage of repeated components. If a component
+ * is used more than once, it checks for the presence of a key.
+ *
+ * @param {RuleContext} context - The context object.
+ * @returns {Object} A dictionary of functions to be called on traversal of the template body by
+ * the eslint parser.
+ */
+ create(context) {
+ /**
+ * Map to store conditionally rendered components and their respective conditional directives.
+ *
+ * @type {Map}
+ */
+ const conditionalFamilies = new Map()
+
+ /**
+ * Array of Maps to keep track of components and their usage counts along with the first
+ * node instance. Each Map represents a different scope level, and maps a component name to
+ * an object containing the count and a reference to the first node.
+ */
+ /** @type {Map[]} */
+ const componentUsageStack = [new Map()]
+
+ /**
+ * Checks if a given node represents a custom component without any conditional directives.
+ *
+ * @param {VElement} node - The AST node to check.
+ * @returns {boolean} True if the node represents a custom component without any conditional directives, false otherwise.
+ */
+ const isCustomComponentWithoutCondition = (node) =>
+ node.type === 'VElement' &&
+ utils.isCustomComponent(node) &&
+ !hasConditionalDirective(node)
+
+ /** Set of built-in Vue components that are exempt from the rule. */
+ /** @type {Set} */
+ const exemptTags = new Set(['component', 'slot', 'template'])
+
+ /** Set to keep track of nodes we've pushed to the stack. */
+ /** @type {Set} */
+ const pushedNodes = new Set()
+
+ /**
+ * Creates and returns an object representing a conditional family.
+ *
+ * @param {VElement} ifNode - The VElement associated with the 'v-if' directive.
+ * @returns {ConditionalFamily}
+ */
+ const createConditionalFamily = (ifNode) => ({
+ if: ifNode,
+ elseIf: [],
+ else: null
+ })
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /**
+ * Callback to be executed when a Vue element is traversed. This function checks if the
+ * element is a component, increments the usage count of the component in the
+ * current scope, and checks for the key directive if the component is repeated.
+ *
+ * @param {VElement} node - The traversed Vue element.
+ */
+ VElement(node) {
+ if (exemptTags.has(node.rawName)) {
+ return
+ }
+
+ const condition =
+ utils.getDirective(node, 'if') ||
+ utils.getDirective(node, 'else-if') ||
+ utils.getDirective(node, 'else')
+
+ if (condition) {
+ const conditionType = condition.key.name.name
+
+ if (node.parent && node.parent.type === 'VElement') {
+ let conditionalFamily = conditionalFamilies.get(node.parent)
+
+ if (!conditionalFamily) {
+ conditionalFamily = createConditionalFamily(node)
+ conditionalFamilies.set(node.parent, conditionalFamily)
+ }
+
+ if (conditionalFamily) {
+ switch (conditionType) {
+ case 'if': {
+ conditionalFamily = createConditionalFamily(node)
+ conditionalFamilies.set(node.parent, conditionalFamily)
+ break
+ }
+ case 'else-if': {
+ conditionalFamily.elseIf.push(node)
+ break
+ }
+ case 'else': {
+ conditionalFamily.else = node
+ break
+ }
+ }
+ }
+ }
+ }
+
+ if (isCustomComponentWithoutCondition(node)) {
+ componentUsageStack.push(new Map())
+ return
+ }
+
+ if (!utils.isCustomComponent(node)) {
+ return
+ }
+
+ const componentName = node.rawName
+ const currentScope = componentUsageStack[componentUsageStack.length - 1]
+ const usageInfo = currentScope.get(componentName) || {
+ count: 0,
+ firstNode: null
+ }
+
+ if (hasConditionalDirective(node)) {
+ // Store the first node if this is the first occurrence
+ if (usageInfo.count === 0) {
+ usageInfo.firstNode = node
+ }
+
+ if (usageInfo.count > 0) {
+ const uniqueKey = `${casing.kebabCase(componentName)}-${
+ usageInfo.count + 1
+ }`
+ checkForKey(
+ node,
+ context,
+ componentName,
+ uniqueKey,
+ conditionalFamilies
+ )
+
+ // If this is the second occurrence, also apply a fix to the first occurrence
+ if (usageInfo.count === 1) {
+ const uniqueKeyForFirstInstance = `${casing.kebabCase(
+ componentName
+ )}-1`
+ checkForKey(
+ usageInfo.firstNode,
+ context,
+ componentName,
+ uniqueKeyForFirstInstance,
+ conditionalFamilies
+ )
+ }
+ }
+ usageInfo.count += 1
+ currentScope.set(componentName, usageInfo)
+ }
+ componentUsageStack.push(new Map())
+ pushedNodes.add(node)
+ },
+
+ 'VElement:exit'(node) {
+ if (exemptTags.has(node.rawName)) {
+ return
+ }
+ if (isCustomComponentWithoutCondition(node)) {
+ componentUsageStack.pop()
+ return
+ }
+ if (!utils.isCustomComponent(node)) {
+ return
+ }
+ if (pushedNodes.has(node)) {
+ componentUsageStack.pop()
+ pushedNodes.delete(node)
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/v-on-event-hyphenation.js b/lib/rules/v-on-event-hyphenation.js
index 60823e013..c9fac76e8 100644
--- a/lib/rules/v-on-event-hyphenation.js
+++ b/lib/rules/v-on-event-hyphenation.js
@@ -2,16 +2,19 @@
const utils = require('../utils')
const casing = require('../utils/casing')
+const { toRegExp } = require('../utils/regexp')
module.exports = {
meta: {
+ type: 'suggestion',
docs: {
description:
'enforce v-on event naming style on custom components in template',
- // TODO Change with major version.
- // categories: ['vue3-strongly-recommended'],
- categories: undefined,
- url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html'
+ categories: ['vue3-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html',
+ defaultOptions: {
+ vue3: ['always', { autofix: true }]
+ }
},
fixable: 'code',
schema: [
@@ -28,17 +31,28 @@ module.exports = {
allOf: [
{ type: 'string' },
{ not: { type: 'string', pattern: ':exit$' } },
- { not: { type: 'string', pattern: '^\\s*$' } }
+ { not: { type: 'string', pattern: String.raw`^\s*$` } }
]
},
uniqueItems: true,
additionalItems: false
+ },
+ ignoreTags: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true,
+ additionalItems: false
}
},
additionalProperties: false
}
],
- type: 'suggestion'
+ messages: {
+ // eslint-disable-next-line eslint-plugin/report-message-format
+ mustBeHyphenated: "v-on event '{{text}}' must be hyphenated.",
+ // eslint-disable-next-line eslint-plugin/report-message-format
+ cannotBeHyphenated: "v-on event '{{text}}' can't be hyphenated."
+ }
},
/** @param {RuleContext} context */
@@ -49,35 +63,38 @@ module.exports = {
const useHyphenated = option !== 'never'
/** @type {string[]} */
const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || []
+ /** @type {RegExp[]} */
+ const ignoredTagsRegexps = (
+ (optionsPayload && optionsPayload.ignoreTags) ||
+ []
+ ).map(toRegExp)
const autofix = Boolean(optionsPayload && optionsPayload.autofix)
- const caseConverter = casing.getExactConverter(
+ const caseConverter = casing.getConverter(
useHyphenated ? 'kebab-case' : 'camelCase'
)
/**
* @param {VDirective} node
+ * @param {VIdentifier} argument
* @param {string} name
*/
- function reportIssue(node, name) {
+ function reportIssue(node, argument, name) {
const text = sourceCode.getText(node.key)
context.report({
node: node.key,
loc: node.loc,
- message: useHyphenated
- ? "v-on event '{{text}}' must be hyphenated."
- : "v-on event '{{text}}' can't be hyphenated.",
+ messageId: useHyphenated ? 'mustBeHyphenated' : 'cannotBeHyphenated',
data: {
text
},
- fix: autofix
- ? (fixer) =>
- fixer.replaceText(
- node.key,
- text.replace(name, caseConverter(name))
- )
- : null
+ fix:
+ autofix &&
+ // It cannot be converted in snake_case.
+ !name.includes('_')
+ ? (fixer) => fixer.replaceText(argument, caseConverter(name))
+ : null
})
}
@@ -85,9 +102,7 @@ module.exports = {
* @param {string} value
*/
function isIgnoredAttribute(value) {
- const isIgnored = ignoredAttributes.some((attr) => {
- return value.includes(attr)
- })
+ const isIgnored = ignoredAttributes.some((attr) => value.includes(attr))
if (isIgnored) {
return true
@@ -96,17 +111,27 @@ module.exports = {
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
}
+ /** @param {string} name */
+ function isIgnoredTagName(name) {
+ return ignoredTagsRegexps.some((re) => re.test(name))
+ }
+
return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name.name='on']"(node) {
- if (!utils.isCustomComponent(node.parent.parent)) return
-
- const name =
- node.key.argument &&
- node.key.argument.type === 'VIdentifier' &&
- node.key.argument.rawName
+ const element = node.parent.parent
+ if (
+ !utils.isCustomComponent(element) ||
+ isIgnoredTagName(element.rawName)
+ ) {
+ return
+ }
+ if (!node.key.argument || node.key.argument.type !== 'VIdentifier') {
+ return
+ }
+ const name = node.key.argument.rawName
if (!name || isIgnoredAttribute(name)) return
- reportIssue(node, name)
+ reportIssue(node, node.key.argument, name)
}
})
}
diff --git a/lib/rules/v-on-function-call.js b/lib/rules/v-on-function-call.js
deleted file mode 100644
index 90da5ed43..000000000
--- a/lib/rules/v-on-function-call.js
+++ /dev/null
@@ -1,209 +0,0 @@
-/**
- * @author Niklas Higi
- */
-'use strict'
-
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
-const utils = require('../utils')
-
-/**
- * @typedef { import('../utils').ComponentPropertyData } ComponentPropertyData
- */
-
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Check whether the given token is a quote.
- * @param {Token} token The token to check.
- * @returns {boolean} `true` if the token is a quote.
- */
-function isQuote(token) {
- return (
- token != null &&
- token.type === 'Punctuator' &&
- (token.value === '"' || token.value === "'")
- )
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
-module.exports = {
- meta: {
- type: 'suggestion',
- docs: {
- description:
- 'enforce or forbid parentheses after method calls without arguments in `v-on` directives',
- categories: undefined,
- url: 'https://eslint.vuejs.org/rules/v-on-function-call.html'
- },
- fixable: 'code',
- schema: [
- { enum: ['always', 'never'] },
- {
- type: 'object',
- properties: {
- ignoreIncludesComment: {
- type: 'boolean'
- }
- },
- additionalProperties: false
- }
- ]
- },
- /** @param {RuleContext} context */
- create(context) {
- const always = context.options[0] === 'always'
-
- /**
- * @param {VOnExpression} node
- * @returns {CallExpression | null}
- */
- function getInvalidNeverCallExpression(node) {
- /** @type {ExpressionStatement} */
- let exprStatement
- let body = node.body
- while (true) {
- const statements = body.filter((st) => st.type !== 'EmptyStatement')
- if (statements.length !== 1) {
- return null
- }
- const statement = statements[0]
- if (statement.type === 'ExpressionStatement') {
- exprStatement = statement
- break
- }
- if (statement.type === 'BlockStatement') {
- body = statement.body
- continue
- }
- return null
- }
- const expression = exprStatement.expression
- if (expression.type !== 'CallExpression' || expression.arguments.length) {
- return null
- }
- if (expression.optional) {
- // Allow optional chaining
- return null
- }
- const callee = expression.callee
- if (callee.type !== 'Identifier') {
- return null
- }
- return expression
- }
-
- if (always) {
- return utils.defineTemplateBodyVisitor(context, {
- /** @param {Identifier} node */
- "VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"(
- node
- ) {
- context.report({
- node,
- message:
- "Method calls inside of 'v-on' directives must have parentheses."
- })
- }
- })
- }
-
- const option = context.options[1] || {}
- const ignoreIncludesComment = !!option.ignoreIncludesComment
- /** @type {Set} */
- const useArgsMethods = new Set()
-
- return utils.defineTemplateBodyVisitor(
- context,
- {
- /** @param {VOnExpression} node */
- "VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"(
- node
- ) {
- const expression = getInvalidNeverCallExpression(node)
- if (!expression) {
- return
- }
-
- const tokenStore = context.parserServices.getTemplateBodyTokenStore()
- const tokens = tokenStore.getTokens(node.parent, {
- includeComments: true
- })
- /** @type {Token | undefined} */
- let leftQuote
- /** @type {Token | undefined} */
- let rightQuote
- if (isQuote(tokens[0])) {
- leftQuote = tokens.shift()
- rightQuote = tokens.pop()
- }
-
- const hasComment = tokens.some(
- (token) => token.type === 'Block' || token.type === 'Line'
- )
-
- if (ignoreIncludesComment && hasComment) {
- return
- }
-
- if (expression.callee.type === 'Identifier') {
- if (useArgsMethods.has(expression.callee.name)) {
- // The behavior of target method can change given the arguments.
- return
- }
- }
-
- context.report({
- node: expression,
- message:
- "Method calls without arguments inside of 'v-on' directives must not have parentheses.",
- fix: hasComment
- ? null /* The comment is included and cannot be fixed. */
- : (fixer) => {
- /** @type {Range} */
- const range =
- leftQuote && rightQuote
- ? [leftQuote.range[1], rightQuote.range[0]]
- : [tokens[0].range[0], tokens[tokens.length - 1].range[1]]
-
- return fixer.replaceTextRange(
- range,
- context.getSourceCode().getText(expression.callee)
- )
- }
- })
- }
- },
- utils.defineVueVisitor(context, {
- onVueObjectEnter(node) {
- for (const method of utils.iterateProperties(
- node,
- new Set(['methods'])
- )) {
- if (useArgsMethods.has(method.name)) {
- continue
- }
- if (method.type !== 'object') {
- continue
- }
- const value = method.property.value
- if (
- (value.type === 'FunctionExpression' ||
- value.type === 'ArrowFunctionExpression') &&
- value.params.length > 0
- ) {
- useArgsMethods.add(method.name)
- }
- }
- }
- })
- )
- }
-}
diff --git a/lib/rules/v-on-handler-style.js b/lib/rules/v-on-handler-style.js
new file mode 100644
index 000000000..10ff9b6b1
--- /dev/null
+++ b/lib/rules/v-on-handler-style.js
@@ -0,0 +1,587 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef {import('eslint').ReportDescriptorFix} ReportDescriptorFix
+ * @typedef {'method' | 'inline' | 'inline-function'} HandlerKind
+ * @typedef {object} ObjectOption
+ * @property {boolean} [ignoreIncludesComment]
+ */
+
+/**
+ * @param {RuleContext} context
+ */
+function parseOptions(context) {
+ /** @type {[HandlerKind | HandlerKind[] | undefined, ObjectOption | undefined]} */
+ const options = /** @type {any} */ (context.options)
+ /** @type {HandlerKind[]} */
+ const allows = []
+ if (options[0]) {
+ if (Array.isArray(options[0])) {
+ allows.push(...options[0])
+ } else {
+ allows.push(options[0])
+ }
+ } else {
+ allows.push('method', 'inline-function')
+ }
+
+ const option = options[1] || {}
+ const ignoreIncludesComment = !!option.ignoreIncludesComment
+
+ return { allows, ignoreIncludesComment }
+}
+
+/**
+ * Check whether the given token is a quote.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a quote.
+ */
+function isQuote(token) {
+ return (
+ token != null &&
+ token.type === 'Punctuator' &&
+ (token.value === '"' || token.value === "'")
+ )
+}
+/**
+ * Check whether the given node is an identifier call expression. e.g. `foo()`
+ * @param {Expression} node The node to check.
+ * @returns {node is CallExpression & {callee: Identifier}}
+ */
+function isIdentifierCallExpression(node) {
+ if (node.type !== 'CallExpression') {
+ return false
+ }
+ if (node.optional) {
+ // optional chaining
+ return false
+ }
+ const callee = node.callee
+ return callee.type === 'Identifier'
+}
+
+/**
+ * Returns a call expression node if the given VOnExpression or BlockStatement consists
+ * of only a single identifier call expression.
+ * e.g.
+ * @click="foo()"
+ * @click="{ foo() }"
+ * @click="foo();;"
+ * @param {VOnExpression | BlockStatement} node
+ * @returns {CallExpression & {callee: Identifier} | null}
+ */
+function getIdentifierCallExpression(node) {
+ /** @type {ExpressionStatement} */
+ let exprStatement
+ let body = node.body
+ while (true) {
+ const statements = body.filter((st) => st.type !== 'EmptyStatement')
+ if (statements.length !== 1) {
+ return null
+ }
+ const statement = statements[0]
+ if (statement.type === 'ExpressionStatement') {
+ exprStatement = statement
+ break
+ }
+ if (statement.type === 'BlockStatement') {
+ body = statement.body
+ continue
+ }
+ return null
+ }
+ const expression = exprStatement.expression
+ if (!isIdentifierCallExpression(expression)) {
+ return null
+ }
+ return expression
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce writing style for handlers in `v-on` directives',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/v-on-handler-style.html'
+ },
+ fixable: 'code',
+ schema: [
+ {
+ oneOf: [
+ { enum: ['inline', 'inline-function'] },
+ {
+ type: 'array',
+ items: [
+ { const: 'method' },
+ { enum: ['inline', 'inline-function'] }
+ ],
+ uniqueItems: true,
+ additionalItems: false,
+ minItems: 2,
+ maxItems: 2
+ }
+ ]
+ },
+ {
+ type: 'object',
+ properties: {
+ ignoreIncludesComment: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ preferMethodOverInline:
+ 'Prefer method handler over inline handler in v-on.',
+ preferMethodOverInlineWithoutIdCall:
+ 'Prefer method handler over inline handler in v-on. Note that you may need to create a new method.',
+ preferMethodOverInlineFunction:
+ 'Prefer method handler over inline function in v-on.',
+ preferMethodOverInlineFunctionWithoutIdCall:
+ 'Prefer method handler over inline function in v-on. Note that you may need to create a new method.',
+ preferInlineOverMethod:
+ 'Prefer inline handler over method handler in v-on.',
+ preferInlineOverInlineFunction:
+ 'Prefer inline handler over inline function in v-on.',
+ preferInlineOverInlineFunctionWithMultipleParams:
+ 'Prefer inline handler over inline function in v-on. Note that the custom event must be changed to a single payload.',
+ preferInlineFunctionOverMethod:
+ 'Prefer inline function over method handler in v-on.',
+ preferInlineFunctionOverInline:
+ 'Prefer inline function over inline handler in v-on.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const { allows, ignoreIncludesComment } = parseOptions(context)
+
+ /** @type {Set} */
+ const upperElements = new Set()
+ /** @type {Map} */
+ const methodParamCountMap = new Map()
+ /** @type {Identifier[]} */
+ const $eventIdentifiers = []
+
+ /**
+ * Verify for inline handler.
+ * @param {VOnExpression} node
+ * @param {HandlerKind} kind
+ * @returns {boolean} Returns `true` if reported.
+ */
+ function verifyForInlineHandler(node, kind) {
+ switch (kind) {
+ case 'method': {
+ return verifyCanUseMethodHandlerForInlineHandler(node)
+ }
+ case 'inline-function': {
+ reportCanUseInlineFunctionForInlineHandler(node)
+ return true
+ }
+ }
+ return false
+ }
+ /**
+ * Report for method handler.
+ * @param {Identifier} node
+ * @param {HandlerKind} kind
+ * @returns {boolean} Returns `true` if reported.
+ */
+ function reportForMethodHandler(node, kind) {
+ switch (kind) {
+ case 'inline':
+ case 'inline-function': {
+ context.report({
+ node,
+ messageId:
+ kind === 'inline'
+ ? 'preferInlineOverMethod'
+ : 'preferInlineFunctionOverMethod'
+ })
+ return true
+ }
+ }
+ // This path is currently not taken.
+ return false
+ }
+ /**
+ * Verify for inline function handler.
+ * @param {ArrowFunctionExpression | FunctionExpression} node
+ * @param {HandlerKind} kind
+ * @returns {boolean} Returns `true` if reported.
+ */
+ function verifyForInlineFunction(node, kind) {
+ switch (kind) {
+ case 'method': {
+ return verifyCanUseMethodHandlerForInlineFunction(node)
+ }
+ case 'inline': {
+ reportCanUseInlineHandlerForInlineFunction(node)
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Get token information for the given VExpressionContainer node.
+ * @param {VExpressionContainer} node
+ */
+ function getVExpressionContainerTokenInfo(node) {
+ const sourceCode = context.getSourceCode()
+ const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore()
+ const tokens = tokenStore.getTokens(node, {
+ includeComments: true
+ })
+ const firstToken = tokens[0]
+ const lastToken = tokens[tokens.length - 1]
+
+ const hasQuote = isQuote(firstToken)
+ /** @type {Range} */
+ const rangeWithoutQuotes = hasQuote
+ ? [firstToken.range[1], lastToken.range[0]]
+ : [firstToken.range[0], lastToken.range[1]]
+
+ return {
+ rangeWithoutQuotes,
+ get hasComment() {
+ return tokens.some(
+ (token) => token.type === 'Block' || token.type === 'Line'
+ )
+ },
+ hasQuote
+ }
+ }
+
+ /**
+ * Checks whether the given node refers to a variable of the element.
+ * @param {Expression | VOnExpression} node
+ */
+ function hasReferenceUpperElementVariable(node) {
+ for (const element of upperElements) {
+ for (const vv of element.variables) {
+ for (const reference of vv.references) {
+ const { range } = reference.id
+ if (node.range[0] <= range[0] && range[1] <= node.range[1]) {
+ return true
+ }
+ }
+ }
+ }
+ return false
+ }
+ /**
+ * Check if `v-on:click="foo()"` can be converted to `v-on:click="foo"` and report if it can.
+ * @param {VOnExpression} node
+ * @returns {boolean} Returns `true` if reported.
+ */
+ function verifyCanUseMethodHandlerForInlineHandler(node) {
+ const { rangeWithoutQuotes, hasComment } =
+ getVExpressionContainerTokenInfo(node.parent)
+ if (ignoreIncludesComment && hasComment) {
+ return false
+ }
+
+ const idCallExpr = getIdentifierCallExpression(node)
+ if (
+ (!idCallExpr || idCallExpr.arguments.length > 0) &&
+ hasReferenceUpperElementVariable(node)
+ ) {
+ // It cannot be converted to method because it refers to the variable of the element.
+ // e.g.
+ return false
+ }
+
+ context.report({
+ node,
+ messageId: idCallExpr
+ ? 'preferMethodOverInline'
+ : 'preferMethodOverInlineWithoutIdCall',
+ fix: (fixer) => {
+ if (
+ hasComment /* The statement contains comment and cannot be fixed. */ ||
+ !idCallExpr /* The statement is not a simple identifier call and cannot be fixed. */ ||
+ idCallExpr.arguments.length > 0
+ ) {
+ return null
+ }
+ const paramCount = methodParamCountMap.get(idCallExpr.callee.name)
+ if (paramCount != null && paramCount > 0) {
+ // The behavior of target method can change given the arguments.
+ return null
+ }
+ return fixer.replaceTextRange(
+ rangeWithoutQuotes,
+ context.getSourceCode().getText(idCallExpr.callee)
+ )
+ }
+ })
+ return true
+ }
+ /**
+ * Check if `v-on:click="() => foo()"` can be converted to `v-on:click="foo"` and report if it can.
+ * @param {ArrowFunctionExpression | FunctionExpression} node
+ * @returns {boolean} Returns `true` if reported.
+ */
+ function verifyCanUseMethodHandlerForInlineFunction(node) {
+ const { rangeWithoutQuotes, hasComment } =
+ getVExpressionContainerTokenInfo(
+ /** @type {VExpressionContainer} */ (node.parent)
+ )
+ if (ignoreIncludesComment && hasComment) {
+ return false
+ }
+
+ /** @type {CallExpression & {callee: Identifier} | null} */
+ let idCallExpr = null
+ if (node.body.type === 'BlockStatement') {
+ idCallExpr = getIdentifierCallExpression(node.body)
+ } else if (isIdentifierCallExpression(node.body)) {
+ idCallExpr = node.body
+ }
+ if (
+ (!idCallExpr || !isSameParamsAndArgs(idCallExpr)) &&
+ hasReferenceUpperElementVariable(node)
+ ) {
+ // It cannot be converted to method because it refers to the variable of the element.
+ // e.g.
+ return false
+ }
+
+ context.report({
+ node,
+ messageId: idCallExpr
+ ? 'preferMethodOverInlineFunction'
+ : 'preferMethodOverInlineFunctionWithoutIdCall',
+ fix: (fixer) => {
+ if (
+ hasComment /* The function contains comment and cannot be fixed. */ ||
+ !idCallExpr /* The function is not a simple identifier call and cannot be fixed. */
+ ) {
+ return null
+ }
+ if (!isSameParamsAndArgs(idCallExpr)) {
+ // It is not a call with the arguments given as is.
+ return null
+ }
+ const paramCount = methodParamCountMap.get(idCallExpr.callee.name)
+ if (
+ paramCount != null &&
+ paramCount !== idCallExpr.arguments.length
+ ) {
+ // The behavior of target method can change given the arguments.
+ return null
+ }
+ return fixer.replaceTextRange(
+ rangeWithoutQuotes,
+ context.getSourceCode().getText(idCallExpr.callee)
+ )
+ }
+ })
+ return true
+
+ /**
+ * Checks whether parameters are passed as arguments as-is.
+ * @param {CallExpression} expression
+ */
+ function isSameParamsAndArgs(expression) {
+ return (
+ node.params.length === expression.arguments.length &&
+ node.params.every((param, index) => {
+ if (param.type !== 'Identifier') {
+ return false
+ }
+ const arg = expression.arguments[index]
+ if (!arg || arg.type !== 'Identifier') {
+ return false
+ }
+ return param.name === arg.name
+ })
+ )
+ }
+ }
+ /**
+ * Report `v-on:click="foo()"` can be converted to `v-on:click="()=>foo()"`.
+ * @param {VOnExpression} node
+ * @returns {void}
+ */
+ function reportCanUseInlineFunctionForInlineHandler(node) {
+ context.report({
+ node,
+ messageId: 'preferInlineFunctionOverInline',
+ *fix(fixer) {
+ const has$Event = $eventIdentifiers.some(
+ ({ range }) =>
+ node.range[0] <= range[0] && range[1] <= node.range[1]
+ )
+ if (has$Event) {
+ /* The statements contains $event and cannot be fixed. */
+ return
+ }
+ const { rangeWithoutQuotes, hasQuote } =
+ getVExpressionContainerTokenInfo(node.parent)
+ if (!hasQuote) {
+ /* The statements is not enclosed in quotes and cannot be fixed. */
+ return
+ }
+ yield fixer.insertTextBeforeRange(rangeWithoutQuotes, '() => ')
+ const sourceCode = context.getSourceCode()
+ const tokenStore =
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+ const firstToken = tokenStore.getFirstToken(node)
+ const lastToken = tokenStore.getLastToken(node)
+ if (firstToken.value === '{' && lastToken.value === '}') return
+ if (
+ lastToken.value !== ';' &&
+ node.body.length === 1 &&
+ node.body[0].type === 'ExpressionStatement'
+ ) {
+ // it is a single expression
+ return
+ }
+ yield fixer.insertTextBefore(firstToken, '{')
+ yield fixer.insertTextAfter(lastToken, '}')
+ }
+ })
+ }
+ /**
+ * Report `v-on:click="() => foo()"` can be converted to `v-on:click="foo()"`.
+ * @param {ArrowFunctionExpression | FunctionExpression} node
+ * @returns {void}
+ */
+ function reportCanUseInlineHandlerForInlineFunction(node) {
+ // If a function has one parameter, you can turn it into an inline handler using $event.
+ // If a function has two or more parameters, it cannot be easily converted to an inline handler.
+ // However, users can use inline handlers by changing the payload of the component's custom event.
+ // So we report it regardless of the number of parameters.
+
+ context.report({
+ node,
+ messageId:
+ node.params.length > 1
+ ? 'preferInlineOverInlineFunctionWithMultipleParams'
+ : 'preferInlineOverInlineFunction',
+ fix:
+ node.params.length > 0
+ ? null /* The function has parameters and cannot be fixed. */
+ : (fixer) => {
+ let text = context.getSourceCode().getText(node.body)
+ if (node.body.type === 'BlockStatement') {
+ text = text.slice(1, -1) // strip braces
+ }
+ return fixer.replaceText(node, text)
+ }
+ })
+ }
+
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {
+ VElement(node) {
+ upperElements.add(node)
+ },
+ 'VElement:exit'(node) {
+ upperElements.delete(node)
+ },
+ /** @param {VExpressionContainer} node */
+ "VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer.value:exit"(
+ node
+ ) {
+ const expression = node.expression
+ if (!expression) {
+ return
+ }
+ switch (expression.type) {
+ case 'VOnExpression': {
+ // e.g. v-on:click="foo()"
+ if (allows[0] === 'inline') {
+ return
+ }
+ for (const allow of allows) {
+ if (verifyForInlineHandler(expression, allow)) {
+ return
+ }
+ }
+ break
+ }
+ case 'Identifier': {
+ // e.g. v-on:click="foo"
+ if (allows[0] === 'method') {
+ return
+ }
+ for (const allow of allows) {
+ if (reportForMethodHandler(expression, allow)) {
+ return
+ }
+ }
+ break
+ }
+ case 'ArrowFunctionExpression':
+ case 'FunctionExpression': {
+ // e.g. v-on:click="()=>foo()"
+ if (allows[0] === 'inline-function') {
+ return
+ }
+ for (const allow of allows) {
+ if (verifyForInlineFunction(expression, allow)) {
+ return
+ }
+ }
+ break
+ }
+ default: {
+ return
+ }
+ }
+ },
+ ...(allows.includes('inline-function')
+ ? // Collect $event identifiers to check for side effects
+ // when converting from `v-on:click="foo($event)"` to `v-on:click="()=>foo($event)"` .
+ {
+ 'Identifier[name="$event"]'(node) {
+ $eventIdentifiers.push(node)
+ }
+ }
+ : {})
+ },
+ allows.includes('method')
+ ? // Collect method definition with params information to check for side effects.
+ // when converting from `v-on:click="foo()"` to `v-on:click="foo"`, or
+ // converting from `v-on:click="() => foo()"` to `v-on:click="foo"`.
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ for (const method of utils.iterateProperties(
+ node,
+ new Set(['methods'])
+ )) {
+ if (method.type !== 'object') {
+ // This branch is usually not passed.
+ continue
+ }
+ const value = method.property.value
+ if (
+ value.type === 'FunctionExpression' ||
+ value.type === 'ArrowFunctionExpression'
+ ) {
+ methodParamCountMap.set(
+ method.name,
+ value.params.some((p) => p.type === 'RestElement')
+ ? Number.POSITIVE_INFINITY
+ : value.params.length
+ )
+ }
+ }
+ }
+ })
+ : {}
+ )
+ }
+}
diff --git a/lib/rules/v-on-style.js b/lib/rules/v-on-style.js
index 4649aacf9..eb5950026 100644
--- a/lib/rules/v-on-style.js
+++ b/lib/rules/v-on-style.js
@@ -5,26 +5,22 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce `v-on` directive style',
- categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
url: 'https://eslint.vuejs.org/rules/v-on-style.html'
},
fixable: 'code',
- schema: [{ enum: ['shorthand', 'longform'] }]
+ schema: [{ enum: ['shorthand', 'longform'] }],
+ messages: {
+ expectedShorthand: "Expected '@' instead of 'v-on:'.",
+ expectedLonghand: "Expected 'v-on:' instead of '@'."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -44,9 +40,7 @@ module.exports = {
context.report({
node,
loc: node.loc,
- message: preferShorthand
- ? "Expected '@' instead of 'v-on:'."
- : "Expected 'v-on:' instead of '@'.",
+ messageId: preferShorthand ? 'expectedShorthand' : 'expectedLonghand',
fix: (fixer) =>
preferShorthand
? fixer.replaceTextRange([pos, pos + 5], '@')
diff --git a/lib/rules/v-slot-style.js b/lib/rules/v-slot-style.js
index f0cbe3610..964550b98 100644
--- a/lib/rules/v-slot-style.js
+++ b/lib/rules/v-slot-style.js
@@ -28,7 +28,10 @@ function normalizeOptions(options) {
}
if (typeof options === 'string') {
- normalized.atComponent = normalized.default = normalized.named = /** @type {"shorthand" | "longform"} */ (options)
+ normalized.atComponent =
+ normalized.default =
+ normalized.named =
+ /** @type {"shorthand" | "longform"} */ (options)
} else if (options != null) {
/** @type {(keyof Options)[]} */
const keys = ['atComponent', 'default', 'named']
@@ -83,13 +86,13 @@ module.exports = {
type: 'suggestion',
docs: {
description: 'enforce `v-slot` directive style',
- categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
url: 'https://eslint.vuejs.org/rules/v-slot-style.html'
},
fixable: 'code',
schema: [
{
- anyOf: [
+ oneOf: [
{ enum: ['shorthand', 'longform'] },
{
type: 'object',
@@ -138,14 +141,18 @@ module.exports = {
fix(fixer) {
switch (expected) {
- case 'shorthand':
+ case 'shorthand': {
return fixer.replaceTextRange(range, `#${argumentText}`)
- case 'longform':
+ }
+ case 'longform': {
return fixer.replaceTextRange(range, `v-slot:${argumentText}`)
- case 'v-slot':
+ }
+ case 'v-slot': {
return fixer.replaceTextRange(range, 'v-slot')
- default:
+ }
+ default: {
return null
+ }
}
}
})
diff --git a/lib/rules/valid-attribute-name.js b/lib/rules/valid-attribute-name.js
new file mode 100644
index 000000000..51d52a1b6
--- /dev/null
+++ b/lib/rules/valid-attribute-name.js
@@ -0,0 +1,69 @@
+/**
+ * @author Doug Wade
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const xnv = require('xml-name-validator')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'require valid attribute names',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-attribute-name.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ attribute: 'Attribute name {{name}} is not valid.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /**
+ * @param {string | VIdentifier} key
+ * @return {string}
+ */
+ const getName = (key) => (typeof key === 'string' ? key : key.name)
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective | VAttribute} node */
+ VAttribute(node) {
+ if (utils.isCustomComponent(node.parent.parent)) {
+ return
+ }
+
+ const name = getName(node.key.name)
+
+ if (
+ node.directive &&
+ name === 'bind' &&
+ node.key.argument &&
+ node.key.argument.type === 'VIdentifier' &&
+ !xnv.name(node.key.argument.name)
+ ) {
+ context.report({
+ node,
+ messageId: 'attribute',
+ data: {
+ name: node.key.argument.name
+ }
+ })
+ }
+
+ if (!node.directive && !xnv.name(name)) {
+ context.report({
+ node,
+ messageId: 'attribute',
+ data: {
+ name
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js
new file mode 100644
index 000000000..8d2306c23
--- /dev/null
+++ b/lib/rules/valid-define-emits.js
@@ -0,0 +1,144 @@
+/**
+ * @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 `defineEmits` compiler macro',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-define-emits.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ hasTypeAndArg: '`defineEmits` has both a type-only emit and an argument.',
+ referencingLocally:
+ '`defineEmits` is referencing locally declared variables.',
+ multiple: '`defineEmits` has been called multiple times.',
+ notDefined: 'Custom events are not defined.',
+ definedInBoth:
+ 'Custom events are defined in both `defineEmits` and `export default {}`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const scriptSetup = utils.getScriptSetupElement(context)
+ if (!scriptSetup) {
+ return {}
+ }
+
+ /** @type {Set} */
+ const emitsDefExpressions = new Set()
+ let hasDefaultExport = false
+ /** @type {CallExpression[]} */
+ const defineEmitsNodes = []
+ /** @type {CallExpression | null} */
+ let emptyDefineEmits = null
+
+ return utils.compositingVisitors(
+ utils.defineScriptSetupVisitor(context, {
+ onDefineEmitsEnter(node) {
+ defineEmitsNodes.push(node)
+
+ const typeArguments =
+ 'typeArguments' in node ? node.typeArguments : node.typeParameters
+ if (node.arguments.length > 0) {
+ if (typeArguments && typeArguments.params.length > 0) {
+ // `defineEmits` has both a literal type and an argument.
+ context.report({
+ node,
+ messageId: 'hasTypeAndArg'
+ })
+ return
+ }
+
+ emitsDefExpressions.add(node.arguments[0])
+ } else {
+ if (!typeArguments || typeArguments.params.length === 0) {
+ emptyDefineEmits = node
+ }
+ }
+ },
+ Identifier(node) {
+ for (const defineEmits of emitsDefExpressions) {
+ if (utils.inRange(defineEmits.range, node)) {
+ const variable = findVariable(utils.getScope(context, node), 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(defineEmits.range, def.name)
+ )
+ ) {
+ if (utils.withinTypeNode(node)) {
+ continue
+ }
+ //`defineEmits` is referencing locally declared variables.
+ context.report({
+ node,
+ messageId: 'referencingLocally'
+ })
+ }
+ }
+ }
+ }
+ }),
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node, { type }) {
+ if (type !== 'export' || utils.inRange(scriptSetup.range, node)) {
+ return
+ }
+
+ hasDefaultExport = Boolean(utils.findProperty(node, 'emits'))
+ }
+ }),
+ {
+ 'Program:exit'() {
+ if (defineEmitsNodes.length === 0) {
+ return
+ }
+ if (defineEmitsNodes.length > 1) {
+ // `defineEmits` has been called multiple times.
+ for (const node of defineEmitsNodes) {
+ context.report({
+ node,
+ messageId: 'multiple'
+ })
+ }
+ return
+ }
+ if (emptyDefineEmits) {
+ if (!hasDefaultExport) {
+ // Custom events are not defined.
+ context.report({
+ node: emptyDefineEmits,
+ messageId: 'notDefined'
+ })
+ }
+ } else {
+ if (hasDefaultExport) {
+ // Custom events are defined in both `defineEmits` and `export default {}`.
+ for (const node of defineEmitsNodes) {
+ context.report({
+ node,
+ messageId: 'definedInBoth'
+ })
+ }
+ }
+ }
+ }
+ }
+ )
+ }
+}
diff --git a/lib/rules/valid-define-options.js b/lib/rules/valid-define-options.js
new file mode 100644
index 000000000..12771b8fe
--- /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',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-define-options.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ referencingLocally:
+ '`defineOptions` is 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'
+ })
+ }
+
+ const typeArguments =
+ 'typeArguments' in node ? node.typeArguments : node.typeParameters
+ if (typeArguments) {
+ context.report({
+ node: typeArguments,
+ messageId: 'typeArgs'
+ })
+ }
+ },
+ Identifier(node) {
+ for (const defineOptions of optionsDefExpressions) {
+ if (utils.inRange(defineOptions.range, node)) {
+ const variable = findVariable(utils.getScope(context, node), 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` is 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/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js
new file mode 100644
index 000000000..2ba81b188
--- /dev/null
+++ b/lib/rules/valid-define-props.js
@@ -0,0 +1,145 @@
+/**
+ * @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 `defineProps` compiler macro',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-define-props.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ hasTypeAndArg:
+ '`defineProps` has both a type-only props and an argument.',
+ referencingLocally:
+ '`defineProps` is referencing locally declared variables.',
+ multiple: '`defineProps` has been called multiple times.',
+ notDefined: 'Props are not defined.',
+ definedInBoth:
+ 'Props are defined in both `defineProps` and `export default {}`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const scriptSetup = utils.getScriptSetupElement(context)
+ if (!scriptSetup) {
+ return {}
+ }
+
+ /** @type {Set} */
+ const propsDefExpressions = new Set()
+ let hasDefaultExport = false
+ /** @type {CallExpression[]} */
+ const definePropsNodes = []
+ /** @type {CallExpression | null} */
+ let emptyDefineProps = null
+
+ return utils.compositingVisitors(
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(node) {
+ definePropsNodes.push(node)
+
+ const typeArguments =
+ 'typeArguments' in node ? node.typeArguments : node.typeParameters
+ if (node.arguments.length > 0) {
+ if (typeArguments && typeArguments.params.length > 0) {
+ // `defineProps` has both a literal type and an argument.
+ context.report({
+ node,
+ messageId: 'hasTypeAndArg'
+ })
+ return
+ }
+
+ propsDefExpressions.add(node.arguments[0])
+ } else {
+ if (!typeArguments || typeArguments.params.length === 0) {
+ emptyDefineProps = node
+ }
+ }
+ },
+ Identifier(node) {
+ for (const defineProps of propsDefExpressions) {
+ if (utils.inRange(defineProps.range, node)) {
+ const variable = findVariable(utils.getScope(context, node), 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(defineProps.range, def.name)
+ )
+ ) {
+ if (utils.withinTypeNode(node)) {
+ continue
+ }
+ //`defineProps` is referencing locally declared variables.
+ context.report({
+ node,
+ messageId: 'referencingLocally'
+ })
+ }
+ }
+ }
+ }
+ }),
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node, { type }) {
+ if (type !== 'export' || utils.inRange(scriptSetup.range, node)) {
+ return
+ }
+
+ hasDefaultExport = Boolean(utils.findProperty(node, 'props'))
+ }
+ }),
+ {
+ 'Program:exit'() {
+ if (definePropsNodes.length === 0) {
+ return
+ }
+ if (definePropsNodes.length > 1) {
+ // `defineProps` has been called multiple times.
+ for (const node of definePropsNodes) {
+ context.report({
+ node,
+ messageId: 'multiple'
+ })
+ }
+ return
+ }
+ if (emptyDefineProps) {
+ if (!hasDefaultExport) {
+ // Props are not defined.
+ context.report({
+ node: emptyDefineProps,
+ messageId: 'notDefined'
+ })
+ }
+ } else {
+ if (hasDefaultExport) {
+ // Props are defined in both `defineProps` and `export default {}`.
+ for (const node of definePropsNodes) {
+ context.report({
+ node,
+ messageId: 'definedInBoth'
+ })
+ }
+ }
+ }
+ }
+ }
+ )
+ }
+}
diff --git a/lib/rules/valid-model-definition.js b/lib/rules/valid-model-definition.js
new file mode 100644
index 000000000..6d82fca32
--- /dev/null
+++ b/lib/rules/valid-model-definition.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Requires valid keys in model option.
+ * @author Alex Sokolov
+ */
+'use strict'
+
+const utils = require('../utils')
+
+const VALID_MODEL_KEYS = new Set(['prop', 'event'])
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'require valid keys in model option',
+ categories: ['vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-model-definition.html'
+ },
+ fixable: null,
+ deprecated: true,
+ schema: [],
+ messages: {
+ invalidKey: "Invalid key '{{name}}' in model option."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.executeOnVue(context, (obj) => {
+ const modelProperty = utils.findProperty(obj, 'model')
+ if (!modelProperty || modelProperty.value.type !== 'ObjectExpression') {
+ return
+ }
+
+ for (const p of modelProperty.value.properties) {
+ if (p.type !== 'Property') {
+ continue
+ }
+ const name = utils.getStaticPropertyName(p)
+ if (!name) {
+ continue
+ }
+ if (!VALID_MODEL_KEYS.has(name)) {
+ context.report({
+ node: p,
+ messageId: 'invalidKey',
+ data: {
+ name
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/valid-next-tick.js b/lib/rules/valid-next-tick.js
index 4e41abbf2..c384f6ac0 100644
--- a/lib/rules/valid-next-tick.js
+++ b/lib/rules/valid-next-tick.js
@@ -6,16 +6,8 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-const { findVariable } = require('eslint-utils')
-
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
+const { findVariable } = require('@eslint-community/eslint-utils')
/**
* @param {Identifier} identifier
@@ -43,7 +35,7 @@ function getVueNextTickNode(identifier, context) {
}
// Vue 3 Global API: import { nextTick as nt } from 'vue'; nt()
- const variable = findVariable(context.getScope(), identifier)
+ const variable = findVariable(utils.getScope(context, identifier), identifier)
if (variable != null && variable.defs.length === 1) {
const def = variable.defs[0]
@@ -76,6 +68,13 @@ function isAwaitedPromise(callExpression) {
// cases like `return nextTick()`
return true
}
+ if (
+ callExpression.parent.type === 'ArrowFunctionExpression' &&
+ callExpression.parent.body === callExpression
+ ) {
+ // cases like `() => nextTick()`
+ return true
+ }
if (
callExpression.parent.type === 'MemberExpression' &&
@@ -109,21 +108,26 @@ function isAwaitedPromise(callExpression) {
return false
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `nextTick` function calls',
- // categories: ['vue3-essential', 'essential'],
- categories: undefined,
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-next-tick.html'
},
fixable: 'code',
- schema: []
+ hasSuggestions: true,
+ schema: [],
+ messages: {
+ shouldBeFunction: '`nextTick` is a function.',
+ missingCallbackOrAwait:
+ 'Await the Promise returned by `nextTick` or pass a callback function.',
+ addAwait: 'Add missing `await` statement.',
+ tooManyParameters: '`nextTick` expects zero or one parameters.',
+ eitherAwaitOrCallback:
+ 'Either await the Promise or pass a callback function to `nextTick`.'
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -135,7 +139,12 @@ module.exports = {
return
}
- const parentNode = nextTickNode.parent
+ let parentNode = nextTickNode.parent
+
+ // skip conditional expressions like `foo ? nextTick : bar`
+ if (parentNode.type === 'ConditionalExpression') {
+ parentNode = parentNode.parent
+ }
if (
parentNode.type === 'CallExpression' &&
@@ -156,7 +165,7 @@ module.exports = {
if (parentNode.type !== 'CallExpression') {
context.report({
node,
- message: '`nextTick` is a function.',
+ messageId: 'shouldBeFunction',
fix(fixer) {
return fixer.insertTextAfter(node, '()')
}
@@ -168,11 +177,10 @@ module.exports = {
if (!isAwaitedPromise(parentNode)) {
context.report({
node,
- message:
- 'Await the Promise returned by `nextTick` or pass a callback function.',
+ messageId: 'missingCallbackOrAwait',
suggest: [
{
- desc: 'Add missing `await` statement.',
+ messageId: 'addAwait',
fix(fixer) {
return fixer.insertTextBefore(parentNode, 'await ')
}
@@ -186,7 +194,7 @@ module.exports = {
if (parentNode.arguments.length > 1) {
context.report({
node,
- message: '`nextTick` expects zero or one parameters.'
+ messageId: 'tooManyParameters'
})
return
}
@@ -194,8 +202,7 @@ module.exports = {
if (isAwaitedPromise(parentNode)) {
context.report({
node,
- message:
- 'Either await the Promise or pass a callback function to `nextTick`.'
+ messageId: 'eitherAwaitOrCallback'
})
}
}
diff --git a/lib/rules/valid-template-root.js b/lib/rules/valid-template-root.js
index 703ab8b8e..c604a43a8 100644
--- a/lib/rules/valid-template-root.js
+++ b/lib/rules/valid-template-root.js
@@ -5,26 +5,23 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid template root',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-template-root.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ emptySrc:
+ "The template root with 'src' attribute is required to be empty.",
+ noChild: 'The template requires child element.'
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -47,20 +44,19 @@ module.exports = {
}
}
- if (hasSrc && rootElements.length) {
+ if (hasSrc && rootElements.length > 0) {
for (const element of rootElements) {
context.report({
node: element,
loc: element.loc,
- message:
- "The template root with 'src' attribute is required to be empty."
+ messageId: 'emptySrc'
})
}
} else if (rootElements.length === 0 && !hasSrc) {
context.report({
node: element,
loc: element.loc,
- message: 'The template requires child element.'
+ messageId: 'noChild'
})
}
}
diff --git a/lib/rules/valid-v-bind-sync.js b/lib/rules/valid-v-bind-sync.js
index 945ab0a77..ba04d6117 100644
--- a/lib/rules/valid-v-bind-sync.js
+++ b/lib/rules/valid-v-bind-sync.js
@@ -4,16 +4,8 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
/**
* Check whether the given node is valid or not.
* @param {VElement} node The element node to check.
@@ -78,19 +70,16 @@ function maybeNullObjectMemberExpression(node) {
return false
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `.sync` modifier on `v-bind` directives',
- categories: ['essential'],
+ categories: ['vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-bind-sync.html'
},
fixable: null,
+ deprecated: true,
schema: [],
messages: {
unexpectedInvalidElement:
@@ -119,7 +108,6 @@ module.exports = {
if (!isValidElement(element)) {
context.report({
node,
- loc: node.loc,
messageId: 'unexpectedInvalidElement',
data: { name }
})
@@ -136,20 +124,17 @@ module.exports = {
}
if (isOptionalChainingMemberExpression(expression)) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedOptionalChaining'
})
} else if (!isLhs(expression)) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedNonLhsExpression'
})
} else if (maybeNullObjectMemberExpression(expression)) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedNullObject'
})
}
@@ -162,8 +147,7 @@ module.exports = {
const variable = reference.variable
if (variable) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedUpdateIterationVariable',
data: { varName: id.name }
})
diff --git a/lib/rules/valid-v-bind.js b/lib/rules/valid-v-bind.js
index 385272f71..0f44dad26 100644
--- a/lib/rules/valid-v-bind.js
+++ b/lib/rules/valid-v-bind.js
@@ -5,32 +5,25 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-const VALID_MODIFIERS = new Set(['prop', 'camel', 'sync'])
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
+const VALID_MODIFIERS = new Set(['prop', 'camel', 'sync', 'attr'])
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-bind` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-bind.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ unsupportedModifier:
+ "'v-bind' directives don't support the modifier '{{name}}'.",
+ expectedValue: "'v-bind' directives require an attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -40,10 +33,8 @@ module.exports = {
for (const modifier of node.key.modifiers) {
if (!VALID_MODIFIERS.has(modifier.name)) {
context.report({
- node,
- loc: node.key.loc,
- message:
- "'v-bind' directives don't support the modifier '{{name}}'.",
+ node: modifier,
+ messageId: 'unsupportedModifier',
data: { name: modifier.name }
})
}
@@ -52,8 +43,7 @@ module.exports = {
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-bind' directives require an attribute value."
+ messageId: 'expectedValue'
})
}
}
diff --git a/lib/rules/valid-v-cloak.js b/lib/rules/valid-v-cloak.js
index 4fd51d781..5ff956818 100644
--- a/lib/rules/valid-v-cloak.js
+++ b/lib/rules/valid-v-cloak.js
@@ -5,26 +5,23 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-cloak` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-cloak.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-cloak' directives require no argument.",
+ unexpectedModifier: "'v-cloak' directives require no modifier.",
+ unexpectedValue: "'v-cloak' directives require no attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -33,23 +30,24 @@ module.exports = {
"VAttribute[directive=true][key.name.name='cloak']"(node) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-cloak' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-cloak' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (node.value) {
context.report({
- node,
- loc: node.loc,
- message: "'v-cloak' directives require no attribute value."
+ node: node.value,
+ messageId: 'unexpectedValue'
})
}
}
diff --git a/lib/rules/valid-v-else-if.js b/lib/rules/valid-v-else-if.js
index aafe97b1c..b94437466 100644
--- a/lib/rules/valid-v-else-if.js
+++ b/lib/rules/valid-v-else-if.js
@@ -5,26 +5,29 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-else-if` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-else-if.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ missingVIf:
+ "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive.",
+ withVIf:
+ "'v-else-if' and 'v-if' directives can't exist on the same element.",
+ withVElse:
+ "'v-else-if' and 'v-else' directives can't exist on the same element.",
+ unexpectedArgument: "'v-else-if' directives require no argument.",
+ unexpectedModifier: "'v-else-if' directives require no modifier.",
+ expectedValue: "'v-else-if' directives require that attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -36,46 +39,41 @@ module.exports = {
if (!utils.prevElementHasIf(element)) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."
+ messageId: 'missingVIf'
})
}
if (utils.hasDirective(element, 'if')) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-else-if' and 'v-if' directives can't exist on the same element."
+ messageId: 'withVIf'
})
}
if (utils.hasDirective(element, 'else')) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-else-if' and 'v-else' directives can't exist on the same element."
+ messageId: 'withVElse'
})
}
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-else-if' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-else-if' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-else-if' directives require that attribute value."
+ messageId: 'expectedValue'
})
}
}
diff --git a/lib/rules/valid-v-else.js b/lib/rules/valid-v-else.js
index 7a8542787..f35ac661d 100644
--- a/lib/rules/valid-v-else.js
+++ b/lib/rules/valid-v-else.js
@@ -5,26 +5,29 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-else` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-else.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ missingVIf:
+ "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive.",
+ withVIf:
+ "'v-else' and 'v-if' directives can't exist on the same element. You may want 'v-else-if' directives.",
+ withVElseIf:
+ "'v-else' and 'v-else-if' directives can't exist on the same element.",
+ unexpectedArgument: "'v-else' directives require no argument.",
+ unexpectedModifier: "'v-else' directives require no modifier.",
+ unexpectedValue: "'v-else' directives require no attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -36,46 +39,41 @@ module.exports = {
if (!utils.prevElementHasIf(element)) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."
+ messageId: 'missingVIf'
})
}
if (utils.hasDirective(element, 'if')) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-else' and 'v-if' directives can't exist on the same element. You may want 'v-else-if' directives."
+ messageId: 'withVIf'
})
}
if (utils.hasDirective(element, 'else-if')) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-else' and 'v-else-if' directives can't exist on the same element."
+ messageId: 'withVElseIf'
})
}
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-else' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-else' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (node.value) {
context.report({
- node,
- loc: node.loc,
- message: "'v-else' directives require no attribute value."
+ node: node.value,
+ messageId: 'unexpectedValue'
})
}
}
diff --git a/lib/rules/valid-v-for.js b/lib/rules/valid-v-for.js
index 0d560aa91..05183dd7e 100644
--- a/lib/rules/valid-v-for.js
+++ b/lib/rules/valid-v-for.js
@@ -5,16 +5,8 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
/**
* Check whether the given attribute is using the variables which are defined by `v-for` directives.
* @param {VDirective} vFor The attribute node of `v-for` to check.
@@ -84,34 +76,40 @@ function checkKey(context, vFor, element) {
if (utils.isCustomComponent(element) && vBindKey == null) {
context.report({
node: element.startTag,
- loc: element.startTag.loc,
- message: "Custom elements in iteration require 'v-bind:key' directives."
+ messageId: 'requireKey'
})
}
if (vBindKey != null && !isUsingIterationVar(vFor, vBindKey)) {
context.report({
node: vBindKey,
- loc: vBindKey.loc,
- message:
- "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."
+ messageId: 'keyUseFVorVars'
})
}
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-for` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-for.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ requireKey:
+ "Custom elements in iteration require 'v-bind:key' directives.",
+ keyUseFVorVars:
+ "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive.",
+ unexpectedArgument: "'v-for' directives require no argument.",
+ unexpectedModifier: "'v-for' directives require no modifier.",
+ expectedValue: "'v-for' directives require that attribute value.",
+ unexpectedExpression:
+ "'v-for' directives require the special syntax ' in '.",
+ invalidEmptyAlias: "Invalid alias ''.",
+ invalidAlias: "Invalid alias '{{text}}'."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -126,23 +124,24 @@ module.exports = {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-for' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-for' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-for' directives require that attribute value."
+ messageId: 'expectedValue'
})
return
}
@@ -154,9 +153,7 @@ module.exports = {
if (expr.type !== 'VForExpression') {
context.report({
node: node.value,
- loc: node.value.loc,
- message:
- "'v-for' directives require the special syntax ' in '."
+ messageId: 'unexpectedExpression'
})
return
}
@@ -169,22 +166,20 @@ module.exports = {
if (value === null) {
context.report({
node: expr,
- message: "Invalid alias ''."
+ messageId: 'invalidEmptyAlias'
})
}
if (key !== undefined && (!key || key.type !== 'Identifier')) {
context.report({
node: key || expr,
- loc: key && key.loc,
- message: "Invalid alias '{{text}}'.",
+ messageId: 'invalidAlias',
data: { text: key ? sourceCode.getText(key) : '' }
})
}
if (index !== undefined && (!index || index.type !== 'Identifier')) {
context.report({
node: index || expr,
- loc: index && index.loc,
- message: "Invalid alias '{{text}}'.",
+ messageId: 'invalidAlias',
data: { text: index ? sourceCode.getText(index) : '' }
})
}
diff --git a/lib/rules/valid-v-html.js b/lib/rules/valid-v-html.js
index d88997682..1df254033 100644
--- a/lib/rules/valid-v-html.js
+++ b/lib/rules/valid-v-html.js
@@ -5,26 +5,23 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-html` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-html.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-html' directives require no argument.",
+ unexpectedModifier: "'v-html' directives require no modifier.",
+ expectedValue: "'v-html' directives require that attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -33,23 +30,24 @@ module.exports = {
"VAttribute[directive=true][key.name.name='html']"(node) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-html' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-html' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-html' directives require that attribute value."
+ messageId: 'expectedValue'
})
}
}
diff --git a/lib/rules/valid-v-if.js b/lib/rules/valid-v-if.js
index b61435ab0..2e8651b44 100644
--- a/lib/rules/valid-v-if.js
+++ b/lib/rules/valid-v-if.js
@@ -5,26 +5,27 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-if` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-if.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ withVElse:
+ "'v-if' and 'v-else' directives can't exist on the same element. You may want 'v-else-if' directives.",
+ withVElseIf:
+ "'v-if' and 'v-else-if' directives can't exist on the same element.",
+ unexpectedArgument: "'v-if' directives require no argument.",
+ unexpectedModifier: "'v-if' directives require no modifier.",
+ expectedValue: "'v-if' directives require that attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -36,38 +37,35 @@ module.exports = {
if (utils.hasDirective(element, 'else')) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-if' and 'v-else' directives can't exist on the same element. You may want 'v-else-if' directives."
+ messageId: 'withVElse'
})
}
if (utils.hasDirective(element, 'else-if')) {
context.report({
node,
- loc: node.loc,
- message:
- "'v-if' and 'v-else-if' directives can't exist on the same element."
+ messageId: 'withVElseIf'
})
}
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-if' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-if' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-if' directives require that attribute value."
+ messageId: 'expectedValue'
})
}
}
diff --git a/lib/rules/valid-v-is.js b/lib/rules/valid-v-is.js
index 50fa1fb5e..2f97d1fd9 100644
--- a/lib/rules/valid-v-is.js
+++ b/lib/rules/valid-v-is.js
@@ -4,16 +4,8 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
/**
* Check whether the given node is valid or not.
* @param {VElement} node The element node to check.
@@ -30,10 +22,6 @@ function isValidElement(node) {
return true
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
@@ -58,22 +46,23 @@ module.exports = {
"VAttribute[directive=true][key.name.name='is']"(node) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
+ node: node.key.argument,
messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
messageId: 'unexpectedModifier'
})
}
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
messageId: 'expectedValue'
})
}
@@ -84,7 +73,6 @@ module.exports = {
const name = element.name
context.report({
node,
- loc: node.loc,
messageId: 'ownerMustBeHTMLElement',
data: { name }
})
diff --git a/lib/rules/valid-v-memo.js b/lib/rules/valid-v-memo.js
new file mode 100644
index 000000000..66ba9a75d
--- /dev/null
+++ b/lib/rules/valid-v-memo.js
@@ -0,0 +1,119 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `v-memo` directives',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-memo.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-memo' directives require no argument.",
+ unexpectedModifier: "'v-memo' directives require no modifier.",
+ expectedValue: "'v-memo' directives require that attribute value.",
+ expectedArray:
+ "'v-memo' directives require the attribute value to be an array.",
+ insideVFor: "'v-memo' directive does not work inside 'v-for'."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {VElement | null} */
+ let vForElement = null
+ return utils.defineTemplateBodyVisitor(context, {
+ VElement(node) {
+ if (!vForElement && utils.hasDirective(node, 'for')) {
+ vForElement = node
+ }
+ },
+ 'VElement:exit'(node) {
+ if (vForElement === node) {
+ vForElement = null
+ }
+ },
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='memo']"(node) {
+ if (vForElement && vForElement !== node.parent.parent) {
+ context.report({
+ node: node.key,
+ messageId: 'insideVFor'
+ })
+ }
+ if (node.key.argument) {
+ context.report({
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
+ })
+ }
+ if (node.key.modifiers.length > 0) {
+ context.report({
+ node,
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
+ })
+ }
+ if (!node.value || utils.isEmptyValueDirective(node, context)) {
+ context.report({
+ node,
+ messageId: 'expectedValue'
+ })
+ return
+ }
+ if (!node.value.expression) {
+ return
+ }
+ const expressions = [node.value.expression]
+ let expression
+ while ((expression = expressions.pop())) {
+ switch (expression.type) {
+ case 'ObjectExpression':
+ case 'ClassExpression':
+ case 'ArrowFunctionExpression':
+ case 'FunctionExpression':
+ case 'Literal':
+ case 'TemplateLiteral':
+ case 'UnaryExpression':
+ case 'BinaryExpression':
+ case 'UpdateExpression': {
+ context.report({
+ node: expression,
+ messageId: 'expectedArray'
+ })
+ break
+ }
+ case 'AssignmentExpression': {
+ expressions.push(expression.right)
+ break
+ }
+ case 'TSAsExpression': {
+ expressions.push(expression.expression)
+ break
+ }
+ case 'SequenceExpression': {
+ expressions.push(
+ expression.expressions[expression.expressions.length - 1]
+ )
+ break
+ }
+ case 'ConditionalExpression': {
+ expressions.push(expression.consequent, expression.alternate)
+ break
+ }
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/valid-v-model.js b/lib/rules/valid-v-model.js
index adfe9c2da..ca2830dc6 100644
--- a/lib/rules/valid-v-model.js
+++ b/lib/rules/valid-v-model.js
@@ -5,16 +5,8 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
const VALID_MODIFIERS = new Set(['lazy', 'number', 'trim'])
/**
@@ -57,6 +49,10 @@ function isOptionalChainingMemberExpression(node) {
* @returns {boolean} `true` if the node can be LHS.
*/
function isLhs(node) {
+ if (node.type === 'TSAsExpression' || node.type === 'TSNonNullExpression') {
+ return isLhs(node.expression)
+ }
+
return node.type === 'Identifier' || node.type === 'MemberExpression'
}
@@ -114,17 +110,13 @@ function getVariable(name, leafNode) {
return null
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
/** @type {RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-model` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-model.html'
},
fixable: null,
@@ -159,7 +151,6 @@ module.exports = {
if (!isValidElement(element)) {
context.report({
node,
- loc: node.loc,
messageId: 'unexpectedInvalidElement',
data: { name }
})
@@ -168,7 +159,6 @@ module.exports = {
if (name === 'input' && utils.hasAttribute(element, 'type', 'file')) {
context.report({
node,
- loc: node.loc,
messageId: 'unexpectedInputFile'
})
}
@@ -176,8 +166,7 @@ module.exports = {
if (!utils.isCustomComponent(element)) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
+ node: node.key.argument,
messageId: 'unexpectedArgument'
})
}
@@ -185,8 +174,7 @@ module.exports = {
for (const modifier of node.key.modifiers) {
if (!VALID_MODIFIERS.has(modifier.name)) {
context.report({
- node,
- loc: node.loc,
+ node: modifier,
messageId: 'unexpectedModifier',
data: { name: modifier.name }
})
@@ -197,7 +185,6 @@ module.exports = {
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
messageId: 'missingValue'
})
return
@@ -209,20 +196,17 @@ module.exports = {
}
if (isOptionalChainingMemberExpression(expression)) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedOptionalChaining'
})
} else if (!isLhs(expression)) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedNonLhsExpression'
})
} else if (maybeNullObjectMemberExpression(expression)) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedNullObject'
})
}
@@ -236,8 +220,7 @@ module.exports = {
const variable = getVariable(id.name, element)
if (variable != null) {
context.report({
- node,
- loc: node.loc,
+ node: expression,
messageId: 'unexpectedUpdateIterationVariable',
data: { varName: id.name }
diff --git a/lib/rules/valid-v-on.js b/lib/rules/valid-v-on.js
index 4d55d7f19..82612d496 100644
--- a/lib/rules/valid-v-on.js
+++ b/lib/rules/valid-v-on.js
@@ -5,17 +5,9 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
const keyAliases = require('../utils/key-aliases.json')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
const VALID_MODIFIERS = new Set([
'stop',
'prevent',
@@ -56,9 +48,9 @@ function isValidModifier(modifierNode, customModifiers) {
// built-in aliases
VALID_MODIFIERS.has(modifier) ||
// keyCode
- Number.isInteger(parseInt(modifier, 10)) ||
+ Number.isInteger(Number.parseInt(modifier, 10)) ||
// keyAlias (an Unicode character)
- Array.from(modifier).length === 1 ||
+ [...modifier].length === 1 ||
// keyAlias (special keys)
KEY_ALIASES.has(modifier) ||
// custom modifiers
@@ -66,16 +58,12 @@ function isValidModifier(modifierNode, customModifiers) {
)
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-on` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-on.html'
},
fixable: null,
@@ -89,7 +77,15 @@ module.exports = {
},
additionalProperties: false
}
- ]
+ ],
+ messages: {
+ unsupportedModifier:
+ "'v-on' directives don't support the modifier '{{modifier}}'.",
+ avoidKeyword:
+ 'Avoid using JavaScript keyword as "v-on" value: {{value}}.',
+ expectedValueOrVerb:
+ "'v-on' directives require a value or verb modifier (like 'stop' or 'prevent')."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -104,10 +100,8 @@ module.exports = {
for (const modifier of node.key.modifiers) {
if (!isValidModifier(modifier, customModifiers)) {
context.report({
- node,
- loc: node.loc,
- message:
- "'v-on' directives don't support the modifier '{{modifier}}'.",
+ node: modifier,
+ messageId: 'unsupportedModifier',
data: { modifier: modifier.name }
})
}
@@ -131,19 +125,15 @@ module.exports = {
}
if (/^\w+$/.test(innerText)) {
context.report({
- node,
- loc: node.loc,
- message:
- 'Avoid using JavaScript keyword as "v-on" value: {{value}}.',
+ node: node.value,
+ messageId: 'avoidKeyword',
data: { value: valueText }
})
}
} else {
context.report({
node,
- loc: node.loc,
- message:
- "'v-on' directives require a value or verb modifier (like 'stop' or 'prevent')."
+ messageId: 'expectedValueOrVerb'
})
}
}
diff --git a/lib/rules/valid-v-once.js b/lib/rules/valid-v-once.js
index 83ff3ddcf..9d3d8bf8f 100644
--- a/lib/rules/valid-v-once.js
+++ b/lib/rules/valid-v-once.js
@@ -5,26 +5,23 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-once` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-once.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-once' directives require no argument.",
+ unexpectedModifier: "'v-once' directives require no modifier.",
+ unexpectedValue: "'v-once' directives require no attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -33,23 +30,24 @@ module.exports = {
"VAttribute[directive=true][key.name.name='once']"(node) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-once' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-once' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (node.value) {
context.report({
- node,
- loc: node.loc,
- message: "'v-once' directives require no attribute value."
+ node: node.value,
+ messageId: 'unexpectedValue'
})
}
}
diff --git a/lib/rules/valid-v-pre.js b/lib/rules/valid-v-pre.js
index 90174db12..0505b9cb5 100644
--- a/lib/rules/valid-v-pre.js
+++ b/lib/rules/valid-v-pre.js
@@ -5,26 +5,23 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-pre` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-pre.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-pre' directives require no argument.",
+ unexpectedModifier: "'v-pre' directives require no modifier.",
+ unexpectedValue: "'v-pre' directives require no attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -33,23 +30,24 @@ module.exports = {
"VAttribute[directive=true][key.name.name='pre']"(node) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-pre' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-pre' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (node.value) {
context.report({
- node,
- loc: node.loc,
- message: "'v-pre' directives require no attribute value."
+ node: node.value,
+ messageId: 'unexpectedValue'
})
}
}
diff --git a/lib/rules/valid-v-show.js b/lib/rules/valid-v-show.js
index 721904f4f..1da617806 100644
--- a/lib/rules/valid-v-show.js
+++ b/lib/rules/valid-v-show.js
@@ -5,26 +5,25 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-show` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-show.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-show' directives require no argument.",
+ unexpectedModifier: "'v-show' directives require no modifier.",
+ expectedValue: "'v-show' directives require that attribute value.",
+ unexpectedTemplate:
+ "'v-show' directives cannot be put on tags."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -33,30 +32,30 @@ module.exports = {
"VAttribute[directive=true][key.name.name='show']"(node) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-show' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-show' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-show' directives require that attribute value."
+ messageId: 'expectedValue'
})
}
if (node.parent.parent.name === 'template') {
context.report({
node,
- loc: node.loc,
- message: "'v-show' directives cannot be put on tags."
+ messageId: 'unexpectedTemplate'
})
}
}
diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js
index 4914edef9..d5c0efd1e 100644
--- a/lib/rules/valid-v-slot.js
+++ b/lib/rules/valid-v-slot.js
@@ -28,54 +28,22 @@ function getSlotDirectivesOnElement(node) {
* by `v-if`/`v-else-if`/`v-else`.
*/
function getSlotDirectivesOnChildren(node) {
- return node.children
- .reduce(
- ({ groups, vIf }, childNode) => {
- if (childNode.type === 'VElement') {
- let connected
- if (utils.hasDirective(childNode, 'if')) {
- connected = false
- vIf = true
- } else if (utils.hasDirective(childNode, 'else-if')) {
- connected = vIf
- vIf = true
- } else if (utils.hasDirective(childNode, 'else')) {
- connected = vIf
- vIf = false
- } else {
- connected = false
- vIf = false
- }
+ /** @type {VDirective[][]} */
+ const groups = []
+ for (const group of utils.iterateChildElementsChains(node)) {
+ const slotDirs = group
+ .map((childElement) =>
+ childElement.name === 'template'
+ ? utils.getDirective(childElement, 'slot')
+ : null
+ )
+ .filter(utils.isDef)
+ if (slotDirs.length > 0) {
+ groups.push(slotDirs)
+ }
+ }
- if (connected) {
- groups[groups.length - 1].push(childNode)
- } else {
- groups.push([childNode])
- }
- } else if (
- childNode.type !== 'VText' ||
- childNode.value.trim() !== ''
- ) {
- vIf = false
- }
- return { groups, vIf }
- },
- {
- /** @type {VElement[][]} */
- groups: [],
- vIf: false
- }
- )
- .groups.map((group) =>
- group
- .map((childElement) =>
- childElement.name === 'template'
- ? utils.getDirective(childElement, 'slot')
- : null
- )
- .filter(utils.isDef)
- )
- .filter((group) => group.length >= 1)
+ return groups
}
/**
@@ -132,7 +100,7 @@ function filterSameSlot(
return true
})
)
- .filter((slots) => slots.length >= 1)
+ .filter((slots) => slots.length > 0)
}
/**
@@ -174,14 +142,11 @@ function equalVSlotVForVariables(a, b, tokenStore) {
return false
}
}
- for (const v of a.variables) {
- if (!checkedVarNames.has(v.id.name)) {
- if (b.variables.every((bv) => v.id.name !== bv.id.name)) {
- return false
- }
- }
- }
- return true
+ return a.variables.every(
+ (v) =>
+ checkedVarNames.has(v.id.name) ||
+ b.variables.some((bv) => v.id.name === bv.id.name)
+ )
/**
* Determines whether the two given nodes are considered to be equal.
@@ -208,7 +173,7 @@ function getVSlotVForVariableIfUsingIterationVars(vSlot, vFor) {
vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
const variables =
expr && getUsingIterationVars(vSlot.key.argument, vSlot.parent.parent)
- return expr && variables && variables.length ? { expr, variables } : null
+ return expr && variables && variables.length > 0 ? { expr, variables } : null
}
/**
@@ -267,8 +232,8 @@ function isUsingScopeVar(vSlot) {
*/
function hasInvalidModifiers(vSlot, allowModifiers) {
return allowModifiers
- ? vSlot.key.argument == null && vSlot.key.modifiers.length >= 1
- : vSlot.key.modifiers.length >= 1
+ ? vSlot.key.argument == null && vSlot.key.modifiers.length > 0
+ : vSlot.key.modifiers.length > 0
}
module.exports = {
@@ -276,7 +241,7 @@ module.exports = {
type: 'problem',
docs: {
description: 'enforce valid `v-slot` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-slot.html'
},
fixable: null,
@@ -313,8 +278,8 @@ module.exports = {
create(context) {
const sourceCode = context.getSourceCode()
const tokenStore =
- context.parserServices.getTemplateBodyTokenStore &&
- context.parserServices.getTemplateBodyTokenStore()
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
const options = context.options[0] || {}
const allowModifiers = options.allowModifiers === true
@@ -349,7 +314,7 @@ module.exports = {
messageId: 'namedSlotMustBeOnTemplate'
})
}
- if (ownerElement === element && vSlotGroupsOnChildren.length >= 1) {
+ if (ownerElement === element && vSlotGroupsOnChildren.length > 0) {
context.report({
node,
messageId: 'defaultSlotMustBeOnTemplate'
@@ -410,6 +375,10 @@ module.exports = {
// E.g.,
context.report({
node,
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
messageId: 'disallowAnyModifier'
})
}
diff --git a/lib/rules/valid-v-text.js b/lib/rules/valid-v-text.js
index 1e90ec688..b41e7f2b3 100644
--- a/lib/rules/valid-v-text.js
+++ b/lib/rules/valid-v-text.js
@@ -5,26 +5,23 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-text` directives',
- categories: ['vue3-essential', 'essential'],
+ categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-text.html'
},
fixable: null,
- schema: []
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-text' directives require no argument.",
+ unexpectedModifier: "'v-text' directives require no modifier.",
+ expectedValue: "'v-text' directives require that attribute value."
+ }
},
/** @param {RuleContext} context */
create(context) {
@@ -33,23 +30,24 @@ module.exports = {
"VAttribute[directive=true][key.name.name='text']"(node) {
if (node.key.argument) {
context.report({
- node,
- loc: node.loc,
- message: "'v-text' directives require no argument."
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
})
}
if (node.key.modifiers.length > 0) {
context.report({
node,
- loc: node.loc,
- message: "'v-text' directives require no modifier."
+ loc: {
+ start: node.key.modifiers[0].loc.start,
+ end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
+ },
+ messageId: 'unexpectedModifier'
})
}
if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-text' directives require that attribute value."
+ messageId: 'expectedValue'
})
}
}
diff --git a/lib/utils/casing.js b/lib/utils/casing.js
index b2f2e89b5..a47e978cd 100644
--- a/lib/utils/casing.js
+++ b/lib/utils/casing.js
@@ -1,7 +1,3 @@
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
/**
* Capitalize a string.
* @param {string} str
@@ -41,15 +37,12 @@ function kebabCase(str) {
* @param {string} str
*/
function isKebabCase(str) {
- if (
- hasUpper(str) ||
- hasSymbols(str) ||
- /^-/u.exec(str) || // starts with hyphen is not kebab-case
- /_|--|\s/u.exec(str)
- ) {
- return false
- }
- return true
+ return (
+ !hasUpper(str) &&
+ !hasSymbols(str) &&
+ !str.startsWith('-') && // starts with hyphen is not kebab-case
+ !/_|--|\s/u.test(str)
+ )
}
/**
@@ -69,10 +62,7 @@ function snakeCase(str) {
* @param {string} str
*/
function isSnakeCase(str) {
- if (hasUpper(str) || hasSymbols(str) || /-|__|\s/u.exec(str)) {
- return false
- }
- return true
+ return !hasUpper(str) && !hasSymbols(str) && !/-|__|\s/u.test(str)
}
/**
@@ -92,14 +82,7 @@ function camelCase(str) {
* @param {string} str
*/
function isCamelCase(str) {
- if (
- hasSymbols(str) ||
- /^[A-Z]/u.exec(str) ||
- /-|_|\s/u.exec(str) // kebab or snake or space
- ) {
- return false
- }
- return true
+ return !hasSymbols(str) && !/^[A-Z]/u.test(str) && !/-|_|\s/u.test(str)
}
/**
@@ -116,14 +99,7 @@ function pascalCase(str) {
* @param {string} str
*/
function isPascalCase(str) {
- if (
- hasSymbols(str) ||
- /^[a-z]/u.exec(str) ||
- /-|_|\s/u.exec(str) // kebab or snake or space
- ) {
- return false
- }
- return true
+ return !hasSymbols(str) && !/^[a-z]/u.test(str) && !/-|_|\s/u.test(str)
}
const convertersMap = {
diff --git a/lib/utils/comments.js b/lib/utils/comments.js
new file mode 100644
index 000000000..d285e7cac
--- /dev/null
+++ b/lib/utils/comments.js
@@ -0,0 +1,21 @@
+/**
+ * @param {Comment} node
+ * @returns {boolean}
+ */
+const isJSDocComment = (node) =>
+ node.type === 'Block' &&
+ node.value.charAt(0) === '*' &&
+ node.value.charAt(1) !== '*'
+
+/**
+ * @param {Comment} node
+ * @returns {boolean}
+ */
+const isBlockComment = (node) =>
+ node.type === 'Block' &&
+ (node.value.charAt(0) !== '*' || node.value.charAt(1) === '*')
+
+module.exports = {
+ isJSDocComment,
+ isBlockComment
+}
diff --git a/lib/utils/deprecated-html-elements.json b/lib/utils/deprecated-html-elements.json
index daf23f512..63a3f7162 100644
--- a/lib/utils/deprecated-html-elements.json
+++ b/lib/utils/deprecated-html-elements.json
@@ -1 +1,31 @@
-["acronym","applet","basefont","bgsound","big","blink","center","command","content","dir","element","font","frame","frameset","image","isindex","keygen","listing","marquee","menuitem","multicol","nextid","nobr","noembed","noframes","plaintext","shadow","spacer","strike","tt","xmp"]
\ No newline at end of file
+[
+ "acronym",
+ "applet",
+ "basefont",
+ "bgsound",
+ "big",
+ "blink",
+ "center",
+ "dir",
+ "font",
+ "frame",
+ "frameset",
+ "isindex",
+ "keygen",
+ "listing",
+ "marquee",
+ "menuitem",
+ "multicol",
+ "nextid",
+ "nobr",
+ "noembed",
+ "noframes",
+ "param",
+ "plaintext",
+ "rb",
+ "rtc",
+ "spacer",
+ "strike",
+ "tt",
+ "xmp"
+]
diff --git a/lib/utils/html-comments.js b/lib/utils/html-comments.js
index aced46e71..53f7df5ae 100644
--- a/lib/utils/html-comments.js
+++ b/lib/utils/html-comments.js
@@ -10,19 +10,11 @@
* @typedef { Token & { type: 'HTMLCommentCloseDecoration' } } HTMLCommentCloseDecoration
* @typedef { { open: HTMLCommentOpen, openDecoration: HTMLCommentOpenDecoration | null, value: HTMLCommentValue | null, closeDecoration: HTMLCommentCloseDecoration | null, close: HTMLCommentClose } } ParsedHTMLComment
*/
-// -----------------------------------------------------------------------------
-// Requirements
-// -----------------------------------------------------------------------------
-
const utils = require('./')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
const COMMENT_DIRECTIVE = /^\s*eslint-(?:en|dis)able/
const IE_CONDITIONAL_IF = /^\[if\s+/
-const IE_CONDITIONAL_ENDIF = /\[endif\]$/
+const IE_CONDITIONAL_ENDIF = /\[endif]$/
/** @type { 'HTMLCommentOpen' } */
const TYPE_HTML_COMMENT_OPEN = 'HTMLCommentOpen'
@@ -110,8 +102,8 @@ function defineParser(sourceCode, config) {
/**
* Parse HTMLComment.
- * @param {ASTToken} node a comment token
- * @returns {HTMLComment | null} the result of HTMLComment tokens.
+ * @param {Token} node a comment token
+ * @returns {ParsedHTMLComment | null} the result of HTMLComment tokens.
*/
return function parseHTMLComment(node) {
if (node.type !== 'HTMLComment') {
diff --git a/lib/utils/html-elements.json b/lib/utils/html-elements.json
index 721f7876d..ff9cdf313 100644
--- a/lib/utils/html-elements.json
+++ b/lib/utils/html-elements.json
@@ -1 +1,116 @@
-["html","body","base","head","link","meta","style","title","address","article","aside","footer","header","h1","h2","h3","h4","h5","h6","hgroup","nav","section","div","dd","dl","dt","figcaption","figure","hr","img","li","main","ol","p","pre","ul","a","b","abbr","bdi","bdo","br","cite","code","data","dfn","em","i","kbd","mark","q","rp","rt","rtc","ruby","s","samp","small","span","strong","sub","sup","time","u","var","wbr","area","audio","map","track","video","embed","object","param","source","canvas","script","noscript","del","ins","caption","col","colgroup","table","thead","tbody","tfoot","td","th","tr","button","datalist","fieldset","form","input","label","legend","meter","optgroup","option","output","progress","select","textarea","details","dialog","menu","menuitem","summary","content","element","shadow","template","slot","blockquote","iframe","noframes","picture"]
+[
+ "a",
+ "abbr",
+ "address",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "b",
+ "base",
+ "bdi",
+ "bdo",
+ "blockquote",
+ "body",
+ "br",
+ "button",
+ "canvas",
+ "caption",
+ "cite",
+ "code",
+ "col",
+ "colgroup",
+ "data",
+ "datalist",
+ "dd",
+ "del",
+ "details",
+ "dfn",
+ "dialog",
+ "div",
+ "dl",
+ "dt",
+ "em",
+ "embed",
+ "fencedframe",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "footer",
+ "form",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "header",
+ "hgroup",
+ "hr",
+ "html",
+ "i",
+ "iframe",
+ "img",
+ "input",
+ "ins",
+ "kbd",
+ "label",
+ "legend",
+ "li",
+ "link",
+ "main",
+ "map",
+ "mark",
+ "menu",
+ "meta",
+ "meter",
+ "nav",
+ "noscript",
+ "object",
+ "ol",
+ "optgroup",
+ "option",
+ "output",
+ "p",
+ "picture",
+ "pre",
+ "progress",
+ "q",
+ "rp",
+ "rt",
+ "ruby",
+ "s",
+ "samp",
+ "script",
+ "search",
+ "section",
+ "select",
+ "selectedcontent",
+ "slot",
+ "small",
+ "source",
+ "span",
+ "strong",
+ "style",
+ "sub",
+ "summary",
+ "sup",
+ "table",
+ "tbody",
+ "td",
+ "template",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "time",
+ "title",
+ "tr",
+ "track",
+ "u",
+ "ul",
+ "var",
+ "video",
+ "wbr"
+]
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index 9e6bfdeff..d2879b120 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -4,112 +4,45 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/** @type {Set} */
-const KNOWN_NODES = new Set([
- 'ArrayExpression',
- 'ArrayPattern',
- 'ArrowFunctionExpression',
- 'AssignmentExpression',
- 'AssignmentPattern',
- 'AwaitExpression',
- 'BinaryExpression',
- 'BlockStatement',
- 'BreakStatement',
- 'CallExpression',
- 'CatchClause',
- 'ChainExpression',
- 'ClassBody',
- 'ClassDeclaration',
- 'ClassExpression',
- 'ConditionalExpression',
- 'ContinueStatement',
- 'DebuggerStatement',
- 'DoWhileStatement',
- 'EmptyStatement',
- 'ExportAllDeclaration',
- 'ExportDefaultDeclaration',
- 'ExportNamedDeclaration',
- 'ExportSpecifier',
- 'ExpressionStatement',
- 'ForInStatement',
- 'ForOfStatement',
- 'ForStatement',
- 'FunctionDeclaration',
- 'FunctionExpression',
- 'Identifier',
- 'IfStatement',
- 'ImportDeclaration',
- 'ImportDefaultSpecifier',
- 'ImportExpression',
- 'ImportNamespaceSpecifier',
- 'ImportSpecifier',
- 'LabeledStatement',
- 'Literal',
- 'LogicalExpression',
- 'MemberExpression',
- 'MetaProperty',
- 'MethodDefinition',
- 'NewExpression',
- 'ObjectExpression',
- 'ObjectPattern',
- 'Program',
- 'Property',
- 'RestElement',
- 'ReturnStatement',
- 'SequenceExpression',
- 'SpreadElement',
- 'Super',
- 'SwitchCase',
- 'SwitchStatement',
- 'TaggedTemplateExpression',
- 'TemplateElement',
- 'TemplateLiteral',
- 'ThisExpression',
- 'ThrowStatement',
- 'TryStatement',
- 'UnaryExpression',
- 'UpdateExpression',
- 'VariableDeclaration',
- 'VariableDeclarator',
- 'WhileStatement',
- 'WithStatement',
- 'YieldExpression',
- 'VAttribute',
- 'VDirectiveKey',
- 'VDocumentFragment',
- 'VElement',
- 'VEndTag',
- 'VExpressionContainer',
- 'VFilter',
- 'VFilterSequenceExpression',
- 'VForExpression',
- 'VIdentifier',
- 'VLiteral',
- 'VOnExpression',
- 'VSlotScopeExpression',
- 'VStartTag',
- 'VText'
-])
-const NON_STANDARD_KNOWN_NODES = new Set([
- 'ExperimentalRestProperty',
- 'ExperimentalSpreadProperty'
-])
-const LT_CHAR = /[\r\n\u2028\u2029]/
-const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g
+const {
+ isArrowToken,
+ isOpeningParenToken,
+ isClosingParenToken,
+ isNotOpeningParenToken,
+ isNotClosingParenToken,
+ isOpeningBraceToken,
+ isClosingBraceToken,
+ isNotOpeningBraceToken,
+ isOpeningBracketToken,
+ isClosingBracketToken,
+ isSemicolonToken,
+ isNotSemicolonToken
+} = require('@eslint-community/eslint-utils')
+const {
+ isComment,
+ isNotComment,
+ isWildcard,
+ isExtendsKeyword,
+ isNotWhitespace,
+ isNotEmptyTextNode,
+ isPipeOperator,
+ last
+} = require('./indent-utils')
+const { defineVisitor: tsDefineVisitor } = require('./indent-ts')
+
+/**
+ * @typedef {import('../../typings/eslint-plugin-vue/util-types/node').HasLocation} HasLocation
+ * @typedef { { type: string } & HasLocation } MaybeNode
+ */
+
+const LT_CHAR = /[\n\r\u2028\u2029]/
+const LINES = /[^\n\r\u2028\u2029]+(?:$|\r\n|[\n\r\u2028\u2029])/g
const BLOCK_COMMENT_PREFIX = /^\s*\*/
const ITERATION_OPTS = Object.freeze({
includeComments: true,
filter: isNotWhitespace
})
-const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea']
+const PREFORMATTED_ELEMENT_NAMES = new Set(['pre', 'textarea'])
/**
* @typedef {object} IndentOptions
@@ -170,10 +103,10 @@ function parseOptions(type, options, defaultOptions) {
ret.indentSize = 1
}
- if (Number.isSafeInteger(options.baseIndent)) {
+ if (options.baseIndent != null && Number.isSafeInteger(options.baseIndent)) {
ret.baseIndent = options.baseIndent
}
- if (Number.isSafeInteger(options.attribute)) {
+ if (options.attribute != null && Number.isSafeInteger(options.attribute)) {
ret.attribute = options.attribute
}
if (Number.isSafeInteger(options.closeBracket)) {
@@ -193,7 +126,7 @@ function parseOptions(type, options, defaultOptions) {
options.closeBracket
)
}
- if (Number.isSafeInteger(options.switchCase)) {
+ if (options.switchCase != null && Number.isSafeInteger(options.switchCase)) {
ret.switchCase = options.switchCase
}
@@ -207,191 +140,11 @@ function parseOptions(type, options, defaultOptions) {
return ret
}
-/**
- * Check whether the given token is an arrow.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is an arrow.
- */
-function isArrow(token) {
- return token != null && token.type === 'Punctuator' && token.value === '=>'
-}
-
-/**
- * Check whether the given token is a left parenthesis.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a left parenthesis.
- */
-function isLeftParen(token) {
- return token != null && token.type === 'Punctuator' && token.value === '('
-}
-
-/**
- * Check whether the given token is a left parenthesis.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `false` if the token is a left parenthesis.
- */
-function isNotLeftParen(token) {
- return token != null && (token.type !== 'Punctuator' || token.value !== '(')
-}
-
-/**
- * Check whether the given token is a right parenthesis.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a right parenthesis.
- */
-function isRightParen(token) {
- return token != null && token.type === 'Punctuator' && token.value === ')'
-}
-
-/**
- * Check whether the given token is a right parenthesis.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `false` if the token is a right parenthesis.
- */
-function isNotRightParen(token) {
- return token != null && (token.type !== 'Punctuator' || token.value !== ')')
-}
-
-/**
- * Check whether the given token is a left brace.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a left brace.
- */
-function isLeftBrace(token) {
- return token != null && token.type === 'Punctuator' && token.value === '{'
-}
-
-/**
- * Check whether the given token is a right brace.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a right brace.
- */
-function isRightBrace(token) {
- return token != null && token.type === 'Punctuator' && token.value === '}'
-}
-
-/**
- * Check whether the given token is a left bracket.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a left bracket.
- */
-function isLeftBracket(token) {
- return token != null && token.type === 'Punctuator' && token.value === '['
-}
-
-/**
- * Check whether the given token is a right bracket.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a right bracket.
- */
-function isRightBracket(token) {
- return token != null && token.type === 'Punctuator' && token.value === ']'
-}
-
-/**
- * Check whether the given token is a semicolon.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a semicolon.
- */
-function isSemicolon(token) {
- return token != null && token.type === 'Punctuator' && token.value === ';'
-}
-
-/**
- * Check whether the given token is a comma.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a comma.
- */
-function isComma(token) {
- return token != null && token.type === 'Punctuator' && token.value === ','
-}
-/**
- * Check whether the given token is a wildcard.
- * @param {Token} token The token to check.
- * @returns {boolean} `true` if the token is a wildcard.
- */
-function isWildcard(token) {
- return token != null && token.type === 'Punctuator' && token.value === '*'
-}
-
-/**
- * Check whether the given token is a whitespace.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a whitespace.
- */
-function isNotWhitespace(token) {
- return token != null && token.type !== 'HTMLWhitespace'
-}
-
-/**
- * Check whether the given token is a comment.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a comment.
- */
-function isComment(token) {
- return (
- token != null &&
- (token.type === 'Block' ||
- token.type === 'Line' ||
- token.type === 'Shebang' ||
- (typeof token.type ===
- 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ &&
- token.type.endsWith('Comment')))
- )
-}
-
-/**
- * Check whether the given token is a comment.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `false` if the token is a comment.
- */
-function isNotComment(token) {
- return (
- token != null &&
- token.type !== 'Block' &&
- token.type !== 'Line' &&
- token.type !== 'Shebang' &&
- !(
- typeof token.type ===
- 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ &&
- token.type.endsWith('Comment')
- )
- )
-}
-
-/**
- * Check whether the given node is not an empty text node.
- * @param {ASTNode} node The node to check.
- * @returns {boolean} `false` if the token is empty text node.
- */
-function isNotEmptyTextNode(node) {
- return !(node.type === 'VText' && node.value.trim() === '')
-}
-
-/**
- * Check whether the given token is a pipe operator.
- * @param {Token|undefined|null} token The token to check.
- * @returns {boolean} `true` if the token is a pipe operator.
- */
-function isPipeOperator(token) {
- return token != null && token.type === 'Punctuator' && token.value === '|'
-}
-
-/**
- * Get the last element.
- * @template T
- * @param {T[]} xs The array to get the last element.
- * @returns {T | undefined} The last element or undefined.
- */
-function last(xs) {
- return xs.length === 0 ? undefined : xs[xs.length - 1]
-}
-
/**
* Check whether the node is at the beginning of line.
- * @param {ASTNode|null} node The node to check.
+ * @param {MaybeNode|null} node The node to check.
* @param {number} index The index of the node in the nodes.
- * @param {(ASTNode|null)[]} nodes The array of nodes.
+ * @param {(MaybeNode|null)[]} nodes The array of nodes.
* @returns {boolean} `true` if the node is at the beginning of line.
*/
function isBeginningOfLine(node, index, nodes) {
@@ -423,6 +176,15 @@ function isClosingToken(token) {
)
}
+/**
+ * Checks whether a given token is a optional token.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a optional token.
+ */
+function isOptionalToken(token) {
+ return token.type === 'Punctuator' && token.value === '?.'
+}
+
/**
* Creates AST event handlers for html-indent.
*
@@ -444,6 +206,10 @@ module.exports.defineVisitor = function create(
defaultOptions
)
const sourceCode = context.getSourceCode()
+ /**
+ * @typedef { { baseToken: Token | null, offset: number, baseline: boolean, expectedIndent: number | undefined } } OffsetData
+ */
+ /** @type {Map} */
const offsets = new Map()
const ignoreTokens = new Set()
@@ -455,11 +221,12 @@ module.exports.defineVisitor = function create(
* @returns {void}
*/
function setOffset(token, offset, baseToken) {
- if (!token) {
+ if (!token || token === baseToken) {
return
}
if (Array.isArray(token)) {
for (const t of token) {
+ if (!t || t === baseToken) continue
offsets.set(t, {
baseToken,
offset,
@@ -477,6 +244,33 @@ module.exports.defineVisitor = function create(
}
}
+ /**
+ * Copy offset to the given tokens from srcToken.
+ * @param {Token} token The token to set.
+ * @param {Token} srcToken The token of the source offset.
+ * @returns {void}
+ */
+ function copyOffset(token, srcToken) {
+ if (!token) {
+ return
+ }
+ const offsetData = offsets.get(srcToken)
+ if (!offsetData) {
+ return
+ }
+
+ setOffset(
+ token,
+ offsetData.offset,
+ /** @type {Token} */ (offsetData.baseToken)
+ )
+ if (offsetData.baseline) {
+ setBaseline(token)
+ }
+ const o = /** @type {OffsetData} */ (offsets.get(token))
+ o.expectedIndent = offsetData.expectedIndent
+ }
+
/**
* Set baseline flag to the given token.
* @param {Token} token The token to set.
@@ -500,7 +294,7 @@ module.exports.defineVisitor = function create(
tokenStore.getTokenAfter(node)
/** @type {SourceCode.CursorWithSkipOptions} */
- const option = {
+ const cursorOptions = {
includeComments: true,
filter: (token) =>
token != null &&
@@ -510,11 +304,11 @@ module.exports.defineVisitor = function create(
token.type === 'HTMLEndTagOpen' ||
token.type === 'HTMLComment')
}
- for (const token of tokenStore.getTokensBetween(
- node.startTag,
- endToken,
- option
- )) {
+ const contentTokens = endToken
+ ? tokenStore.getTokensBetween(node.startTag, endToken, cursorOptions)
+ : tokenStore.getTokensAfter(node.startTag, cursorOptions)
+
+ for (const token of contentTokens) {
ignoreTokens.add(token)
}
ignoreTokens.add(endToken)
@@ -523,12 +317,12 @@ module.exports.defineVisitor = function create(
/**
* Get the first and last tokens of the given node.
* If the node is parenthesized, this gets the outermost parentheses.
- * @param {ASTNode} node The node to get.
+ * @param {MaybeNode} node The node to get.
* @param {number} [borderOffset] The least offset of the first token. Defailt is 0. This value is used to prevent false positive in the following case: `(a) => {}` The parentheses are enclosing the whole parameter part rather than the first parameter, but this offset parameter is needed to distinguish.
* @returns {{firstToken:Token,lastToken:Token}} The gotten tokens.
*/
function getFirstAndLastTokens(node, borderOffset = 0) {
- borderOffset |= 0
+ borderOffset = Math.trunc(borderOffset)
let firstToken = tokenStore.getFirstToken(node)
let lastToken = tokenStore.getLastToken(node)
@@ -538,8 +332,8 @@ module.exports.defineVisitor = function create(
while (
(t = tokenStore.getTokenBefore(firstToken)) != null &&
(u = tokenStore.getTokenAfter(lastToken)) != null &&
- isLeftParen(t) &&
- isRightParen(u) &&
+ isOpeningParenToken(t) &&
+ isClosingParenToken(u) &&
t.range[0] >= borderOffset
) {
firstToken = t
@@ -553,9 +347,9 @@ module.exports.defineVisitor = function create(
* Process the given node list.
* The first node is offsetted from the given left token.
* Rest nodes are adjusted to the first node.
- * @param {(ASTNode|null)[]} nodeList The node to process.
- * @param {ASTNode|Token|null} left The left parenthesis token.
- * @param {ASTNode|Token|null} right The right parenthesis token.
+ * @param {(MaybeNode|null)[]} nodeList The node to process.
+ * @param {MaybeNode|Token|null} left The left parenthesis token.
+ * @param {MaybeNode|Token|null} right The right parenthesis token.
* @param {number} offset The offset to set.
* @param {boolean} [alignVertically=true] The flag to align vertically. If `false`, this doesn't align vertically even if the first node is not at beginning of line.
* @returns {void}
@@ -565,21 +359,20 @@ module.exports.defineVisitor = function create(
const leftToken = left && tokenStore.getFirstToken(left)
const rightToken = right && tokenStore.getFirstToken(right)
- if (nodeList.length >= 1) {
+ if (nodeList.length > 0) {
let baseToken = null
let lastToken = left
const alignTokensBeforeBaseToken = []
const alignTokens = []
- for (let i = 0; i < nodeList.length; ++i) {
- const node = nodeList[i]
+ for (const node of nodeList) {
if (node == null) {
// Holes of an array.
continue
}
const elementTokens = getFirstAndLastTokens(
node,
- lastToken != null ? lastToken.range[1] : 0
+ lastToken == null ? 0 : lastToken.range[1]
)
// Collect comma/comment tokens between the last token of the previous node and the first token of this node.
@@ -661,28 +454,35 @@ module.exports.defineVisitor = function create(
*/
function processMaybeBlock(node, baseToken) {
const firstToken = getFirstAndLastTokens(node).firstToken
- setOffset(firstToken, isLeftBrace(firstToken) ? 0 : 1, baseToken)
+ setOffset(firstToken, isOpeningBraceToken(firstToken) ? 0 : 1, baseToken)
}
/**
- * Collect prefix tokens of the given property.
- * The prefix includes `async`, `get`, `set`, `static`, and `*`.
- * @param {Property|MethodDefinition} node The property node to collect prefix tokens.
+ * Process semicolons of the given statement node.
+ * @param {MaybeNode} node The statement node to process.
+ * @returns {void}
*/
- function getPrefixTokens(node) {
- const prefixes = []
-
- /** @type {Token|null} */
- let token = tokenStore.getFirstToken(node)
- while (token != null && token.range[1] <= node.key.range[0]) {
- prefixes.push(token)
- token = tokenStore.getTokenAfter(token)
- }
- while (isLeftParen(last(prefixes)) || isLeftBracket(last(prefixes))) {
- prefixes.pop()
+ function processSemicolons(node) {
+ const firstToken = tokenStore.getFirstToken(node)
+ const lastToken = tokenStore.getLastToken(node)
+ if (isSemicolonToken(lastToken) && firstToken !== lastToken) {
+ setOffset(lastToken, 0, firstToken)
}
- return prefixes
+ // Set to the semicolon of the previous token for semicolon-free style.
+ // E.g.,
+ // foo
+ // ;[1,2,3].forEach(f)
+ const info = offsets.get(firstToken)
+ const prevToken = tokenStore.getTokenBefore(firstToken)
+ if (
+ info != null &&
+ prevToken &&
+ isSemicolonToken(prevToken) &&
+ prevToken.loc.end.line === firstToken.loc.start.line
+ ) {
+ offsets.set(prevToken, info)
+ }
}
/**
@@ -694,7 +494,7 @@ module.exports.defineVisitor = function create(
const type = node.type
while (node.parent && node.parent.type === type) {
const prevToken = tokenStore.getTokenBefore(node)
- if (isLeftParen(prevToken)) {
+ if (isOpeningParenToken(prevToken)) {
// The chaining is broken by parentheses.
break
}
@@ -732,17 +532,16 @@ module.exports.defineVisitor = function create(
return false
}
const prevToken = tokenStore.getTokenBefore(belongingNode)
- if (isLeftParen(prevToken)) {
+ if (isOpeningParenToken(prevToken)) {
// It is not the first token because it is enclosed in parentheses.
return false
}
return true
}
if (parent.type === 'CallExpression' || parent.type === 'NewExpression') {
- const openParen = /** @type {Token} */ (tokenStore.getTokenAfter(
- parent.callee,
- isNotRightParen
- ))
+ const openParen = /** @type {Token} */ (
+ tokenStore.getTokenAfter(parent.callee, isNotClosingParenToken)
+ )
return parent.arguments.some(
(param) =>
getFirstAndLastTokens(param, openParen.range[1]).firstToken
@@ -779,15 +578,15 @@ module.exports.defineVisitor = function create(
function processTopLevelNode(node, expectedIndent) {
const token = tokenStore.getFirstToken(node)
const offsetInfo = offsets.get(token)
- if (offsetInfo != null) {
- offsetInfo.expectedIndent = expectedIndent
- } else {
+ if (offsetInfo == null) {
offsets.set(token, {
baseToken: null,
offset: 0,
baseline: false,
expectedIndent
})
+ } else {
+ offsetInfo.expectedIndent = expectedIndent
}
}
@@ -836,14 +635,11 @@ module.exports.defineVisitor = function create(
function getExpectedIndents(tokens) {
const expectedIndents = []
- for (let i = 0; i < tokens.length; ++i) {
- const token = tokens[i]
+ for (const [i, token] of tokens.entries()) {
const offsetInfo = offsets.get(token)
if (offsetInfo != null) {
- if (offsetInfo.expectedIndent != null) {
- expectedIndents.push(offsetInfo.expectedIndent)
- } else {
+ if (offsetInfo.expectedIndent == null) {
const baseOffsetInfo = offsets.get(offsetInfo.baseToken)
if (
baseOffsetInfo != null &&
@@ -858,16 +654,18 @@ module.exports.defineVisitor = function create(
break
}
}
+ } else {
+ expectedIndents.push(offsetInfo.expectedIndent)
}
}
}
- if (!expectedIndents.length) {
+ if (expectedIndents.length === 0) {
return null
}
return {
expectedIndent: expectedIndents[0],
- expectedBaseIndent: expectedIndents.reduce((a, b) => Math.min(a, b))
+ expectedBaseIndent: Math.min(...expectedIndents)
}
}
@@ -927,7 +725,7 @@ module.exports.defineVisitor = function create(
* Validate the given token with the pre-calculated expected indentation.
* @param {Token} token The token to validate.
* @param {number} expectedIndent The expected indentation.
- * @param {number[]} [optionalExpectedIndents] The optional expected indentation.
+ * @param {[number, number?]} [optionalExpectedIndents] The optional expected indentation.
* @returns {void}
*/
function validateCore(token, expectedIndent, optionalExpectedIndents) {
@@ -944,8 +742,8 @@ module.exports.defineVisitor = function create(
const actualIndent = token.loc.start.column
const unit = options.indentChar === '\t' ? 'tab' : 'space'
- for (let i = 0; i < indentText.length; ++i) {
- if (indentText[i] !== options.indentChar) {
+ for (const [i, char] of [...indentText].entries()) {
+ if (char !== options.indentChar) {
context.report({
loc: {
start: { line, column: i },
@@ -955,7 +753,7 @@ module.exports.defineVisitor = function create(
'Expected {{expected}} character, but found {{actual}} character.',
data: {
expected: JSON.stringify(options.indentChar),
- actual: JSON.stringify(indentText[i])
+ actual: JSON.stringify(char)
},
fix: defineFix(token, actualIndent, expectedIndent)
})
@@ -991,8 +789,8 @@ module.exports.defineVisitor = function create(
* Get the expected indent of comments.
* @param {Token} nextToken The next token of comments.
* @param {number} nextExpectedIndent The expected indent of the next token.
- * @param {number} lastExpectedIndent The expected indent of the last token.
- * @returns {number[]}
+ * @param {number|undefined} lastExpectedIndent The expected indent of the last token.
+ * @returns {[number, number?]}
*/
function getCommentExpectedIndents(
nextToken,
@@ -1064,22 +862,20 @@ module.exports.defineVisitor = function create(
if (offsetInfo != null) {
if (offsetInfo.baseline) {
// This is a baseline token, so the expected indent is the column of this token.
- if (options.indentChar === ' ') {
- offsetInfo.expectedIndent = Math.max(
- 0,
- token.loc.start.column + expectedBaseIndent - actualIndent
- )
- } else {
- // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset.
- // But the additional offset isn't needed if it's at the beginning of the line.
- offsetInfo.expectedIndent =
- expectedBaseIndent + (token === tokens[0] ? 0 : 1)
- }
+ offsetInfo.expectedIndent =
+ options.indentChar === ' '
+ ? Math.max(
+ 0,
+ token.loc.start.column + expectedBaseIndent - actualIndent
+ )
+ : // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset.
+ // But the additional offset isn't needed if it's at the beginning of the line.
+ expectedBaseIndent + (token === tokens[0] ? 0 : 1)
baseline.add(token)
} else if (baseline.has(offsetInfo.baseToken)) {
// The base token is a baseline token on this line, so inherit it.
- offsetInfo.expectedIndent = offsets.get(
- offsetInfo.baseToken
+ offsetInfo.expectedIndent = /** @type {OffsetData} */ (
+ offsets.get(offsetInfo.baseToken)
).expectedIndent
baseline.add(token)
} else {
@@ -1124,8 +920,14 @@ module.exports.defineVisitor = function create(
// Main
// ------------------------------------------------------------------------------
- return processIgnores({
- /** @param {VAttribute} node */
+ /** @type {Set} */
+ const knownNodes = new Set()
+ /** @type {TemplateListener} */
+ const visitor = {
+ // ----------------------------------------------------------------------
+ // Vue NODES
+ // ----------------------------------------------------------------------
+ /** @param {VAttribute | VDirective} node */
VAttribute(node) {
const keyToken = tokenStore.getFirstToken(node)
const eqToken = tokenStore.getTokenAfter(node.key)
@@ -1141,7 +943,12 @@ module.exports.defineVisitor = function create(
},
/** @param {VElement} node */
VElement(node) {
- if (!PREFORMATTED_ELEMENT_NAMES.includes(node.name)) {
+ if (PREFORMATTED_ELEMENT_NAMES.has(node.name)) {
+ const startTagToken = tokenStore.getFirstToken(node)
+ const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)
+ setOffset(endTagToken, 0, startTagToken)
+ setPreformattedTokens(node)
+ } else {
const isTopLevel = node.parent.type !== 'VElement'
const offset = isTopLevel ? options.baseIndent : 1
processNodeList(
@@ -1151,11 +958,6 @@ module.exports.defineVisitor = function create(
offset,
false
)
- } else {
- const startTagToken = tokenStore.getFirstToken(node)
- const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)
- setOffset(endTagToken, 0, startTagToken)
- setPreformattedTokens(node)
}
},
/** @param {VEndTag} node */
@@ -1186,7 +988,7 @@ module.exports.defineVisitor = function create(
VFilter(node) {
const idToken = tokenStore.getFirstToken(node)
const lastToken = tokenStore.getLastToken(node)
- if (isRightParen(lastToken)) {
+ if (isClosingParenToken(lastToken)) {
const leftParenToken = tokenStore.getTokenAfter(node.callee)
setOffset(leftParenToken, 1, idToken)
processNodeList(node.arguments, leftParenToken, lastToken, 1)
@@ -1215,14 +1017,16 @@ module.exports.defineVisitor = function create(
VForExpression(node) {
const firstToken = tokenStore.getFirstToken(node)
const lastOfLeft = last(node.left) || firstToken
- const inToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- lastOfLeft,
- isNotRightParen
- ))
+ const inToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(lastOfLeft, isNotClosingParenToken)
+ )
const rightToken = tokenStore.getFirstToken(node.right)
- if (isLeftParen(firstToken)) {
- const rightToken = tokenStore.getTokenAfter(lastOfLeft, isRightParen)
+ if (isOpeningParenToken(firstToken)) {
+ const rightToken = tokenStore.getTokenAfter(
+ lastOfLeft,
+ isClosingParenToken
+ )
processNodeList(node.left, firstToken, rightToken, 1)
}
setOffset(inToken, 1, firstToken)
@@ -1246,9 +1050,9 @@ module.exports.defineVisitor = function create(
)
if (closeToken != null && closeToken.type.endsWith('TagClose')) {
const offset =
- closeToken.type !== 'HTMLSelfClosingTagClose'
- ? options.closeBracket.startTag
- : options.closeBracket.selfClosingTag
+ closeToken.type === 'HTMLSelfClosingTagClose'
+ ? options.closeBracket.selfClosingTag
+ : options.closeBracket.startTag
setOffset(closeToken, offset, openToken)
}
},
@@ -1261,29 +1065,42 @@ module.exports.defineVisitor = function create(
offsets.set(token, Object.assign({}, firstTokenInfo))
}
},
+ // ----------------------------------------------------------------------
+ // SINGLE TOKEN NODES
+ // ----------------------------------------------------------------------
+ VIdentifier() {},
+ VLiteral() {},
+ // ----------------------------------------------------------------------
+ // WRAPPER NODES
+ // ----------------------------------------------------------------------
+ VDirectiveKey() {},
+ VSlotScopeExpression() {},
+ // ----------------------------------------------------------------------
+ // ES NODES
+ // ----------------------------------------------------------------------
/** @param {ArrayExpression | ArrayPattern} node */
'ArrayExpression, ArrayPattern'(node) {
- processNodeList(
- node.elements,
- tokenStore.getFirstToken(node),
- tokenStore.getLastToken(node),
- 1
+ const firstToken = tokenStore.getFirstToken(node)
+ const rightToken = tokenStore.getTokenAfter(
+ node.elements[node.elements.length - 1] || firstToken,
+ isClosingBracketToken
)
+ processNodeList(node.elements, firstToken, rightToken, 1)
},
/** @param {ArrowFunctionExpression} node */
ArrowFunctionExpression(node) {
const firstToken = tokenStore.getFirstToken(node)
const secondToken = tokenStore.getTokenAfter(firstToken)
const leftToken = node.async ? secondToken : firstToken
- const arrowToken = tokenStore.getTokenBefore(node.body, isArrow)
+ const arrowToken = tokenStore.getTokenBefore(node.body, isArrowToken)
if (node.async) {
setOffset(secondToken, 1, firstToken)
}
- if (isLeftParen(leftToken)) {
+ if (isOpeningParenToken(leftToken)) {
const rightToken = tokenStore.getTokenAfter(
last(node.params) || leftToken,
- isRightParen
+ isClosingParenToken
)
processNodeList(node.params, leftToken, rightToken, 1)
}
@@ -1296,10 +1113,9 @@ module.exports.defineVisitor = function create(
node
) {
const leftToken = getChainHeadToken(node)
- const opToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.left,
- isNotRightParen
- ))
+ const opToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(node.left, isNotClosingParenToken)
+ )
const rightToken = tokenStore.getTokenAfter(opToken)
const prevToken = tokenStore.getTokenBefore(leftToken)
const shouldIndent =
@@ -1325,6 +1141,16 @@ module.exports.defineVisitor = function create(
1
)
},
+ StaticBlock(node) {
+ const firstToken = tokenStore.getFirstToken(node)
+ let next = tokenStore.getTokenAfter(firstToken)
+ while (next && isNotOpeningBraceToken(next)) {
+ setOffset(next, 0, firstToken)
+ next = tokenStore.getTokenAfter(next)
+ }
+ setOffset(next, 0, firstToken)
+ processNodeList(node.body, next, tokenStore.getLastToken(node), 1)
+ },
/** @param {BreakStatement | ContinueStatement | ReturnStatement | ThrowStatement} node */
'BreakStatement, ContinueStatement, ReturnStatement, ThrowStatement'(node) {
if (
@@ -1342,9 +1168,28 @@ module.exports.defineVisitor = function create(
},
/** @param {CallExpression} node */
CallExpression(node) {
+ const typeArguments =
+ 'typeArguments' in node ? node.typeArguments : node.typeParameters
const firstToken = tokenStore.getFirstToken(node)
const rightToken = tokenStore.getLastToken(node)
- const leftToken = tokenStore.getTokenAfter(node.callee, isLeftParen)
+ const leftToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(
+ typeArguments || node.callee,
+ isOpeningParenToken
+ )
+ )
+
+ if (typeArguments) {
+ setOffset(tokenStore.getFirstToken(typeArguments), 1, firstToken)
+ }
+
+ for (const optionalToken of tokenStore.getTokensBetween(
+ tokenStore.getLastToken(typeArguments || node.callee),
+ leftToken,
+ isOptionalToken
+ )) {
+ setOffset(optionalToken, 1, firstToken)
+ }
setOffset(leftToken, 1, firstToken)
processNodeList(node.arguments, leftToken, rightToken, 1)
@@ -1353,7 +1198,10 @@ module.exports.defineVisitor = function create(
ImportExpression(node) {
const firstToken = tokenStore.getFirstToken(node)
const rightToken = tokenStore.getLastToken(node)
- const leftToken = tokenStore.getTokenAfter(firstToken, isLeftParen)
+ const leftToken = tokenStore.getTokenAfter(
+ firstToken,
+ isOpeningParenToken
+ )
setOffset(leftToken, 1, firstToken)
processNodeList([node.source], leftToken, rightToken, 1)
@@ -1381,7 +1229,9 @@ module.exports.defineVisitor = function create(
setOffset(tokenStore.getFirstToken(node.id), 1, firstToken)
}
if (node.superClass != null) {
- const extendsToken = tokenStore.getTokenAfter(node.id || firstToken)
+ const extendsToken = /** @type {Token} */ (
+ tokenStore.getTokenBefore(node.superClass, isExtendsKeyword)
+ )
const superClassToken = tokenStore.getTokenAfter(extendsToken)
setOffset(extendsToken, 1, firstToken)
setOffset(superClassToken, 1, extendsToken)
@@ -1392,15 +1242,13 @@ module.exports.defineVisitor = function create(
ConditionalExpression(node) {
const prevToken = tokenStore.getTokenBefore(node)
const firstToken = tokenStore.getFirstToken(node)
- const questionToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.test,
- isNotRightParen
- ))
+ const questionToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(node.test, isNotClosingParenToken)
+ )
const consequentToken = tokenStore.getTokenAfter(questionToken)
- const colonToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.consequent,
- isNotRightParen
- ))
+ const colonToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(node.consequent, isNotClosingParenToken)
+ )
const alternateToken = tokenStore.getTokenAfter(colonToken)
const isFlat =
prevToken &&
@@ -1421,14 +1269,13 @@ module.exports.defineVisitor = function create(
/** @param {DoWhileStatement} node */
DoWhileStatement(node) {
const doToken = tokenStore.getFirstToken(node)
- const whileToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.body,
- isNotRightParen
- ))
+ const whileToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(node.body, isNotClosingParenToken)
+ )
const leftToken = tokenStore.getTokenAfter(whileToken)
const testToken = tokenStore.getTokenAfter(leftToken)
const lastToken = tokenStore.getLastToken(node)
- const rightToken = isSemicolon(lastToken)
+ const rightToken = isSemicolonToken(lastToken)
? tokenStore.getTokenBefore(lastToken)
: lastToken
@@ -1440,32 +1287,51 @@ module.exports.defineVisitor = function create(
},
/** @param {ExportAllDeclaration} node */
ExportAllDeclaration(node) {
- const tokens = tokenStore.getTokens(node)
- const firstToken = /** @type {Token} */ (tokens.shift())
- if (isSemicolon(last(tokens))) {
- tokens.pop()
- }
- if (!node.exported) {
- setOffset(tokens, 1, firstToken)
- } else {
+ const exportToken = tokenStore.getFirstToken(node)
+ const tokens = [
+ ...tokenStore.getTokensBetween(exportToken, node.source),
+ tokenStore.getFirstToken(node.source)
+ ]
+ if (node.exported) {
// export * as foo from "mod"
const starToken = /** @type {Token} */ (tokens.find(isWildcard))
const asToken = tokenStore.getTokenAfter(starToken)
const exportedToken = tokenStore.getTokenAfter(asToken)
const afterTokens = tokens.slice(tokens.indexOf(exportedToken) + 1)
- setOffset(starToken, 1, firstToken)
+ setOffset(starToken, 1, exportToken)
setOffset(asToken, 1, starToken)
setOffset(exportedToken, 1, starToken)
- setOffset(afterTokens, 1, firstToken)
+ setOffset(afterTokens, 1, exportToken)
+ } else {
+ setOffset(tokens, 1, exportToken)
+ }
+
+ // assertions
+ const lastToken = /** @type {Token} */ (
+ tokenStore.getLastToken(node, isNotSemicolonToken)
+ )
+ const assertionTokens = tokenStore.getTokensBetween(
+ node.source,
+ lastToken
+ )
+ if (assertionTokens.length > 0) {
+ const assertToken = /** @type {Token} */ (assertionTokens.shift())
+ setOffset(assertToken, 0, exportToken)
+ const assertionOpen = assertionTokens.shift()
+ if (assertionOpen) {
+ setOffset(assertionOpen, 1, assertToken)
+ processNodeList(assertionTokens, assertionOpen, lastToken, 1)
+ }
}
},
/** @param {ExportDefaultDeclaration} node */
ExportDefaultDeclaration(node) {
const exportToken = tokenStore.getFirstToken(node)
const defaultToken = tokenStore.getFirstToken(node, 1)
- const declarationToken = getFirstAndLastTokens(node.declaration)
- .firstToken
+ const declarationToken = getFirstAndLastTokens(
+ node.declaration
+ ).firstToken
setOffset([defaultToken, declarationToken], 1, exportToken)
},
/** @param {ExportNamedDeclaration} node */
@@ -1479,32 +1345,66 @@ module.exports.defineVisitor = function create(
const firstSpecifier = node.specifiers[0]
if (!firstSpecifier || firstSpecifier.type === 'ExportSpecifier') {
// export {foo, bar}; or export {foo, bar} from "mod";
- const leftParenToken = tokenStore.getFirstToken(node, 1)
- const rightParenToken = /** @type {Token} */ (tokenStore.getLastToken(
- node,
- isRightBrace
- ))
- setOffset(leftParenToken, 0, exportToken)
- processNodeList(node.specifiers, leftParenToken, rightParenToken, 1)
-
- const maybeFromToken = tokenStore.getTokenAfter(rightParenToken)
- if (
- maybeFromToken != null &&
- sourceCode.getText(maybeFromToken) === 'from'
- ) {
- const fromToken = maybeFromToken
- const nameToken = tokenStore.getTokenAfter(fromToken)
- setOffset([fromToken, nameToken], 1, exportToken)
+ const leftBraceTokens = firstSpecifier
+ ? tokenStore.getTokensBetween(exportToken, firstSpecifier)
+ : [tokenStore.getTokenAfter(exportToken)]
+ const rightBraceToken = /** @type {Token} */ (
+ node.source
+ ? tokenStore.getTokenBefore(node.source, isClosingBraceToken)
+ : tokenStore.getLastToken(node, isClosingBraceToken)
+ )
+ setOffset(leftBraceTokens, 0, exportToken)
+ processNodeList(
+ node.specifiers,
+ /** @type {Token} */ (last(leftBraceTokens)),
+ rightBraceToken,
+ 1
+ )
+
+ if (node.source) {
+ const tokens = tokenStore.getTokensBetween(
+ rightBraceToken,
+ node.source
+ )
+ setOffset(
+ [...tokens, sourceCode.getFirstToken(node.source)],
+ 1,
+ exportToken
+ )
+
+ // assertions
+ const lastToken = /** @type {Token} */ (
+ tokenStore.getLastToken(node, isNotSemicolonToken)
+ )
+ const assertionTokens = tokenStore.getTokensBetween(
+ node.source,
+ lastToken
+ )
+ if (assertionTokens.length > 0) {
+ const assertToken = /** @type {Token} */ (assertionTokens.shift())
+ setOffset(assertToken, 0, exportToken)
+ const assertionOpen = assertionTokens.shift()
+ if (assertionOpen) {
+ setOffset(assertionOpen, 1, assertToken)
+ processNodeList(assertionTokens, assertionOpen, lastToken, 1)
+ }
+ }
}
} else {
- // maybe babel-eslint
+ // maybe babel parser
}
}
},
- /** @param {ExportSpecifier} node */
- ExportSpecifier(node) {
+ /** @param {ExportSpecifier | ImportSpecifier} node */
+ 'ExportSpecifier, ImportSpecifier'(node) {
const tokens = tokenStore.getTokens(node)
- const firstToken = /** @type {Token} */ (tokens.shift())
+ let firstToken = /** @type {Token} */ (tokens.shift())
+ if (firstToken.value === 'type') {
+ const typeToken = firstToken
+ firstToken = /** @type {Token} */ (tokens.shift())
+ setOffset(firstToken, 0, typeToken)
+ }
+
setOffset(tokens, 1, firstToken)
},
/** @param {ForInStatement | ForOfStatement} node */
@@ -1517,14 +1417,13 @@ module.exports.defineVisitor = function create(
null
const leftParenToken = tokenStore.getTokenAfter(awaitToken || forToken)
const leftToken = tokenStore.getTokenAfter(leftParenToken)
- const inToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- leftToken,
- isNotRightParen
- ))
+ const inToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(leftToken, isNotClosingParenToken)
+ )
const rightToken = tokenStore.getTokenAfter(inToken)
const rightParenToken = tokenStore.getTokenBefore(
node.body,
- isNotLeftParen
+ isNotOpeningParenToken
)
if (awaitToken != null) {
@@ -1543,7 +1442,7 @@ module.exports.defineVisitor = function create(
const leftParenToken = tokenStore.getTokenAfter(forToken)
const rightParenToken = tokenStore.getTokenBefore(
node.body,
- isNotLeftParen
+ isNotOpeningParenToken
)
setOffset(leftParenToken, 1, forToken)
@@ -1558,48 +1457,51 @@ module.exports.defineVisitor = function create(
/** @param {FunctionDeclaration | FunctionExpression} node */
'FunctionDeclaration, FunctionExpression'(node) {
const firstToken = tokenStore.getFirstToken(node)
- if (isLeftParen(firstToken)) {
+ let leftParenToken, bodyBaseToken
+ if (isOpeningParenToken(firstToken)) {
// Methods.
- const leftToken = firstToken
- const rightToken = tokenStore.getTokenAfter(
- last(node.params) || leftToken,
- isRightParen
- )
- const bodyToken = tokenStore.getFirstToken(node.body)
-
- processNodeList(node.params, leftToken, rightToken, 1)
- setOffset(bodyToken, 0, tokenStore.getFirstToken(node.parent))
+ leftParenToken = firstToken
+ bodyBaseToken = tokenStore.getFirstToken(node.parent)
} else {
// Normal functions.
- const functionToken = node.async
- ? tokenStore.getTokenAfter(firstToken)
- : firstToken
- const starToken = node.generator
- ? tokenStore.getTokenAfter(functionToken)
- : null
- const idToken = node.id && tokenStore.getFirstToken(node.id)
- const leftToken = tokenStore.getTokenAfter(
- idToken || starToken || functionToken
- )
- const rightToken = tokenStore.getTokenAfter(
- last(node.params) || leftToken,
- isRightParen
- )
- const bodyToken = tokenStore.getFirstToken(node.body)
-
- if (node.async) {
- setOffset(functionToken, 0, firstToken)
- }
- if (node.generator) {
- setOffset(starToken, 1, firstToken)
- }
- if (node.id != null) {
- setOffset(idToken, 1, firstToken)
+ let nextToken = tokenStore.getTokenAfter(firstToken)
+ let nextTokenOffset = 0
+ while (
+ nextToken &&
+ !isOpeningParenToken(nextToken) &&
+ nextToken.value !== '<'
+ ) {
+ if (
+ nextToken.value === '*' ||
+ (node.id && nextToken.range[0] === node.id.range[0])
+ ) {
+ nextTokenOffset = 1
+ }
+ setOffset(nextToken, nextTokenOffset, firstToken)
+ nextToken = tokenStore.getTokenAfter(nextToken)
}
- setOffset(leftToken, 1, firstToken)
- processNodeList(node.params, leftToken, rightToken, 1)
- setOffset(bodyToken, 0, firstToken)
+
+ leftParenToken = nextToken
+ bodyBaseToken = firstToken
}
+
+ if (
+ !isOpeningParenToken(leftParenToken) &&
+ /** @type {any} */ (node).typeParameters
+ ) {
+ leftParenToken = tokenStore.getTokenAfter(
+ /** @type {any} */ (node).typeParameters
+ )
+ }
+ const rightParenToken = tokenStore.getTokenAfter(
+ node.params[node.params.length - 1] || leftParenToken,
+ isClosingParenToken
+ )
+ setOffset(leftParenToken, 1, bodyBaseToken)
+ processNodeList(node.params, leftParenToken, rightParenToken, 1)
+
+ const bodyToken = tokenStore.getFirstToken(node.body)
+ setOffset(bodyToken, 0, bodyBaseToken)
},
/** @param {IfStatement} node */
IfStatement(node) {
@@ -1607,7 +1509,7 @@ module.exports.defineVisitor = function create(
const ifLeftParenToken = tokenStore.getTokenAfter(ifToken)
const ifRightParenToken = tokenStore.getTokenBefore(
node.consequent,
- isRightParen
+ isClosingParenToken
)
setOffset(ifLeftParenToken, 1, ifToken)
@@ -1615,10 +1517,9 @@ module.exports.defineVisitor = function create(
processMaybeBlock(node.consequent, ifToken)
if (node.alternate != null) {
- const elseToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.consequent,
- isNotRightParen
- ))
+ const elseToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(node.consequent, isNotClosingParenToken)
+ )
setOffset(elseToken, 0, ifToken)
processMaybeBlock(node.alternate, elseToken)
@@ -1626,99 +1527,91 @@ module.exports.defineVisitor = function create(
},
/** @param {ImportDeclaration} node */
ImportDeclaration(node) {
- const firstSpecifier = node.specifiers[0]
- const secondSpecifier = node.specifiers[1]
const importToken = tokenStore.getFirstToken(node)
- const hasSemi = tokenStore.getLastToken(node).value === ';'
- /** @type {Token[]} */
- const tokens = [] // tokens to one indent
-
- if (!firstSpecifier) {
- // There are 2 patterns:
- // import "foo"
- // import {} from "foo"
- const secondToken = tokenStore.getFirstToken(node, 1)
- if (isLeftBrace(secondToken)) {
- setOffset(
- [secondToken, tokenStore.getTokenAfter(secondToken)],
- 0,
- importToken
- )
- tokens.push(
- tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
- tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
- )
+ const tokens = tokenStore.getTokensBetween(importToken, node.source)
+ const fromIndex = tokens.map((t) => t.value).lastIndexOf('from')
+ const { fromToken, beforeTokens, afterTokens } =
+ fromIndex === -1
+ ? {
+ fromToken: null,
+ beforeTokens: [...tokens, tokenStore.getFirstToken(node.source)],
+ afterTokens: []
+ }
+ : {
+ fromToken: tokens[fromIndex],
+ beforeTokens: tokens.slice(0, fromIndex),
+ afterTokens: [
+ ...tokens.slice(fromIndex + 1),
+ tokenStore.getFirstToken(node.source)
+ ]
+ }
+
+ /** @type {ImportSpecifier[]} */
+ const namedSpecifiers = []
+ for (const specifier of node.specifiers) {
+ if (specifier.type === 'ImportSpecifier') {
+ namedSpecifiers.push(specifier)
} else {
- tokens.push(tokenStore.getLastToken(node, hasSemi ? 1 : 0))
+ const removeTokens = tokenStore.getTokens(specifier)
+ removeTokens.shift()
+ for (const token of removeTokens) {
+ const i = beforeTokens.indexOf(token)
+ if (i !== -1) {
+ beforeTokens.splice(i, 1)
+ }
+ }
}
- } else if (firstSpecifier.type === 'ImportDefaultSpecifier') {
- if (
- secondSpecifier &&
- secondSpecifier.type === 'ImportNamespaceSpecifier'
- ) {
- // There is a pattern:
- // import Foo, * as foo from "foo"
- tokens.push(
- tokenStore.getFirstToken(firstSpecifier), // Foo
- tokenStore.getTokenAfter(firstSpecifier), // comma
- tokenStore.getFirstToken(secondSpecifier), // *
- tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
- tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
+ }
+ if (namedSpecifiers.length > 0) {
+ const leftBrace = tokenStore.getTokenBefore(namedSpecifiers[0])
+ const rightBrace = /** @type {Token} */ (
+ tokenStore.getTokenAfter(
+ namedSpecifiers[namedSpecifiers.length - 1],
+ isClosingBraceToken
)
- } else {
- // There are 3 patterns:
- // import Foo from "foo"
- // import Foo, {} from "foo"
- // import Foo, {a} from "foo"
- const idToken = tokenStore.getFirstToken(firstSpecifier)
- const nextToken = tokenStore.getTokenAfter(firstSpecifier)
- if (isComma(nextToken)) {
- const leftBrace = tokenStore.getTokenAfter(nextToken)
- const rightBrace = tokenStore.getLastToken(node, hasSemi ? 3 : 2)
- setOffset([idToken, nextToken], 1, importToken)
- setOffset(leftBrace, 0, idToken)
- processNodeList(node.specifiers.slice(1), leftBrace, rightBrace, 1)
- tokens.push(
- tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
- tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
- )
- } else {
- tokens.push(
- idToken,
- nextToken, // from
- tokenStore.getTokenAfter(nextToken) // "foo"
- )
+ )
+ processNodeList(namedSpecifiers, leftBrace, rightBrace, 1)
+ for (const token of [
+ ...tokenStore.getTokensBetween(leftBrace, rightBrace),
+ rightBrace
+ ]) {
+ const i = beforeTokens.indexOf(token)
+ if (i !== -1) {
+ beforeTokens.splice(i, 1)
}
}
- } else if (firstSpecifier.type === 'ImportNamespaceSpecifier') {
- // There is a pattern:
- // import * as foo from "foo"
- tokens.push(
- tokenStore.getFirstToken(firstSpecifier), // *
- tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
- tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
+ }
+
+ if (
+ beforeTokens.every(
+ (t) => isOpeningBraceToken(t) || isClosingBraceToken(t)
)
+ ) {
+ setOffset(beforeTokens, 0, importToken)
} else {
- // There is a pattern:
- // import {a} from "foo"
- const leftBrace = tokenStore.getFirstToken(node, 1)
- const rightBrace = tokenStore.getLastToken(node, hasSemi ? 3 : 2)
- setOffset(leftBrace, 0, importToken)
- processNodeList(node.specifiers, leftBrace, rightBrace, 1)
- tokens.push(
- tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
- tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
- )
+ setOffset(beforeTokens, 1, importToken)
+ }
+ if (fromToken) {
+ setOffset(fromToken, 1, importToken)
+ setOffset(afterTokens, 0, fromToken)
}
- setOffset(tokens, 1, importToken)
- },
- /** @param {ImportSpecifier} node */
- ImportSpecifier(node) {
- if (node.local.range[0] !== node.imported.range[0]) {
- const tokens = tokenStore.getTokens(node)
- const firstToken = /** @type {Token} */ (tokens.shift())
- setOffset(tokens, 1, firstToken)
+ // assertions
+ const lastToken = /** @type {Token} */ (
+ tokenStore.getLastToken(node, isNotSemicolonToken)
+ )
+ const assertionTokens = tokenStore.getTokensBetween(
+ node.source,
+ lastToken
+ )
+ if (assertionTokens.length > 0) {
+ const assertToken = /** @type {Token} */ (assertionTokens.shift())
+ setOffset(assertToken, 0, importToken)
+ const assertionOpen = assertionTokens.shift()
+ if (assertionOpen) {
+ setOffset(assertionOpen, 1, assertToken)
+ processNodeList(assertionTokens, assertionOpen, lastToken, 1)
+ }
}
},
/** @param {ImportNamespaceSpecifier} node */
@@ -1739,16 +1632,23 @@ module.exports.defineVisitor = function create(
'MemberExpression, MetaProperty'(node) {
const objectToken = tokenStore.getFirstToken(node)
if (node.type === 'MemberExpression' && node.computed) {
- const leftBracketToken = /** @type {Token} */ (tokenStore.getTokenBefore(
- node.property,
- isLeftBracket
- ))
+ const leftBracketToken = /** @type {Token} */ (
+ tokenStore.getTokenBefore(node.property, isOpeningBracketToken)
+ )
const propertyToken = tokenStore.getTokenAfter(leftBracketToken)
const rightBracketToken = tokenStore.getTokenAfter(
node.property,
- isRightBracket
+ isClosingBracketToken
)
+ for (const optionalToken of tokenStore.getTokensBetween(
+ tokenStore.getLastToken(node.object),
+ leftBracketToken,
+ isOptionalToken
+ )) {
+ setOffset(optionalToken, 1, objectToken)
+ }
+
setOffset(leftBracketToken, 1, objectToken)
setOffset(propertyToken, 1, leftBracketToken)
setOffset(rightBracketToken, 0, leftBracketToken)
@@ -1759,62 +1659,60 @@ module.exports.defineVisitor = function create(
setOffset([dotToken, propertyToken], 1, objectToken)
}
},
- /** @param {MethodDefinition | Property} node */
- 'MethodDefinition, Property'(node) {
- const isMethod = node.type === 'MethodDefinition' || node.method === true
- const prefixTokens = getPrefixTokens(node)
- const hasPrefix = prefixTokens.length >= 1
-
- for (let i = 1; i < prefixTokens.length; ++i) {
- setOffset(prefixTokens[i], 0, prefixTokens[i - 1])
+ /** @param {MethodDefinition | Property | PropertyDefinition} node */
+ 'MethodDefinition, Property, PropertyDefinition'(node) {
+ const firstToken = tokenStore.getFirstToken(node)
+ const keyTokens = getFirstAndLastTokens(node.key)
+ const prefixTokens = tokenStore.getTokensBetween(
+ firstToken,
+ keyTokens.firstToken
+ )
+ if (node.computed) {
+ prefixTokens.pop() // pop [
}
+ setOffset(prefixTokens, 0, firstToken)
- /** @type {Token} */
let lastKeyToken
if (node.computed) {
- const keyLeftToken = /** @type {Token} */ (tokenStore.getFirstToken(
- node,
- isLeftBracket
+ const leftBracketToken = tokenStore.getTokenBefore(keyTokens.firstToken)
+ const rightBracketToken = (lastKeyToken = tokenStore.getTokenAfter(
+ keyTokens.lastToken
))
- const keyToken = tokenStore.getTokenAfter(keyLeftToken)
- const keyRightToken = (lastKeyToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.key,
- isRightBracket
- )))
-
- if (hasPrefix) {
- setOffset(keyLeftToken, 0, /** @type {Token} */ (last(prefixTokens)))
- }
- setOffset(keyToken, 1, keyLeftToken)
- setOffset(keyRightToken, 0, keyLeftToken)
+ setOffset(leftBracketToken, 0, firstToken)
+ processNodeList([node.key], leftBracketToken, rightBracketToken, 1)
} else {
- const idToken = (lastKeyToken = tokenStore.getFirstToken(node.key))
-
- if (hasPrefix) {
- setOffset(idToken, 0, /** @type {Token} */ (last(prefixTokens)))
- }
+ setOffset(keyTokens.firstToken, 0, firstToken)
+ lastKeyToken = keyTokens.lastToken
}
- if (isMethod) {
- const leftParenToken = tokenStore.getTokenAfter(lastKeyToken)
-
- setOffset(leftParenToken, 1, lastKeyToken)
- } else if (node.type === 'Property' && !node.shorthand) {
- const colonToken = tokenStore.getTokenAfter(lastKeyToken)
- const valueToken = tokenStore.getTokenAfter(colonToken)
-
- setOffset([colonToken, valueToken], 1, lastKeyToken)
+ if (node.value != null) {
+ const initToken = tokenStore.getFirstToken(node.value)
+ setOffset(
+ [...tokenStore.getTokensBetween(lastKeyToken, initToken), initToken],
+ 1,
+ lastKeyToken
+ )
}
},
/** @param {NewExpression} node */
NewExpression(node) {
+ const typeArguments =
+ 'typeArguments' in node ? node.typeArguments : node.typeParameters
const newToken = tokenStore.getFirstToken(node)
const calleeToken = tokenStore.getTokenAfter(newToken)
const rightToken = tokenStore.getLastToken(node)
- const leftToken = isRightParen(rightToken)
- ? tokenStore.getFirstTokenBetween(node.callee, rightToken, isLeftParen)
+ const leftToken = isClosingParenToken(rightToken)
+ ? tokenStore.getFirstTokenBetween(
+ typeArguments || node.callee,
+ rightToken,
+ isOpeningParenToken
+ )
: null
+ if (typeArguments) {
+ setOffset(tokenStore.getFirstToken(typeArguments), 1, calleeToken)
+ }
+
setOffset(calleeToken, 1, newToken)
if (leftToken != null) {
setOffset(leftToken, 1, calleeToken)
@@ -1823,12 +1721,12 @@ module.exports.defineVisitor = function create(
},
/** @param {ObjectExpression | ObjectPattern} node */
'ObjectExpression, ObjectPattern'(node) {
- processNodeList(
- node.properties,
- tokenStore.getFirstToken(node),
- tokenStore.getLastToken(node),
- 1
+ const firstToken = tokenStore.getFirstToken(node)
+ const rightToken = tokenStore.getTokenAfter(
+ node.properties[node.properties.length - 1] || firstToken,
+ isClosingBraceToken
)
+ processNodeList(node.properties, firstToken, rightToken, 1)
},
/** @param {SequenceExpression} node */
SequenceExpression(node) {
@@ -1838,15 +1736,18 @@ module.exports.defineVisitor = function create(
SwitchCase(node) {
const caseToken = tokenStore.getFirstToken(node)
- if (node.test != null) {
- const testToken = tokenStore.getTokenAfter(caseToken)
- const colonToken = tokenStore.getTokenAfter(node.test, isNotRightParen)
-
- setOffset([testToken, colonToken], 1, caseToken)
- } else {
+ if (node.test == null) {
const colonToken = tokenStore.getTokenAfter(caseToken)
setOffset(colonToken, 1, caseToken)
+ } else {
+ const testToken = tokenStore.getTokenAfter(caseToken)
+ const colonToken = tokenStore.getTokenAfter(
+ node.test,
+ isNotClosingParenToken
+ )
+
+ setOffset([testToken, colonToken], 1, caseToken)
}
if (
@@ -1854,7 +1755,7 @@ module.exports.defineVisitor = function create(
node.consequent[0].type === 'BlockStatement'
) {
setOffset(tokenStore.getFirstToken(node.consequent[0]), 0, caseToken)
- } else if (node.consequent.length >= 1) {
+ } else if (node.consequent.length > 0) {
setOffset(tokenStore.getFirstToken(node.consequent[0]), 1, caseToken)
processNodeList(node.consequent, null, null, 0)
}
@@ -1864,10 +1765,9 @@ module.exports.defineVisitor = function create(
const switchToken = tokenStore.getFirstToken(node)
const leftParenToken = tokenStore.getTokenAfter(switchToken)
const discriminantToken = tokenStore.getTokenAfter(leftParenToken)
- const leftBraceToken = /** @type {Token} */ (tokenStore.getTokenAfter(
- node.discriminant,
- isLeftBrace
- ))
+ const leftBraceToken = /** @type {Token} */ (
+ tokenStore.getTokenAfter(node.discriminant, isOpeningBraceToken)
+ )
const rightParenToken = tokenStore.getTokenBefore(leftBraceToken)
const rightBraceToken = tokenStore.getLastToken(node)
@@ -1952,7 +1852,10 @@ module.exports.defineVisitor = function create(
'WhileStatement, WithStatement'(node) {
const firstToken = tokenStore.getFirstToken(node)
const leftParenToken = tokenStore.getTokenAfter(firstToken)
- const rightParenToken = tokenStore.getTokenBefore(node.body, isRightParen)
+ const rightParenToken = tokenStore.getTokenBefore(
+ node.body,
+ isClosingParenToken
+ )
setOffset(leftParenToken, 1, firstToken)
setOffset(rightParenToken, 0, leftParenToken)
@@ -1969,38 +1872,45 @@ module.exports.defineVisitor = function create(
}
}
},
+ // ----------------------------------------------------------------------
+ // SINGLE TOKEN NODES
+ // ----------------------------------------------------------------------
+ DebuggerStatement() {},
+ Identifier() {},
+ ImportDefaultSpecifier() {},
+ Literal() {},
+ PrivateIdentifier() {},
+ Super() {},
+ TemplateElement() {},
+ ThisExpression() {},
+ // ----------------------------------------------------------------------
+ // WRAPPER NODES
+ // ----------------------------------------------------------------------
+ ExpressionStatement() {},
+ ChainExpression() {},
+ EmptyStatement() {},
+ // ----------------------------------------------------------------------
+ // COMMONS
+ // ----------------------------------------------------------------------
/** @param {Statement} node */
// Process semicolons.
- ':statement'(node) {
- const firstToken = tokenStore.getFirstToken(node)
- const lastToken = tokenStore.getLastToken(node)
- if (isSemicolon(lastToken) && firstToken !== lastToken) {
- setOffset(lastToken, 0, firstToken)
- }
-
- // Set to the semicolon of the previous token for semicolon-free style.
- // E.g.,
- // foo
- // ;[1,2,3].forEach(f)
- const info = offsets.get(firstToken)
- const prevToken = tokenStore.getTokenBefore(firstToken)
- if (
- info != null &&
- isSemicolon(prevToken) &&
- prevToken.loc.end.line === firstToken.loc.start.line
- ) {
- offsets.set(prevToken, info)
- }
+ ':statement, PropertyDefinition'(node) {
+ processSemicolons(node)
},
/** @param {Expression | MetaProperty | TemplateLiteral} node */
// Process parentheses.
// `:expression` does not match with MetaProperty and TemplateLiteral as a bug: https://github.com/estools/esquery/pull/59
- ':expression, MetaProperty, TemplateLiteral'(node) {
+ ':expression'(node) {
let leftToken = tokenStore.getTokenBefore(node)
let rightToken = tokenStore.getTokenAfter(node)
let firstToken = tokenStore.getFirstToken(node)
- while (isLeftParen(leftToken) && isRightParen(rightToken)) {
+ while (
+ leftToken &&
+ rightToken &&
+ isOpeningParenToken(leftToken) &&
+ isClosingParenToken(rightToken)
+ ) {
setOffset(firstToken, 1, leftToken)
setOffset(rightToken, 0, leftToken)
@@ -2009,13 +1919,22 @@ module.exports.defineVisitor = function create(
rightToken = tokenStore.getTokenAfter(rightToken)
}
},
+
+ .../** @type {TemplateListener} */ (
+ tsDefineVisitor({
+ processNodeList,
+ tokenStore,
+ setOffset,
+ copyOffset,
+ processSemicolons,
+ getFirstAndLastTokens
+ })
+ ),
+
/** @param {ASTNode} node */
// Ignore tokens of unknown nodes.
'*:exit'(node) {
- if (
- !KNOWN_NODES.has(node.type) &&
- !NON_STANDARD_KNOWN_NODES.has(node.type)
- ) {
+ if (!knownNodes.has(node.type)) {
ignore(node)
}
},
@@ -2050,9 +1969,10 @@ module.exports.defineVisitor = function create(
// Validate indentation of tokens.
for (const token of tokenStore.getTokens(node, ITERATION_OPTS)) {
+ const tokenStartLine = token.loc.start.line
if (
tokensOnSameLine.length === 0 ||
- tokensOnSameLine[0].loc.start.line === token.loc.start.line
+ tokensOnSameLine[0].loc.start.line === tokenStartLine
) {
// This is on the same line (or the first token).
tokensOnSameLine.push(token)
@@ -2062,7 +1982,7 @@ module.exports.defineVisitor = function create(
comments.push(tokensOnSameLine[0])
isBesideMultilineToken =
/** @type {Token} */ (last(tokensOnSameLine)).loc.end.line ===
- token.loc.start.line
+ tokenStartLine
tokensOnSameLine = [token]
} else {
// New line is detected, so validate the tokens.
@@ -2072,14 +1992,25 @@ module.exports.defineVisitor = function create(
}
isBesideMultilineToken =
/** @type {Token} */ (last(tokensOnSameLine)).loc.end.line ===
- token.loc.start.line
+ tokenStartLine
tokensOnSameLine = [token]
comments = []
}
}
- if (tokensOnSameLine.length >= 1 && tokensOnSameLine.some(isNotComment)) {
+ if (tokensOnSameLine.some(isNotComment)) {
validate(tokensOnSameLine, comments, lastValidatedToken)
}
}
- })
+ }
+
+ for (const key of Object.keys(visitor)) {
+ for (const nodeName of key
+ .split(/\s*,\s*/gu)
+ .map((s) => s.trim())
+ .filter((s) => /[a-z]+/i.test(s))) {
+ knownNodes.add(nodeName)
+ }
+ }
+
+ return processIgnores(visitor)
}
diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js
new file mode 100644
index 000000000..314858c9a
--- /dev/null
+++ b/lib/utils/indent-ts.js
@@ -0,0 +1,1449 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const {
+ isClosingParenToken,
+ isOpeningParenToken,
+ isOpeningBraceToken,
+ isNotClosingParenToken,
+ isClosingBracketToken,
+ isOpeningBracketToken
+} = require('@eslint-community/eslint-utils')
+const { isTypeNode } = require('./ts-utils')
+
+/**
+ * @typedef {import('../../typings/eslint-plugin-vue/util-types/indent-helper').TSNodeListener} TSNodeListener
+ * @typedef {import('../../typings/eslint-plugin-vue/util-types/node').HasLocation} HasLocation
+ * @typedef { { type: string } & HasLocation } MaybeNode
+ */
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.Node} TSESTreeNode
+ * @typedef {import('@typescript-eslint/types').TSESTree.ClassExpression} ClassExpression
+ * @typedef {import('@typescript-eslint/types').TSESTree.ClassDeclaration} ClassDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeAliasDeclaration} TSTypeAliasDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSCallSignatureDeclaration} TSCallSignatureDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSConstructSignatureDeclaration} TSConstructSignatureDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSImportEqualsDeclaration} TSImportEqualsDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSAbstractMethodDefinition} TSAbstractMethodDefinition
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSAbstractPropertyDefinition} TSAbstractPropertyDefinition
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSAbstractAccessorProperty} TSAbstractAccessorProperty
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSEnumMember} TSEnumMember
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSPropertySignature} TSPropertySignature
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSIndexSignature} TSIndexSignature
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSMethodSignature} TSMethodSignature
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeParameterInstantiation} TSTypeParameterInstantiation
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeParameterDeclaration} TSTypeParameterDeclaration
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSConstructorType} TSConstructorType
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSFunctionType} TSFunctionType
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSUnionType} TSUnionType
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSIntersectionType} TSIntersectionType
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceHeritage} TSInterfaceHeritage
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSClassImplements} TSClassImplements
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSInterfaceBody
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSModuleBlock} TSModuleBlock
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSDeclareFunction} TSDeclareFunction
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSEmptyBodyFunctionExpression} TSEmptyBodyFunctionExpression
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeOperator} TSTypeOperator
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeQuery} TSTypeQuery
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSInferType} TSInferType
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSOptionalType} TSOptionalType
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSNonNullExpression} TSNonNullExpression
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSAsExpression} TSAsExpression
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSSatisfiesExpression} TSSatisfiesExpression
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeReference} TSTypeReference
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSInstantiationExpression} TSInstantiationExpression
+ * @typedef {import('@typescript-eslint/types').TSESTree.JSXChild} JSXChild
+ * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TypeNode
+ *
+ */
+/**
+ * Deprecated in @typescript-eslint/parser v5
+ * @typedef {import('@typescript-eslint/types').TSESTree.PropertyDefinition} ClassProperty
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSAbstractPropertyDefinition} TSAbstractClassProperty
+ */
+
+module.exports = {
+ defineVisitor
+}
+
+/**
+ * Process the given node list.
+ * The first node is offsetted from the given left token.
+ * Rest nodes are adjusted to the first node.
+ * @callback ProcessNodeList
+ * @param {(MaybeNode|null)[]} nodeList The node to process.
+ * @param {MaybeNode|Token|null} left The left parenthesis token.
+ * @param {MaybeNode|Token|null} right The right parenthesis token.
+ * @param {number} offset The offset to set.
+ * @param {boolean} [alignVertically=true] The flag to align vertically. If `false`, this doesn't align vertically even if the first node is not at beginning of line.
+ * @returns {void}
+ */
+/**
+ * Set offset to the given tokens.
+ * @callback SetOffset
+ * @param {Token|Token[]|null|(Token|null)[]} token The token to set.
+ * @param {number} offset The offset of the tokens.
+ * @param {Token} baseToken The token of the base offset.
+ * @returns {void}
+ */
+/**
+ *
+ * Copy offset to the given tokens from srcToken.
+ * @callback CopyOffset
+ * @param {Token} token The token to set.
+ * @param {Token} srcToken The token of the source offset.
+ * @returns {void}
+ */
+/**
+ * Process semicolons of the given statement node.
+ * @callback ProcessSemicolons
+ * @param {MaybeNode} node The statement node to process.
+ * @returns {void}
+ */
+/**
+ * Get the first and last tokens of the given node.
+ * If the node is parenthesized, this gets the outermost parentheses.
+ * @callback GetFirstAndLastTokens
+ * @param {MaybeNode} node The node to get.
+ * @param {number} [borderOffset] The least offset of the first token. Defailt is 0. This value is used to prevent false positive in the following case: `(a) => {}` The parentheses are enclosing the whole parameter part rather than the first parameter, but this offset parameter is needed to distinguish.
+ * @returns {{firstToken:Token,lastToken:Token}} The gotten tokens.
+ */
+/**
+ * @typedef {object} DefineVisitorParam
+ * @property {ProcessNodeList} processNodeList
+ * @property {ParserServices.TokenStore | SourceCode} tokenStore
+ * @property {SetOffset} setOffset
+ * @property {CopyOffset} copyOffset
+ * @property {ProcessSemicolons} processSemicolons
+ * @property {GetFirstAndLastTokens} getFirstAndLastTokens
+ */
+
+/**
+ * @param {DefineVisitorParam} param
+ * @returns {TSNodeListener}
+ */
+function defineVisitor({
+ processNodeList,
+ tokenStore,
+ setOffset,
+ copyOffset,
+ processSemicolons,
+ getFirstAndLastTokens
+}) {
+ /**
+ * Check whether a given token is the first token of:
+ *
+ * - A parameter of TSTypeParameterInstantiation
+ * - An element of TSTupleType
+ *
+ * @param {Token} token The token to check.
+ * @param {TSUnionType | TSIntersectionType} belongingNode The node that the token is belonging to.
+ * @returns {boolean} `true` if the token is the first token of an element.
+ */
+ function isBeginningOfElement(token, belongingNode) {
+ /** @type {TSESTreeNode | null} */
+ let node = belongingNode
+
+ while (node != null && node.parent != null) {
+ /** @type {TSESTreeNode} */
+ const parent = node.parent
+ if (parent.type === 'TSTypeParameterInstantiation') {
+ return (
+ parent.params.length >= 2 &&
+ parent.params.some(
+ (param) =>
+ getFirstAndLastTokens(param).firstToken.range[0] ===
+ token.range[0]
+ )
+ )
+ }
+ if (parent.type === 'TSTupleType') {
+ return parent.elementTypes.some(
+ (element) =>
+ element != null &&
+ getFirstAndLastTokens(element).firstToken.range[0] ===
+ token.range[0]
+ )
+ }
+
+ node = parent
+ }
+
+ return false
+ }
+
+ return {
+ // Support TypeScript
+ /** @param {ClassDeclaration | ClassExpression} node */
+ ['ClassDeclaration[implements], ClassDeclaration[typeParameters], ClassDeclaration[superTypeParameters],' +
+ 'ClassExpression[implements], ClassExpression[typeParameters], ClassExpression[superTypeParameters]'](
+ node
+ ) {
+ if (node.typeParameters != null) {
+ setOffset(
+ tokenStore.getFirstToken(node.typeParameters),
+ 1,
+ tokenStore.getFirstToken(node.id || node)
+ )
+ }
+ if (node.superTypeParameters != null && node.superClass != null) {
+ setOffset(
+ tokenStore.getFirstToken(node.superTypeParameters),
+ 1,
+ tokenStore.getFirstToken(node.superClass)
+ )
+ }
+ if (node.implements != null && node.implements.length > 0) {
+ const classToken = tokenStore.getFirstToken(node)
+ const implementsToken = tokenStore.getTokenBefore(node.implements[0])
+ setOffset(implementsToken, 1, classToken)
+ processNodeList(node.implements, implementsToken, null, 1)
+ }
+ },
+ // Process semicolons.
+ /**
+ * @param {TSTypeAliasDeclaration
+ * | TSCallSignatureDeclaration
+ * | TSConstructSignatureDeclaration
+ * | TSImportEqualsDeclaration
+ * | TSAbstractMethodDefinition
+ * | TSAbstractPropertyDefinition
+ * | TSAbstractAccessorProperty
+ * | TSEnumMember
+ * | TSPropertySignature
+ * | TSIndexSignature
+ * | TSMethodSignature
+ * | ClassProperty
+ * | TSAbstractClassProperty} node
+ */
+ ['TSTypeAliasDeclaration, TSCallSignatureDeclaration, TSConstructSignatureDeclaration, TSImportEqualsDeclaration,' +
+ 'TSAbstractMethodDefinition, TSAbstractPropertyDefinition, TSAbstractAccessorProperty, TSEnumMember,' +
+ 'TSPropertySignature, TSIndexSignature, TSMethodSignature,' +
+ // Deprecated in @typescript-eslint/parser v5
+ 'ClassProperty, TSAbstractClassProperty'](node) {
+ processSemicolons(node)
+ },
+ /**
+ * @param {ASTNode} node
+ */
+ '*[type=/^TS/]'(node) {
+ if (!isTypeNode(node)) {
+ return
+ }
+ const typeNode = node
+ if (/** @type {any} */ (typeNode.parent).type === 'TSParenthesizedType') {
+ return
+ }
+ // Process parentheses.
+ let leftToken = tokenStore.getTokenBefore(node)
+ let rightToken = tokenStore.getTokenAfter(node)
+ let firstToken = tokenStore.getFirstToken(node)
+
+ while (
+ leftToken &&
+ rightToken &&
+ isOpeningParenToken(leftToken) &&
+ isClosingParenToken(rightToken)
+ ) {
+ setOffset(firstToken, 1, leftToken)
+ setOffset(rightToken, 0, leftToken)
+
+ firstToken = leftToken
+ leftToken = tokenStore.getTokenBefore(leftToken)
+ rightToken = tokenStore.getTokenAfter(rightToken)
+ }
+ },
+ /**
+ * Process type annotation
+ *
+ * e.g.
+ * ```
+ * const foo: Type
+ * // ^^^^^^
+ * type foo = () => string
+ * // ^^^^^^^^^
+ * ```
+ */
+ TSTypeAnnotation(node) {
+ const [colonOrArrowToken, secondToken] = tokenStore.getFirstTokens(node, {
+ count: 2,
+ includeComments: false
+ })
+ const baseToken = tokenStore.getFirstToken(
+ /** @type {HasLocation} */ (node.parent)
+ )
+ setOffset([colonOrArrowToken, secondToken], 1, baseToken)
+
+ // a ?: T
+ const before = tokenStore.getTokenBefore(colonOrArrowToken)
+ if (before && before.value === '?') {
+ setOffset(before, 1, baseToken)
+ }
+ },
+ /**
+ * Process as expression or satisfies expression
+ *
+ * e.g.
+ * ```
+ * var foo = bar as boolean
+ * // ^^^^^^^^^^^^^^
+ * ```
+ *
+ * e.g.
+ * ```
+ * var foo = bar satisfies Bar
+ * // ^^^^^^^^^^^^^^^^^
+ * ```
+ *
+ * @param {TSAsExpression | TSSatisfiesExpression} node
+ */
+ 'TSAsExpression, TSSatisfiesExpression'(node) {
+ const expressionTokens = getFirstAndLastTokens(node.expression)
+ const asOrSatisfiesToken = tokenStore.getTokenAfter(
+ expressionTokens.lastToken
+ )
+ setOffset(
+ [
+ asOrSatisfiesToken,
+ getFirstAndLastTokens(node.typeAnnotation).firstToken
+ ],
+ 1,
+ expressionTokens.firstToken
+ )
+ },
+ /**
+ * Process type reference and instantiation expression
+ *
+ * e.g.
+ * ```
+ * const foo: Type
+
+
+ `,
+ options: [['composition']]
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is part of the Options API. Use `
+ `,
+ options: [['options']],
+ errors: [
+ {
+ message:
+ '`
+ `,
+ options: [['options']],
+ errors: [
+ {
+ message:
+ 'Composition API is not allowed in your project. `setup` function is part of the Composition API. Use Options API instead.',
+ line: 5,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.js',
+ code: `
+ import { defineComponent } from 'vue'
+ defineComponent({
+ data () {
+ return {
+ msg: 'Hello World!',
+ // ...
+ }
+ },
+ // ...
+ })
+ `,
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is part of the Options API. Use Composition API instead.',
+ line: 4,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.js',
+ code: `
+ import { ref, defineComponent } from 'vue'
+ defineComponent({
+ setup() {
+ const msg = ref('Hello World!')
+ // ...
+ return {
+ msg,
+ // ...
+ }
+ }
+ })
+ `,
+ options: [['options']],
+ errors: [
+ {
+ message:
+ 'Composition API is not allowed in your project. `setup` function is part of the Composition API. Use Options API instead.',
+ line: 4,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['script-setup']],
+ errors: [
+ {
+ message:
+ 'Composition API is not allowed in your project. Use `
+ `,
+ options: [['script-setup']],
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. Use `
+ `,
+ options: [['composition']],
+ errors: [
+ {
+ message:
+ '`
+ `,
+ options: [['composition']],
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is part of the Options API. Use Composition API instead.',
+ line: 4,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['composition']],
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `mixins` option is part of the Options API. Use Composition API instead.',
+ line: 4,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `extends` option is part of the Options API. Use Composition API instead.',
+ line: 5,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is part of the Options API. Use Composition API instead.',
+ line: 7,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `computed` option is part of the Options API. Use Composition API instead.',
+ line: 8,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `methods` option is part of the Options API. Use Composition API instead.',
+ line: 9,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `watch` option is part of the Options API. Use Composition API instead.',
+ line: 10,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `provide` option is part of the Options API. Use Composition API instead.',
+ line: 11,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `inject` option is part of the Options API. Use Composition API instead.',
+ line: 12,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeCreate` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 14,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `created` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 15,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeMount` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 16,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `mounted` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 17,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeUpdate` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 18,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `updated` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 19,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `activated` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 20,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `deactivated` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 21,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeDestroy` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 22,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeUnmount` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 23,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `destroyed` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 24,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `unmounted` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 25,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `render` function is part of the Options API. Use Composition API instead.',
+ line: 26,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `renderTracked` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 27,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `renderTriggered` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 28,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `errorCaptured` lifecycle hook is part of the Options API. Use Composition API instead.',
+ line: 29,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `expose` option is part of the Options API. Use Composition API instead.',
+ line: 31,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['composition-vue2']],
+ errors: [
+ {
+ message:
+ '`
+ `,
+ options: [['composition-vue2']],
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 4,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['composition-vue2']],
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `mixins` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 4,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `extends` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 5,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 7,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `computed` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 8,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `methods` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 9,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `watch` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 10,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `provide` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 11,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `inject` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 12,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeCreate` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 14,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `created` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 15,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeMount` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 16,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `mounted` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 17,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeUpdate` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 18,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `updated` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 19,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `activated` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 20,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `deactivated` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 21,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeDestroy` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 22,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeUnmount` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 23,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `destroyed` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 24,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `unmounted` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 25,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `errorCaptured` lifecycle hook is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 29,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `expose` option is part of the Options API. Use Composition API (Vue 2) instead.',
+ line: 31,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['script-setup', 'composition', 'composition-vue2']],
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is part of the Options API. Use ``,
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ options: ['PascalCase'],
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ options: ['kebab-case'],
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions
+ }
}
],
@@ -169,7 +187,7 @@ ruleTester.run('component-definition-name-casing', rule, {
name: 'FooBar'
}
`,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo-bar" is not PascalCase.',
@@ -186,7 +204,7 @@ ruleTester.run('component-definition-name-casing', rule, {
}
`,
output: null,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo bar" is not PascalCase.',
@@ -203,7 +221,7 @@ ruleTester.run('component-definition-name-casing', rule, {
}
`,
output: null,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo!bar" is not PascalCase.',
@@ -220,7 +238,7 @@ ruleTester.run('component-definition-name-casing', rule, {
})
`,
output: null,
- parserOptions: { ecmaVersion: 6 },
+ languageOptions: { ecmaVersion: 6 },
errors: [
{
message: 'Property name "foo!bar" is not PascalCase.',
@@ -241,7 +259,7 @@ ruleTester.run('component-definition-name-casing', rule, {
name: 'FooBar'
}
`,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo_bar" is not PascalCase.',
@@ -263,7 +281,7 @@ ruleTester.run('component-definition-name-casing', rule, {
}
`,
options: ['PascalCase'],
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo_bar" is not PascalCase.',
@@ -285,7 +303,7 @@ ruleTester.run('component-definition-name-casing', rule, {
}
`,
options: ['kebab-case'],
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo_bar" is not kebab-case.',
@@ -298,7 +316,7 @@ ruleTester.run('component-definition-name-casing', rule, {
filename: 'test.vue',
code: `Vue.component('foo-bar', component)`,
output: `Vue.component('FooBar', component)`,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo-bar" is not PascalCase.',
@@ -311,7 +329,7 @@ ruleTester.run('component-definition-name-casing', rule, {
filename: 'test.vue',
code: `app.component('foo-bar', component)`,
output: `app.component('FooBar', component)`,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo-bar" is not PascalCase.',
@@ -324,8 +342,10 @@ ruleTester.run('component-definition-name-casing', rule, {
filename: 'test.vue',
code: `(Vue as VueConstructor).component('foo-bar', component)`,
output: `(Vue as VueConstructor).component('FooBar', component)`,
- parserOptions,
- parser: require.resolve('@typescript-eslint/parser'),
+ languageOptions: {
+ parser: require('@typescript-eslint/parser'),
+ ...languageOptions
+ },
errors: [
{
message: 'Property name "foo-bar" is not PascalCase.',
@@ -338,7 +358,7 @@ ruleTester.run('component-definition-name-casing', rule, {
filename: 'test.vue',
code: `Vue.component('foo-bar', {})`,
output: `Vue.component('FooBar', {})`,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo-bar" is not PascalCase.',
@@ -351,7 +371,7 @@ ruleTester.run('component-definition-name-casing', rule, {
filename: 'test.vue',
code: `app.component('foo-bar', {})`,
output: `app.component('FooBar', {})`,
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo-bar" is not PascalCase.',
@@ -365,7 +385,7 @@ ruleTester.run('component-definition-name-casing', rule, {
code: `Vue.component('foo_bar', {})`,
output: `Vue.component('FooBar', {})`,
options: ['PascalCase'],
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo_bar" is not PascalCase.',
@@ -379,7 +399,7 @@ ruleTester.run('component-definition-name-casing', rule, {
code: `Vue.component('foo_bar', {})`,
output: `Vue.component('foo-bar', {})`,
options: ['kebab-case'],
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo_bar" is not kebab-case.',
@@ -393,7 +413,7 @@ ruleTester.run('component-definition-name-casing', rule, {
code: `Vue.component(\`foo_bar\`, {})`,
output: `Vue.component(\`foo-bar\`, {})`,
options: ['kebab-case'],
- parserOptions,
+ languageOptions,
errors: [
{
message: 'Property name "foo_bar" is not kebab-case.',
@@ -401,6 +421,38 @@ ruleTester.run('component-definition-name-casing', rule, {
line: 1
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ output: ``,
+ options: ['PascalCase'],
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions
+ },
+ errors: [
+ {
+ message: 'Property name "foo-bar" is not PascalCase.',
+ line: 1
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ output: ``,
+ options: ['kebab-case'],
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions
+ },
+ errors: [
+ {
+ message: 'Property name "FooBar" is not kebab-case.',
+ line: 1
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js
index 31c4ae832..44a23d532 100644
--- a/tests/lib/rules/component-name-in-template-casing.js
+++ b/tests/lib/rules/component-name-in-template-casing.js
@@ -3,20 +3,13 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const rule = require('../../../lib/rules/component-name-in-template-casing')
-const RuleTester = require('eslint').RuleTester
-
-// ------------------------------------------------------------------------------
-// Tests
-// ------------------------------------------------------------------------------
+const semver = require('semver')
+const RuleTester = require('../../eslint-compat').RuleTester
const tester = new RuleTester({
- parser: require.resolve('vue-eslint-parser'),
- parserOptions: {
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
ecmaVersion: 2018,
sourceType: 'module'
}
@@ -26,6 +19,7 @@ tester.run('component-name-in-template-casing', rule, {
valid: [
// default
{
+ filename: 'test.vue',
code: `
@@ -40,8 +34,7 @@ tester.run('component-name-in-template-casing', rule, {
}
}
- `,
- filename: 'test.vue'
+ `
},
// element types test
@@ -69,6 +62,10 @@ tester.run('component-name-in-template-casing', rule, {
code: '