+
+
+
+
+
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
new file mode 100644
index 000000000..757e63cab
--- /dev/null
+++ b/docs/.vitepress/theme/index.ts
@@ -0,0 +1,32 @@
+// @ts-expect-error -- Browser
+if (typeof window !== 'undefined') {
+ if (typeof require === 'undefined') {
+ // @ts-expect-error -- Browser
+ ;(window as any).require = () => {
+ const e = new Error('require is not defined')
+ ;(e as any).code = 'MODULE_NOT_FOUND'
+ throw e
+ }
+ }
+}
+// @ts-expect-error -- Cannot change `module` option
+import type { Theme } from 'vitepress'
+// @ts-expect-error -- Cannot change `module` option
+import DefaultTheme from 'vitepress/theme'
+// @ts-expect-error -- ignore
+import Layout from './Layout.vue'
+// @ts-expect-error -- ignore
+import ESLintCodeBlock from './components/eslint-code-block.vue'
+// @ts-expect-error -- ignore
+import RulesTable from './components/rules-table.vue'
+
+const theme: Theme = {
+ ...DefaultTheme,
+ Layout,
+ enhanceApp(ctx) {
+ DefaultTheme.enhanceApp(ctx)
+ ctx.app.component('eslint-code-block', ESLintCodeBlock)
+ ctx.app.component('rules-table', RulesTable)
+ }
+}
+export default theme
diff --git a/docs/.vitepress/vite-plugin.mts b/docs/.vitepress/vite-plugin.mts
new file mode 100644
index 000000000..cd6811cb6
--- /dev/null
+++ b/docs/.vitepress/vite-plugin.mts
@@ -0,0 +1,83 @@
+import type { UserConfig } from 'vitepress'
+import path from 'pathe'
+import { fileURLToPath } from 'url'
+import esbuild from 'esbuild'
+type Plugin = Extract<
+ NonNullable['plugins']>[number],
+ { name: string }
+>
+
+const libRoot = path.join(fileURLToPath(import.meta.url), '../../../lib')
+export function vitePluginRequireResolve(): Plugin {
+ return {
+ name: 'vite-plugin-require.resolve',
+ transform(code, id, _options) {
+ if (id.startsWith(libRoot)) {
+ return code.replace(/require\.resolve/gu, '(function(){return 0})')
+ }
+ return undefined
+ }
+ }
+}
+
+export function viteCommonjs(): Plugin {
+ return {
+ name: 'vite-plugin-cjs-to-esm',
+ apply: () => true,
+ async transform(code, id) {
+ if (!id.startsWith(libRoot)) {
+ return undefined
+ }
+ const base = transformRequire(code)
+ try {
+ const transformed = esbuild.transformSync(base, {
+ format: 'esm'
+ })
+ return transformed.code
+ } catch (e) {
+ console.error('Transform error. base code:\n' + base, e)
+ }
+ return undefined
+ }
+ }
+}
+
+/**
+ * Transform `require()` to `import`
+ */
+function transformRequire(code: string) {
+ if (!code.includes('require')) {
+ return code
+ }
+ const modules = new Map()
+ const replaced = code.replace(
+ /(\/\/[^\n\r]*|\/\*[\s\S]*?\*\/)|\brequire\s*\(\s*(["'].*?["'])\s*\)/gu,
+ (match, comment, moduleString) => {
+ if (comment) {
+ return match
+ }
+
+ let id =
+ '__' +
+ moduleString.replace(/[^a-zA-Z0-9_$]+/gu, '_') +
+ Math.random().toString(32).substring(2)
+ while (code.includes(id) || modules.has(id)) {
+ id += Math.random().toString(32).substring(2)
+ }
+ modules.set(id, moduleString)
+ return id + '()'
+ }
+ )
+
+ return (
+ [...modules]
+ .map(([id, moduleString]) => {
+ return `import * as __temp_${id} from ${moduleString};
+const ${id} = () => __temp_${id}.default || __temp_${id};
+`
+ })
+ .join('') +
+ ';\n' +
+ replaced
+ )
+}
diff --git a/docs/developer-guide/index.md b/docs/developer-guide/index.md
new file mode 100644
index 000000000..2e1bb7da9
--- /dev/null
+++ b/docs/developer-guide/index.md
@@ -0,0 +1,60 @@
+# Developer Guide
+
+Contributing is welcome.
+
+## :bug: Bug reporting
+
+If you think you’ve found a bug in ESLint, please [create a new issue](https://github.com/vuejs/eslint-plugin-vue/issues/new?labels=&template=bug_report.md) or a pull request on GitHub.
+
+Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that’s time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues.
+
+## :sparkles: Proposing a new rule or a rule change
+
+In order to add a new rule or a rule change, you should:
+
+- Create issue on GitHub with description of proposed rule
+- Generate a new rule using the `npm run new ` command
+- Write test scenarios & implement logic
+- Describe the rule in the generated `docs` file
+- Make sure all tests are passing
+- Run `npm run lint` and fix any errors
+- Run `npm run update` in order to update readme and recommended configuration
+- Create PR and link created issue in description
+
+We're more than happy to see potential contributions, so don't hesitate. If you have any suggestions, ideas or problems feel free to add new [issue](https://github.com/vuejs/eslint-plugin-vue/issues), but first please make sure your question does not repeat previous ones.
+
+## :fire: Working with rules
+
+Before you start writing new rule, please read the [official ESLint guide](https://eslint.org/docs/developer-guide/working-with-rules).
+
+Next, in order to get an idea how does the AST of the code that you want to check looks like, use the [astexplorer.net].
+The [astexplorer.net] is a great tool to inspect ASTs, also Vue templates are supported.
+
+After opening [astexplorer.net], select `Vue` as the syntax and `vue-eslint-parser` as the parser.
+
+[astexplorer.net]: https://astexplorer.net/
+
+Since single file components in Vue are not plain JavaScript, we can't use the default parser, and we had to introduce additional one: `vue-eslint-parser`, that generates enhanced AST with nodes that represent specific parts of the template syntax, as well as what's inside the `
+```
+
+
+
+
+
+```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
new file mode 100644
index 000000000..e9f16b8d0
--- /dev/null
+++ b/docs/rules/block-spacing.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/block-spacing
+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 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 ``.
+
+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/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
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [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 Stylistic](https://eslint.style/rules/ts/block-spacing)
diff --git a/docs/rules/block-tag-newline.md b/docs/rules/block-tag-newline.md
new file mode 100644
index 000000000..8e336b284
--- /dev/null
+++ b/docs/rules/block-tag-newline.md
@@ -0,0 +1,168 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule enforces a line break (or no line break) after opening and before closing block tags.
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/block-tag-newline": ["error", {
+ "singleline": "always" | "never" | "consistent" | "ignore",
+ "multiline": "always" | "never" | "consistent" | "ignore",
+ "maxEmptyLines": 0,
+ "blocks": {
+ "template": {
+ "singleline": "always" | "never" | "consistent" | "ignore",
+ "multiline": "always" | "never" | "consistent" | "ignore",
+ "maxEmptyLines": 0,
+ },
+ "script": {
+ "singleline": "always" | "never" | "consistent" | "ignore",
+ "multiline": "always" | "never" | "consistent" | "ignore",
+ "maxEmptyLines": 0,
+ },
+ "my-block": {
+ "singleline": "always" | "never" | "consistent" | "ignore",
+ "multiline": "always" | "never" | "consistent" | "ignore",
+ "maxEmptyLines": 0,
+ }
+ }
+ }]
+}
+```
+
+- `singleline` ... the configuration for single-line blocks.
+ - `"consistent"` ... (default) requires consistent usage of line breaks for each pair of tags. It reports an error if one tag in the pair has a linebreak inside it and the other tag does not.
+ - `"always"` ... require one line break after opening and before closing block tags.
+ - `"never"` ... disallow line breaks after opening and before closing block tags.
+- `multiline` ... the configuration for multi-line blocks.
+ - `"consistent"` ... requires consistent usage of line breaks for each pair of tags. It reports an error if one tag in the pair has a linebreak inside it and the other tag does not.
+ - `"always"` ... (default) require one line break after opening and before closing block tags.
+ - `"never"` ... disallow line breaks after opening and before closing block tags.
+- `maxEmptyLines` ... specifies the maximum number of empty lines allowed. default 0.
+- `blocks` ... specifies for each block name.
+
+### `{ "singleline": "never", "multiline": "always" }`
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+### `{ "singleline": "always", "multiline": "always", "maxEmptyLines": 1 }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/block-tag-newline.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/block-tag-newline.js)
diff --git a/docs/rules/brace-style.md b/docs/rules/brace-style.md
new file mode 100644
index 000000000..1727f42fb
--- /dev/null
+++ b/docs/rules/brace-style.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/brace-style
+description: Enforce consistent brace style for blocks in ``
+since: v5.2.0
+---
+
+# vue/brace-style
+
+> 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 ``.
+
+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/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
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [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 Stylistic](https://eslint.style/rules/ts/brace-style)
diff --git a/docs/rules/camelcase.md b/docs/rules/camelcase.md
new file mode 100644
index 000000000..6ed97876a
--- /dev/null
+++ b/docs/rules/camelcase.md
@@ -0,0 +1,30 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/camelcase
+description: Enforce camelcase naming convention in ``
+since: v5.2.0
+---
+
+# vue/camelcase
+
+> Enforce camelcase naming convention in ``
+
+This rule is the same rule as core [camelcase] rule but it applies to the expressions in ``.
+
+## :books: Further Reading
+
+- [camelcase]
+
+[camelcase]: https://eslint.org/docs/rules/camelcase
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [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/latest/rules/camelcase)
diff --git a/docs/rules/comma-dangle.md b/docs/rules/comma-dangle.md
new file mode 100644
index 000000000..aceaa6dc1
--- /dev/null
+++ b/docs/rules/comma-dangle.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/comma-dangle
+description: Require or disallow trailing commas in ``
+since: v5.2.0
+---
+
+# vue/comma-dangle
+
+> 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 ``.
+
+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/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
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [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 Stylistic](https://eslint.style/rules/ts/comma-dangle)
diff --git a/docs/rules/comma-spacing.md b/docs/rules/comma-spacing.md
new file mode 100644
index 000000000..b585fadf7
--- /dev/null
+++ b/docs/rules/comma-spacing.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/comma-spacing
+description: Enforce consistent spacing before and after commas in ``
+since: v7.0.0
+---
+
+# vue/comma-spacing
+
+> 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 ``.
+
+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/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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [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 Stylistic](https://eslint.style/rules/ts/comma-spacing)
diff --git a/docs/rules/comma-style.md b/docs/rules/comma-style.md
new file mode 100644
index 000000000..9e08fdaaf
--- /dev/null
+++ b/docs/rules/comma-style.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/comma-style
+description: Enforce consistent comma style in ``
+since: v7.0.0
+---
+
+# vue/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 ``.
+
+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/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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [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 Stylistic](https://eslint.style/rules/js/comma-style)
diff --git a/docs/rules/comment-directive.md b/docs/rules/comment-directive.md
index 42a12a860..294dcc15c 100644
--- a/docs/rules/comment-directive.md
+++ b/docs/rules/comment-directive.md
@@ -1,8 +1,18 @@
-# support comment-directives in `` (vue/comment-directive)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/comment-directive
+description: support comment-directives in ``
+since: v4.1.0
+---
-- :gear: This rule is included in all of `"plugin:vue/base"`, `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/comment-directive
-Sole purpose of this rule is to provide `eslint-disable` functionality in ``.
+> support comment-directives in ``
+
+- :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:
- `eslint-disable`
@@ -10,26 +20,121 @@ It supports usage of the following comments:
- `eslint-disable-line`
- `eslint-disable-next-line`
-For example:
+::: warning Note
+We can't write HTML comments in tags.
+:::
+
+## :book: Rule Details
+
+ESLint doesn't provide any API to enhance `eslint-disable` functionality and ESLint rules cannot affect other rules. But ESLint provides [processors API](https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins).
+
+This rule sends all `eslint-disable`-like comments as errors to the post-process of the `.vue` file processor, then the post-process removes all `vue/comment-directive` errors and the reported errors in disabled areas.
+
+
-```html
+```vue
-
-
+
```
-> Note: we can't write HTML comments in tags.
+
-This rule doesn't throw any warning.
+The `eslint-disable`-like comments can be used in the `` and in the block level.
-## :book: Rule Details
+
-ESLint doesn't provide any API to enhance `eslint-disable` functionality and ESLint rules cannot affect other rules. But ESLint provides [processors API](https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins).
+```vue
+
+
+
+
-This rule sends all `eslint-disable`-like comments as errors to the post-process of the `.vue` file processor, then the post-process removes all `vue/comment-directive` errors and the reported errors in disabled areas.
+
+
+```
+
+
+
+The `eslint-disable` comments has no effect after one block.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+The `eslint-disable`-like comments can include descriptions to explain why the comment is necessary. The description must occur after the directive and is separated from the directive by two or more consecutive `-` characters. For example:
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/comment-directive": ["error", {
+ "reportUnusedDisableDirectives": false
+ }]
+}
+```
+
+- `reportUnusedDisableDirectives` ... If `true`, to report unused `eslint-disable` HTML comments. default `false`
+
+### `{ "reportUnusedDisableDirectives": true }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Note
+Unused reports cannot be suppressed with `eslint-disable` HTML comments.
+:::
+
+## :books: Further Reading
+
+- [Disabling rules with inline comments]
+
+[Disabling rules with inline comments]: https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v4.1.0
-## :books: Further reading
+## :mag: Implementation
-- [Disabling rules with inline comments](https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/comment-directive.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/comment-directive.js)
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
new file mode 100644
index 000000000..18c625970
--- /dev/null
+++ b/docs/rules/component-definition-name-casing.md
@@ -0,0 +1,126 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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.
+
+## :book: Rule Details
+
+This rule aims to warn the component definition names other than the configured casing.
+
+## :wrench: Options
+
+Default casing is set to `PascalCase`.
+
+```json
+{
+ "vue/component-definition-name-casing": ["error", "PascalCase" | "kebab-case"]
+}
+```
+
+- `"PascalCase"` (default) ... enforce component definition names to pascal case.
+- `"kebab-case"` ... enforce component definition names to kebab case.
+
+### `"PascalCase"` (default)
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```js
+/* ✓ GOOD */
+Vue.component('MyComponent', {})
+
+/* ✗ BAD */
+Vue.component('my-component', {})
+```
+
+
+
+### `"kebab-case"`
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```js
+/* ✓ GOOD */
+Vue.component('my-component', {})
+
+/* ✗ BAD */
+Vue.component('MyComponent', {})
+```
+
+
+
+## :books: Further Reading
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-definition-name-casing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-definition-name-casing.js)
diff --git a/docs/rules/component-name-in-template-casing.md b/docs/rules/component-name-in-template-casing.md
new file mode 100644
index 000000000..dd59d40e4
--- /dev/null
+++ b/docs/rules/component-name-in-template-casing.md
@@ -0,0 +1,173 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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#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.
+
+## :book: Rule Details
+
+This rule aims to warn the tag names other than the configured casing in Vue.js template.
+
+## :wrench: Options
+
+```json
+{
+ "vue/component-name-in-template-casing": ["error", "PascalCase" | "kebab-case", {
+ "registeredComponentsOnly": true,
+ "ignores": []
+ }]
+}
+```
+
+- `"PascalCase"` (default) ... enforce tag names to pascal case. E.g. ``. This is consistent with the JSX practice.
+- `"kebab-case"` ... enforce tag names to kebab case: E.g. ``. This is consistent with the HTML practice which is case-insensitive originally.
+- `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)
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"kebab-case"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"PascalCase", { registeredComponentsOnly: false }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"PascalCase", { ignores: ["/^custom-/"], registeredComponentsOnly: false }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"PascalCase", { globals: ["RouterView"] }`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [Style guide - Component name casing in templates](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-name-casing-in-templates)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-name-in-template-casing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-name-in-template-casing.js)
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
new file mode 100644
index 000000000..a5037cdee
--- /dev/null
+++ b/docs/rules/component-tags-order.md
@@ -0,0 +1,199 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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
+
+- :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 top-level tags, such as `
+...
+
+```
+
+
+
+
+
+```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 v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-tags-order.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-tags-order.js)
diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md
new file mode 100644
index 000000000..a57e0eb80
--- /dev/null
+++ b/docs/rules/custom-event-name-casing.md
@@ -0,0 +1,187 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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
+
+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. (Default is **camelCase**.)
+
+Vue 2 recommends using kebab-case for custom event names.
+
+> Event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, `v-on` event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so `v-on:myEvent` would become `v-on:myevent` – making `myEvent` impossible to listen to.
+>
+> For these reasons, we recommend you **always use kebab-case for event names**.
+
+See [Guide (for v2) - Custom Events] for more details.
+
+In Vue 3, using either camelCase or kebab-case for your custom event name does not limit its use in v-on. However, following JavaScript conventions, camelCase is more natural.
+
+See [Guide - Custom Events] for more details.
+
+This rule enforces camelCase by default.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/custom-event-name-casing": ["error",
+ "camelCase" | "kebab-case",
+ {
+ "ignores": []
+ }
+ ]
+}
+```
+
+- `"camelCase"` (default) ... Enforce custom event names to camelCase.
+- `"kebab-case"` ... Enforce custom event names to kebab-case.
+- `ignores` (`string[]`) ... The event names to ignore. Sets the event name to allow. For example, custom event names, Vue components event with special name, or Vue library component event name. You can set the regexp by writing it like `"/^name/"` or `click:row` or `fooBar`.
+
+### `"kebab-case"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"camelCase"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"ignores": ["foo-bar", "/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u"]`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
+- [vue/prop-name-casing](./prop-name-casing.md)
+
+## :books: Further Reading
+
+- [Guide - Custom Events]
+- [Guide (for v2) - Custom Events]
+
+[Guide - Custom Events]: https://vuejs.org/guide/components/events.html
+[Guide (for v2) - Custom Events]: https://v2.vuejs.org/v2/guide/components-custom-events.html
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/custom-event-name-casing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/custom-event-name-casing.js)
diff --git a/docs/rules/define-emits-declaration.md b/docs/rules/define-emits-declaration.md
new file mode 100644
index 000000000..c24f5ec62
--- /dev/null
+++ b/docs/rules/define-emits-declaration.md
@@ -0,0 +1,133 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/define-emits-declaration
+description: enforce declaration style of `defineEmits`
+since: v9.5.0
+---
+
+# vue/define-emits-declaration
+
+> enforce declaration style of `defineEmits`
+
+## :book: Rule Details
+
+This rule enforces `defineEmits` typing style which you should use `type-based`, strict `type-literal`
+(introduced in Vue 3.3), or `runtime` declaration.
+
+This rule only works in setup script and `lang="ts"`.
+
+
+
+```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
new file mode 100644
index 000000000..edb785080
--- /dev/null
+++ b/docs/rules/dot-location.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/dot-location
+description: Enforce consistent newlines before and after dots in ``
+since: v6.0.0
+---
+
+# vue/dot-location
+
+> 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 ``.
+
+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/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
+
+This rule was introduced in eslint-plugin-vue v6.0.0
+
+## :mag: Implementation
+
+- [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 Stylistic](https://eslint.style/rules/js/dot-location)
diff --git a/docs/rules/dot-notation.md b/docs/rules/dot-notation.md
new file mode 100644
index 000000000..101caf5cf
--- /dev/null
+++ b/docs/rules/dot-notation.md
@@ -0,0 +1,32 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/dot-notation
+description: Enforce dot notation whenever possible in ``
+since: v7.0.0
+---
+
+# vue/dot-notation
+
+> Enforce dot notation whenever possible 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 [dot-notation] rule but it applies to the expressions in ``.
+
+## :books: Further Reading
+
+- [dot-notation]
+
+[dot-notation]: https://eslint.org/docs/rules/dot-notation
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [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/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
new file mode 100644
index 000000000..392deff7f
--- /dev/null
+++ b/docs/rules/eqeqeq.md
@@ -0,0 +1,32 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/eqeqeq
+description: Require the use of `===` and `!==` in ``
+since: v5.2.0
+---
+
+# vue/eqeqeq
+
+> Require the use of `===` and `!==` 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 [eqeqeq] rule but it applies to the expressions in ``.
+
+## :books: Further Reading
+
+- [eqeqeq]
+
+[eqeqeq]: https://eslint.org/docs/rules/eqeqeq
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [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/latest/rules/eqeqeq)
diff --git a/docs/rules/experimental-script-setup-vars.md b/docs/rules/experimental-script-setup-vars.md
new file mode 100644
index 000000000..82e7de884
--- /dev/null
+++ b/docs/rules/experimental-script-setup-vars.md
@@ -0,0 +1,49 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/experimental-script-setup-vars
+description: prevent variables defined in `
+```
+
+
+
+After turning on, `props` and `emit` are being marked as defined and `no-undef` rule doesn't report an issue.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/experimental-script-setup-vars.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/experimental-script-setup-vars.js)
diff --git a/docs/rules/first-attribute-linebreak.md b/docs/rules/first-attribute-linebreak.md
new file mode 100644
index 000000000..6292ef262
--- /dev/null
+++ b/docs/rules/first-attribute-linebreak.md
@@ -0,0 +1,169 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/first-attribute-linebreak
+description: enforce the location of first attribute
+since: v8.0.0
+---
+
+# vue/first-attribute-linebreak
+
+> enforce the location of first attribute
+
+- :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
+
+This rule aims to enforce a consistent location for the first attribute.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/first-attribute-linebreak": ["error", {
+ "singleline": "ignore",
+ "multiline": "below"
+ }]
+}
+```
+
+- `singleline` ... The location of the first attribute when the attributes on single line. Default is `"ignore"`.
+ - `"below"` ... Requires a newline before the first attribute.
+ - `"beside"` ... Disallows a newline before the first attribute.
+ - `"ignore"` ... Ignores attribute checking.
+- `multiline` ... The location of the first attribute when the attributes span multiple lines. Default is `"below"`.
+ - `"below"` ... Requires a newline before the first attribute.
+ - `"beside"` ... Disallows a newline before the first attribute.
+ - `"ignore"` ... Ignores attribute checking.
+
+### `"singleline": "beside"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"singleline": "below"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"multiline": "beside"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"multiline": "below"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/max-attributes-per-line](./max-attributes-per-line.md)
+
+## :books: Further Reading
+
+- [Style guide - Multi attribute elements](https://vuejs.org/style-guide/rules-strongly-recommended.html#multi-attribute-elements)
+
+## :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/first-attribute-linebreak.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/first-attribute-linebreak.js)
diff --git a/docs/rules/func-call-spacing.md b/docs/rules/func-call-spacing.md
new file mode 100644
index 000000000..f330255d1
--- /dev/null
+++ b/docs/rules/func-call-spacing.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/func-call-spacing
+description: Require or disallow spacing between function identifiers and their invocations in ``
+since: v7.0.0
+---
+
+# vue/func-call-spacing
+
+> Require or disallow spacing between function identifiers and their invocations 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/function-call-spacing] 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/function-call-spacing]
+- [func-call-spacing]
+
+[@stylistic/function-call-spacing]: https://eslint.style/rules/default/function-call-spacing
+[func-call-spacing]: https://eslint.org/docs/rules/func-call-spacing
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/func-call-spacing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/func-call-spacing.js)
+
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/function-call-spacing)
diff --git a/docs/rules/html-button-has-type.md b/docs/rules/html-button-has-type.md
new file mode 100644
index 000000000..e507d42b8
--- /dev/null
+++ b/docs/rules/html-button-has-type.md
@@ -0,0 +1,67 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-button-has-type
+description: disallow usage of button without an explicit type attribute
+since: v7.6.0
+---
+
+# vue/html-button-has-type
+
+> disallow usage of button without an explicit type attribute
+
+Forgetting the type attribute on a button defaults it to being a submit type.
+This is nearly never what is intended, especially in your average one-page application.
+
+## :book: Rule Details
+
+This rule aims to warn if no type or an invalid type is used on a button type attribute.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/html-button-has-type": ["error", {
+ "button": true,
+ "submit": true,
+ "reset": true
+ }]
+}
+```
+
+- `button` ... ``
+ - `true` (default) ... allow value `button`.
+ - `false` ... disallow value `button`.
+- `submit` ... ``
+ - `true` (default) ... allow value `submit`.
+ - `false` ... disallow value `submit`.
+- `reset` ... ``
+ - `true` (default) ... allow value `reset`.
+ - `false` ... disallow value `reset`.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.6.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-button-has-type.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-button-has-type.js)
diff --git a/docs/rules/html-closing-bracket-newline.md b/docs/rules/html-closing-bracket-newline.md
index 4d2e9aed1..964317590 100644
--- a/docs/rules/html-closing-bracket-newline.md
+++ b/docs/rules/html-closing-bracket-newline.md
@@ -1,95 +1,137 @@
-# require or disallow a line break before tag's closing brackets (vue/html-closing-bracket-newline)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-closing-bracket-newline
+description: require or disallow a line break before tag's closing brackets
+since: v4.1.0
+---
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/html-closing-bracket-newline
-People have own preference about the location of closing brackets.
+> require or disallow a line break before tag's closing brackets
+
+- :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.
+
+People have their own preference about the location of closing brackets.
This rule enforces a line break (or no line break) before tag's closing brackets.
```html
+ id="foo"
+ class="bar">
```
-## Rule Details
+## :book: Rule Details
+
+This rule aims to warn the right angle brackets which are at the location other than the configured location.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
```json
{
- "html-closing-bracket-newline": ["error", {
+ "vue/html-closing-bracket-newline": [
+ "error",
+ {
+ "singleline": "never",
+ "multiline": "always",
+ "selfClosingTag": {
"singleline": "never",
- "multiline": "never"
- }]
+ "multiline": "always"
+ }
+ }
+ ]
}
```
-- `singleline` ... the configuration for single-line elements. It's a single-line element if the element does not have attributes or the last attribute is on the same line as the opening bracket.
- - `"never"` ... disallow line breaks before the closing bracket. This is the default.
- - `"always"` ... require one line break before the closing bracket.
-- `multiline` ... the configuration for multiline elements. It's a multiline element if the last attribute is not on the same line of the opening bracket.
- - `"never"` ... disallow line breaks before the closing bracket. This is the default.
- - `"always"` ... require one line break before the closing bracket.
+- `singleline` (`"never"` by default) ... the configuration for single-line elements. It's a single-line element if the element does not have attributes or the last attribute is on the same line as the opening bracket.
+- `multiline` (`"always"` by default) ... the configuration for multiline elements. It's a multiline element if the last attribute is not on the same line of the opening bracket.
+- `selfClosingTag.singleline` ... the configuration for single-line self closing elements.
+- `selfClosingTag.multiline` ... the configuration for multiline self closing elements.
+
+Every option can be set to one of the following values:
+
+- `"always"` ... require one line break before the closing bracket.
+- `"never"` ... disallow line breaks before the closing bracket.
+
+If `selfClosingTag` is not specified, the `singleline` and `multiline` options are inherited for self-closing tags.
Plus, you can use [`vue/html-indent`](./html-indent.md) rule to enforce indent-level of the closing brackets.
-:-1: Examples of **incorrect** code for this rule:
+### `"multiline": "never"`
-```html
-/*eslint html-closing-bracket-newline: "error"*/
+
-
-
+
+
-
+
+
+
+ >
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-/*eslint html-closing-bracket-newline: "error"*/
+### `"selfClosingTag": { "multiline": "always" }`
-
+
+
+
```
-:+1: Examples of **correct** code for `{ "multiline": "always" }`:
+
-```html
-/*eslint html-closing-bracket-newline: ["error", { multiline: always }]*/
+## :rocket: Version
-
-
-
-```
+This rule was introduced in eslint-plugin-vue v4.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-closing-bracket-newline.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-closing-bracket-newline.js)
diff --git a/docs/rules/html-closing-bracket-spacing.md b/docs/rules/html-closing-bracket-spacing.md
index 4e92f5477..d1927b4e1 100644
--- a/docs/rules/html-closing-bracket-spacing.md
+++ b/docs/rules/html-closing-bracket-spacing.md
@@ -1,83 +1,99 @@
-# require or disallow a space before tag's closing brackets (vue/html-closing-bracket-spacing)
-
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
-
-This rule enforces consistent spacing style before closing brackets `>` of tags.
-
-```html
-
or
- or
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-closing-bracket-spacing
+description: require or disallow a space before tag's closing brackets
+since: v4.1.0
+---
+
+# vue/html-closing-bracket-spacing
+
+> require or disallow a space before tag's closing brackets
+
+- :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
+
+This rule aims to enforce consistent spacing style before closing brackets `>` of tags.
+
+
+
+```vue
+
+
+
`). Default is `"never"`.
- - `"always"` ... requires one or more spaces.
- - `"never"` ... disallows spaces.
+ - `"always"` ... requires one or more spaces.
+ - `"never"` ... disallows spaces.
- `endTag` (`"always" | "never"`) ... Setting for the `>` of end tags (e.g. `
`). Default is `"never"`.
- - `"always"` ... requires one or more spaces.
- - `"never"` ... disallows spaces.
+ - `"always"` ... requires one or more spaces.
+ - `"never"` ... disallows spaces.
- `selfClosingTag` (`"always" | "never"`) ... Setting for the `/>` of self-closing tags (e.g. ``). Default is `"always"`.
- - `"always"` ... requires one or more spaces.
- - `"never"` ... disallows spaces.
-
-Examples of **incorrect** code for this rule:
-
-```html
-
-
-
-
-
-
-```
+## :rocket: Version
-# Related rules
+This rule was introduced in eslint-plugin-vue v4.1.0
-- [vue/no-multi-spaces](./no-multi-spaces.md)
-- [vue/html-closing-bracket-newline](./html-closing-bracket-newline.md)
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-closing-bracket-spacing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-closing-bracket-spacing.js)
diff --git a/docs/rules/html-comment-content-newline.md b/docs/rules/html-comment-content-newline.md
new file mode 100644
index 000000000..cffd7cdff
--- /dev/null
+++ b/docs/rules/html-comment-content-newline.md
@@ -0,0 +1,237 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-comment-content-newline
+description: enforce unified line break in HTML comments
+since: v7.0.0
+---
+
+# vue/html-comment-content-newline
+
+> enforce unified line break in HTML comments
+
+- :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 will enforce consistency of line break after the `` of comment. It also provides several exceptions for various documentation styles.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/html-comment-content-newline": ["error",
+ {
+ "singleline": "always" | "never" | "ignore",
+ "multiline": "always" | "never" | "ignore",
+ },
+ {
+ "exceptions": []
+ }
+ ]
+}
+```
+
+- The first option is either an object with `"singleline"` and `"multiline"` keys.
+
+ - `singleline` ... the configuration for single-line comments.
+ - `"never"` (default) ... disallow line breaks after the ``.
+ - `"always"` ... require one line break after the ``.
+ - `multiline` ... the configuration for multiline comments.
+ - `"never"` ... disallow line breaks after the ``.
+ - `"always"` (default) ... require one line break after the ``.
+
+ You can also set the same value for both `singleline` and `multiline` by specifies a string.
+
+- This rule can also take a 2nd option, an object with the following key: `"exceptions"`.
+
+ - The `"exceptions"` value is an array of string patterns which are considered exceptions to the rule.
+
+ ```json
+ "vue/html-comment-content-newline": ["error", { ... }, { "exceptions": ["*"] }]
+ ```
+
+### `"always"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"never"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `{"singleline": "always", "multiline": "ignore"}`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `{"singleline": "ignore", "multiline": "always"}`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"always", { "exceptions": ["*"] }`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/html-comment-indent](./html-comment-indent.md)
+- [vue/html-comment-content-spacing](./html-comment-content-spacing.md)
+- [spaced-comment](https://eslint.org/docs/rules/spaced-comment)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-comment-content-newline.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-comment-content-newline.js)
diff --git a/docs/rules/html-comment-content-spacing.md b/docs/rules/html-comment-content-spacing.md
new file mode 100644
index 000000000..5b3948827
--- /dev/null
+++ b/docs/rules/html-comment-content-spacing.md
@@ -0,0 +1,123 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-comment-content-spacing
+description: enforce unified spacing in HTML comments
+since: v7.0.0
+---
+
+# vue/html-comment-content-spacing
+
+> enforce unified spacing in HTML comments
+
+- :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 will enforce consistency of spacing after the `` of comment. It also provides several exceptions for various documentation styles.
+
+Whitespace after the `` makes it easier to read text in comments.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/html-comment-content-spacing": ["error",
+ "always" | "never",
+ {
+ "exceptions": []
+ }
+ ]
+}
+```
+
+- The first is a string which be either `"always"` or `"never"`. The default is `"always"`.
+
+ - `"always"` (default) ... there must be at least one whitespace at after the ``.
+ - `"never"` ... there should be no whitespace at after the ``.
+
+- This rule can also take a 2nd option, an object with the following key: `"exceptions"`.
+
+ - The `"exceptions"` value is an array of string patterns which are considered exceptions to the rule.
+ Please note that exceptions are ignored if the first argument is `"never"`.
+
+ ```json
+ "vue/html-comment-content-spacing": ["error", "always", { "exceptions": ["*"] }]
+ ```
+
+### `"always"`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+### `"never"`
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+### `"always", { "exceptions": ["*"] }`
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [spaced-comment](https://eslint.org/docs/rules/spaced-comment)
+- [vue/html-comment-content-newline](./html-comment-content-newline.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-comment-content-spacing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-comment-content-spacing.js)
diff --git a/docs/rules/html-comment-indent.md b/docs/rules/html-comment-indent.md
new file mode 100644
index 000000000..2fa8ed7f1
--- /dev/null
+++ b/docs/rules/html-comment-indent.md
@@ -0,0 +1,140 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-comment-indent
+description: enforce consistent indentation in HTML comments
+since: v7.0.0
+---
+
+# vue/html-comment-indent
+
+> enforce consistent indentation in HTML comments
+
+- :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 enforces a consistent indentation style in HTML comment (``). The default style is 2 spaces.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/html-comment-indent": ["error", type]
+}
+```
+
+- `type` (`number | "tab"`) ... The type of indentation. Default is `2`. If this is a number, it's the number of spaces for one indent. If this is `"tab"`, it uses one tab for one indent.
+
+### `2`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+### `4`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+### `0`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+### `"tab"`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-comment-indent.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-comment-indent.js)
diff --git a/docs/rules/html-end-tags.md b/docs/rules/html-end-tags.md
index d15f40608..fa6333b06 100644
--- a/docs/rules/html-end-tags.md
+++ b/docs/rules/html-end-tags.md
@@ -1,35 +1,50 @@
-# enforce end tag style (vue/html-end-tags)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-end-tags
+description: enforce end tag style
+since: v3.0.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/html-end-tags
-## :book: Rule Details
+> enforce end tag style
-This rule reports:
+- :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.
-- presence of and end tag on [Void elements](https://www.w3.org/TR/html51/syntax.html#void-elements)
-- absence of both an end tag (e.g. `
`) and a self-closing opening tag (e.g. ``) on other elements
+## :book: Rule Details
-:-1: Examples of **incorrect** code for this rule:
+This rule aims to disallow lacking end tags.
-```html
-
-
-
-
-
-```
+
-:+1: Examples of **correct** code for this rule:
+```vue
+
+
+
+
+
+
+
-```html
-
-
-
-
-
+
+
+
+
```
+
+
## :wrench: Options
Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-end-tags.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-end-tags.js)
diff --git a/docs/rules/html-indent.md b/docs/rules/html-indent.md
index 4b816eab0..2cab727aa 100644
--- a/docs/rules/html-indent.md
+++ b/docs/rules/html-indent.md
@@ -1,37 +1,33 @@
-# enforce consistent indentation in `` (vue/html-indent)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-indent
+description: enforce consistent indentation in ``
+since: v3.14.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/html-indent
+
+> enforce consistent indentation in ``
+
+- :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
This rule enforces a consistent indentation style in ``. The default style is 2 spaces.
- This rule checks all tags, also all expressions in directives and mustaches.
-- In the expressions, this rule supports ECMAScript 2017 syntaxes. It ignores unknown AST nodes, but it might be confused by non-standard syntaxes.
+- In the expressions, this rule supports ECMAScript 2022 syntaxes. It ignores unknown AST nodes, but it might be confused by non-standard syntaxes.
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
-
- Hello.
-
-
-```
-
-:+1: Examples of **correct** code for this rule:
-
-```html
+```vue
+
Hello.
-
-```
-
-```html
-
Hello.
@@ -56,15 +52,23 @@ This rule enforces a consistent indentation style in ``. The default s
displayMessage
}}
+
+
+
+ Hello.
+
```
+
+
## :wrench: Options
```json
{
"vue/html-indent": ["error", type, {
"attribute": 1,
+ "baseIndent": 1,
"closeBracket": 0,
"alignAttributesVertically": true,
"ignores": []
@@ -74,14 +78,22 @@ This rule enforces a consistent indentation style in ``. The default s
- `type` (`number | "tab"`) ... The type of indentation. Default is `2`. If this is a number, it's the number of spaces for one indent. If this is `"tab"`, it uses one tab for one indent.
- `attribute` (`integer`) ... The multiplier of indentation for attributes. Default is `1`.
-- `closeBracket` (`integer`) ... The multiplier of indentation for right brackets. Default is `0`.
+- `baseIndent` (`integer`) ... The multiplier of indentation for top-level statements. Default is `1`.
+- `closeBracket` (`integer | object`) ... The multiplier of indentation for right brackets. Default is `0`.
+ You can apply all of the following by setting a number value.
+ - `closeBracket.startTag` (`integer`) ... The multiplier of indentation for right brackets of start tags (`
`). Default is `0`.
+ - `closeBracket.endTag` (`integer`) ... The multiplier of indentation for right brackets of end tags (`
`). Default is `0`.
+ - `closeBracket.selfClosingTag` (`integer`) ... The multiplier of indentation for right brackets of start tags (``). Default is `0`.
- `alignAttributesVertically` (`boolean`) ... Condition for whether attributes should be vertically aligned to the first attribute in multiline case or not. Default is `true`
-- `ignores` (`string[]`) ... The selector to ignore nodes. The AST spec is [here](https://github.com/mysticatea/vue-eslint-parser/blob/master/docs/ast.md). You can use [esquery](https://github.com/estools/esquery#readme) to select nodes. Default is an empty array.
+- `ignores` (`string[]`) ... The selector to ignore nodes. The AST spec is [here](https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md). You can use [esquery](https://github.com/estools/esquery#readme) to select nodes. Default is an empty array.
+
+### `2, {"attribute": 1, "closeBracket": 1}`
-:+1: Examples of **correct** code for `{attribute: 1, closeBracket: 1}`:
+
-```html
+```vue
+
`. The default s
```
-:+1: Examples of **correct** code for `{attribute: 2, closeBracket: 1}`:
+
+
+### `2, {"attribute": 2, "closeBracket": 1}`
+
+
-```html
+```vue
+
`. The default s
```
-:+1: Examples of **correct** code for `{ignores: ["VAttribute"]}`:
+
-```html
+### `2, {"ignores": ["VAttribute"]}`
+
+
+
+```vue
+
`. The default s
```
-:+1: Examples of **correct** code for `{alignAttributesVertically: true}`:
+
+
+### `2, {"alignAttributesVertically": false}`
-```html
+
+
+```vue
+
+
+
+
`. The default s
```
-:+1: Examples of **correct** code for `{alignAttributesVertically: false}`:
+
+
+### `2, {"baseIndent": 0}`
+
+
-```html
+```vue
-
+
+
+
+ Hello!
+
+
+
+
+
+
+ Hello!
+
+
```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.14.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-indent.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-indent.js)
diff --git a/docs/rules/html-quotes.md b/docs/rules/html-quotes.md
index 16cce04e0..2bbf38c3d 100644
--- a/docs/rules/html-quotes.md
+++ b/docs/rules/html-quotes.md
@@ -1,7 +1,17 @@
-# enforce quotes style of HTML attributes (vue/html-quotes)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-quotes
+description: enforce quotes style of HTML attributes
+since: v3.0.0
+---
-- :gear: This rule is included in `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/html-quotes
+
+> enforce quotes style of HTML attributes
+
+- :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.
You can choose quotes of HTML attributes from:
@@ -15,33 +25,83 @@ This rule enforces the quotes style of HTML attributes.
This rule reports the quotes of attributes if it is different to configured quotes.
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
-
+```vue
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
+## :wrench: Options
+
+Default is set to `double`.
+
+```json
+{
+ "vue/html-quotes": [ "error", "double" | "single", { "avoidEscape": false } ]
+}
```
-:-1: Examples of **incorrect** code for this rule with `"single"` option:
+String option:
+
+- `"double"` (default) ... requires double quotes.
+- `"single"` ... requires single quotes.
+
+Object option:
+
+- `avoidEscape` ... If `true`, allows strings to use single-quotes or double-quotes so long as the string contains a quote that would have to be escaped otherwise.
+
+### `"single"`
+
+
-```html
-
-
+```vue
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule with `"single"` option:
+
+
+### `"double", { "avoidEscape": true }`
+
+
-```html
-
+```vue
+
+
+
+
+
+
+
+
```
-## :wrench: Options
+
-- `"double"` (default) ... requires double quotes.
-- `"single"` ... requires single quotes.
+## :books: Further Reading
+
+- [Style guide - Quoted attribute values](https://vuejs.org/style-guide/rules-strongly-recommended.html#quoted-attribute-values)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-quotes.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-quotes.js)
diff --git a/docs/rules/html-self-closing.md b/docs/rules/html-self-closing.md
index 4c269b2fe..316f462c8 100644
--- a/docs/rules/html-self-closing.md
+++ b/docs/rules/html-self-closing.md
@@ -1,19 +1,50 @@
-# enforce self-closing style (vue/html-self-closing)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/html-self-closing
+description: enforce self-closing style
+since: v3.11.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/html-self-closing
+
+> enforce self-closing style
+
+- :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
+
+This rule aims to enforce the self-closing sign as the configured style.
In Vue.js template, we can use either two styles for elements which don't have their content.
1. ``
2. `` (self-closing)
-Self-closing is simple and shorter, but it's not supported in raw HTML.
-This rule helps you to unify the self-closing style.
+Self-closing is simple and shorter, but it's not supported in the HTML spec.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
-## Rule Details
+
-This rule has options which specify self-closing style for each context.
+## :wrench: Options
```json
{
@@ -41,23 +72,37 @@ Every option can be set to one of the following values:
- `"never"` ... Disallow self-closing.
- `"any"` ... Don't enforce self-closing style.
-----
+### `html: {normal: "never", void: "always"}`
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
-
-
-
-
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-
-
-
-```
+## :books: Further Reading
+
+- [Style guide - Self closing components](https://vuejs.org/style-guide/rules-strongly-recommended.html#self-closing-components)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.11.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-self-closing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-self-closing.js)
diff --git a/docs/rules/index.md b/docs/rules/index.md
new file mode 100644
index 000000000..7828b58bb
--- /dev/null
+++ b/docs/rules/index.md
@@ -0,0 +1,647 @@
+---
+sidebarDepth: 0
+pageClass: rule-list
+---
+
+# Available rules
+
+
+
+::: tip Legend
+ :wrench: Indicates that the rule is fixable, and using `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the reported problems.
+
+ :bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+:::
+
+Mark indicating rule type:
+
+- :warning: Possible Problems: These rules relate to possible logic errors in code.
+- :hammer: Suggestions: These rules suggest alternate ways of doing things.
+- :lipstick: Layout & Formatting: These rules care about how the code looks rather than how it executes.
+
+## Base Rules (Enabling Correct ESLint Parsing)
+
+Rules in this category are enabled for all presets provided by eslint-plugin-vue.
+
+
+
+| Rule ID | Description | | |
+|:--------|:------------|:--:|:--:|
+| [vue/comment-directive] | support comment-directives in `` | | :warning: |
+| [vue/jsx-uses-vars] | prevent variables used in JSX to be marked as unused | | :warning: |
+
+
+
+## Priority A: Essential (Error Prevention)
+
+- :three: Indicates that the rule is for Vue 3 and 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"]` presets.
+- :two: Indicates that the rule is for Vue 2 and is included in all of `"plugin:vue/vue2-essential"`,`*.configs["flat/vue2-essential"]`, `"plugin:vue/vue2-strongly-recommended"`,`*.configs["flat/vue2-strongly-recommended"]` and `"plugin:vue/vue2-recommended"`,`*.configs["flat/vue2-recommended"]` presets.
+
+
+
+| Rule ID | Description | | |
+|:--------|:------------|:--:|:--:|
+| [vue/multi-word-component-names] | require component names to be always multi-word | | :three::two::hammer: |
+| [vue/no-arrow-functions-in-watch] | disallow using arrow functions to define watcher | | :three::two::warning: |
+| [vue/no-async-in-computed-properties] | disallow asynchronous actions in computed properties | | :three::two::warning: |
+| [vue/no-child-content] | disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text` | :bulb: | :three::two::warning: |
+| [vue/no-computed-properties-in-data] | disallow accessing computed properties in `data` | | :three::two::warning: |
+| [vue/no-custom-modifiers-on-v-model] | disallow custom modifiers on v-model used on the component | | :two::warning: |
+| [vue/no-deprecated-data-object-declaration] | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
+| [vue/no-deprecated-delete-set] | disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-destroyed-lifecycle] | disallow using deprecated `destroyed` and `beforeDestroy` lifecycle hooks (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
+| [vue/no-deprecated-dollar-listeners-api] | disallow using deprecated `$listeners` (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-dollar-scopedslots-api] | disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
+| [vue/no-deprecated-events-api] | disallow using deprecated events api (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-filter] | disallow using deprecated filters syntax (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-functional-template] | disallow using deprecated the `functional` template (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-html-element-is] | disallow using deprecated the `is` attribute on HTML elements (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-inline-template] | disallow using deprecated `inline-template` attribute (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-model-definition] | disallow deprecated `model` definition (in Vue.js 3.0.0+) | :bulb: | :three::warning: |
+| [vue/no-deprecated-props-default-this] | disallow deprecated `this` access in props default function (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-router-link-tag-prop] | disallow using deprecated `tag` property on `RouterLink` (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-scope-attribute] | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: | :three::hammer: |
+| [vue/no-deprecated-slot-attribute] | disallow deprecated `slot` attribute (in Vue.js 2.6.0+) | :wrench: | :three::hammer: |
+| [vue/no-deprecated-slot-scope-attribute] | disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+) | :wrench: | :three::hammer: |
+| [vue/no-deprecated-v-bind-sync] | disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
+| [vue/no-deprecated-v-is] | disallow deprecated `v-is` directive (in Vue.js 3.1.0+) | | :three::hammer: |
+| [vue/no-deprecated-v-on-native-modifier] | disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-deprecated-v-on-number-modifiers] | disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
+| [vue/no-deprecated-vue-config-keycodes] | disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+) | | :three::warning: |
+| [vue/no-dupe-keys] | disallow duplication of field names | | :three::two::warning: |
+| [vue/no-dupe-v-else-if] | disallow duplicate conditions in `v-if` / `v-else-if` chains | | :three::two::warning: |
+| [vue/no-duplicate-attributes] | disallow duplication of attributes | | :three::two::warning: |
+| [vue/no-export-in-script-setup] | disallow `export` in `
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/match-component-file-name": ["error", {
+ "extensions": ["jsx"],
+ "shouldMatchCase": false
+ }]
+}
+```
+
+- `"extensions": []` ... array of file extensions to be verified. Default is set to `["jsx"]`.
+- `"shouldMatchCase": false` ... boolean indicating if component's name
+ should also match its file name case. Default is set to `false`.
+
+### `{extensions: ["vue"]}`
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+### `{extensions: ["js"]}`
+
+
+
+```js
+// file name: src/MyComponent.js
+new Vue({
+ /* ✓ GOOD */
+ name: 'MyComponent',
+ template: ''
+})
+```
+
+
+
+
+
+```js
+// file name: src/MyComponent.js
+/* ✓ GOOD */
+Vue.component('MyComponent', {
+ template: ''
+})
+```
+
+
+
+
+
+```js
+// file name: src/MyComponent.js
+new Vue({
+ /* ✗ BAD */
+ name: 'MComponent',
+ template: ''
+})
+```
+
+
+
+
+
+```js
+// file name: src/MyComponent.js
+/* ✗ BAD */
+Vue.component('MComponent', {
+ template: ''
+})
+```
+
+
+
+
+
+```js
+// file name: src/components.js
+/* defines multiple components, so this rule is ignored */
+Vue.component('MyComponent', {
+ template: ''
+})
+
+Vue.component('OtherComponent', {
+ template: ''
+})
+
+new Vue({
+ name: 'ThirdComponent',
+ template: ''
+})
+```
+
+
+
+
+
+```js
+// file name: src/MyComponent.js
+/* no name property defined */
+new Vue({
+ template: ''
+})
+```
+
+
+
+### `{shouldMatchCase: true}`
+
+
+
+```jsx
+// file name: src/MyComponent.jsx
+export default {
+ /* ✓ GOOD */
+ name: 'MyComponent',
+ render() { return }
+}
+```
+
+
+
+
+
+```jsx
+// file name: src/my-component.jsx
+export default {
+ /* ✓ GOOD */
+ name: 'my-component',
+ render() { return }
+}
+```
+
+
+
+
+
+```jsx
+// file name: src/MyComponent.jsx
+export default {
+ /* ✗ BAD */
+ name: 'my-component',
+ render() { return }
+}
+```
+
+
+
+
+
+```jsx
+// file name: src/my-component.jsx
+export default {
+ /* ✗ BAD */
+ name: 'MyComponent',
+ render() { return }
+}
+```
+
+
+
+## :books: Further Reading
+
+- [Style guide - Single-file component filename casing](https://vuejs.org/style-guide/rules-strongly-recommended.html#single-file-component-filename-casing)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/match-component-file-name.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/match-component-file-name.js)
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 0fd4d918b..141b136e0 100644
--- a/docs/rules/max-attributes-per-line.md
+++ b/docs/rules/max-attributes-per-line.md
@@ -1,9 +1,19 @@
-# enforce the maximum number of attributes per line (vue/max-attributes-per-line)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/max-attributes-per-line
+description: enforce the maximum number of attributes per line
+since: v3.12.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/max-attributes-per-line
-Limits the maximum number of attributes/properties per line to improve readability.
+> enforce the maximum number of attributes per line
+
+- :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.
## :book: Rule Details
@@ -13,114 +23,105 @@ An attribute is considered to be in a new line when there is a line break betwee
There is a configurable number of attributes that are acceptable in one-line case (default 1), as well as how many attributes are acceptable per line in multi-line case (default 1).
-:-1: Examples of **incorrect** code for this rule:
-
-```html
-
-
-
-
-
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-
-
-
-
-```
-
-### :wrench: Options
+## :wrench: Options
```json
{
- "vue/max-attributes-per-line": [2, {
- "singleline": 1,
+ "vue/max-attributes-per-line": ["error", {
+ "singleline": {
+ "max": 1
+ },
"multiline": {
- "max": 1,
- "allowFirstLine": false
+ "max": 1
}
}]
}
```
-#### `allowFirstLine`
+- `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 }}`.
-For multi-line declarations, defines if allows attributes to be put in the first line. (Default false)
+### `"singleline": 3`
-:-1: Example of **incorrect** code for this setting:
+
-```html
-
-
+```vue
+
+
+
+
+
+
+
```
-:+1: Example of **correct** code for this setting:
+
-```html
-
-
-```
+### `"multiline": 2`
-#### `singleline`
+
-Number of maximum attributes per line when the opening tag is in a single line. (Default is 1)
+```vue
+
+
+
-:-1: Example of **incorrect** code for this setting:
-```html
-
-
+
+
+
```
-:+1: Example of **correct** code for this setting:
-```html
-
-
-```
+
-#### `multiline`
+## :couple: Related Rules
-Number of maximum attributes per line when a tag is in multiple lines. (Default is 1)
+- [vue/first-attribute-linebreak](./first-attribute-linebreak.md)
-:-1: Example of **incorrect** code for this setting:
+## :books: Further Reading
-```html
-
-
-```
+- [Style guide - Multi attribute elements](https://vuejs.org/style-guide/rules-strongly-recommended.html#multi-attribute-elements)
-:+1: Example of **correct** code for this setting:
+## :rocket: Version
-```html
-
-
-```
+This rule was introduced in eslint-plugin-vue v3.12.0
-## When Not To Use It
+## :mag: Implementation
-If you do not want to check the number of attributes declared per line you can disable this rule.
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-attributes-per-line.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-attributes-per-line.js)
diff --git a/docs/rules/max-len.md b/docs/rules/max-len.md
new file mode 100644
index 000000000..0816000ee
--- /dev/null
+++ b/docs/rules/max-len.md
@@ -0,0 +1,337 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/max-len
+description: enforce a maximum line length in `.vue` files
+since: v6.1.0
+---
+
+# vue/max-len
+
+> enforce a maximum line length in `.vue` files
+
+## :book: Rule Details
+
+This rule enforces a maximum line length to increase code readability and maintainability.
+This rule is the similar rule as core [max-len] rule but it applies to the source code in `.vue`.
+
+
+
+```vue
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```js
+{
+ "vue/max-len": ["error", {
+ "code": 80,
+ "template": 80,
+ "tabWidth": 2,
+ "comments": 80,
+ "ignorePattern": "",
+ "ignoreComments": false,
+ "ignoreTrailingComments": false,
+ "ignoreUrls": false,
+ "ignoreStrings": false,
+ "ignoreTemplateLiterals": false,
+ "ignoreRegExpLiterals": false,
+ "ignoreHTMLAttributeValues": false,
+ "ignoreHTMLTextContents": false,
+ }]
+}
+```
+
+- `code` ... enforces a maximum line length. default `80`
+- `template` ... enforces a maximum line length for ``. defaults to value of `code`
+- `tabWidth` ... specifies the character width for tab characters. default `2`
+- `comments` ... enforces a maximum line length for comments. defaults to value of `code`
+- `ignorePattern` ... ignores lines matching a regular expression. can only match a single line and need to be double escaped when written in YAML or JSON
+- `ignoreComments` ... if `true`, ignores all trailing comments and comments on their own line. default `false`
+- `ignoreTrailingComments` ... if `true`, ignores only trailing comments. default `false`
+- `ignoreUrls` ... if `true`, ignores lines that contain a URL. default `false`
+- `ignoreStrings` ... if `true`, ignores lines that contain a double-quoted or single-quoted string. default `false`
+- `ignoreTemplateLiterals` ... if `true`, ignores lines that contain a template literal. default `false`
+- `ignoreRegExpLiterals` ... if `true`, ignores lines that contain a RegExp literal. default `false`
+- `ignoreHTMLAttributeValues` ... if `true`, ignores lines that contain a HTML attribute value. default `false`
+- `ignoreHTMLTextContents` ... if `true`, ignores lines that contain a HTML text content. default `false`
+
+### `"code": 40`
+
+
+
+```vue
+
+
+
this is a really really really really really really really long text content!
+
+```
+
+
+
+## :books: Further Reading
+
+- [max-len]
+
+[max-len]: https://eslint.org/docs/rules/max-len
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-len.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-len.js)
+
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/max-len)
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
new file mode 100644
index 000000000..bf654aafe
--- /dev/null
+++ b/docs/rules/multiline-html-element-content-newline.md
@@ -0,0 +1,159 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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
+
+This rule enforces a line break before and after the contents of a multiline element.
+
+
+
+```vue
+
+
+
+ multiline
+ content
+
+
+
some
+ content
+
+
+ multiline start tag
+
+
+
+
+
multiline
+
children
+
+
+
+
+
+
+
+
+
+
+
singleline element
+
+
+
multiline
+ content
+
+
multiline start tag
+
+
multiline
+
children
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```js
+{
+ "vue/multiline-html-element-content-newline": ["error", {
+ "ignoreWhenEmpty": true,
+ "ignores": ["pre", "textarea", ...INLINE_ELEMENTS],
+ "allowEmptyLines": false
+ }]
+}
+```
+
+- `ignoreWhenEmpty` ... disables reporting when element has no content.
+ default `true`
+- `ignores` ... the configuration for element names to ignore line breaks style.
+ default `["pre", "textarea", ...INLINE_ELEMENTS]`.
+- `allowEmptyLines` ... if `true`, it allows empty lines around content. If you want to disallow multiple empty lines, use [no-multiple-empty-lines] in combination.
+ default `false`
+
+::: info
+ All inline non void elements can be found [here](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/inline-non-void-elements.json).
+:::
+
+### `"ignores": ["VueComponent", "pre", "textarea"]`
+
+
+
+```vue
+
+
+ multiline
+ content
+
+
some
+ content
+
+ For example,
+ Defines the Vue component that accepts preformatted text.
+
+```
+
+
+
+### `"allowEmptyLines": true`
+
+
+
+```vue
+
+
+
+ content
+
+
+
+ content
+
+
+
+
+
content
+ content
+
+```
+
+
+
+## :books: Further Reading
+
+- [no-multiple-empty-lines]
+
+[no-multiple-empty-lines]: https://eslint.org/docs/rules/no-multiple-empty-lines
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/multiline-html-element-content-newline.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/multiline-html-element-content-newline.js)
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 25efd4fa1..1e66fc942 100644
--- a/docs/rules/mustache-interpolation-spacing.md
+++ b/docs/rules/mustache-interpolation-spacing.md
@@ -1,58 +1,70 @@
-# enforce unified spacing in mustache interpolations (vue/mustache-interpolation-spacing)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/mustache-interpolation-spacing
+description: enforce unified spacing in mustache interpolations
+since: v3.13.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/mustache-interpolation-spacing
-## :book: Rule Details
+> enforce unified spacing in mustache interpolations
-This rule aims to enforce unified spacing in mustache interpolations.
+- :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.
-:-1: Examples of **incorrect** code for this rule:
+## :book: Rule Details
-```html
-
{{ text }}
-
{{text}}
-```
+This rule aims at enforcing unified spacing in mustache interpolations.
-:+1: Examples of **correct** code for this rule:
+
-```html
-
{{ text }}
+```vue
+
+
+
{{ text }}
+
{{ text }}
+
+
+
{{text}}
+
```
-## :wrench: Options
+
-Default spacing is set to `always`
+## :wrench: Options
-```
-'vue/mustache-interpolation-spacing': [2, 'always'|'never']
+```json
+{
+ "vue/mustache-interpolation-spacing": ["error", "always" | "never"]
+}
```
-### `"always"` - Expect one space between expression and curly brackets.
+- `"always"` (default) ... Expect one space between expression and curly brackets.
+- `"never"` ... Expect no spaces between expression and curly brackets.
-:-1: Examples of **incorrect** code for this rule:
+### `"never"`
-```html
-
{{ text }}
-
{{text}}
-```
+
-:+1: Examples of **correct** code for this rule:
+```vue
+
+
+
{{text}}
-```html
-
{{ text }}
+
+
{{ text }}
+
{{ text }}
+
```
-### `"never"` - Expect no spaces between expression and curly brackets.
+
-:-1: Examples of **incorrect** code for this rule:
+## :rocket: Version
-```html
-
{{ text }}
-```
+This rule was introduced in eslint-plugin-vue v3.13.0
-:+1: Examples of **correct** code for this rule:
+## :mag: Implementation
-```html
-
{{text}}
-```
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/mustache-interpolation-spacing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/mustache-interpolation-spacing.js)
diff --git a/docs/rules/name-property-casing.md b/docs/rules/name-property-casing.md
index 3349351e7..e1840987b 100644
--- a/docs/rules/name-property-casing.md
+++ b/docs/rules/name-property-casing.md
@@ -1,32 +1,95 @@
-# enforce specific casing for the name property in Vue components (vue/name-property-casing)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/name-property-casing
+description: enforce specific casing for the name property in Vue components
+since: v3.8.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/name-property-casing
-Define a style for the `name` property casing for consistency purposes.
+> enforce specific casing for the name property in Vue components
+
+- :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
-:+1: Examples of **correct** code for `PascalCase`:
+This rule aims at enforcing the style for the `name` property casing for consistency purposes.
+
+
-```js
+```vue
+
```
-:+1: Examples of **correct** code for `kebab-case`:
+
-```js
+
+
+```vue
+
```
+
+
## :wrench: Options
-Default casing is set to `PascalCase`.
+```json
+{
+ "vue/name-property-casing": ["error", "PascalCase" | "kebab-case"]
+}
+```
+
+- `"PascalCase"` (default) ... Enforce the `name` property to Pascal case.
+- `"kebab-case"` ... Enforce the `name` property to kebab case.
+### `"kebab-case"`
+
+
+
+```vue
+
```
-"vue/name-property-casing": ["error", "PascalCase|kebab-case"]
+
+
+
+
+
+```vue
+
```
+
+
+
+## :books: Further Reading
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v3.8.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/name-property-casing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/name-property-casing.js)
diff --git a/docs/rules/new-line-between-multi-line-property.md b/docs/rules/new-line-between-multi-line-property.md
new file mode 100644
index 000000000..1191a1567
--- /dev/null
+++ b/docs/rules/new-line-between-multi-line-property.md
@@ -0,0 +1,102 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule aims at enforcing new lines between multi-line properties in Vue components to help readability
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/new-line-between-multi-line-property": ["error", {
+ "minLineOfMultilineProperty": 2
+ }]
+}
+```
+
+- `minLineOfMultilineProperty` ... Define the minimum number of rows for a multi-line property. default `2`
+
+## :books: Further Reading
+
+- [Style guide - Empty lines in component/instance options](https://vuejs.org/style-guide/rules-recommended.html#empty-lines-in-component-instance-options)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.3.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/new-line-between-multi-line-property.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/new-line-between-multi-line-property.js)
diff --git a/docs/rules/next-tick-style.md b/docs/rules/next-tick-style.md
new file mode 100644
index 000000000..0e9d7812f
--- /dev/null
+++ b/docs/rules/next-tick-style.md
@@ -0,0 +1,109 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule enforces whether the callback version or Promise version (which was introduced in Vue v2.1.0) should be used in `Vue.nextTick` and `this.$nextTick`.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Default is set to `promise`.
+
+```json
+{
+ "vue/next-tick-style": ["error", "promise" | "callback"]
+}
+```
+
+- `"promise"` (default) ... requires using the promise version.
+- `"callback"` ... requires using the callback version. Use this if you use a Vue version below v2.1.0.
+
+### `"callback"`
+
+
+
+```vue
+
+```
+
+
+
+## :books: Further Reading
+
+- [`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
+
+This rule was introduced in eslint-plugin-vue v7.5.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/next-tick-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/next-tick-style.js)
diff --git a/docs/rules/no-arrow-functions-in-watch.md b/docs/rules/no-arrow-functions-in-watch.md
new file mode 100644
index 000000000..fb1f55215
--- /dev/null
+++ b/docs/rules/no-arrow-functions-in-watch.md
@@ -0,0 +1,70 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 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).
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-arrow-functions-in-watch.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-arrow-functions-in-watch.js)
diff --git a/docs/rules/no-async-in-computed-properties.md b/docs/rules/no-async-in-computed-properties.md
index 1cc1ece0e..814a53238 100644
--- a/docs/rules/no-async-in-computed-properties.md
+++ b/docs/rules/no-async-in-computed-properties.md
@@ -1,59 +1,124 @@
-# disallow asynchronous actions in computed properties (vue/no-async-in-computed-properties)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-async-in-computed-properties
+description: disallow asynchronous actions in computed properties
+since: v3.8.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/no-async-in-computed-properties
-Computed properties 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.
+> disallow asynchronous actions in computed properties
+
+- :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]
## :book: Rule Details
-This rule is aimed at preventing asynchronous methods from being called in computed properties.
-
-:-1: Examples of **incorrect** code for this rule:
-
-```js
-computed: {
- pro () {
- return Promise.all([new Promise((resolve, reject) => {})])
- },
- foo: async function () {
- return await someFunc()
- },
- bar () {
- return fetch(url).then(response => {})
- },
- tim () {
- setTimeout(() => { }, 0)
- },
- inter () {
- setInterval(() => { }, 0)
- },
- anim () {
- requestAnimationFrame(() => {})
+This rule is aimed at preventing asynchronous methods from being called in computed properties and functions.
+
+
+
+```vue
+
```
-:+1: Examples of **correct** code for this rule:
-
-```js
-computed: {
- foo () {
- var bar = 0
- try {
- bar = bar / this.a
- } catch (e) {
- return 0
- } finally {
- return bar
- }
+
+
+
+
+```vue
+
```
+
+
## :wrench: Options
Nothing.
+## :books: Further Reading
+
+- [vue-async-computed](https://github.com/foxbenjaminfox/vue-async-computed)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.8.0
+
+## :mag: Implementation
-[vue-async-computed]: https://github.com/foxbenjaminfox/vue-async-computed
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-async-in-computed-properties.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-async-in-computed-properties.js)
diff --git a/docs/rules/no-bare-strings-in-template.md b/docs/rules/no-bare-strings-in-template.md
new file mode 100644
index 000000000..23a23c116
--- /dev/null
+++ b/docs/rules/no-bare-strings-in-template.md
@@ -0,0 +1,94 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-bare-strings-in-template
+description: disallow the use of bare strings in ``
+since: v7.0.0
+---
+
+# vue/no-bare-strings-in-template
+
+> disallow the use of bare strings in ``
+
+## :book: Rule Details
+
+This rule disallows the use of bare strings in ``.
+In order to be able to internationalize your application, you will need to avoid using plain strings in your templates. Instead, you would need to use a template helper specializing in translation.
+
+This rule was inspired by [no-bare-strings rule in ember-template-lint](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-bare-strings.md).
+
+
+
+```vue
+
+
+
{{ $t('foo.bar') }}
+
{{ foo }}
+
+
+
+
Lorem ipsum
+
+
+
+
+
+
+
{{ 'Lorem ipsum' }}
+
+
+```
+
+
+
+:::tip
+This rule does not check for string literals, in bindings and mustaches interpolation. This is because it looks like a conscious decision.
+If you want to report these string literals, enable the [vue/no-useless-v-bind] and [vue/no-useless-mustaches] rules and fix the useless string literals.
+:::
+
+## :wrench: Options
+
+```js
+{
+ "vue/no-bare-strings-in-template": ["error", {
+ "allowlist": [
+ "(", ")", ",", ".", "&", "+", "-", "=", "*", "/", "#", "%", "!", "?", ":", "[", "]", "{", "}", "<", ">", "\u00b7", "\u2022", "\u2010", "\u2013", "\u2014", "\u2212", "|"
+ ],
+ "attributes": {
+ "/.+/": ["title", "aria-label", "aria-placeholder", "aria-roledescription", "aria-valuetext"],
+ "input": ["placeholder"],
+ "img": ["alt"]
+ },
+ "directives": ["v-text"]
+ }]
+}
+```
+
+- `allowlist` ... An array of allowed strings or regular expression patterns (e.g. `/\d+/` to allow numbers).
+- `attributes` ... An object whose keys are tag name or patterns and value is an array of attributes to check for that tag name.
+- `directives` ... An array of directive names to check literal value.
+
+## :couple: Related Rules
+
+- [vue/no-useless-v-bind]
+- [vue/no-useless-mustaches]
+
+[vue/no-useless-v-bind]: ./no-useless-v-bind.md
+[vue/no-useless-mustaches]: ./no-useless-mustaches.md
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-bare-strings-in-template.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-bare-strings-in-template.js)
diff --git a/docs/rules/no-boolean-default.md b/docs/rules/no-boolean-default.md
new file mode 100644
index 000000000..78fdbc270
--- /dev/null
+++ b/docs/rules/no-boolean-default.md
@@ -0,0 +1,60 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-boolean-default
+description: disallow boolean defaults
+since: v7.0.0
+---
+
+# vue/no-boolean-default
+
+> disallow boolean defaults
+
+The rule prevents Boolean props from having a default value.
+
+## :book: Rule Details
+
+The rule is to enforce the HTML standard of always defaulting boolean attributes to false.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+- `'no-default'` (default) allows a prop definition object, but enforces that the `default` property not be defined.
+- `'default-false'` enforces that the default can be set but must be set to `false`.
+
+```json
+ "vue/no-boolean-default": ["error", "no-default|default-false"]
+```
+
+## :couple: Related Rules
+
+- [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md)
+- [vue/require-default-prop](./require-default-prop.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-boolean-default.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-boolean-default.js)
diff --git a/docs/rules/no-child-content.md b/docs/rules/no-child-content.md
new file mode 100644
index 000000000..a0a69a9bb
--- /dev/null
+++ b/docs/rules/no-child-content.md
@@ -0,0 +1,59 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-child-content
+description: disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`
+since: v8.1.0
+---
+
+# vue/no-child-content
+
+> disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`
+
+- :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
+
+This rule reports child content of elements that have a directive which overwrites that child content. By default, those are `v-html` and `v-text`, additional ones (e.g. [Vue I18n's `v-t` directive](https://vue-i18n.intlify.dev/api/directive.html)) can be configured manually.
+
+
+
+```vue
+
+
+
child content
+
+
+
+
child content
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-child-content": ["error", {
+ "additionalDirectives": ["foo"] // checks v-foo directive
+ }]
+}
+```
+
+- `additionalDirectives` ... An array of additional directives to check, without the `v-` prefix. Empty by default; `v-html` and `v-text` are always checked.
+
+## :books: Further Reading
+
+- [`v-html` directive](https://vuejs.org/api/built-in-directives.html#v-html)
+- [`v-text` directive](https://vuejs.org/api/built-in-directives.html#v-text)
+
+## :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-child-content.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-child-content.js)
diff --git a/docs/rules/no-computed-properties-in-data.md b/docs/rules/no-computed-properties-in-data.md
new file mode 100644
index 000000000..960a28c86
--- /dev/null
+++ b/docs/rules/no-computed-properties-in-data.md
@@ -0,0 +1,51 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-computed-properties-in-data
+description: disallow accessing computed properties in `data`
+since: v7.20.0
+---
+
+# vue/no-computed-properties-in-data
+
+> disallow accessing computed properties in `data`
+
+- :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 accessing computed properties in `data()`.
+The computed property cannot be accessed in `data()` because is before initialization.
+
+
+
+```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 c08732cb2..f463c883e 100644
--- a/docs/rules/no-confusing-v-for-v-if.md
+++ b/docs/rules/no-confusing-v-for-v-if.md
@@ -1,12 +1,16 @@
-# disallow confusing `v-for` and `v-if` on the same element (vue/no-confusing-v-for-v-if)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-confusing-v-for-v-if
+description: disallow confusing `v-for` and `v-if` on the same element
+since: v3.0.0
+---
-- :gear: This rule is included in `"plugin:vue/recommended"`.
+# vue/no-confusing-v-for-v-if
-> 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://vuejs.org/v2/guide/list.html#v-for-with-v-if
+> disallow confusing `v-for` and `v-if` on the same element
-So when they exist on the same node, `v-if` directive should use the reference which is to the variables which are defined by the `v-for` directives.
+- :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
@@ -16,35 +20,55 @@ This rule reports the elements which have both `v-for` and `v-if` directives in
In that case, the `v-if` should be written on the wrapper element.
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
-```
-
-:+1: Examples of **correct** code for this rule:
-
-```html
-
-```
+```vue
+
+
+
+
+
+
-```html
-
+
-
+
```
+
+
+::: 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://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
Nothing.
+
+## :books: Further Reading
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v3.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-confusing-v-for-v-if.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-confusing-v-for-v-if.js)
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
new file mode 100644
index 000000000..36520e25b
--- /dev/null
+++ b/docs/rules/no-constant-condition.md
@@ -0,0 +1,30 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-constant-condition
+description: Disallow constant expressions in conditions in ``
+since: v7.5.0
+---
+
+# vue/no-constant-condition
+
+> 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 ``.
+
+## :books: Further Reading
+
+- [no-constant-condition]
+
+[no-constant-condition]: https://eslint.org/docs/rules/no-constant-condition
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.5.0
+
+## :mag: Implementation
+
+- [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/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
new file mode 100644
index 000000000..f657f9cf7
--- /dev/null
+++ b/docs/rules/no-custom-modifiers-on-v-model.md
@@ -0,0 +1,58 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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.
+
+## :book: Rule Details
+
+This rule reports `v-model` directives in the following cases:
+
+- The directive used on the component has custom modifiers. E.g. ``
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/valid-v-model]
+
+[vue/valid-v-model]: ./valid-v-model.md
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-custom-modifiers-on-v-model.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-custom-modifiers-on-v-model.js)
diff --git a/docs/rules/no-deprecated-data-object-declaration.md b/docs/rules/no-deprecated-data-object-declaration.md
new file mode 100644
index 000000000..214aacc30
--- /dev/null
+++ b/docs/rules/no-deprecated-data-object-declaration.md
@@ -0,0 +1,93 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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-migration.vuejs.org/breaking-changes/data-option.html) for more details.
+
+
+
+```js
+createApp({
+ /* ✗ BAD */
+ data: {
+ foo: null
+ }
+}).mount('#app')
+
+createApp({
+ /* ✓ GOOD */
+ data() {
+ return {
+ foo: null
+ }
+ }
+}).mount('#app')
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Data Option](https://v3-migration.vuejs.org/breaking-changes/data-option.html)
+- [Vue RFCs - 0019-remove-data-object-declaration](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0019-remove-data-object-declaration.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-data-object-declaration.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-data-object-declaration.js)
diff --git a/docs/rules/no-deprecated-delete-set.md b/docs/rules/no-deprecated-delete-set.md
new file mode 100644
index 000000000..c96f7a730
--- /dev/null
+++ b/docs/rules/no-deprecated-delete-set.md
@@ -0,0 +1,57 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-delete-set
+description: disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+)
+since: v9.29.0
+---
+
+# vue/no-deprecated-delete-set
+
+> disallow using deprecated `$delete` and `$set` (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 use of deprecated `$delete` and `$set`. (in Vue.js 3.0.0+).
+
+
+
+```vue
+
+```
+
+
+
+## :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
new file mode 100644
index 000000000..9c681e0ea
--- /dev/null
+++ b/docs/rules/no-deprecated-destroyed-lifecycle.md
@@ -0,0 +1,55 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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
+
+```
+
+
+
+## :wrench: Options
+
+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
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-destroyed-lifecycle.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-destroyed-lifecycle.js)
diff --git a/docs/rules/no-deprecated-dollar-listeners-api.md b/docs/rules/no-deprecated-dollar-listeners-api.md
new file mode 100644
index 000000000..91854806b
--- /dev/null
+++ b/docs/rules/no-deprecated-dollar-listeners-api.md
@@ -0,0 +1,59 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 `$listeners`. (in Vue.js 3.0.0+).
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-dollar-listeners-api.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-dollar-listeners-api.js)
diff --git a/docs/rules/no-deprecated-dollar-scopedslots-api.md b/docs/rules/no-deprecated-dollar-scopedslots-api.md
new file mode 100644
index 000000000..45e67d480
--- /dev/null
+++ b/docs/rules/no-deprecated-dollar-scopedslots-api.md
@@ -0,0 +1,57 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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-migration.vuejs.org/breaking-changes/slots-unification.html) for more details.
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-dollar-scopedslots-api.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-dollar-scopedslots-api.js)
diff --git a/docs/rules/no-deprecated-events-api.md b/docs/rules/no-deprecated-events-api.md
new file mode 100644
index 000000000..a3539d7cf
--- /dev/null
+++ b/docs/rules/no-deprecated-events-api.md
@@ -0,0 +1,75 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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-migration.vuejs.org/breaking-changes/events-api.html) for more details.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Events API](https://v3-migration.vuejs.org/breaking-changes/events-api.html)
+- [Vue RFCs - 0020-events-api-change](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-events-api.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-events-api.js)
diff --git a/docs/rules/no-deprecated-filter.md b/docs/rules/no-deprecated-filter.md
new file mode 100644
index 000000000..452d9d03c
--- /dev/null
+++ b/docs/rules/no-deprecated-filter.md
@@ -0,0 +1,77 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-filter
+description: disallow using deprecated filters syntax (in Vue.js 3.0.0+)
+since: v7.0.0
+---
+
+# vue/no-deprecated-filter
+
+> disallow using deprecated filters syntax (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 `filters` syntax (removed in Vue.js v3.0.0+).
+
+See [Migration Guide - Filters](https://v3-migration.vuejs.org/breaking-changes/filters.html) for more details.
+
+
+
+```vue
+
+
+ {{ filter(msg) }}
+ {{ filter(msg, '€') }}
+ {{ filterB(filterA(msg)) }}
+
+
+
+
+
+ {{ msg | filter }}
+ {{ msg | filter('€') }}
+ {{ msg | filterA | filterB }}
+
+
+
+
+```
+
+
+
+:::warning
+Do not disable [`"parserOptions.vueFeatures.filter"`](https://github.com/vuejs/vue-eslint-parser#parseroptionsvuefeaturesfilter) to use this rule.
+
+```json5
+{
+ "parser": "vue-eslint-parser",
+ "parserOptions": {
+ "vueFeatures": {
+ "filter": false // Don't!!
+ }
+ }
+}
+```
+
+:::
+
+### :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Filters](https://v3-migration.vuejs.org/breaking-changes/filters.html)
+- [Vue RFCs - 0015-remove-filters](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0015-remove-filters.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-filter.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-filter.js)
diff --git a/docs/rules/no-deprecated-functional-template.md b/docs/rules/no-deprecated-functional-template.md
new file mode 100644
index 000000000..32656eb79
--- /dev/null
+++ b/docs/rules/no-deprecated-functional-template.md
@@ -0,0 +1,49 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-functional-template
+description: disallow using deprecated the `functional` template (in Vue.js 3.0.0+)
+since: v7.0.0
+---
+
+# vue/no-deprecated-functional-template
+
+> disallow using deprecated the `functional` template (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 `functional` template (in Vue.js 3.0.0+).
+
+See [Migration Guide - Functional Components](https://v3-migration.vuejs.org/breaking-changes/functional-components.html) for more details.
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+### :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Functional Components](https://v3-migration.vuejs.org/breaking-changes/functional-components.html)
+- [Vue RFCs - 0007-functional-async-api-change](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0007-functional-async-api-change.md)
+- [Guide - Functional Components](https://v2.vuejs.org/v2/guide/render-function.html#Functional-Components)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-functional-template.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-functional-template.js)
diff --git a/docs/rules/no-deprecated-html-element-is.md b/docs/rules/no-deprecated-html-element-is.md
new file mode 100644
index 000000000..844c424ed
--- /dev/null
+++ b/docs/rules/no-deprecated-html-element-is.md
@@ -0,0 +1,53 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-html-element-is
+description: disallow using deprecated the `is` attribute on HTML elements (in Vue.js 3.0.0+)
+since: v7.0.0
+---
+
+# vue/no-deprecated-html-element-is
+
+> disallow using deprecated the `is` attribute on HTML elements (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 `is` attribute on HTML elements (removed in Vue.js v3.0.0+).
+
+See [Migration Guide - Custom Elements Interop](https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html#customized-built-in-elements) for more details.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Custom Elements Interop](https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html#customized-built-in-elements)
+- [Vue RFCs - 0027-custom-elements-interop](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0027-custom-elements-interop.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-html-element-is.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-html-element-is.js)
diff --git a/docs/rules/no-deprecated-inline-template.md b/docs/rules/no-deprecated-inline-template.md
new file mode 100644
index 000000000..5dcbc22b3
--- /dev/null
+++ b/docs/rules/no-deprecated-inline-template.md
@@ -0,0 +1,56 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-inline-template
+description: disallow using deprecated `inline-template` attribute (in Vue.js 3.0.0+)
+since: v7.0.0
+---
+
+# vue/no-deprecated-inline-template
+
+> disallow using deprecated `inline-template` attribute (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 `inline-template` attributes (removed in Vue.js v3.0.0+).
+
+See [Migration Guide - Inline Template Attribute](https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html) for more details.
+
+
+
+```vue
+
+
+
+
+
+
+
+
These are compiled as the component's own template.
+
Not parent's transclusion content.
+
+
+
+```
+
+
+
+### :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Inline Template Attribute](https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html)
+- [Vue RFCs - 0016-remove-inline-templates](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0016-remove-inline-templates.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-inline-template.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-inline-template.js)
diff --git a/docs/rules/no-deprecated-model-definition.md b/docs/rules/no-deprecated-model-definition.md
new file mode 100644
index 000000000..084652b91
--- /dev/null
+++ b/docs/rules/no-deprecated-model-definition.md
@@ -0,0 +1,82 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-model-definition
+description: disallow deprecated `model` definition (in Vue.js 3.0.0+)
+since: v9.16.0
+---
+
+# vue/no-deprecated-model-definition
+
+> disallow deprecated `model` definition (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"]`.
+- :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 use of the component `model` option, which has been deprecated in Vue.js 3.0.0+.
+
+See [Migration Guide – `v-model`](https://v3-migration.vuejs.org/breaking-changes/v-model.html) for more details.
+
+
+
+```vue
+
+```
+
+
+
+## :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
new file mode 100644
index 000000000..af8136fa2
--- /dev/null
+++ b/docs/rules/no-deprecated-props-default-this.md
@@ -0,0 +1,77 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-props-default-this
+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 deprecated `this` access in props default function (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 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-migration.vuejs.org/breaking-changes/props-default-this.html) for more details.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Props Default Function `this` Access](https://v3-migration.vuejs.org/breaking-changes/props-default-this.html)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-props-default-this.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-props-default-this.js)
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
new file mode 100644
index 000000000..d19b172a8
--- /dev/null
+++ b/docs/rules/no-deprecated-scope-attribute.md
@@ -0,0 +1,55 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 deprecated `scope` attribute in Vue.js v2.5.0+.
+
+
+
+```vue
+
+
+
+
+ {{ props.title }}
+
+
+ {{ props.title }}
+
+
+
+
+
+ {{ props.title }}
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [API - scope](https://v2.vuejs.org/v2/api/#scope-removed)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-scope-attribute.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-scope-attribute.js)
diff --git a/docs/rules/no-deprecated-slot-attribute.md b/docs/rules/no-deprecated-slot-attribute.md
new file mode 100644
index 000000000..64f2c5e00
--- /dev/null
+++ b/docs/rules/no-deprecated-slot-attribute.md
@@ -0,0 +1,95 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 deprecated `slot` attribute in Vue.js v2.6.0+.
+
+
+
+```vue
+
+
+
+
+ {{ props.title }}
+
+
+
+
+
+ {{ props.title }}
+
+
+
+```
+
+
+
+## :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://v2.vuejs.org/v2/api/#slot-deprecated)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-slot-attribute.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-slot-attribute.js)
diff --git a/docs/rules/no-deprecated-slot-scope-attribute.md b/docs/rules/no-deprecated-slot-scope-attribute.md
new file mode 100644
index 000000000..86212de0f
--- /dev/null
+++ b/docs/rules/no-deprecated-slot-scope-attribute.md
@@ -0,0 +1,52 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 deprecated `slot-scope` attribute in Vue.js v2.6.0+.
+
+
+
+```vue
+
+
+
+
+ {{ props.title }}
+
+
+
+
+
+ {{ props.title }}
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [API - slot-scope](https://v2.vuejs.org/v2/api/#slot-scope-deprecated)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-slot-scope-attribute.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-slot-scope-attribute.js)
diff --git a/docs/rules/no-deprecated-v-bind-sync.md b/docs/rules/no-deprecated-v-bind-sync.md
new file mode 100644
index 000000000..77a79f16f
--- /dev/null
+++ b/docs/rules/no-deprecated-v-bind-sync.md
@@ -0,0 +1,62 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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-migration.vuejs.org/breaking-changes/v-model.html) for more details.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/valid-v-bind]
+
+[vue/valid-v-bind]: ./valid-v-bind.md
+
+## :books: Further Reading
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-bind-sync.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-bind-sync.js)
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
new file mode 100644
index 000000000..8b8312235
--- /dev/null
+++ b/docs/rules/no-deprecated-v-on-native-modifier.md
@@ -0,0 +1,57 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 `.native` modifier on `v-on` directive (in Vue.js 3.0.0+)
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/valid-v-on]
+
+[vue/valid-v-on]: ./valid-v-on.md
+
+## :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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-on-native-modifier.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-on-native-modifier.js)
diff --git a/docs/rules/no-deprecated-v-on-number-modifiers.md b/docs/rules/no-deprecated-v-on-number-modifiers.md
new file mode 100644
index 000000000..54ea7d431
--- /dev/null
+++ b/docs/rules/no-deprecated-v-on-number-modifiers.md
@@ -0,0 +1,62 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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-migration.vuejs.org/breaking-changes/keycode-modifiers.html) for more details.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/valid-v-on]
+
+[vue/valid-v-on]: ./valid-v-on.md
+
+## :books: Further Reading
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-on-number-modifiers.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-on-number-modifiers.js)
diff --git a/docs/rules/no-deprecated-vue-config-keycodes.md b/docs/rules/no-deprecated-vue-config-keycodes.md
new file mode 100644
index 000000000..697604b8c
--- /dev/null
+++ b/docs/rules/no-deprecated-vue-config-keycodes.md
@@ -0,0 +1,59 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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-migration.vuejs.org/breaking-changes/keycode-modifiers.html) for more details.
+
+
+
+```js
+/* ✗ BAD */
+Vue.config.keyCodes = {
+ // ...
+}
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/no-deprecated-v-on-number-modifiers]
+
+[vue/no-deprecated-v-on-number-modifiers]: ./no-deprecated-v-on-number-modifiers.md
+
+## :books: Further Reading
+
+- [Migration Guide - KeyCode Modifiers]
+- [Vue RFCs - 0014-drop-keycode-support]
+- [API - Global Config - keyCodes]
+
+[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://v2.vuejs.org/v2/api/#keyCodes
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-vue-config-keycodes.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-vue-config-keycodes.js)
diff --git a/docs/rules/no-dupe-keys.md b/docs/rules/no-dupe-keys.md
index aa86d0f58..3372a8e26 100644
--- a/docs/rules/no-dupe-keys.md
+++ b/docs/rules/no-dupe-keys.md
@@ -1,76 +1,87 @@
-# disallow duplication of field names (vue/no-dupe-keys)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-dupe-keys
+description: disallow duplication of field names
+since: v3.9.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/no-dupe-keys
-This rule prevents to use duplicated names.
+> disallow duplication of field names
+
+- :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 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 `
```
-:+1: Examples of **correct** code for this rule:
-
-```js
-export default {
- props: ['foo'],
- computed: {
- bar () {}
- },
- data () {
- return {
- baz: null
- }
- },
- methods: {
- boo () {}
- }
-}
-```
+
## :wrench: Options
-This rule has an object option:
-
-`"groups"`: [] (default) array of additional groups to search for duplicates.
+```json
+{
+ "vue/no-dupe-keys": ["error", {
+ "groups": []
+ }]
+}
+```
-### Example:
+- `"groups"` (`string[]`) Array of additional groups to search for duplicates. Default is empty.
-``` json
-"vue/no-dupe-keys": [2, {
- groups: ["firebase"]
-}]
-```
+### `"groups": ["firebase"]`
-:-1: Examples of **incorrect** code for this configuration
+
-```js
+```vue
+
```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.9.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-dupe-keys.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-dupe-keys.js)
diff --git a/docs/rules/no-dupe-v-else-if.md b/docs/rules/no-dupe-v-else-if.md
new file mode 100644
index 000000000..d5bf83797
--- /dev/null
+++ b/docs/rules/no-dupe-v-else-if.md
@@ -0,0 +1,105 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 duplicate conditions in the same `v-if` / `v-else-if` chain.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+This rule can also detect some cases where the conditions are not identical, but the branch can never execute due to the logic of `||` and `&&` operators.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [no-dupe-else-if]
+
+[no-dupe-else-if]: https://eslint.org/docs/rules/no-dupe-else-if
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-dupe-v-else-if.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-dupe-v-else-if.js)
diff --git a/docs/rules/no-duplicate-attr-inheritance.md b/docs/rules/no-duplicate-attr-inheritance.md
new file mode 100644
index 000000000..fe3cd37bf
--- /dev/null
+++ b/docs/rules/no-duplicate-attr-inheritance.md
@@ -0,0 +1,93 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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 duplicate attribute inheritance.
+This rule suggests applying `inheritAttrs: false` when it detects `v-bind="$attrs"` being used.
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```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://vuejs.org/api/options-misc.html#inheritattrs)
+- [Fallthrough Attributes](https://vuejs.org/guide/components/attrs.html#attribute-inheritance-on-multiple-root-nodes)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-duplicate-attr-inheritance.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-duplicate-attr-inheritance.js)
diff --git a/docs/rules/no-duplicate-attributes.md b/docs/rules/no-duplicate-attributes.md
index 51a377893..9ee2d2be6 100644
--- a/docs/rules/no-duplicate-attributes.md
+++ b/docs/rules/no-duplicate-attributes.md
@@ -1,51 +1,80 @@
-# disallow duplication of attributes (vue/no-duplicate-attributes)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-duplicate-attributes
+description: disallow duplication of attributes
+since: v3.0.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/no-duplicate-attributes
-When duplicate arguments exist, only the last one is valid.
-It's possibly mistakes.
+> disallow duplication of attributes
-## :book: Rule Details
+- :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 duplicate attributes.
-`v-bind:foo` directives are handled as the attributes `foo`.
+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.
-:-1: Examples of **incorrect** code for this rule:
+## :book: Rule Details
-```html
-
+This rule reports duplicate attributes.
+`v-bind:foo` directives are handled as the attribute `foo`.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-```
+## :wrench: Options
-```html
-
+```json
+{
+ "vue/no-duplicate-attributes": ["error", {
+ "allowCoexistClass": true,
+ "allowCoexistStyle": true
+ }]
+}
```
-## :wrench: Options
+- `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`.
-`allowCoexistClass` - Enables [`v-bind:class`] directive can coexist with the plain `class` attribute.
-`allowCoexistStyle` - Enables [`v-bind:style`] directive can coexist with the plain `style` attribute.
+[`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`
+
+
+
+```vue
+
+
+
+
+
```
-'vue/no-duplicate-attributes': [2, {
- allowCoexistClass: Boolean // default: true
- allowCoexistStyle: Boolean, // default: true
-}]
-```
-## TODO: ``
+
+
+## :rocket: Version
-`parse5` remove duplicate attributes on the tokenization phase.
-Needs investigation to check.
+This rule was introduced in eslint-plugin-vue v3.0.0
+## :mag: Implementation
-[`v-bind:class`]: https://vuejs.org/v2/guide/class-and-style.html
-[`v-bind:style`]: https://vuejs.org/v2/guide/class-and-style.html
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-duplicate-attributes.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-duplicate-attributes.js)
diff --git a/docs/rules/no-empty-component-block.md b/docs/rules/no-empty-component-block.md
new file mode 100644
index 000000000..344bd0cba
--- /dev/null
+++ b/docs/rules/no-empty-component-block.md
@@ -0,0 +1,76 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-empty-component-block
+description: disallow the `` `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-empty-component-block.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-empty-component-block.js)
diff --git a/docs/rules/no-empty-pattern.md b/docs/rules/no-empty-pattern.md
new file mode 100644
index 000000000..6434a7bff
--- /dev/null
+++ b/docs/rules/no-empty-pattern.md
@@ -0,0 +1,30 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-empty-pattern
+description: Disallow empty destructuring patterns in ``
+since: v6.0.0
+---
+
+# vue/no-empty-pattern
+
+> Disallow empty destructuring patterns in ``
+
+This rule is the same rule as core [no-empty-pattern] rule but it applies to the expressions in ``.
+
+## :books: Further Reading
+
+- [no-empty-pattern]
+
+[no-empty-pattern]: https://eslint.org/docs/rules/no-empty-pattern
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.0.0
+
+## :mag: Implementation
+
+- [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/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
new file mode 100644
index 000000000..442a965ba
--- /dev/null
+++ b/docs/rules/no-extra-parens.md
@@ -0,0 +1,61 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-extra-parens
+description: Disallow unnecessary parentheses in ``
+since: v7.0.0
+---
+
+# vue/no-extra-parens
+
+> 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 ``.
+
+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
+
+## :book: Rule Details
+
+This rule restricts the use of parentheses to only where they are necessary.
+This rule extends the [@stylistic/no-extra-parens] rule and applies it to the ``. This rule also checks some Vue.js syntax.
+
+
+
+```vue
+
+
+
+ {{ foo + bar }}
+ {{ foo + bar | filter }}
+
+
+ {{ (foo + bar) }}
+ {{ (foo + bar) | filter }}
+
+```
+
+
+
+## :books: Further Reading
+
+- [@stylistic/no-extra-parens]
+- [no-extra-parens]
+
+[@stylistic/no-extra-parens]: https://eslint.style/rules/default/no-extra-parens
+[no-extra-parens]: https://eslint.org/docs/rules/no-extra-parens
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-extra-parens.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-extra-parens.js)
+
+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
new file mode 100644
index 000000000..615f2294a
--- /dev/null
+++ b/docs/rules/no-irregular-whitespace.md
@@ -0,0 +1,178 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-irregular-whitespace
+description: disallow irregular whitespace in `.vue` files
+since: v6.1.0
+---
+
+# vue/no-irregular-whitespace
+
+> disallow irregular whitespace in `.vue` files
+
+## :book: Rule Details
+
+`vue/no-irregular-whitespace` rule is aimed at catching invalid whitespace that is not a normal tab and space. Some of these characters may cause issues in modern browsers and others will be a debugging issue to spot.
+`vue/no-irregular-whitespace` rule is the similar rule as core [no-irregular-whitespace] rule but it applies to the source code in .vue.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```js
+{
+ "vue/no-irregular-whitespace": ["error", {
+ "skipStrings": true,
+ "skipComments": false,
+ "skipRegExps": false,
+ "skipTemplates": false,
+ "skipHTMLAttributeValues": false,
+ "skipHTMLTextContents": false
+ }]
+}
+```
+
+- `skipStrings`: if `true`, allows any whitespace characters in string literals. default `true`
+- `skipComments`: if `true`, allows any whitespace characters in comments. default `false`
+- `skipRegExps`: if `true`, allows any whitespace characters in regular expression literals. default `false`
+- `skipTemplates`: if `true`, allows any whitespace characters in template literals. default `false`
+- `skipHTMLAttributeValues`: if `true`, allows any whitespace characters in HTML attribute values. default `false`
+- `skipHTMLTextContents`: if `true`, allows any whitespace characters in HTML text contents. default `false`
+
+### `"skipStrings": true` (default)
+
+
+
+```vue
+
+```
+
+
+
+### `"skipStrings": false`
+
+
+
+```vue
+
+```
+
+
+
+### `"skipComments": true`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+### `"skipRegExps": true`
+
+
+
+```vue
+
+```
+
+
+
+### `"skipTemplates": true`
+
+
+
+```vue
+
+```
+
+
+
+### `"skipHTMLAttributeValues": true`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+### `"skipHTMLTextContents": true`
+
+
+
+```vue
+
+
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [no-irregular-whitespace]
+
+[no-irregular-whitespace]: https://eslint.org/docs/rules/no-irregular-whitespace
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-irregular-whitespace.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-irregular-whitespace.js)
+
+Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/no-irregular-whitespace)
diff --git a/docs/rules/no-lifecycle-after-await.md b/docs/rules/no-lifecycle-after-await.md
new file mode 100644
index 000000000..78d54c526
--- /dev/null
+++ b/docs/rules/no-lifecycle-after-await.md
@@ -0,0 +1,57 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 lifecycle hooks after `await` expression.
+In `setup()` function, `onXXX` lifecycle hooks should be registered synchronously.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-lifecycle-after-await.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-lifecycle-after-await.js)
diff --git a/docs/rules/no-lone-template.md b/docs/rules/no-lone-template.md
new file mode 100644
index 000000000..170b63ce3
--- /dev/null
+++ b/docs/rules/no-lone-template.md
@@ -0,0 +1,89 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-lone-template
+description: disallow unnecessary ``
+since: v7.0.0
+---
+
+# vue/no-lone-template
+
+> disallow unnecessary ``
+
+- :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
+
+This rule aims to eliminate unnecessary and potentially confusing ``.
+In Vue.js 2.x, the `` elements that have no specific directives have no effect.
+In Vue.js 3.x, the `` elements that have no specific directives render the `` elements as is, but in most cases this may not be what you intended.
+
+
+
+```vue
+
+
+ ...
+ ...
+ ...
+ ...
+ ...
+
+
+ ...
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-lone-template": ["error", {
+ "ignoreAccessible": false
+ }]
+}
+```
+
+- `ignoreAccessible` ... If `true`, ignore accessible `` elements. default `false`.
+ Note: this option is useless if you are using Vue.js 2.x.
+
+### `"ignoreAccessible": true`
+
+
+
+```vue
+
+
+ ...
+ ...
+
+
+ ...
+
+```
+
+
+
+## :mute: When Not To Use It
+
+If you are using Vue.js 3.x and want to define the `` element intentionally, you will have to turn this rule off or use `"ignoreAccessible"` option.
+
+## :couple: Related Rules
+
+- [vue/no-template-key]
+- [no-lone-blocks]
+
+[no-lone-blocks]: https://eslint.org/docs/rules/no-lone-blocks
+[vue/no-template-key]: ./no-template-key.md
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-lone-template.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-lone-template.js)
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 196bee568..66c6a81d7 100644
--- a/docs/rules/no-multi-spaces.md
+++ b/docs/rules/no-multi-spaces.md
@@ -1,30 +1,86 @@
-# disallow multiple spaces (vue/no-multi-spaces)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-multi-spaces
+description: disallow multiple spaces
+since: v3.12.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/no-multi-spaces
-The `--fix` option on the command line can automatically fix some of the problems reported by this rule.
+> disallow multiple spaces
-This rule aims to remove multiple spaces in a row between attributes witch are not used for indentation.
+- :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.
-## Rule Details
+## :book: Rule Details
-Examples of **incorrect** code for this rule:
+This rule aims at removing multiple spaces in tags, which are not used for indentation.
-```html
-
+
+
+```vue
+
+
+
+
+
+
+
+
+
```
-Examples of **correct** code for this rule:
+
+
+## :wrench: Options
-```html
-
+```json
+{
+ "vue/no-multi-spaces": ["error", {
+ "ignoreProperties": false
+ }]
+}
```
-### Options
+- `ignoreProperties` ... whether or not objects' properties should be ignored. default `false`
+
+### `"ignoreProperties": true`
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.12.0
+
+## :mag: Implementation
-Nothing
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-multi-spaces.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-multi-spaces.js)
diff --git a/docs/rules/no-multiple-objects-in-class.md b/docs/rules/no-multiple-objects-in-class.md
new file mode 100644
index 000000000..13f1f591b
--- /dev/null
+++ b/docs/rules/no-multiple-objects-in-class.md
@@ -0,0 +1,44 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-multiple-objects-in-class
+description: disallow passing multiple objects in an array to class
+since: v7.0.0
+---
+
+# vue/no-multiple-objects-in-class
+
+> disallow passing multiple objects in an array to class
+
+## :book: Rule Details
+
+This rule disallows to pass multiple objects into array to class.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-multiple-objects-in-class.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-multiple-objects-in-class.js)
diff --git a/docs/rules/no-multiple-slot-args.md b/docs/rules/no-multiple-slot-args.md
new file mode 100644
index 000000000..74d6c2931
--- /dev/null
+++ b/docs/rules/no-multiple-slot-args.md
@@ -0,0 +1,56 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-multiple-slot-args
+description: disallow passing multiple arguments to scoped slots
+since: v7.0.0
+---
+
+# vue/no-multiple-slot-args
+
+> disallow passing multiple arguments to scoped slots
+
+- :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
+
+This rule disallows to pass multiple arguments to scoped slots.
+In details, it reports call expressions if a call of `this.$scopedSlots` members has 2 or more arguments.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [vuejs/vue#9468](https://github.com/vuejs/vue/issues/9468#issuecomment-462210146)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-multiple-slot-args.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-multiple-slot-args.js)
diff --git a/docs/rules/no-multiple-template-root.md b/docs/rules/no-multiple-template-root.md
new file mode 100644
index 000000000..8a85c9201
--- /dev/null
+++ b/docs/rules/no-multiple-template-root.md
@@ -0,0 +1,98 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 checks whether template contains single root element valid for Vue 2.
+
+
+
+```vue
+
+Lorem ipsum
+```
+
+
+
+
+
+```vue
+
+
+
hello
+
hello
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-multiple-template-root.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-multiple-template-root.js)
diff --git a/docs/rules/no-mutating-props.md b/docs/rules/no-mutating-props.md
new file mode 100644
index 000000000..3ddee4cc2
--- /dev/null
+++ b/docs/rules/no-mutating-props.md
@@ -0,0 +1,176 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 mutation of component props.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```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://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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-mutating-props.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-mutating-props.js)
diff --git a/docs/rules/no-parsing-error.md b/docs/rules/no-parsing-error.md
index c5988b793..a6fcf2908 100644
--- a/docs/rules/no-parsing-error.md
+++ b/docs/rules/no-parsing-error.md
@@ -1,41 +1,52 @@
-# disallow parsing errors in `` (vue/no-parsing-error)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-parsing-error
+description: disallow parsing errors in ``
+since: v3.0.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/no-parsing-error
+
+> disallow parsing errors in ``
+
+- :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: 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
This rule tries to parse directives/mustaches in `` by the parser which parses `
+```
+
+
+
+> 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`
+
+```json
+{
+ "vue/no-potential-component-option-typo": ["error", {
+ "presets": ["vue", "nuxt"],
+ "threshold": 5
+ }]
+}
+```
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-potential-component-option-typo": ["error", {
+ "presets": ["vue"],
+ "custom": [],
+ "threshold": 1
+ }]
+}
+```
+
+- `presets` ... `enum type`, contains several common vue component option set, `["all"]` is the same as `["vue", "vue-router", "nuxt"]`. **default** `["vue"]`
+- `custom` ... `array type`, a list store your custom component option want to detect. **default** `[]`
+- `threshold` ... `number type`, a number used to control the upper limit of the reported editing distance, we recommend don't change this config option, even if it is required, not bigger than `2`. **default** `1`
+
+## :rocket: Suggestion
+
+- We provide all the possible component option that edit distance between your vue component option and configuration options is greater than 0 and less equal than threshold
+
+## :books: Further Reading
+
+- [Edit distance](https://en.wikipedia.org/wiki/Edit_distance)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-potential-component-option-typo.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-potential-component-option-typo.js)
diff --git a/docs/rules/no-ref-as-operand.md b/docs/rules/no-ref-as-operand.md
new file mode 100644
index 000000000..af10e0b7d
--- /dev/null
+++ b/docs/rules/no-ref-as-operand.md
@@ -0,0 +1,72 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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
+
+This rule reports cases where a ref is used incorrectly as an operand.
+You must use `.value` to access the `Ref` value.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Guide - Reactivity - Reactivity Fundamentals / Creating Standalone Reactive Values as `refs`](https://v3.vuejs.org/guide/reactivity-fundamentals.html#creating-standalone-reactive-values-as-refs)
+- [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 v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-ref-as-operand.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-ref-as-operand.js)
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
new file mode 100644
index 000000000..c99191872
--- /dev/null
+++ b/docs/rules/no-reserved-component-names.md
@@ -0,0 +1,126 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/no-reserved-component-names": ["error", {
+ "disallowVueBuiltInComponents": 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`
+
+
+
+```vue
+
+```
+
+
+
+### `"disallowVue3BuiltInComponents": true`
+
+
+
+```vue
+
+```
+
+
+
+### `"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://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
+
+This rule was introduced in eslint-plugin-vue v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-reserved-component-names.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-reserved-component-names.js)
diff --git a/docs/rules/no-reserved-keys.md b/docs/rules/no-reserved-keys.md
index 80331d4d7..9244ee0b7 100644
--- a/docs/rules/no-reserved-keys.md
+++ b/docs/rules/no-reserved-keys.md
@@ -1,58 +1,90 @@
-# disallow overwriting reserved keys (vue/no-reserved-keys)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-reserved-keys
+description: disallow overwriting reserved keys
+since: v3.9.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/no-reserved-keys
-This rule prevents to use reserved names from to avoid conflicts and unexpected behavior.
+> disallow overwriting reserved keys
-## Rule Details
+- :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"]`.
-:-1: Examples of **incorrect** code for this rule:
+## :book: Rule Details
-```js
+This rule prevents to use [reserved names](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/vue-reserved.json) to avoid conflicts and unexpected behavior.
+
+
+
+```vue
+
```
-## :wrench: Options
-
-This rule has an object option:
+
-`"reserved"`: [] (default) array of dissalowed names inside `groups`.
+## :wrench: Options
-`"groups"`: [] (default) array of additional groups to search for duplicates.
+```json
+{
+ "vue/no-reserved-keys": ["error", {
+ "reserved": [],
+ "groups": []
+ }]
+}
+```
-### Example:
+- `reserved` (`string[]`) ... Array of additional restricted attributes inside `groups`. Default is empty.
+- `groups` (`string[]`) ... Array of additional group names to search for duplicates in. Default is empty.
-``` json
-"vue/no-reserved-keys": [2, {
- reserved: ['foo', 'foo2'],
- groups: ['firebase']
-}]
-```
+### `"reserved": ["foo", "foo2"], "groups": ["firebase"]`
-:-1: Examples of **incorrect** code for this configuration
+
-```js
+```vue
+
```
+
+
+
+## :books: Further Reading
+
+- [List of reserved keys](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/vue-reserved.json)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.9.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-reserved-keys.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-reserved-keys.js)
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
new file mode 100644
index 000000000..0a96a64a4
--- /dev/null
+++ b/docs/rules/no-restricted-block.md
@@ -0,0 +1,92 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-block
+description: disallow specific block
+since: v7.4.0
+---
+
+# vue/no-restricted-block
+
+> disallow specific block
+
+## :book: Rule Details
+
+This rule allows you to specify block names that you don't want to use in your application.
+
+## :wrench: Options
+
+This rule takes a list of strings, where each string is a block name or pattern to be restricted:
+
+```json
+{
+ "vue/no-restricted-block": ["error", "style", "foo", "bar"]
+}
+```
+
+
+
+```vue
+
+
+ Custom block
+
+
+ Custom block
+
+
+```
+
+
+
+Alternatively, the rule also accepts objects.
+
+```json
+{
+ "vue/no-restricted-block": ["error",
+ {
+ "element": "style",
+ "message": "Do not use
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/padding-line-between-blocks": ["error", "always" | "never"]
+}
+```
+
+- `"always"` (default) ... Requires one or more blank lines. Note it does not count lines that comments exist as blank lines.
+- `"never"` ... Disallows blank lines.
+
+### `"always"` (default)
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+### `"never"`
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [padding-line-between-statements]
+- [lines-between-class-members]
+
+[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
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/padding-line-between-blocks.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/padding-line-between-blocks.js)
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
new file mode 100644
index 000000000..ae4d3fc29
--- /dev/null
+++ b/docs/rules/prefer-template.md
@@ -0,0 +1,32 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-template
+description: Require template literals instead of string concatenation in ``
+since: v7.0.0
+---
+
+# vue/prefer-template
+
+> 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#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 ``.
+
+## :books: Further Reading
+
+- [prefer-template]
+
+[prefer-template]: https://eslint.org/docs/rules/prefer-template
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [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/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
new file mode 100644
index 000000000..32681643c
--- /dev/null
+++ b/docs/rules/prop-name-casing.md
@@ -0,0 +1,117 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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 enforce proper casing of props in vue components(camelCase).
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "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"`
+
+
+
+```vue
+
+```
+
+
+
+### `"ignoreProps": ["foo-bar", "/^_[a-z]+/u"]`
+
+
+
+```vue
+
+```
+
+
+
+## :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
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prop-name-casing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prop-name-casing.js)
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 cf8a9d89e..40280a975 100644
--- a/docs/rules/require-component-is.md
+++ b/docs/rules/require-component-is.md
@@ -1,29 +1,54 @@
-# require `v-bind:is` of `` elements (vue/require-component-is)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-component-is
+description: require `v-bind:is` of `` elements
+since: v3.0.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/require-component-is
-> You can use the same mount point and dynamically switch between multiple components using the reserved `` element and dynamically bind to its `is` attribute:
->
-> https://vuejs.org/v2/guide/components.html#Dynamic-Components
+> require `v-bind:is` of `` elements
+
+- :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.
-:-1: Examples of **incorrect** code for this rule:
+
+
+```vue
+
+
+
+
-```html
-
-
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-
-```
+::: warning Note
+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://vuejs.org/guide/essentials/component-basics.html#dynamic-components)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-component-is.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-component-is.js)
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 bacaae0b9..c96edc24e 100644
--- a/docs/rules/require-default-prop.md
+++ b/docs/rules/require-default-prop.md
@@ -1,43 +1,80 @@
-# require default value for props (vue/require-default-prop)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-default-prop
+description: require default value for props
+since: v3.13.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/require-default-prop
-This rule requires default value to be set for each props that are not marked as `required`.
+> require default value for props
-## Rule Details
+- :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"]`.
-Examples of **incorrect** code for this rule:
+## :book: Rule Details
-```js
-props: {
- a: Number,
- b: [Number, String],
- c: {
- type: Number
- },
- d: {
- type: Number,
- required: false
- }
-}
-```
+This rule requires default value to be set for each props that are not marked as `required` (except `Boolean` props).
+
+
-Examples of **correct** code for this rule:
-
-```js
-props: {
- a: {
- type: Number,
- required: true
- },
- b: {
- type: Number,
- default: 0
- },
- c: {
- type: Number,
- default: 0,
- required: false
+```vue
+
```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/no-boolean-default](./no-boolean-default.md)
+
+## :books: Further Reading
+
+- [Style guide - Prop definitions](https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.13.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-default-prop.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-default-prop.js)
diff --git a/docs/rules/require-direct-export.md b/docs/rules/require-direct-export.md
new file mode 100644
index 000000000..4f2e37650
--- /dev/null
+++ b/docs/rules/require-direct-export.md
@@ -0,0 +1,100 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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
+
+## :book: Rule Details
+
+This rule aims to require that the component object be directly exported.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/require-direct-export": ["error", {
+ "disallowFunctionalComponentFunction": false
+ }]
+}
+```
+
+- `"disallowFunctionalComponentFunction"` ... If `true`, disallow functional component functions, available in Vue 3.x. default `false`
+
+### `"disallowFunctionalComponentFunction": false`
+
+
+
+```vue
+
+```
+
+
+
+### `"disallowFunctionalComponentFunction": true`
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-direct-export.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-direct-export.js)
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
new file mode 100644
index 000000000..5c5d3f8a9
--- /dev/null
+++ b/docs/rules/require-explicit-emits.md
@@ -0,0 +1,130 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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 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.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/require-explicit-emits": ["error", {
+ "allowProps": false
+ }]
+}
+```
+
+- `"allowProps"` ... If `true`, allow event names defined in `props`. default `false`
+
+### `"allowProps": true`
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/no-unused-emit-declarations](./no-unused-emit-declarations.md)
+- [vue/require-explicit-slots](./require-explicit-slots.md)
+
+## :books: Further Reading
+
+- [Guide - Custom Events / Defining Custom Events](https://v3.vuejs.org/guide/component-custom-events.html#defining-custom-events)
+- [Vue RFCs - 0030-emits-option](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-explicit-emits.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-explicit-emits.js)
diff --git a/docs/rules/require-explicit-slots.md b/docs/rules/require-explicit-slots.md
new file mode 100644
index 000000000..4d40be209
--- /dev/null
+++ b/docs/rules/require-explicit-slots.md
@@ -0,0 +1,76 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-explicit-slots
+description: require slots to be explicitly defined
+since: v9.21.0
+---
+
+# vue/require-explicit-slots
+
+> require slots to be explicitly defined
+
+## :book: Rule Details
+
+This rule enforces all slots used in the template to be defined once either in the `script setup` block with the [`defineSlots`](https://vuejs.org/api/sfc-script-setup.html#defineslots) macro, or with the [`slots property`](https://vuejs.org/api/options-rendering.html#slots) in the Options API.
+
+
+
+```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
new file mode 100644
index 000000000..6d9974fc4
--- /dev/null
+++ b/docs/rules/require-name-property.md
@@ -0,0 +1,67 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-name-property.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-name-property.js)
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
new file mode 100644
index 000000000..e872fc5d4
--- /dev/null
+++ b/docs/rules/require-prop-type-constructor.md
@@ -0,0 +1,85 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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
+
+This rule reports prop types that can't be presumed as constructors.
+
+It's impossible to catch every possible case and know whether the prop type is a constructor or not, hence this rule restricts few types of nodes, instead of allowing correct ones.
+
+The following types are forbidden and will be reported:
+
+- Literal
+- TemplateLiteral
+- BinaryExpression
+- UpdateExpression
+
+It will catch most commonly made mistakes which are using strings instead of constructors.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Guide - Prop Validation](https://vuejs.org/guide/components/props.html#prop-validation)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-prop-type-constructor.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-prop-type-constructor.js)
diff --git a/docs/rules/require-prop-types.md b/docs/rules/require-prop-types.md
index c163a6709..20e46453f 100644
--- a/docs/rules/require-prop-types.md
+++ b/docs/rules/require-prop-types.md
@@ -1,39 +1,78 @@
-# require type definitions in props (vue/require-prop-types)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-prop-types
+description: require type definitions in props
+since: v3.9.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/require-prop-types
-In committed code, prop definitions should always be as detailed as possible, specifying at least type(s).
+> require type definitions in props
+
+- :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
This rule enforces that a `props` statement contains type definition.
-:-1: Examples of **incorrect** code for this rule:
+In committed code, prop definitions should always be as detailed as possible, specifying at least type(s).
-```js
-props: ['status']
-```
+
-:+1: Examples of **correct** code for this rule:
+```vue
+
```
+
+
## :wrench: Options
Nothing.
+
+## :books: Further Reading
+
+- [Style guide - Prop definitions](https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.9.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-prop-types.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-prop-types.js)
diff --git a/docs/rules/require-render-return.md b/docs/rules/require-render-return.md
index a28826c2a..88cbf1efb 100644
--- a/docs/rules/require-render-return.md
+++ b/docs/rules/require-render-return.md
@@ -1,39 +1,66 @@
-# enforce render function to always return value (vue/require-render-return)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-render-return
+description: enforce render function to always return value
+since: v3.10.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/require-render-return
-This rule aims to enforce render function to always return value
+> enforce render function to always return value
+
+- :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
-:-1: Examples of **incorrect** code for this rule:
+This rule aims to enforce render function to always return value
-```js
+
+
+```vue
+
```
-```js
+
+
+
+
+```vue
+
```
-:+1: Examples of **correct** code for this rule:
-
-```js
-export default {
- render (h) {
- return h('div', 'hello')
- }
-}
-```
+
## :wrench: Options
Nothing.
+
+## :books: Further Reading
+
+- [Guide - Render Functions](https://vuejs.org/guide/extras/render-function.html)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.10.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-render-return.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-render-return.js)
diff --git a/docs/rules/require-slots-as-functions.md b/docs/rules/require-slots-as-functions.md
new file mode 100644
index 000000000..56df0f88c
--- /dev/null
+++ b/docs/rules/require-slots-as-functions.md
@@ -0,0 +1,56 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-slots-as-functions
+description: enforce properties of `$slots` to be used as a function
+since: v7.0.0
+---
+
+# vue/require-slots-as-functions
+
+> enforce properties of `$slots` to be used as a function
+
+- :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 enforces the properties of `$slots` to be used as a function.
+`this.$slots.default` was an array of VNode in Vue.js 2.x, but changed to a function that returns an array of VNode in Vue.js 3.x.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [API - $slots](https://vuejs.org/api/component-instance.html#slots)
+- [Vue RFCs - 0006-slots-unification](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0006-slots-unification.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-slots-as-functions.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-slots-as-functions.js)
diff --git a/docs/rules/require-toggle-inside-transition.md b/docs/rules/require-toggle-inside-transition.md
new file mode 100644
index 000000000..6dd663fd1
--- /dev/null
+++ b/docs/rules/require-toggle-inside-transition.md
@@ -0,0 +1,76 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-toggle-inside-transition
+description: require control the display of the content inside ``
+since: v7.0.0
+---
+
+# vue/require-toggle-inside-transition
+
+> require control the display of the content inside ``
+
+- :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 elements inside `` that do not control the display.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/require-toggle-inside-transition": ["error", {
+ "additionalDirectives": []
+ }]
+}
+```
+
+- `additionalDirectives` (`string[]`) ... Custom directives which will satisfy this rule in addition to `v-show` and `v-if`. Should be added without the `v-` prefix.
+
+### `additionalDirectives: ["dialog"]`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [Vue RFCs - 0017-transition-as-root](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0017-transition-as-root.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-toggle-inside-transition.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-toggle-inside-transition.js)
diff --git a/docs/rules/require-typed-object-prop.md b/docs/rules/require-typed-object-prop.md
new file mode 100644
index 000000000..c867e26c5
--- /dev/null
+++ b/docs/rules/require-typed-object-prop.md
@@ -0,0 +1,55 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-typed-object-prop
+description: enforce adding type declarations to object props
+since: v9.16.0
+---
+
+# vue/require-typed-object-prop
+
+> enforce adding type declarations to object props
+
+- :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
+
+Prevent missing type declarations for non-primitive object props in TypeScript projects.
+
+
+
+```vue
+
+```
+
+
+
+## :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 5c54a3d20..6811b9419 100644
--- a/docs/rules/require-v-for-key.md
+++ b/docs/rules/require-v-for-key.md
@@ -1,38 +1,61 @@
-# require `v-bind:key` with `v-for` directives (vue/require-v-for-key)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-v-for-key
+description: require `v-bind:key` with `v-for` directives
+since: v3.0.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/require-v-for-key
-When `v-for` is written on custom components, it requires `v-bind:key` at the same time.
-On other elements, it's better that `v-bind:key` is written as well.
+> require `v-bind:key` with `v-for` directives
-## :book: Rule Details
+- :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 the elements which have `v-for` and do not have `v-bind:key`.
+## :book: Rule Details
-This rule does not report custom components.
-It will be reported by [no-invalid-v-for] rule.
+This rule reports the elements which have `v-for` and do not have `v-bind:key` with exception to custom components.
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
+```vue
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-```
+::: warning Note
+This rule does not report missing `v-bind:key` on custom components.
+It will be reported by [vue/valid-v-for] rule.
+:::
## :wrench: Options
Nothing.
-## :couple: Related rules
+## :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://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
+
+This rule was introduced in eslint-plugin-vue v3.0.0
-- [no-invalid-v-for]
+## :mag: Implementation
-[no-invalid-v-for]: ./no-invalid-v-for.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-v-for-key.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-v-for-key.js)
diff --git a/docs/rules/require-valid-default-prop.md b/docs/rules/require-valid-default-prop.md
index 568d65aef..bea185798 100644
--- a/docs/rules/require-valid-default-prop.md
+++ b/docs/rules/require-valid-default-prop.md
@@ -1,61 +1,91 @@
-# enforce props default values to be valid (vue/require-valid-default-prop)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-valid-default-prop
+description: enforce props default values to be valid
+since: v3.13.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/require-valid-default-prop
-This rule checks whether the default value of each prop is valid for the given type. It should report an error when default value for type `Array` or `Object` is not returned using function.
+> enforce props default values to be valid
+
+- :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
-:-1: Examples of **incorrect** code for this rule:
-
-```js
-props: {
- propA: {
- type: String,
- default: {}
- },
- propB: {
- type: String,
- default: []
- },
- propC: {
- type: Object,
- default: []
- },
- propD: {
- type: Array,
- default: []
- },
- propE: {
- type: Object,
- default: { message: 'hello' }
- }
-}
-```
+This rule checks whether the default value of each prop is valid for the given type. It should report an error when default value for type `Array` or `Object` is not returned using function.
+
+
-:+1: Examples of **correct** code for this rule:
-
-```js
-props: {
- // basic type check (`null` means accept any type)
- propA: Number,
- // multiple possible types
- propB: [String, Number],
- // a number with default value
- propD: {
- type: Number,
- default: 100
- },
- // object/array defaults should be returned from a factory function
- propE: {
- type: Object,
- default: function () {
- return { message: 'hello' }
+```vue
+
```
+
+
## :wrench: Options
Nothing.
+
+## :books: Further Reading
+
+- [Guide - Prop Validation](https://vuejs.org/guide/components/props.html#prop-validation)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.13.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-valid-default-prop.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-valid-default-prop.js)
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 761db13a1..5a1906060 100644
--- a/docs/rules/return-in-computed-property.md
+++ b/docs/rules/return-in-computed-property.md
@@ -1,46 +1,137 @@
-# enforce that a return statement is present in computed property (vue/return-in-computed-property)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/return-in-computed-property
+description: enforce that a return statement is present in computed property
+since: v3.7.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# 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/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 enforces that a `return` statement is present in `computed` properties.
+This rule enforces that a `return` statement is present in `computed` properties and functions.
-:-1: Examples of **incorrect** code for this rule:
+
-```js
+```vue
+
```
-:+1: Examples of **correct** code for this rule:
+
+
+
-```js
+```vue
+
```
-## :wrench: Options
+
-This rule has an object option:
-- `"treatUndefinedAsUnspecified"`: `true` (default) disallows implicitly returning undefined with a `return;` statement.
+## :wrench: Options
```json
{
- "vue/return-in-computed-property": [2, {
+ "vue/return-in-computed-property": ["error", {
"treatUndefinedAsUnspecified": true
}]
}
```
+
+This rule has an object option:
+
+- `"treatUndefinedAsUnspecified"`: `true` (default) disallows implicitly returning undefined with a `return` statement.
+
+### `treatUndefinedAsUnspecified: false`
+
+
+
+```vue
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.7.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/return-in-computed-property.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/return-in-computed-property.js)
diff --git a/docs/rules/return-in-emits-validator.md b/docs/rules/return-in-emits-validator.md
new file mode 100644
index 000000000..ec1558241
--- /dev/null
+++ b/docs/rules/return-in-emits-validator.md
@@ -0,0 +1,71 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/return-in-emits-validator
+description: enforce that a return statement is present in emits validator
+since: v7.0.0
+---
+
+# vue/return-in-emits-validator
+
+> enforce that a return statement is present in emits validator
+
+- :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 enforces that a `return` statement is present in `emits` validators.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Guide - Custom Events / Validate Emitted Events](https://vuejs.org/guide/components/events.html#events-validation)
+- [Vue RFCs - 0030-emits-option](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/return-in-emits-validator.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/return-in-emits-validator.js)
diff --git a/docs/rules/script-indent.md b/docs/rules/script-indent.md
index 9168365c4..9922b8c48 100644
--- a/docs/rules/script-indent.md
+++ b/docs/rules/script-indent.md
@@ -1,32 +1,24 @@
-# enforce consistent indentation in `
```
-:+1: Examples of **correct** code for this rule:
+
+
+## :wrench: Options
+
+This rule has some options.
+
+```json
+{
+ "vue/script-indent": ["error", TYPE, {
+ "baseIndent": 0,
+ "switchCase": 0,
+ "ignores": []
+ }]
+}
+```
+
+- `TYPE` (`number | "tab"`) ... The type of indentation. Default is `2`. If this is a number, it's the number of spaces for one indent. If this is `"tab"`, it uses one tab for one indent.
+- `baseIndent` (`integer`) ... The multiplier of indentation for top-level statements. Default is `0`.
+- `switchCase` (`integer`) ... The multiplier of indentation for `case`/`default` clauses. Default is `0`.
+- `ignores` (`string[]`) ... The selector to ignore nodes. The AST spec is [here](https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md). You can use [esquery](https://github.com/estools/esquery#readme) to select nodes. Default is an empty array.
+
+::: warning Note
+This rule only checks `.vue` files and does not interfere with other `.js` files. Unfortunately the default `indent` rule when turned on will try to lint both, so in order to make them complementary you can use `overrides` setting and disable `indent` rule on `.vue` files:
+:::
+
+```json
+{
+ "rules": {
+ "vue/script-indent": ["error", 4, { "baseIndent": 1 }]
+ },
+ "overrides": [
+ {
+ "files": ["*.vue"],
+ "rules": {
+ "indent": "off"
+ }
+ }
+ ]
+}
+```
+
+### `2, "baseIndent": 1`
+
+
-```js
-/*eslint script-indent: ["error", 2, {"baseIndent": 1}]*/
+```vue
```
-## Related rules
+
+
+## :couple: Related Rules
- [indent](https://eslint.org/docs/rules/indent)
- [vue/html-indent](./html-indent.md)
+- [@typescript-eslint/indent](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/indent.md)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v4.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/script-indent.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/script-indent.js)
diff --git a/docs/rules/script-setup-uses-vars.md b/docs/rules/script-setup-uses-vars.md
new file mode 100644
index 000000000..7f0eb15f7
--- /dev/null
+++ b/docs/rules/script-setup-uses-vars.md
@@ -0,0 +1,79 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/script-setup-uses-vars
+description: prevent `
+
+
+
+
+```
+
+
+
+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
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/sort-keys": ["error", "asc", {
+ "caseSensitive": true,
+ "ignoreChildrenOf": ["model"],
+ "ignoreGrandchildrenOf": ["computed", "directives", "inject", "props", "watch"],
+ "minKeys": 2,
+ "natural": false
+ }]
+}
+```
+
+The 1st option is `"asc"` or `"desc"`.
+
+- `"asc"` (default) - enforce properties to be in ascending order.
+- `"desc"` - enforce properties to be in descending order.
+
+The 2nd option is an object which has 5 properties.
+
+- `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`.
+- `ignoreChildrenOf` - an array of properties to ignore the children of. Default is `["model"]`
+- `ignoreGrandchildrenOf` - an array of properties to ignore the grandchildren sort order. Default is `["computed", "directives", "inject", "props", "watch"]`
+- `minKeys` - Specifies the minimum number of keys that an object should have in order for the object's unsorted keys to produce an error. Default is `2`, which means by default all objects with unsorted keys will result in lint errors.
+- `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting.
+
+While using this rule, you may disable the normal `sort-keys` rule. This rule will apply to plain js files as well as Vue component scripts.
+
+## :books: Further Reading
+
+- [sort-keys]
+
+[sort-keys]: https://eslint.org/docs/rules/sort-keys
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/sort-keys.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/sort-keys.js)
diff --git a/docs/rules/space-in-parens.md b/docs/rules/space-in-parens.md
new file mode 100644
index 000000000..3d2d5dbd7
--- /dev/null
+++ b/docs/rules/space-in-parens.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/space-in-parens
+description: Enforce consistent spacing inside parentheses in ``
+since: v7.0.0
+---
+
+# vue/space-in-parens
+
+> Enforce consistent spacing inside 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/space-in-parens] 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/space-in-parens]
+- [space-in-parens]
+
+[@stylistic/space-in-parens]: https://eslint.style/rules/default/space-in-parens
+[space-in-parens]: https://eslint.org/docs/rules/space-in-parens
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/space-in-parens.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/space-in-parens.js)
+
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/js/space-in-parens)
diff --git a/docs/rules/space-infix-ops.md b/docs/rules/space-infix-ops.md
new file mode 100644
index 000000000..2b4e9d0a3
--- /dev/null
+++ b/docs/rules/space-infix-ops.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/space-infix-ops
+description: Require spacing around infix operators in ``
+since: v5.2.0
+---
+
+# vue/space-infix-ops
+
+> Require spacing around infix 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/space-infix-ops] 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/space-infix-ops]
+- [space-infix-ops]
+
+[@stylistic/space-infix-ops]: https://eslint.style/rules/default/space-infix-ops
+[space-infix-ops]: https://eslint.org/docs/rules/space-infix-ops
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/space-infix-ops.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/space-infix-ops.js)
+
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/ts/space-infix-ops)
diff --git a/docs/rules/space-unary-ops.md b/docs/rules/space-unary-ops.md
new file mode 100644
index 000000000..54fe5e192
--- /dev/null
+++ b/docs/rules/space-unary-ops.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/space-unary-ops
+description: Enforce consistent spacing before or after unary operators in ``
+since: v5.2.0
+---
+
+# vue/space-unary-ops
+
+> Enforce consistent spacing before or after unary 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/space-unary-ops] 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/space-unary-ops]
+- [space-unary-ops]
+
+[@stylistic/space-unary-ops]: https://eslint.style/rules/default/space-unary-ops
+[space-unary-ops]: https://eslint.org/docs/rules/space-unary-ops
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/space-unary-ops.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/space-unary-ops.js)
+
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/js/space-unary-ops)
diff --git a/docs/rules/static-class-names-order.md b/docs/rules/static-class-names-order.md
new file mode 100644
index 000000000..c13f28fdd
--- /dev/null
+++ b/docs/rules/static-class-names-order.md
@@ -0,0 +1,44 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/static-class-names-order
+description: enforce static class names order
+since: v6.1.0
+---
+
+# vue/static-class-names-order
+
+> enforce static class names order
+
+- :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 the order of static class names.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/static-class-names-order.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/static-class-names-order.js)
diff --git a/docs/rules/template-curly-spacing.md b/docs/rules/template-curly-spacing.md
new file mode 100644
index 000000000..dfca50a64
--- /dev/null
+++ b/docs/rules/template-curly-spacing.md
@@ -0,0 +1,39 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/template-curly-spacing
+description: Require or disallow spacing around embedded expressions of template strings in ``
+since: v7.0.0
+---
+
+# vue/template-curly-spacing
+
+> Require or disallow spacing around embedded expressions of template strings 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/template-curly-spacing] 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/template-curly-spacing]
+- [template-curly-spacing]
+
+[@stylistic/template-curly-spacing]: https://eslint.style/rules/default/template-curly-spacing
+[template-curly-spacing]: https://eslint.org/docs/rules/template-curly-spacing
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/template-curly-spacing.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/template-curly-spacing.js)
+
+Taken with ❤️ [from ESLint Stylistic](https://eslint.style/rules/js/template-curly-spacing)
diff --git a/docs/rules/this-in-template.md b/docs/rules/this-in-template.md
index d0603edd2..7cd3e1251 100644
--- a/docs/rules/this-in-template.md
+++ b/docs/rules/this-in-template.md
@@ -1,65 +1,76 @@
-# enforce usage of `this` in template (vue/this-in-template)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/this-in-template
+description: disallow usage of `this` in template
+since: v3.13.0
+---
-- :gear: This rule is included in `"plugin:vue/recommended"`.
+# vue/this-in-template
+
+> disallow usage of `this` in template
+
+- :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.
## :book: Rule Details
-:-1: Examples of **incorrect** code for this rule:
+This rule aims at preventing usage of `this` in Vue templates.
-```html
-
- {{ this.text }}
-
-```
+
-:+1: Examples of **correct** code for this rule:
+```vue
+
+
+
+ {{ text }}
+
-```html
-
- {{ text }}
-
+
+
+ {{ this.text }}
+
+
```
-## :wrench: Options
+
-Default is set to `never`.
+## :wrench: Options
-```
-'vue/this-in-template': [2, 'always'|'never']
+```json
+{
+ "vue/this-in-template": ["error", "always" | "never"]
+}
```
-### `"always"` - Always use `this` while accessing properties from Vue
+- `"always"` ... Always use `this` while accessing properties from Vue.
+- `"never"` (default) ... Never use `this` keyword in expressions.
-:-1: Examples of **incorrect** code:
+### `"always"`
-```html
-
- {{ text }}
-
-```
+
-:+1: Examples of **correct** code`:
+```vue
+
+
+
+ {{ this.text }}
+
-```html
-
- {{ this.text }}
-
+
+
+ {{ text }}
+
+
```
-### `"never"` - Never use `this` keyword in expressions
+
-:-1: Examples of **incorrect** code:
+## :rocket: Version
-```html
-
- {{ this.text }}
-
-```
+This rule was introduced in eslint-plugin-vue v3.13.0
-:+1: Examples of **correct** code:
+## :mag: Implementation
-```html
-
- {{ text }}
-
-```
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/this-in-template.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/this-in-template.js)
diff --git a/docs/rules/use-v-on-exact.md b/docs/rules/use-v-on-exact.md
new file mode 100644
index 000000000..a13b8df30
--- /dev/null
+++ b/docs/rules/use-v-on-exact.md
@@ -0,0 +1,54 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/use-v-on-exact
+description: enforce usage of `exact` modifier on `v-on`
+since: v5.0.0
+---
+
+# vue/use-v-on-exact
+
+> enforce usage of `exact` modifier on `v-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 enforce usage of `exact` modifier on `v-on` when there is another `v-on` with modifier.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/v-on-style](./v-on-style.md)
+- [vue/valid-v-on](./valid-v-on.md)
+
+## :books: Further Reading
+
+- [Guide - .exact Modifier](https://vuejs.org/guide/essentials/event-handling.html#exact-modifier)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/use-v-on-exact.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/use-v-on-exact.js)
diff --git a/docs/rules/v-bind-style.md b/docs/rules/v-bind-style.md
index eb33a8d87..cdcd6f7d5 100644
--- a/docs/rules/v-bind-style.md
+++ b/docs/rules/v-bind-style.md
@@ -1,37 +1,110 @@
-# enforce `v-bind` directive style (vue/v-bind-style)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-bind-style
+description: enforce `v-bind` directive style
+since: v3.0.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/v-bind-style
-This rule enforces `v-bind` directive style which you should use shorthand or long form.
+> enforce `v-bind` directive style
+
+- :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
-:-1: Examples of **incorrect** code for this rule:
+This rule enforces `v-bind` directive style which you should use shorthand or long form.
+
+
-```html
-
+```vue
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
+
+## :wrench: Options
-```html
-
+```json
+{
+ "vue/v-bind-style": ["error", "shorthand" | "longform", {
+ "sameNameShorthand": "ignore" | "always" | "never"
+ }]
+}
```
-:-1: Examples of **incorrect** code for this rule with `"longform"` option:
+- `"shorthand"` (default) ... requires using shorthand.
+- `"longform"` ... requires using long form.
+- `sameNameShorthand` ... enforce the `v-bind` same-name shorthand style (Vue 3.4+).
+ - `"ignore"` (default) ... ignores the same-name shorthand style.
+ - `"always"` ... always enforces same-name shorthand where possible.
+ - `"never"` ... always disallow same-name shorthand where possible.
+
+### `"longform"`
+
+
+
+```vue
+
+
+
-```html
-
+
+
+
```
-:+1: Examples of **correct** code for this rule with `"longform"` option:
+
+
+### `{ "sameNameShorthand": "always" }`
+
+
+
+```vue
+
+
+
-```html
-
+
+
+
```
-## :wrench: Options
+
-- `"shorthand"` (default) ... requires using shorthand.
-- `"longform"` ... requires using long form.
+### `{ "sameNameShorthand": "never" }`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [Style guide - Directive shorthands](https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-bind-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-bind-style.js)
diff --git a/docs/rules/v-for-delimiter-style.md b/docs/rules/v-for-delimiter-style.md
new file mode 100644
index 000000000..191dc921a
--- /dev/null
+++ b/docs/rules/v-for-delimiter-style.md
@@ -0,0 +1,73 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-for-delimiter-style
+description: enforce `v-for` directive's delimiter style
+since: v7.0.0
+---
+
+# vue/v-for-delimiter-style
+
+> enforce `v-for` directive's delimiter style
+
+- :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 enforces which delimiter (`in` or `of`) should be used in `v-for` directives.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Default is set to `in`.
+
+```json
+{
+ "vue/v-for-delimiter-style": ["error", "in" | "of"]
+}
+```
+
+- `"in"` (default) ... requires using `in`.
+- `"of"` ... requires using `of`.
+
+### `"of"`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [Guide - List Rendering](https://vuejs.org/guide/essentials/list.html)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-for-delimiter-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-for-delimiter-style.js)
diff --git a/docs/rules/v-if-else-key.md b/docs/rules/v-if-else-key.md
new file mode 100644
index 000000000..757e04321
--- /dev/null
+++ b/docs/rules/v-if-else-key.md
@@ -0,0 +1,60 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-if-else-key
+description: require key attribute for conditionally rendered repeated components
+since: v9.19.0
+---
+
+# vue/v-if-else-key
+
+> require key attribute for conditionally rendered repeated components
+
+- :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 checks for components that are both repeated and conditionally rendered within the same scope. If such a component is found, the rule then checks for the presence of a 'key' directive. If the 'key' directive is missing, the rule issues a warning and offers a fix.
+
+This rule is not required in Vue 3, as the key is automatically assigned to the elements.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/require-v-for-key]
+
+[vue/require-v-for-key]: ./require-v-for-key.md
+
+## :books: Further Reading
+
+- [Guide (for v2) - v-if without key](https://v2.vuejs.org/v2/style-guide/#v-if-v-else-if-v-else-without-key-use-with-caution)
+
+## :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/v-if-else-key.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-if-else-key.js)
diff --git a/docs/rules/v-on-event-hyphenation.md b/docs/rules/v-on-event-hyphenation.md
new file mode 100644
index 000000000..8c378ef42
--- /dev/null
+++ b/docs/rules/v-on-event-hyphenation.md
@@ -0,0 +1,144 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-on-event-hyphenation
+description: enforce v-on event naming style on custom components in template
+since: v7.4.0
+---
+
+# vue/v-on-event-hyphenation
+
+> enforce v-on event naming style on custom components in template
+
+- :gear: This rule is included in all of `"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 enforces using hyphenated v-on event names on custom components in Vue templates.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/v-on-event-hyphenation": ["error", "always" | "never", {
+ "autofix": false,
+ "ignore": [],
+ "ignoreTags": []
+ }]
+}
+```
+
+- `"always"` (default) ... Use hyphenated event name.
+- `"never"` ... Don't use hyphenated event name.
+- `"ignore"` ... Array of event names that don't need to follow the specified casing.
+- `"ignoreTags"` ... Array of tag names whose events don't need to follow the specified casing.
+- `"autofix"` ... If `true`, enable autofix. If you are using Vue 2, we recommend that you do not use it due to its side effects.
+
+### `"always"`
+
+It errors on upper case letters.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+### `"never"`
+
+It errors on hyphens.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+### `"never", { "ignore": ["custom-event"] }`
+
+Don't use hyphenated name but allow custom event names
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"never", { "ignoreTags": ["/^custom-/"] }`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/custom-event-name-casing](./custom-event-name-casing.md)
+- [vue/attribute-hyphenation](./attribute-hyphenation.md)
+- [vue/v-on-handler-style](./v-on-handler-style.md)
+
+## :books: Further Reading
+
+- [Guide - Custom Events]
+
+[Guide - Custom Events]: https://vuejs.org/guide/components/events.html
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.4.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-event-hyphenation.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-event-hyphenation.js)
diff --git a/docs/rules/v-on-function-call.md b/docs/rules/v-on-function-call.md
new file mode 100644
index 000000000..51fe8714a
--- /dev/null
+++ b/docs/rules/v-on-function-call.md
@@ -0,0 +1,109 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-on-function-call
+description: enforce or forbid parentheses after method calls without arguments in `v-on` directives
+since: v5.2.0
+---
+
+# vue/v-on-function-call
+
+> enforce or forbid parentheses after method calls without arguments in `v-on` directives
+
+- :no_entry: This rule was **removed** in eslint-plugin-vue v10.0.0 and replaced by [vue/v-on-handler-style](v-on-handler-style.md) rule.
+
+## :book: Rule Details
+
+This rule aims to enforce to bind methods to `v-on` or call methods on `v-on` when without arguments.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Default is set to `never`.
+
+```json
+{
+ "vue/v-on-function-call": ["error",
+ "always"|"never",
+ {
+ "ignoreIncludesComment": false
+ }
+ ]
+}
+```
+
+- `"always"` ... Always use parentheses in `v-on` directives.
+- `"never"` ... Never use parentheses in `v-on` directives for method calls without arguments. this is default.
+- `ignoreIncludesComment` ... If `true`, do not report expressions containing comments. default `false`.
+
+### `"always"` - Always use parentheses in `v-on` directives
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+### `"never"` - Never use parentheses in `v-on` directives for method calls without arguments
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"never", { "ignoreIncludesComment": true }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v5.2.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-function-call.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-function-call.js)
diff --git a/docs/rules/v-on-handler-style.md b/docs/rules/v-on-handler-style.md
new file mode 100644
index 000000000..065ebd827
--- /dev/null
+++ b/docs/rules/v-on-handler-style.md
@@ -0,0 +1,224 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-on-handler-style
+description: enforce writing style for handlers in `v-on` directives
+since: v9.7.0
+---
+
+# vue/v-on-handler-style
+
+> enforce writing style for handlers in `v-on` directives
+
+- :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 a consistent style in `v-on` event handlers:
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/v-on-handler-style": ["error",
+ ["method", "inline-function"], // ["method", "inline-function"] | ["method", "inline"] | "inline-function" | "inline"
+ {
+ "ignoreIncludesComment": false
+ }
+ ]
+}
+```
+
+- First option ... Specifies the name of an allowed style. Default is `["method", "inline-function"]`.
+ - `["method", "inline-function"]` ... Allow handlers by method binding. e.g. `v-on:click="handler"`. Allow inline functions where method handlers cannot be used. e.g. `v-on:click="() => handler(listItem)"`.
+ - `["method", "inline"]` ... Allow handlers by method binding. e.g. `v-on:click="handler"`. Allow inline handlers where method handlers cannot be used. e.g. `v-on:click="handler(listItem)"`.
+ - `"inline-function"` ... Allow inline functions. e.g. `v-on:click="() => handler()"`
+ - `"inline"` ... Allow inline handlers. e.g. `v-on:click="handler()"`
+- Second option
+ - `ignoreIncludesComment` ... If `true`, do not report inline handlers or inline functions containing comments, even if the preferred style is `"method"`. Default is `false`.
+
+### `["method", "inline-function"]` (Default)
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `["method", "inline"]`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"inline-function"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"inline"`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `["method", "inline-function"], { "ignoreIncludesComment": true }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `["method", "inline"], { "ignoreIncludesComment": true }`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/v-on-style](./v-on-style.md)
+- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
+
+## :books: Further Reading
+
+- [Guide - Inline Handlers]
+- [Guide - Method Handlers]
+
+[Guide - Inline Handlers]: https://vuejs.org/guide/essentials/event-handling.html#inline-handlers
+[Guide - Method Handlers]: https://vuejs.org/guide/essentials/event-handling.html#method-handlers
+
+## :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/v-on-handler-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-handler-style.js)
diff --git a/docs/rules/v-on-style.md b/docs/rules/v-on-style.md
index 0adc88e55..019251009 100644
--- a/docs/rules/v-on-style.md
+++ b/docs/rules/v-on-style.md
@@ -1,37 +1,78 @@
-# enforce `v-on` directive style (vue/v-on-style)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-on-style
+description: enforce `v-on` directive style
+since: v3.0.0
+---
-- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
-- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+# vue/v-on-style
-This rule enforces `v-on` directive style which you should use shorthand or long form.
+> enforce `v-on` directive style
+
+- :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
-:-1: Examples of **incorrect** code for this rule:
+This rule enforces `v-on` directive style which you should use shorthand or long form.
-```html
-
-```
+
-:+1: Examples of **correct** code for this rule:
+```vue
+
+
+
-```html
-
+
+
+
```
-:-1: Examples of **incorrect** code for this rule with `"longform"` option:
+
-```html
-
-```
+## :wrench: Options
-:+1: Examples of **correct** code for this rule with `"longform"` option:
+Default is set to `shorthand`.
-```html
-
+```json
+{
+ "vue/v-on-style": ["error", "shorthand" | "longform"]
+}
```
-## :wrench: Options
-
- `"shorthand"` (default) ... requires using shorthand.
- `"longform"` ... requires using long form.
+
+### `"longform"`
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/v-on-handler-style](./v-on-handler-style.md)
+
+## :books: Further Reading
+
+- [Style guide - Directive shorthands](https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-style.js)
diff --git a/docs/rules/v-slot-style.md b/docs/rules/v-slot-style.md
new file mode 100644
index 000000000..5c650eb7c
--- /dev/null
+++ b/docs/rules/v-slot-style.md
@@ -0,0 +1,118 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-slot-style
+description: enforce `v-slot` directive style
+since: v6.0.0
+---
+
+# vue/v-slot-style
+
+> enforce `v-slot` directive style
+
+- :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
+
+This rule enforces `v-slot` directive style which you should use shorthand or long form.
+
+
+
+```vue
+
+
+
+ {{ data }}
+
+
+ content
+ content
+ content
+
+
+
+
+ {{ data }}
+
+
+ content
+ content
+ content
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/v-slot-style": ["error", {
+ "atComponent": "shorthand" | "longform" | "v-slot",
+ "default": "shorthand" | "longform" | "v-slot",
+ "named": "shorthand" | "longform",
+ }]
+}
+```
+
+| Name | Type | Default Value | Description
+|:-----|:-----|:--------------|:------------
+| `atComponent` | `"shorthand"` \| `"longform"` \| `"v-slot"` | `"v-slot"` | The style for the default slot at custom components directly (E.g. ``).
+| `default` | `"shorthand"` \| `"longform"` \| `"v-slot"` | `"shorthand"` | The style for the default slot at template wrappers (E.g. ``).
+| `named` | `"shorthand"` \| `"longform"` | `"shorthand"` | The style for named slots (E.g. ``).
+
+Each value means:
+
+- `"shorthand"` ... use `#` shorthand. E.g. `#default`, `#named`, ...
+- `"longform"` ... use `v-slot:` directive notation. E.g. `v-slot:default`, `v-slot:named`, ...
+- `"v-slot"` ... use `v-slot` without that argument. This is shorter than `#default` shorthand.
+
+And a string option is supported to be consistent to similar `vue/v-bind-style` and `vue/v-on-style`.
+
+- `["error", "longform"]` is same as `["error", { atComponent: "longform", default: "longform", named: "longform" }]`.
+- `["error", "shorthand"]` is same as `["error", { atComponent: "shorthand", default: "shorthand", named: "shorthand" }]`.
+
+### `"longform"`
+
+
+
+```vue
+
+
+
+ {{ data }}
+
+
+ content
+ content
+ content
+
+
+
+
+ {{ data }}
+
+
+ content
+ content
+ content
+
+
+```
+
+
+
+## :books: Further Reading
+
+- [Style guide - Directive shorthands](https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v6.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-slot-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-slot-style.js)
diff --git a/docs/rules/valid-attribute-name.md b/docs/rules/valid-attribute-name.md
new file mode 100644
index 000000000..599e84d4e
--- /dev/null
+++ b/docs/rules/valid-attribute-name.md
@@ -0,0 +1,49 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-attribute-name
+description: require valid attribute names
+since: v9.0.0
+---
+
+# vue/valid-attribute-name
+
+> require valid attribute names
+
+- :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 detects invalid HTML attributes.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :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-attribute-name.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-attribute-name.js)
diff --git a/docs/rules/valid-define-emits.md b/docs/rules/valid-define-emits.md
new file mode 100644
index 000000000..062403a63
--- /dev/null
+++ b/docs/rules/valid-define-emits.md
@@ -0,0 +1,153 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-define-emits
+description: enforce valid `defineEmits` compiler macro
+since: v7.13.0
+---
+
+# vue/valid-define-emits
+
+> enforce valid `defineEmits` 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 `defineEmits` compiler macro is valid.
+
+## :book: Rule Details
+
+This rule reports `defineEmits` compiler macros in the following cases:
+
+- `defineEmits` is referencing locally declared variables.
+- `defineEmits` has both a literal type and an argument. e.g. `defineEmits<(e: 'foo')=>void>(['bar'])`
+- `defineEmits` has been called multiple times.
+- Custom events are defined in both `defineEmits` and `export default {}`.
+- Custom events are not defined in either `defineEmits` or `export default {}`.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```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
new file mode 100644
index 000000000..b2c95980b
--- /dev/null
+++ b/docs/rules/valid-next-tick.md
@@ -0,0 +1,95 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-next-tick
+description: enforce valid `nextTick` function calls
+since: v7.5.0
+---
+
+# vue/valid-next-tick
+
+> enforce valid `nextTick` function calls
+
+- :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
+
+Calling `Vue.nextTick` or `vm.$nextTick` without passing a callback and without awaiting the returned Promise is likely a mistake (probably a missing `await`).
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [`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
+
+This rule was introduced in eslint-plugin-vue v7.5.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-next-tick.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-next-tick.js)
diff --git a/docs/rules/valid-template-root.md b/docs/rules/valid-template-root.md
index e113d5242..9dd89a287 100644
--- a/docs/rules/valid-template-root.md
+++ b/docs/rules/valid-template-root.md
@@ -1,6 +1,16 @@
-# enforce valid template root (vue/valid-template-root)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-template-root
+description: enforce valid template root
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-template-root
+
+> enforce valid template root
+
+- :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.
@@ -8,95 +18,33 @@ This rule checks whether every template root is valid.
This rule reports the template root in the following cases:
-- The root is nothing. E.g. ``.
-- The root is text. E.g. `hello`.
-- The root is multiple elements. E.g. `
one
two
`.
-- The root element has `v-for` directives. E.g. `
{{x}}
`.
-- The root element is `` or `` elements. E.g. `hello`.
-
-:-1: Examples of **incorrect** code for this rule:
-
-:-1: Examples of **incorrect** code for this rule:
-
-```js
-template: ''
-```
-
-```html
-
-
-```
-
-```js
-template: `
-
-
-```
+This rule was introduced in eslint-plugin-vue v3.11.0
-## :wrench: Options
+## :mag: Implementation
-Nothing.
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-template-root.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-template-root.js)
diff --git a/docs/rules/valid-v-bind-sync.md b/docs/rules/valid-v-bind-sync.md
new file mode 100644
index 000000000..19129fb3d
--- /dev/null
+++ b/docs/rules/valid-v-bind-sync.md
@@ -0,0 +1,84 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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
+
+- :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.
+
+## :book: Rule Details
+
+This rule reports `.sync` modifier on `v-bind` directives in the following cases:
+
+- The `.sync` modifier does not have the attribute value which is valid as LHS. E.g. ``, ``
+- The `.sync` modifier has potential null object property access. E.g. ``
+- The `.sync` modifier is on non Vue-components. E.g. `
`
+- The `.sync` modifier's reference is iteration variables. 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
+
+- [Guide (for v2) - `.sync` Modifier](https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-bind-sync.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-bind-sync.js)
diff --git a/docs/rules/valid-v-bind.md b/docs/rules/valid-v-bind.md
index dfc9091dd..64badbb6a 100644
--- a/docs/rules/valid-v-bind.md
+++ b/docs/rules/valid-v-bind.md
@@ -1,6 +1,16 @@
-# enforce valid `v-bind` directives (vue/valid-v-bind)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-bind
+description: enforce valid `v-bind` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-bind
+
+> enforce valid `v-bind` directives
+
+- :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.
@@ -13,30 +23,47 @@ This rule reports `v-bind` directives in the following cases:
This rule does not report `v-bind` directives which do not have their argument (E.g. ``) because it's valid if the attribute value is an object.
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
+
-```html
-
-
-
+```vue
+
+
+
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-
-
-
-```
+::: 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
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+- [vue/no-deprecated-v-bind-sync]
+- [vue/valid-v-bind-sync]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+[vue/no-deprecated-v-bind-sync]: ./no-deprecated-v-bind-sync.md
+[vue/valid-v-bind-sync]: ./valid-v-bind-sync.md
+
+## :rocket: Version
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-bind.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-bind.js)
diff --git a/docs/rules/valid-v-cloak.md b/docs/rules/valid-v-cloak.md
index 4b7b9a4a1..57c60edcf 100644
--- a/docs/rules/valid-v-cloak.md
+++ b/docs/rules/valid-v-cloak.md
@@ -1,6 +1,16 @@
-# enforce valid `v-cloak` directives (vue/valid-v-cloak)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-cloak
+description: enforce valid `v-cloak` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-cloak
+
+> enforce valid `v-cloak` directives
+
+- :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.
@@ -12,20 +22,31 @@ This rule reports `v-cloak` directives in the following cases:
- The directive has that modifier. E.g. ``
- The directive has that attribute value. E.g. ``
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
-
-
-```
+```vue
+
+
+
-:+1: Examples of **correct** code for this rule:
-
-```html
-
+
+
+
+
+
```
+
+
## :wrench: Options
Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.11.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-cloak.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-cloak.js)
diff --git a/docs/rules/valid-v-else-if.md b/docs/rules/valid-v-else-if.md
index a73340085..13959825b 100644
--- a/docs/rules/valid-v-else-if.md
+++ b/docs/rules/valid-v-else-if.md
@@ -1,6 +1,16 @@
-# enforce valid `v-else-if` directives (vue/valid-v-else-if)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-else-if
+description: enforce valid `v-else-if` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-else-if
+
+> enforce valid `v-else-if` directives
+
+- :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.
@@ -14,34 +24,47 @@ This rule reports `v-else-if` directives in the following cases:
- The directive is on the elements that the previous element don't have `v-if`/`v-else-if` directives. E.g. ``
- The directive is on the elements which have `v-if`/`v-else` directives. E.g. ``
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
+
-:-1: Examples of **incorrect** code for this rule:
+```vue
+
+
+
+
-```html
-
-
-
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-
-```
+::: 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
+## :couple: Related Rules
+
+- [vue/valid-v-if]
+- [vue/valid-v-else]
+- [vue/no-parsing-error]
+
+[vue/valid-v-if]: ./valid-v-if.md
+[vue/valid-v-else]: ./valid-v-else.md
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [valid-v-if]
-- [valid-v-else]
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[valid-v-if]: valid-v-if.md
-[valid-v-else]: valid-v-else.md
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-else-if.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-else-if.js)
diff --git a/docs/rules/valid-v-else.md b/docs/rules/valid-v-else.md
index 5c63b1554..23df5787b 100644
--- a/docs/rules/valid-v-else.md
+++ b/docs/rules/valid-v-else.md
@@ -1,6 +1,16 @@
-# enforce valid `v-else` directives (vue/valid-v-else)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-else
+description: enforce valid `v-else` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-else
+
+> enforce valid `v-else` directives
+
+- :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.
@@ -11,35 +21,46 @@ This rule reports `v-else` directives in the following cases:
- The directive has that argument. E.g. ``
- The directive has that modifier. E.g. ``
- The directive has that attribute value. E.g. ``
-- The directive is on the elements that the previous element don't have `v-if`/`v-if-else` directives. E.g. ``
-- The directive is on the elements which have `v-if`/`v-if-else` directives. E.g. ``
+- The directive is on the elements that the previous element don't have `v-if`/`v-else-if` directives. E.g. ``
+- The directive is on the elements which have `v-if`/`v-else-if` directives. E.g. ``
-:-1: Examples of **incorrect** code for this rule:
-
-```html
-
-
-
-```
+
-:+1: Examples of **correct** code for this rule:
+```vue
+
+
+
+
-```html
-
-
+
+
+
+
+
+
```
+
+
## :wrench: Options
Nothing.
-## :couple: Related rules
+## :couple: Related Rules
+
+- [vue/valid-v-if]
+- [vue/valid-v-else-if]
+- [vue/no-parsing-error]
+
+[vue/valid-v-if]: ./valid-v-if.md
+[vue/valid-v-else-if]: ./valid-v-else-if.md
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [valid-v-if]
-- [valid-v-else-if]
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[valid-v-if]: valid-v-if.md
-[valid-v-else-if]: valid-v-else-if.md
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-else.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-else.js)
diff --git a/docs/rules/valid-v-for.md b/docs/rules/valid-v-for.md
index 13f8e9ded..f0e49c5e1 100644
--- a/docs/rules/valid-v-for.md
+++ b/docs/rules/valid-v-for.md
@@ -1,6 +1,16 @@
-# enforce valid `v-for` directives (vue/valid-v-for)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-for
+description: enforce valid `v-for` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-for
+
+> enforce valid `v-for` directives
+
+- :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.
@@ -14,55 +24,67 @@ This rule reports `v-for` directives in the following cases:
- If the element which has the directive is a custom component, the component does not have `v-bind:key` directive. E.g. ``
- The `v-bind:key` directive does not use the variables which are defined by the `v-for` directive. E.g. ``
-If the element which has the directive is a reserved element, this rule does not report even if the element does not have `v-bind:key` directive because it's not fatal error. [require-v-for-key] rule reports it.
+If the element which has the directive is a reserved element, this rule does not report it even if the element does not have `v-bind:key` directive because it's not fatal error. [vue/require-v-for-key] rule reports it.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
-This rule does not check syntax errors in directives. [no-parsing-error] rule reports it.
-The following cases are syntax errors:
+
-- The directive's value is not the form `alias in expr`. E.g. ``
-- The alias is not LHS. E.g. ``
-
-:-1: Examples of **incorrect** code for this rule:
-
-```html
-
-
-
-
-
-
-```
+::: warning Note
+This rule does not check syntax errors in directives. [vue/no-parsing-error] rule reports it.
+The following cases are syntax errors:
-:+1: Examples of **correct** code for this rule:
-
-```html
-
-
-
-```
+- The directive's value isn't `alias in expr`. E.g. ``
+- The alias isn't LHS. E.g. ``
+:::
## :wrench: Options
Nothing.
-## :couple: Related rules
+## :couple: Related Rules
+
+- [vue/require-v-for-key]
+- [vue/no-parsing-error]
+
+[vue/require-v-for-key]: ./require-v-for-key.md
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [require-v-for-key]
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[require-v-for-key]: require-v-for-key.md
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-for.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-for.js)
diff --git a/docs/rules/valid-v-html.md b/docs/rules/valid-v-html.md
index d562b6754..75e3c233d 100644
--- a/docs/rules/valid-v-html.md
+++ b/docs/rules/valid-v-html.md
@@ -1,6 +1,16 @@
-# enforce valid `v-html` directives (vue/valid-v-html)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-html
+description: enforce valid `v-html` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-html
+
+> enforce valid `v-html` directives
+
+- :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.
@@ -12,29 +22,41 @@ This rule reports `v-html` directives in the following cases:
- The directive has that modifier. E.g. ``
- The directive does not have that attribute value. E.g. ``
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
+
-:-1: Examples of **incorrect** code for this rule:
+```vue
+
+
+
-```html
-
-
-
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-```
+::: 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
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-html.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-html.js)
diff --git a/docs/rules/valid-v-if.md b/docs/rules/valid-v-if.md
index 53c42f426..8e45793f8 100644
--- a/docs/rules/valid-v-if.md
+++ b/docs/rules/valid-v-if.md
@@ -1,6 +1,16 @@
-# enforce valid `v-if` directives (vue/valid-v-if)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-if
+description: enforce valid `v-if` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-if
+
+> enforce valid `v-if` directives
+
+- :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.
@@ -13,43 +23,55 @@ This rule reports `v-if` directives in the following cases:
- The directive does not have that attribute value. E.g. ``
- The directive is on the elements which have `v-else`/`v-else-if` directives. E.g. ``
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
-
-:-1: Examples of **incorrect** code for this rule:
-
-```html
-
-
-
-
-
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-
-
-```
+::: 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
+## :couple: Related Rules
+
+- [vue/valid-v-else]
+- [vue/valid-v-else-if]
+- [vue/no-parsing-error]
+
+[vue/valid-v-else]: ./valid-v-else.md
+[vue/valid-v-else-if]: ./valid-v-else-if.md
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [valid-v-else]
-- [valid-v-else-if]
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[valid-v-else]: valid-v-else.md
-[valid-v-else-if]: valid-v-else-if.md
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-if.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-if.js)
diff --git a/docs/rules/valid-v-is.md b/docs/rules/valid-v-is.md
new file mode 100644
index 000000000..294b55f5b
--- /dev/null
+++ b/docs/rules/valid-v-is.md
@@ -0,0 +1,73 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-is
+description: enforce valid `v-is` directives
+since: v7.0.0
+---
+
+# vue/valid-v-is
+
+> enforce valid `v-is` 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-is` directive is valid.
+
+## :book: Rule Details
+
+This rule reports `v-is` 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 directive is on Vue-components. 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-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 (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
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-is.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-is.js)
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 2661c6b36..0bc38d1b0 100644
--- a/docs/rules/valid-v-model.md
+++ b/docs/rules/valid-v-model.md
@@ -1,6 +1,16 @@
-# enforce valid `v-model` directives (vue/valid-v-model)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-model
+description: enforce valid `v-model` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-model
+
+> enforce valid `v-model` directives
+
+- :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.
@@ -8,48 +18,66 @@ This rule checks whether every `v-model` directive is valid.
This rule reports `v-model` directives in the following cases:
-- The directive has that argument. E.g. ``
-- The directive has the modifiers which are not supported. E.g. ``
+- The directive used on HTMLElement has an argument. E.g. ``
+- The directive used on HTMLElement has modifiers which are not supported. E.g. ``
- The directive does not have that attribute value. E.g. ``
-- The directive does not have the attribute value which is valid as LHS. E.g. ``
+- The directive does not have the attribute value which is valid as LHS. E.g. ``, ``
+- The directive has potential null object property access. E.g. ``
- The directive is on unsupported elements. E.g. ``
- The directive is on `` elements which their types are `file`. E.g. ``
- The directive's reference is iteration variables. E.g. ``
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
-
-:-1: Examples of **incorrect** code for this rule:
-
-```html
-
-
-
-
-
-
-
-
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-
-
-
-
-
-
-```
+::: 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
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-model.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-model.js)
diff --git a/docs/rules/valid-v-on.md b/docs/rules/valid-v-on.md
index 18580ccc0..acfb630b7 100644
--- a/docs/rules/valid-v-on.md
+++ b/docs/rules/valid-v-on.md
@@ -1,6 +1,16 @@
-# enforce valid `v-on` directives (vue/valid-v-on)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-on
+description: enforce valid `v-on` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-on
+
+> enforce valid `v-on` directives
+
+- :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.
@@ -12,35 +22,70 @@ This rule reports `v-on` directives in the following cases:
- The directive has invalid modifiers. E.g. ``
- The directive does not have that attribute value and any verb modifiers. E.g. ``
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Note
+This rule does not check syntax errors in directives because it's checked by [vue/no-parsing-error] rule.
+:::
-:-1: Examples of **incorrect** code for this rule:
+## :wrench: Options
-```html
-
-
-
-
+```json
+{
+ "vue/valid-v-on": ["error", {
+ "modifiers": []
+ }]
+}
```
-:+1: Examples of **correct** code for this rule:
+This rule has an object option:
-```html
-
-
-
-
-
-
+`"modifiers"` array of additional allowed modifiers.
+
+### `"modifiers": ["foo"]`
+
+
+
+```vue
+
+
+
+
```
-## :wrench: Options
+
+
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
-Nothing.
+[vue/no-parsing-error]: ./no-parsing-error.md
-## :couple: Related rules
+## :rocket: Version
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-on.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-on.js)
diff --git a/docs/rules/valid-v-once.md b/docs/rules/valid-v-once.md
index 0c3aefb9b..daa711b8d 100644
--- a/docs/rules/valid-v-once.md
+++ b/docs/rules/valid-v-once.md
@@ -1,6 +1,16 @@
-# enforce valid `v-once` directives (vue/valid-v-once)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-once
+description: enforce valid `v-once` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-once
+
+> enforce valid `v-once` directives
+
+- :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.
@@ -12,20 +22,31 @@ This rule reports `v-once` directives in the following cases:
- The directive has that modifier. E.g. ``
- The directive has that attribute value. E.g. ``
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
-
-
-```
+```vue
+
+
+
-:+1: Examples of **correct** code for this rule:
-
-```html
-
+
+
+
+
+
```
+
+
## :wrench: Options
Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.11.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-once.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-once.js)
diff --git a/docs/rules/valid-v-pre.md b/docs/rules/valid-v-pre.md
index 4b36030cb..0464ed3d7 100644
--- a/docs/rules/valid-v-pre.md
+++ b/docs/rules/valid-v-pre.md
@@ -1,6 +1,16 @@
-# enforce valid `v-pre` directives (vue/valid-v-pre)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-pre
+description: enforce valid `v-pre` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-pre
+
+> enforce valid `v-pre` directives
+
+- :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.
@@ -12,20 +22,31 @@ This rule reports `v-pre` directives in the following cases:
- The directive has that modifier. E.g. ``
- The directive has that attribute value. E.g. ``
-:-1: Examples of **incorrect** code for this rule:
+
-```html
-
-
-
-```
+```vue
+
+
+
-:+1: Examples of **correct** code for this rule:
-
-```html
-
+
+
+
+
+
```
+
+
## :wrench: Options
Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v3.11.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-pre.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-pre.js)
diff --git a/docs/rules/valid-v-show.md b/docs/rules/valid-v-show.md
index 5742c76b7..5be8a50a5 100644
--- a/docs/rules/valid-v-show.md
+++ b/docs/rules/valid-v-show.md
@@ -1,6 +1,16 @@
-# enforce valid `v-show` directives (vue/valid-v-show)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-show
+description: enforce valid `v-show` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-show
+
+> enforce valid `v-show` directives
+
+- :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.
@@ -11,30 +21,44 @@ This rule reports `v-show` 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 directive is put on `` tag. E.g. ``
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
+
-:-1: Examples of **incorrect** code for this rule:
+```vue
+
+
+
-```html
-
-
-
+
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-```
+::: 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
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-show.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-show.js)
diff --git a/docs/rules/valid-v-slot.md b/docs/rules/valid-v-slot.md
new file mode 100644
index 000000000..0c0c9e564
--- /dev/null
+++ b/docs/rules/valid-v-slot.md
@@ -0,0 +1,157 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+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/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.
+
+## :book: Rule Details
+
+This rule reports `v-slot` directives in the following cases:
+
+- The directive is not owned by a custom element. E.g. ``
+- The directive is a named slot and is on a custom element directly. E.g. ``
+- The directive is the default slot, is on a custom element directly, and there are other named slots. E.g. ``
+- The element which has the directive has another `v-slot` directive. E.g. ``
+- The element which has the directive has another `v-slot` directive that is distributed to the same slot. E.g. ``
+- The directive has a dynamic argument which uses the scope properties that the directive defined. E.g. ``
+- The directive has any modifier. E.g. ``
+- The directive is the default slot, is on a custom element directly, and has no value. E.g. ``
+
+
+
+```vue
+
+
+
+ {{ data }}
+
+
+
+ default
+
+
+ one
+
+
+ two
+
+
+
+
+
+ {{ data }}
+
+
+
+ one
+
+
+
+
+ {{ data }}
+
+
+ {{ data }}
+
+ one
+
+
+
+
+ one and two
+
+
+
+ one 1
+
+
+ one 2
+
+
+
+
+
+ dynamic?
+
+
+
+
+ {{ data }}
+
+
+
+ content
+
+
+```
+
+
+
+::: warning Note
+This rule does not check syntax errors in directives because it's checked by [vue/no-parsing-error] rule.
+:::
+
+## :wrench: Options
+
+```json
+{
+ "vue/valid-v-slot": ["error", {
+ "allowModifiers": false
+ }]
+}
+```
+
+- `allowModifiers` (`boolean`) ... allows having modifiers in the argument of `v-slot` directives. Modifiers just after `v-slot` are still disallowed. E.g. `` default `false`.
+
+### `allowModifiers: true`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+ {{ data }}
+
+
+
+ bar
+
+
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v7.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-slot.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-slot.js)
diff --git a/docs/rules/valid-v-text.md b/docs/rules/valid-v-text.md
index df777712a..093939d01 100644
--- a/docs/rules/valid-v-text.md
+++ b/docs/rules/valid-v-text.md
@@ -1,6 +1,16 @@
-# enforce valid `v-text` directives (vue/valid-v-text)
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-text
+description: enforce valid `v-text` directives
+since: v3.11.0
+---
-- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+# vue/valid-v-text
+
+> enforce valid `v-text` directives
+
+- :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.
@@ -12,29 +22,41 @@ This rule reports `v-text` directives in the following cases:
- The directive has that modifier. E.g. ``
- The directive does not have that attribute value. E.g. ``
-This rule does not check syntax errors in directives because it's checked by [no-parsing-error] rule.
+
-:-1: Examples of **incorrect** code for this rule:
+```vue
+
+
+
-```html
-
-
-
+
+
+
+
+
```
-:+1: Examples of **correct** code for this rule:
+
-```html
-
-```
+::: 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
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :rocket: Version
-- [no-parsing-error]
+This rule was introduced in eslint-plugin-vue v3.11.0
+## :mag: Implementation
-[no-parsing-error]: no-parsing-error.md
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-text.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-text.js)
diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md
new file mode 100644
index 000000000..103471f61
--- /dev/null
+++ b/docs/user-guide/index.md
@@ -0,0 +1,590 @@
+# User Guide
+
+## :cd: Installation
+
+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 vue-eslint-parser globals
+```
+
+::: tip Requirements
+
+- ESLint: `^8.57.0 || ^9.0.0`
+- Node.js: `^18.18.0 || ^20.9.0 || >=21.1.0`
+
+:::
+
+## :book: Usage
+
+### Configuration (`eslint.config.js`)
+
+Use `eslint.config.js` file to configure rules. This is the default in ESLint v9, but can be used starting from ESLint v8.57.0. See also: .
+
+Example **eslint.config.js**:
+
+```js
+import pluginVue from 'eslint-plugin-vue'
+import globals from 'globals'
+
+export default [
+ // add more generic rulesets here, such as:
+ // js.configs.recommended,
+ ...pluginVue.configs['flat/recommended'],
+ // ...pluginVue.configs['flat/vue2-recommended'], // Use this if you are using Vue.js 2.x.
+ {
+ rules: {
+ // override/add rules settings here, such as:
+ // 'vue/no-unused-vars': 'error'
+ },
+ languageOptions: {
+ sourceType: 'module',
+ globals: {
+ ...globals.browser
+ }
+ }
+ }
+]
+```
+
+See [the rule list](../rules/index.md) to get the `configs` & `rules` that this plugin provides.
+
+#### Bundle Configurations (`eslint.config.js`)
+
+This plugin provides some predefined configs.
+You can use the following configs by adding them to `eslint.config.js`.
+(All flat configs in this plugin are provided as arrays, so spread syntax is required when combining them with other configs.)
+
+- `*.configs["flat/base"]` ... Settings and rules to enable correct ESLint parsing.
+- Configurations for using Vue.js 3.x:
+ - `*.configs["flat/essential"]` ... `base`, plus rules to prevent errors or unintended behavior.
+ - `*.configs["flat/strongly-recommended"]` ... Above, plus rules to considerably improve code readability and/or dev experience.
+ - `*.configs["flat/recommended"]` ... Above, plus rules to enforce subjective community defaults to ensure consistency.
+- Configurations for using Vue.js 2.x:
+ - `*.configs["flat/vue2-essential"]` ... `base`, plus rules to prevent errors or unintended behavior.
+ - `*.configs["flat/vue2-strongly-recommended"]` ... Above, plus rules to considerably improve code readability and/or dev experience.
+ - `*.configs["flat/vue2-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).
+:::
+
+#### Specifying Globals (`eslint.config.js`)
+
+Specify global objects depending on how you use Vue.js. More information on how to set globals can be found [here](https://eslint.org/docs/latest/use/configure/language-options#predefined-global-variables).
+
+If you're writing an app that will only render on the browser, use `globals.browser`.
+
+```js
+// ...
+import globals from 'globals'
+
+export default [
+ // ...
+ {
+ languageOptions: {
+ globals: {
+ ...globals.browser
+ }
+ }
+ }
+ // ...
+]
+```
+
+If you're writing an app that is rendered both server-side and on the browser, use `globals.shared-node-browser`.
+
+```js
+// ...
+import globals from 'globals'
+
+export default [
+ // ...
+ {
+ languageOptions: {
+ globals: {
+ ...globals['shared-node-browser']
+ }
+ }
+ }
+ // ...
+]
+```
+
+#### Example configuration with [typescript-eslint](https://typescript-eslint.io/) and [Prettier](https://prettier.io/)
+
+```bash
+npm install --save-dev eslint eslint-config-prettier eslint-plugin-vue globals typescript-eslint
+```
+
+```ts
+import eslint from '@eslint/js';
+import eslintConfigPrettier from 'eslint-config-prettier';
+import eslintPluginVue from 'eslint-plugin-vue';
+import globals from 'globals';
+import typescriptEslint from 'typescript-eslint';
+
+export default typescriptEslint.config(
+ { ignores: ['*.d.ts', '**/coverage', '**/dist'] },
+ {
+ extends: [
+ eslint.configs.recommended,
+ ...typescriptEslint.configs.recommended,
+ ...eslintPluginVue.configs['flat/recommended'],
+ ],
+ files: ['**/*.{ts,vue}'],
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: globals.browser,
+ parserOptions: {
+ parser: typescriptEslint.parser,
+ },
+ },
+ rules: {
+ // your rules
+ },
+ },
+ eslintConfigPrettier
+);
+```
+
+### Configuration (`.eslintrc`)
+
+Use `.eslintrc.*` file to configure rules in ESLint < v9. See also: .
+
+Example **.eslintrc.js**:
+
+```js
+module.exports = {
+ extends: [
+ // add more generic rulesets here, such as:
+ // 'eslint:recommended',
+ 'plugin:vue/recommended',
+ // 'plugin:vue/vue2-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/index.md) to get the `extends` & `rules` that this plugin provides.
+
+#### Bundle Configurations (`.eslintrc`)
+
+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/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.
+- Configurations for using Vue.js 2.x:
+ - `"plugin:vue/vue2-essential"` ... `base`, plus rules to prevent errors or unintended behavior.
+ - `"plugin:vue/vue2-strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience.
+ - `"plugin:vue/vue2-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.2, `
+ *
+ * @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
new file mode 100644
index 000000000..37c526e91
--- /dev/null
+++ b/lib/rules/block-spacing.js
@@ -0,0 +1,11 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// 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
new file mode 100644
index 000000000..22fb12870
--- /dev/null
+++ b/lib/rules/block-tag-newline.js
@@ -0,0 +1,364 @@
+/**
+ * @fileoverview Enforce line breaks style after opening and before closing block-level tags.
+ * @author Yosuke Ota
+ */
+'use strict'
+const utils = require('../utils')
+
+/**
+ * @typedef { 'always' | 'never' | 'consistent' | 'ignore' } OptionType
+ * @typedef { { singleline?: OptionType, multiline?: OptionType, maxEmptyLines?: number } } ContentsOptions
+ * @typedef { ContentsOptions & { blocks?: { [element: string]: ContentsOptions } } } Options
+ * @typedef { Required } ArgsOptions
+ */
+
+/**
+ * @param {string} text Source code as a string.
+ * @returns {number}
+ */
+function getLinebreakCount(text) {
+ return text.split(/\r\n|[\r\n\u2028\u2029]/gu).length - 1
+}
+
+/**
+ * @param {number} lineBreaks
+ */
+function getPhrase(lineBreaks) {
+ switch (lineBreaks) {
+ case 1: {
+ return '1 line break'
+ }
+ default: {
+ return `${lineBreaks} line breaks`
+ }
+ }
+}
+
+const ENUM_OPTIONS = { enum: ['always', 'never', 'consistent', 'ignore'] }
+module.exports = {
+ meta: {
+ type: 'layout',
+ docs: {
+ description:
+ 'enforce line breaks after opening and before closing block-level tags',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/block-tag-newline.html'
+ },
+ fixable: 'whitespace',
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ singleline: ENUM_OPTIONS,
+ multiline: ENUM_OPTIONS,
+ maxEmptyLines: { type: 'number', minimum: 0 },
+ blocks: {
+ type: 'object',
+ patternProperties: {
+ '^(?:\\S+)$': {
+ type: 'object',
+ properties: {
+ singleline: ENUM_OPTIONS,
+ multiline: ENUM_OPTIONS,
+ maxEmptyLines: { type: 'number', minimum: 0 }
+ },
+ additionalProperties: false
+ }
+ },
+ additionalProperties: false
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ unexpectedOpeningLinebreak:
+ "There should be no line break after '<{{tag}}>'.",
+ expectedOpeningLinebreak:
+ "Expected {{expected}} after '<{{tag}}>', but {{actual}} found.",
+ expectedClosingLinebreak:
+ "Expected {{expected}} before '{{tag}}>', but {{actual}} found.",
+ missingOpeningLinebreak: "A line break is required after '<{{tag}}>'.",
+ missingClosingLinebreak: "A line break is required before '{{tag}}>'."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const df =
+ sourceCode.parserServices.getDocumentFragment &&
+ sourceCode.parserServices.getDocumentFragment()
+ if (!df) {
+ return {}
+ }
+
+ /**
+ * @param {VStartTag} startTag
+ * @param {string} beforeText
+ * @param {number} beforeLinebreakCount
+ * @param {'always' | 'never'} beforeOption
+ * @param {number} maxEmptyLines
+ * @returns {void}
+ */
+ function verifyBeforeSpaces(
+ startTag,
+ beforeText,
+ beforeLinebreakCount,
+ beforeOption,
+ maxEmptyLines
+ ) {
+ if (beforeOption === 'always') {
+ if (beforeLinebreakCount === 0) {
+ context.report({
+ loc: {
+ start: startTag.loc.end,
+ end: startTag.loc.end
+ },
+ messageId: 'missingOpeningLinebreak',
+ data: { tag: startTag.parent.name },
+ fix(fixer) {
+ return fixer.insertTextAfter(startTag, '\n')
+ }
+ })
+ } else if (maxEmptyLines < beforeLinebreakCount - 1) {
+ context.report({
+ loc: {
+ start: startTag.loc.end,
+ end: sourceCode.getLocFromIndex(
+ startTag.range[1] + beforeText.length
+ )
+ },
+ messageId: 'expectedOpeningLinebreak',
+ data: {
+ tag: startTag.parent.name,
+ expected: getPhrase(maxEmptyLines + 1),
+ actual: getPhrase(beforeLinebreakCount)
+ },
+ fix(fixer) {
+ return fixer.replaceTextRange(
+ [startTag.range[1], startTag.range[1] + beforeText.length],
+ '\n'.repeat(maxEmptyLines + 1)
+ )
+ }
+ })
+ }
+ } else {
+ if (beforeLinebreakCount > 0) {
+ context.report({
+ loc: {
+ start: startTag.loc.end,
+ end: sourceCode.getLocFromIndex(
+ startTag.range[1] + beforeText.length
+ )
+ },
+ messageId: 'unexpectedOpeningLinebreak',
+ data: { tag: startTag.parent.name },
+ fix(fixer) {
+ return fixer.removeRange([
+ startTag.range[1],
+ startTag.range[1] + beforeText.length
+ ])
+ }
+ })
+ }
+ }
+ }
+ /**
+ * @param {VEndTag} endTag
+ * @param {string} afterText
+ * @param {number} afterLinebreakCount
+ * @param {'always' | 'never'} afterOption
+ * @param {number} maxEmptyLines
+ * @returns {void}
+ */
+ function verifyAfterSpaces(
+ endTag,
+ afterText,
+ afterLinebreakCount,
+ afterOption,
+ maxEmptyLines
+ ) {
+ if (afterOption === 'always') {
+ if (afterLinebreakCount === 0) {
+ context.report({
+ loc: {
+ start: endTag.loc.start,
+ end: endTag.loc.start
+ },
+ messageId: 'missingClosingLinebreak',
+ data: { tag: endTag.parent.name },
+ fix(fixer) {
+ return fixer.insertTextBefore(endTag, '\n')
+ }
+ })
+ } else if (maxEmptyLines < afterLinebreakCount - 1) {
+ context.report({
+ loc: {
+ start: sourceCode.getLocFromIndex(
+ endTag.range[0] - afterText.length
+ ),
+ end: endTag.loc.start
+ },
+ messageId: 'expectedClosingLinebreak',
+ data: {
+ tag: endTag.parent.name,
+ expected: getPhrase(maxEmptyLines + 1),
+ actual: getPhrase(afterLinebreakCount)
+ },
+ fix(fixer) {
+ return fixer.replaceTextRange(
+ [endTag.range[0] - afterText.length, endTag.range[0]],
+ '\n'.repeat(maxEmptyLines + 1)
+ )
+ }
+ })
+ }
+ } else {
+ if (afterLinebreakCount > 0) {
+ context.report({
+ loc: {
+ start: sourceCode.getLocFromIndex(
+ endTag.range[0] - afterText.length
+ ),
+ end: endTag.loc.start
+ },
+ messageId: 'unexpectedOpeningLinebreak',
+ data: { tag: endTag.parent.name },
+ fix(fixer) {
+ return fixer.removeRange([
+ endTag.range[0] - afterText.length,
+ endTag.range[0]
+ ])
+ }
+ })
+ }
+ }
+ }
+ /**
+ * @param {VElement} element
+ * @param {ArgsOptions} options
+ * @returns {void}
+ */
+ function verifyElement(element, options) {
+ const { startTag, endTag } = element
+ if (startTag.selfClosing || endTag == null) {
+ return
+ }
+ const text = sourceCode.text.slice(startTag.range[1], endTag.range[0])
+
+ const trimText = text.trim()
+ if (!trimText) {
+ return
+ }
+
+ const option =
+ options.multiline !== options.singleline &&
+ /[\n\r\u2028\u2029]/u.test(text.trim())
+ ? options.multiline
+ : options.singleline
+ if (option === 'ignore') {
+ return
+ }
+ const beforeText = /** @type {RegExpExecArray} */ (/^\s*/u.exec(text))[0]
+ const afterText = /** @type {RegExpExecArray} */ (/\s*$/u.exec(text))[0]
+ const beforeLinebreakCount = getLinebreakCount(beforeText)
+ const afterLinebreakCount = getLinebreakCount(afterText)
+
+ /** @type {'always' | 'never'} */
+ let beforeOption
+ /** @type {'always' | 'never'} */
+ let afterOption
+ if (option === 'always' || option === 'never') {
+ beforeOption = option
+ afterOption = option
+ } else {
+ // consistent
+ if (beforeLinebreakCount > 0 === afterLinebreakCount > 0) {
+ return
+ }
+ beforeOption = 'always'
+ afterOption = 'always'
+ }
+
+ verifyBeforeSpaces(
+ startTag,
+ beforeText,
+ beforeLinebreakCount,
+ beforeOption,
+ options.maxEmptyLines
+ )
+
+ verifyAfterSpaces(
+ endTag,
+ afterText,
+ afterLinebreakCount,
+ afterOption,
+ options.maxEmptyLines
+ )
+ }
+
+ /**
+ * Normalizes a given option value.
+ * @param { Options | undefined } option An option value to parse.
+ * @returns { (element: VElement) => void } Verify function.
+ */
+ function normalizeOptionValue(option) {
+ if (!option) {
+ return normalizeOptionValue({})
+ }
+
+ /** @type {ContentsOptions} */
+ const contentsOptions = option
+ /** @type {ArgsOptions} */
+ const options = {
+ singleline: contentsOptions.singleline || 'consistent',
+ multiline: contentsOptions.multiline || 'always',
+ maxEmptyLines: contentsOptions.maxEmptyLines || 0
+ }
+ const { blocks } = option
+ if (!blocks) {
+ return (element) => verifyElement(element, options)
+ }
+
+ return (element) => {
+ const { name } = element
+ const elementsOptions = blocks[name]
+ if (elementsOptions) {
+ normalizeOptionValue({
+ singleline: elementsOptions.singleline || options.singleline,
+ multiline: elementsOptions.multiline || options.multiline,
+ maxEmptyLines:
+ elementsOptions.maxEmptyLines == null
+ ? options.maxEmptyLines
+ : elementsOptions.maxEmptyLines
+ })(element)
+ } else {
+ verifyElement(element, options)
+ }
+ }
+ }
+
+ const documentFragment = df
+
+ const verify = normalizeOptionValue(context.options[0])
+
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {},
+ {
+ /** @param {Program} node */
+ Program(node) {
+ if (utils.hasInvalidEOF(node)) {
+ return
+ }
+
+ 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
new file mode 100644
index 000000000..507f5e101
--- /dev/null
+++ b/lib/rules/brace-style.js
@@ -0,0 +1,11 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// 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
new file mode 100644
index 000000000..8aa43e19b
--- /dev/null
+++ b/lib/rules/camelcase.js
@@ -0,0 +1,9 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapCoreRule } = require('../utils')
+
+// 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
new file mode 100644
index 000000000..760611cfa
--- /dev/null
+++ b/lib/rules/comma-dangle.js
@@ -0,0 +1,9 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// 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
new file mode 100644
index 000000000..4be3bc85a
--- /dev/null
+++ b/lib/rules/comma-spacing.js
@@ -0,0 +1,13 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('comma-spacing', {
+ skipDynamicArguments: true,
+ skipDynamicArgumentsReport: true,
+ applyDocument: true
+})
diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js
new file mode 100644
index 000000000..815bd23be
--- /dev/null
+++ b/lib/rules/comma-style.js
@@ -0,0 +1,20 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('comma-style', {
+ create(_context, { baseHandlers }) {
+ return {
+ VSlotScopeExpression(node) {
+ if (baseHandlers.FunctionExpression) {
+ // @ts-expect-error -- Process params of VSlotScopeExpression as FunctionExpression.
+ baseHandlers.FunctionExpression(node)
+ }
+ }
+ }
+ }
+})
diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js
index 2189d0892..655e222bd 100644
--- a/lib/rules/comment-directive.js
+++ b/lib/rules/comment-directive.js
@@ -1,33 +1,62 @@
/**
* @author Toru Nagashima
*/
+/* eslint-disable eslint-plugin/report-message-format */
'use strict'
-// -----------------------------------------------------------------------------
-// Helpers
-// -----------------------------------------------------------------------------
+const utils = require('../utils')
-const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+(\S|\S[\s\S]*\S))?\s*$/
-const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\s+(\S|\S[\s\S]*\S))?\s*$/
+/**
+ * @typedef {object} RuleAndLocation
+ * @property {string} RuleAndLocation.ruleId
+ * @property {number} RuleAndLocation.index
+ * @property {string} [RuleAndLocation.key]
+ */
+
+const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+|$)/
+const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\s+|$)/
+
+/**
+ * Remove the ignored part from a given directive comment and trim it.
+ * @param {string} value The comment text to strip.
+ * @returns {string} The stripped text.
+ */
+function stripDirectiveComment(value) {
+ return value.split(/\s-{2,}\s/u)[0]
+}
/**
* Parse a given comment.
* @param {RegExp} pattern The RegExp pattern to parse.
* @param {string} comment The comment value to parse.
- * @returns {({type:string,rules:string[]})|null} The parsing result.
+ * @returns {({type:string,rules:RuleAndLocation[]})|null} The parsing result.
*/
-function parse (pattern, comment) {
- const match = pattern.exec(comment)
+function parse(pattern, comment) {
+ const text = stripDirectiveComment(comment)
+ const match = pattern.exec(text)
if (match == null) {
return null
}
const type = match[1]
- const rules = (match[2] || '')
- .split(',')
- .map(s => s.trim())
- .filter(Boolean)
+
+ /** @type {RuleAndLocation[]} */
+ const rules = []
+
+ const rulesRe = /([^\s,]+)[\s,]*/g
+ let startIndex = match[0].length
+ rulesRe.lastIndex = startIndex
+
+ let res
+ while ((res = rulesRe.exec(text))) {
+ const ruleId = res[1].trim()
+ rules.push({
+ ruleId,
+ index: startIndex
+ })
+ startIndex = rulesRe.lastIndex
+ }
return { type, rules }
}
@@ -36,15 +65,22 @@ function parse (pattern, comment) {
* Enable rules.
* @param {RuleContext} context The rule context.
* @param {{line:number,column:number}} loc The location information to enable.
- * @param {string} group The group to enable.
- * @param {string[]} rules The rule IDs to enable.
+ * @param { 'block' | 'line' } group The group to enable.
+ * @param {string | null} rule The rule ID to enable.
* @returns {void}
*/
-function enable (context, loc, group, rules) {
- if (rules.length === 0) {
- context.report({ loc, message: '++ {{group}}', data: { group }})
+function enable(context, loc, group, rule) {
+ if (rule) {
+ context.report({
+ loc,
+ messageId: group === 'block' ? 'enableBlockRule' : 'enableLineRule',
+ data: { rule }
+ })
} else {
- context.report({ loc, message: '+ {{group}} {{rules}}', data: { group, rules: rules.join(' ') }})
+ context.report({
+ loc,
+ messageId: group === 'block' ? 'enableBlock' : 'enableLine'
+ })
}
}
@@ -52,15 +88,24 @@ function enable (context, loc, group, rules) {
* Disable rules.
* @param {RuleContext} context The rule context.
* @param {{line:number,column:number}} loc The location information to disable.
- * @param {string} group The group to disable.
- * @param {string[]} rules The rule IDs to disable.
+ * @param { 'block' | 'line' } group The group to disable.
+ * @param {string | null} rule The rule ID to disable.
+ * @param {string} key The disable directive key.
* @returns {void}
*/
-function disable (context, loc, group, rules) {
- if (rules.length === 0) {
- context.report({ loc, message: '-- {{group}}', data: { group }})
+function disable(context, loc, group, rule, key) {
+ if (rule) {
+ context.report({
+ loc,
+ messageId: group === 'block' ? 'disableBlockRule' : 'disableLineRule',
+ data: { rule, key }
+ })
} else {
- context.report({ loc, message: '- {{group}} {{rules}}', data: { group, rules: rules.join(' ') }})
+ context.report({
+ loc,
+ messageId: group === 'block' ? 'disableBlock' : 'disableLine',
+ data: { key }
+ })
}
}
@@ -69,15 +114,40 @@ function disable (context, loc, group, rules) {
* If the comment is `eslint-disable` or `eslint-enable` then it reports the comment.
* @param {RuleContext} context The rule context.
* @param {Token} comment The comment token to process.
+ * @param {boolean} reportUnusedDisableDirectives To report unused eslint-disable comments.
* @returns {void}
*/
-function processBlock (context, comment) {
+function processBlock(context, comment, reportUnusedDisableDirectives) {
const parsed = parse(COMMENT_DIRECTIVE_B, comment.value)
- if (parsed != null) {
- if (parsed.type === 'eslint-disable') {
- disable(context, comment.loc.start, 'block', parsed.rules)
+ 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 {
- enable(context, comment.loc.start, 'block', parsed.rules)
+ 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)
}
}
}
@@ -87,57 +157,200 @@ function processBlock (context, comment) {
* If the comment is `eslint-disable-line` or `eslint-disable-next-line` then it reports the comment.
* @param {RuleContext} context The rule context.
* @param {Token} comment The comment token to process.
+ * @param {boolean} reportUnusedDisableDirectives To report unused eslint-disable comments.
* @returns {void}
*/
-function processLine (context, comment) {
+function processLine(context, comment, reportUnusedDisableDirectives) {
const parsed = parse(COMMENT_DIRECTIVE_L, comment.value)
if (parsed != null && comment.loc.start.line === comment.loc.end.line) {
- const line = comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1)
+ const line =
+ comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1)
const column = -1
- disable(context, { line, column }, 'line', parsed.rules)
- enable(context, { line: line + 1, column }, 'line', parsed.rules)
+ if (parsed.rules.length > 0) {
+ const rules = reportUnusedDisableDirectives
+ ? reportUnusedRules(context, comment, parsed.type, parsed.rules)
+ : parsed.rules
+ for (const rule of rules) {
+ disable(context, { line, column }, 'line', rule.ruleId, rule.key || '')
+ enable(context, { line: line + 1, column }, 'line', rule.ruleId)
+ }
+ } else {
+ const key = reportUnusedDisableDirectives
+ ? reportUnused(context, comment, parsed.type)
+ : ''
+ disable(context, { line, column }, 'line', null, key)
+ enable(context, { line: line + 1, column }, 'line', null)
+ }
}
}
/**
- * The implementation of `vue/comment-directive` rule.
- * @param {Program} node The program node to parse.
- * @returns {Object} The visitor of this rule.
+ * Reports unused disable directive.
+ * Do not check the use of directives here. Filter the directives used with postprocess.
+ * @param {RuleContext} context The rule context.
+ * @param {Token} comment The comment token to report.
+ * @param {string} kind The comment directive kind.
+ * @returns {string} The report key
*/
-function create (context) {
- return {
- Program (node) {
- if (!node.templateBody) {
- return
- }
+function reportUnused(context, comment, kind) {
+ const loc = comment.loc
- // Send directives to the post-process.
- for (const comment of node.templateBody.comments) {
- processBlock(context, comment)
- processLine(context, comment)
- }
+ context.report({
+ loc,
+ messageId: 'unused',
+ data: { kind }
+ })
+
+ return locToKey(loc.start)
+}
+
+/**
+ * Reports unused disable directive rules.
+ * Do not check the use of directives here. Filter the directives used with postprocess.
+ * @param {RuleContext} context The rule context.
+ * @param {Token} comment The comment token to report.
+ * @param {string} kind The comment directive kind.
+ * @param {RuleAndLocation[]} rules To report rule.
+ * @returns { { ruleId: string, key: string }[] }
+ */
+function reportUnusedRules(context, comment, kind, rules) {
+ const sourceCode = context.getSourceCode()
+ const commentStart = comment.range[0] + 4 /* '.",
+ expectedAfterExceptionBlock: 'Expected line break after exception block.',
+ expectedBeforeExceptionBlock:
+ 'Expected line break before exception block.',
+ unexpectedAfterHTMLCommentOpen: "Unexpected line breaks after ''."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const option = parseOption(context.options[0])
+ return htmlComments.defineVisitor(
+ context,
+ context.options[1],
+ (comment) => {
+ const { value, openDecoration, closeDecoration } = comment
+ if (!value) {
+ return
+ }
+
+ const startLine = openDecoration
+ ? openDecoration.loc.end.line
+ : value.loc.start.line
+ const endLine = closeDecoration
+ ? closeDecoration.loc.start.line
+ : value.loc.end.line
+ const newlineType =
+ startLine === endLine ? option.singleline : option.multiline
+ if (newlineType === 'ignore') {
+ return
+ }
+ checkCommentOpen(comment, newlineType !== 'never')
+ checkCommentClose(comment, newlineType !== 'never')
+ }
+ )
+
+ /**
+ * Reports the newline before the contents of a given comment if it's invalid.
+ * @param {ParsedHTMLComment} comment - comment data.
+ * @param {boolean} requireNewline - `true` if line breaks are required.
+ * @returns {void}
+ */
+ function checkCommentOpen(comment, requireNewline) {
+ const { value, openDecoration, open } = comment
+ if (!value) {
+ return
+ }
+ const beforeToken = openDecoration || open
+
+ if (requireNewline) {
+ if (beforeToken.loc.end.line < value.loc.start.line) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: beforeToken.loc.end,
+ end: value.loc.start
+ },
+ messageId: openDecoration
+ ? 'expectedAfterExceptionBlock'
+ : 'expectedAfterHTMLCommentOpen',
+ fix: openDecoration
+ ? undefined
+ : (fixer) => fixer.insertTextAfter(beforeToken, '\n')
+ })
+ } else {
+ if (beforeToken.loc.end.line === value.loc.start.line) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: beforeToken.loc.end,
+ end: value.loc.start
+ },
+ messageId: 'unexpectedAfterHTMLCommentOpen',
+ fix: (fixer) =>
+ fixer.replaceTextRange([beforeToken.range[1], value.range[0]], ' ')
+ })
+ }
+ }
+
+ /**
+ * Reports the space after the contents of a given comment if it's invalid.
+ * @param {ParsedHTMLComment} comment - comment data.
+ * @param {boolean} requireNewline - `true` if line breaks are required.
+ * @returns {void}
+ */
+ function checkCommentClose(comment, requireNewline) {
+ const { value, closeDecoration, close } = comment
+ if (!value) {
+ return
+ }
+ const afterToken = closeDecoration || close
+
+ if (requireNewline) {
+ if (value.loc.end.line < afterToken.loc.start.line) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: value.loc.end,
+ end: afterToken.loc.start
+ },
+ messageId: closeDecoration
+ ? 'expectedBeforeExceptionBlock'
+ : 'expectedBeforeHTMLCommentOpen',
+ fix: closeDecoration
+ ? undefined
+ : (fixer) => fixer.insertTextBefore(afterToken, '\n')
+ })
+ } else {
+ if (value.loc.end.line === afterToken.loc.start.line) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: value.loc.end,
+ end: afterToken.loc.start
+ },
+ messageId: 'unexpectedBeforeHTMLCommentOpen',
+ fix: (fixer) =>
+ fixer.replaceTextRange([value.range[1], afterToken.range[0]], ' ')
+ })
+ }
+ }
+ }
+}
diff --git a/lib/rules/html-comment-content-spacing.js b/lib/rules/html-comment-content-spacing.js
new file mode 100644
index 000000000..b220bd79c
--- /dev/null
+++ b/lib/rules/html-comment-content-spacing.js
@@ -0,0 +1,171 @@
+/**
+ * @author Yosuke ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const htmlComments = require('../utils/html-comments')
+
+/**
+ * @typedef { import('../utils/html-comments').ParsedHTMLComment } ParsedHTMLComment
+ */
+
+module.exports = {
+ meta: {
+ type: 'layout',
+
+ docs: {
+ description: 'enforce unified spacing in HTML comments',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/html-comment-content-spacing.html'
+ },
+ fixable: 'whitespace',
+ schema: [
+ {
+ enum: ['always', 'never']
+ },
+ {
+ type: 'object',
+ properties: {
+ exceptions: {
+ type: 'array',
+ items: {
+ type: 'string'
+ }
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ expectedAfterHTMLCommentOpen: "Expected space after ''.",
+ expectedAfterExceptionBlock: 'Expected space after exception block.',
+ expectedBeforeExceptionBlock: 'Expected space before exception block.',
+ unexpectedAfterHTMLCommentOpen: "Unexpected space after ''."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ // Unless the first option is never, require a space
+ const requireSpace = context.options[0] !== 'never'
+ return htmlComments.defineVisitor(
+ context,
+ context.options[1],
+ (comment) => {
+ checkCommentOpen(comment)
+ checkCommentClose(comment)
+ },
+ { includeDirectives: true }
+ )
+
+ /**
+ * Reports the space before the contents of a given comment if it's invalid.
+ * @param {ParsedHTMLComment} comment - comment data.
+ * @returns {void}
+ */
+ function checkCommentOpen(comment) {
+ const { value, openDecoration, open } = comment
+ if (!value) {
+ return
+ }
+ const beforeToken = openDecoration || open
+ if (beforeToken.loc.end.line !== value.loc.start.line) {
+ // Ignore newline
+ return
+ }
+
+ if (requireSpace) {
+ if (beforeToken.range[1] < value.range[0]) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: beforeToken.loc.end,
+ end: value.loc.start
+ },
+ messageId: openDecoration
+ ? 'expectedAfterExceptionBlock'
+ : 'expectedAfterHTMLCommentOpen',
+ fix: openDecoration
+ ? undefined
+ : (fixer) => fixer.insertTextAfter(beforeToken, ' ')
+ })
+ } else {
+ if (openDecoration) {
+ // Ignore expection block
+ return
+ }
+ if (beforeToken.range[1] === value.range[0]) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: beforeToken.loc.end,
+ end: value.loc.start
+ },
+ messageId: 'unexpectedAfterHTMLCommentOpen',
+ fix: (fixer) =>
+ fixer.removeRange([beforeToken.range[1], value.range[0]])
+ })
+ }
+ }
+
+ /**
+ * Reports the space after the contents of a given comment if it's invalid.
+ * @param {ParsedHTMLComment} comment - comment data.
+ * @returns {void}
+ */
+ function checkCommentClose(comment) {
+ const { value, closeDecoration, close } = comment
+ if (!value) {
+ return
+ }
+ const afterToken = closeDecoration || close
+ if (value.loc.end.line !== afterToken.loc.start.line) {
+ // Ignore newline
+ return
+ }
+
+ if (requireSpace) {
+ if (value.range[1] < afterToken.range[0]) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: value.loc.end,
+ end: afterToken.loc.start
+ },
+ messageId: closeDecoration
+ ? 'expectedBeforeExceptionBlock'
+ : 'expectedBeforeHTMLCommentOpen',
+ fix: closeDecoration
+ ? undefined
+ : (fixer) => fixer.insertTextBefore(afterToken, ' ')
+ })
+ } else {
+ if (closeDecoration) {
+ // Ignore expection block
+ return
+ }
+ if (value.range[1] === afterToken.range[0]) {
+ // Is valid
+ return
+ }
+ context.report({
+ loc: {
+ start: value.loc.end,
+ end: afterToken.loc.start
+ },
+ messageId: 'unexpectedBeforeHTMLCommentOpen',
+ fix: (fixer) =>
+ fixer.removeRange([value.range[1], afterToken.range[0]])
+ })
+ }
+ }
+ }
+}
diff --git a/lib/rules/html-comment-indent.js b/lib/rules/html-comment-indent.js
new file mode 100644
index 000000000..2d4f613d3
--- /dev/null
+++ b/lib/rules/html-comment-indent.js
@@ -0,0 +1,244 @@
+/**
+ * @author Yosuke ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const htmlComments = require('../utils/html-comments')
+
+/**
+ * Normalize options.
+ * @param {number|"tab"|undefined} type The type of indentation.
+ * @returns { { indentChar: string, indentSize: number, indentText: string } } Normalized options.
+ */
+function parseOptions(type) {
+ const ret = {
+ indentChar: ' ',
+ indentSize: 2,
+ indentText: ''
+ }
+
+ if (Number.isSafeInteger(type)) {
+ ret.indentSize = Number(type)
+ } else if (type === 'tab') {
+ ret.indentChar = '\t'
+ ret.indentSize = 1
+ }
+ ret.indentText = ret.indentChar.repeat(ret.indentSize)
+
+ return ret
+}
+
+/**
+ * @param {string} s
+ * @param {string} [unitChar]
+ */
+function toDisplay(s, unitChar) {
+ if (s.length === 0 && unitChar) {
+ return `0 ${toUnit(unitChar)}s`
+ }
+ const char = s[0]
+ if ((char === ' ' || char === '\t') && [...s].every((c) => c === char)) {
+ return `${s.length} ${toUnit(char)}${s.length === 1 ? '' : 's'}`
+ }
+
+ return JSON.stringify(s)
+}
+
+/** @param {string} char */
+function toUnit(char) {
+ if (char === '\t') {
+ return 'tab'
+ }
+ if (char === ' ') {
+ return 'space'
+ }
+ return JSON.stringify(char)
+}
+
+module.exports = {
+ meta: {
+ type: 'layout',
+
+ docs: {
+ description: 'enforce consistent indentation in HTML comments',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/html-comment-indent.html'
+ },
+ fixable: 'whitespace',
+ schema: [
+ {
+ oneOf: [{ type: 'integer', minimum: 0 }, { enum: ['tab'] }]
+ }
+ ],
+ messages: {
+ unexpectedBaseIndentation:
+ 'Expected base point indentation of {{expected}}, but found {{actual}}.',
+ missingBaseIndentation:
+ 'Expected base point indentation of {{expected}}, but not found.',
+ unexpectedIndentationCharacter:
+ 'Expected {{expected}} character, but found {{actual}} character.',
+ unexpectedIndentation:
+ 'Expected indentation of {{expected}} but found {{actual}}.',
+ unexpectedRelativeIndentation:
+ 'Expected relative indentation of {{expected}} but found {{actual}}.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = parseOptions(context.options[0])
+ const sourceCode = context.getSourceCode()
+ return htmlComments.defineVisitor(
+ context,
+ null,
+ (comment) => {
+ const baseIndentText = getLineIndentText(comment.open.loc.start.line)
+ let endLine
+ if (comment.value) {
+ const startLine = comment.value.loc.start.line
+ endLine = comment.value.loc.end.line
+
+ const checkStartLine =
+ comment.open.loc.end.line === startLine ? startLine + 1 : startLine
+
+ for (let line = checkStartLine; line <= endLine; line++) {
+ validateIndentForLine(line, baseIndentText, 1)
+ }
+ } else {
+ endLine = comment.open.loc.end.line
+ }
+
+ if (endLine < comment.close.loc.start.line) {
+ // `-->`
+ validateIndentForLine(comment.close.loc.start.line, baseIndentText, 0)
+ }
+ },
+ { includeDirectives: true }
+ )
+
+ /**
+ * Checks whether the given line is a blank line.
+ * @param {number} line The number of line. Begins with 1.
+ * @returns {boolean} `true` if the given line is a blank line
+ */
+ function isEmptyLine(line) {
+ const lineText = sourceCode.getLines()[line - 1]
+ return !lineText.trim()
+ }
+
+ /**
+ * Get the actual indentation of the given line.
+ * @param {number} line The number of line. Begins with 1.
+ * @returns {string} The actual indentation text
+ */
+ function getLineIndentText(line) {
+ const lineText = sourceCode.getLines()[line - 1]
+ const charIndex = lineText.search(/\S/)
+ // already checked
+ // if (charIndex < 0) {
+ // return lineText
+ // }
+ return lineText.slice(0, charIndex)
+ }
+
+ /**
+ * Define the function which fixes the problem.
+ * @param {number} line The number of line.
+ * @param {string} actualIndentText The actual indentation text.
+ * @param {string} expectedIndentText The expected indentation text.
+ * @returns { (fixer: RuleFixer) => Fix } The defined function.
+ */
+ function defineFix(line, actualIndentText, expectedIndentText) {
+ return (fixer) => {
+ const start = sourceCode.getIndexFromLoc({
+ line,
+ column: 0
+ })
+ return fixer.replaceTextRange(
+ [start, start + actualIndentText.length],
+ expectedIndentText
+ )
+ }
+ }
+
+ /**
+ * Validate the indentation of a line.
+ * @param {number} line The number of line. Begins with 1.
+ * @param {string} baseIndentText The expected base indentation text.
+ * @param {number} offset The number of the indentation offset.
+ */
+ function validateIndentForLine(line, baseIndentText, offset) {
+ if (isEmptyLine(line)) {
+ return
+ }
+ const actualIndentText = getLineIndentText(line)
+
+ const expectedOffsetIndentText = options.indentText.repeat(offset)
+ const expectedIndentText = baseIndentText + expectedOffsetIndentText
+
+ // validate base indent
+ if (
+ baseIndentText &&
+ (actualIndentText.length < baseIndentText.length ||
+ !actualIndentText.startsWith(baseIndentText))
+ ) {
+ context.report({
+ loc: {
+ start: { line, column: 0 },
+ end: { line, column: actualIndentText.length }
+ },
+ messageId: actualIndentText
+ ? 'unexpectedBaseIndentation'
+ : 'missingBaseIndentation',
+ data: {
+ expected: toDisplay(baseIndentText),
+ actual: toDisplay(actualIndentText.slice(0, baseIndentText.length))
+ },
+ fix: defineFix(line, actualIndentText, expectedIndentText)
+ })
+ return
+ }
+
+ const actualOffsetIndentText = actualIndentText.slice(
+ baseIndentText.length
+ )
+
+ // validate indent charctor
+ for (const [i, char] of [...actualOffsetIndentText].entries()) {
+ if (char !== options.indentChar) {
+ context.report({
+ loc: {
+ start: { line, column: baseIndentText.length + i },
+ end: { line, column: baseIndentText.length + i + 1 }
+ },
+ messageId: 'unexpectedIndentationCharacter',
+ data: {
+ expected: toUnit(options.indentChar),
+ actual: toUnit(char)
+ },
+ fix: defineFix(line, actualIndentText, expectedIndentText)
+ })
+ return
+ }
+ }
+
+ // validate indent length
+ if (actualOffsetIndentText.length !== expectedOffsetIndentText.length) {
+ context.report({
+ loc: {
+ start: { line, column: baseIndentText.length },
+ end: { line, column: actualIndentText.length }
+ },
+ messageId: baseIndentText
+ ? 'unexpectedRelativeIndentation'
+ : 'unexpectedIndentation',
+ data: {
+ expected: toDisplay(expectedOffsetIndentText, options.indentChar),
+ actual: toDisplay(actualOffsetIndentText, options.indentChar)
+ },
+ fix: defineFix(line, actualIndentText, expectedIndentText)
+ })
+ }
+ }
+ }
+}
diff --git a/lib/rules/html-end-tags.js b/lib/rules/html-end-tags.js
index 39bb9a310..a0d313cf2 100644
--- a/lib/rules/html-end-tags.js
+++ b/lib/rules/html-end-tags.js
@@ -5,66 +5,55 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for html-end-tags.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- let hasInvalidEOF = false
-
- return utils.defineTemplateBodyVisitor(context, {
- VElement (node) {
- if (hasInvalidEOF) {
- return
- }
-
- const name = node.name
- const isVoid = utils.isHtmlVoidElementName(name)
- const isSelfClosing = node.startTag.selfClosing
- const hasEndTag = node.endTag != null
-
- if (!isVoid && !hasEndTag && !isSelfClosing) {
- context.report({
- node: node.startTag,
- loc: node.startTag.loc,
- message: "'<{{name}}>' should have end tag.",
- data: { name },
- fix: (fixer) => fixer.insertTextAfter(node, `${name}>`)
- })
- }
- }
- }, {
- Program (node) {
- hasInvalidEOF = utils.hasInvalidEOF(node)
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'suggestion',
docs: {
description: 'enforce end tag style',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-end-tags.md'
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/html-end-tags.html'
},
fixable: 'code',
- schema: []
+ schema: [],
+ messages: {
+ missingEndTag: "'<{{name}}>' should have end tag."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ let hasInvalidEOF = false
+
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {
+ VElement(node) {
+ if (hasInvalidEOF) {
+ return
+ }
+
+ const name = node.name
+ const isVoid = utils.isHtmlVoidElementName(name)
+ const isSelfClosing = node.startTag.selfClosing
+ const hasEndTag = node.endTag != null
+
+ if (!isVoid && !hasEndTag && !isSelfClosing) {
+ context.report({
+ node: node.startTag,
+ loc: node.startTag.loc,
+ messageId: 'missingEndTag',
+ data: { name },
+ fix: (fixer) => fixer.insertTextAfter(node, `${name}>`)
+ })
+ }
+ }
+ },
+ {
+ Program(node) {
+ hasInvalidEOF = utils.hasInvalidEOF(node)
+ }
+ }
+ )
}
}
diff --git a/lib/rules/html-indent.js b/lib/rules/html-indent.js
index da04989c6..05cdace92 100644
--- a/lib/rules/html-indent.js
+++ b/lib/rules/html-indent.js
@@ -5,54 +5,64 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const indentCommon = require('../utils/indent-common')
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create (context) {
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
const tokenStore =
- context.parserServices.getTemplateBodyTokenStore &&
- context.parserServices.getTemplateBodyTokenStore()
- const visitor = indentCommon.defineVisitor(context, tokenStore, { baseIndent: 1 })
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+ const visitor = indentCommon.defineVisitor(context, tokenStore, {
+ baseIndent: 1
+ })
return utils.defineTemplateBodyVisitor(context, visitor)
},
+ // eslint-disable-next-line eslint-plugin/prefer-message-ids
meta: {
+ type: 'layout',
docs: {
description: 'enforce consistent indentation in ``',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-indent.md'
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/html-indent.html'
},
+ // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
fixable: 'whitespace',
schema: [
{
- anyOf: [
- { type: 'integer', minimum: 1 },
- { enum: ['tab'] }
- ]
+ oneOf: [{ type: 'integer', minimum: 1 }, { enum: ['tab'] }]
},
{
type: 'object',
properties: {
- 'attribute': { type: 'integer', minimum: 0 },
- 'closeBracket': { type: 'integer', minimum: 0 },
- 'switchCase': { type: 'integer', minimum: 0 },
- 'alignAttributesVertically': { type: 'boolean' },
- 'ignores': {
+ attribute: { type: 'integer', minimum: 0 },
+ baseIndent: { type: 'integer', minimum: 0 },
+ closeBracket: {
+ oneOf: [
+ { type: 'integer', minimum: 0 },
+ {
+ type: 'object',
+ properties: {
+ startTag: { type: 'integer', minimum: 0 },
+ endTag: { type: 'integer', minimum: 0 },
+ selfClosingTag: { type: 'integer', minimum: 0 }
+ },
+ additionalProperties: false
+ }
+ ]
+ },
+ switchCase: { type: 'integer', minimum: 0 },
+ alignAttributesVertically: { type: 'boolean' },
+ ignores: {
type: 'array',
items: {
allOf: [
{ type: 'string' },
- { not: { type: 'string', pattern: ':exit$' }},
- { not: { type: 'string', pattern: '^\\s*$' }}
+ { not: { type: 'string', pattern: ':exit$' } },
+ { not: { type: 'string', pattern: String.raw`^\s*$` } }
]
},
uniqueItems: true,
diff --git a/lib/rules/html-quotes.js b/lib/rules/html-quotes.js
index bd6431af4..f4f6d1c1d 100644
--- a/lib/rules/html-quotes.js
+++ b/lib/rules/html-quotes.js
@@ -5,77 +5,103 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for html-quotes.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- const sourceCode = context.getSourceCode()
- const double = context.options[0] !== 'single'
- const quoteChar = double ? '"' : "'"
- const quoteName = double ? 'double quotes' : 'single quotes'
- const quotePattern = double ? /"/g : /'/g
- const quoteEscaped = double ? '"' : '''
- let hasInvalidEOF
-
- return utils.defineTemplateBodyVisitor(context, {
- 'VAttribute[value!=null]' (node) {
- if (hasInvalidEOF) {
- return
- }
-
- const text = sourceCode.getText(node.value)
- const firstChar = text[0]
-
- if (firstChar !== quoteChar) {
- context.report({
- node: node.value,
- loc: node.value.loc,
- message: 'Expected to be enclosed by {{kind}}.',
- data: { kind: quoteName },
- fix (fixer) {
- const contentText = (firstChar === "'" || firstChar === '"') ? text.slice(1, -1) : text
- const replacement = quoteChar + contentText.replace(quotePattern, quoteEscaped) + quoteChar
-
- return fixer.replaceText(node.value, replacement)
- }
- })
- }
- }
- }, {
- Program (node) {
- hasInvalidEOF = utils.hasInvalidEOF(node)
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'layout',
docs: {
description: 'enforce quotes style of HTML attributes',
- category: 'recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-quotes.md'
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/html-quotes.html'
},
fixable: 'code',
schema: [
- { enum: ['double', 'single'] }
- ]
+ { enum: ['double', 'single'] },
+ {
+ type: 'object',
+ properties: {
+ avoidEscape: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ expected: 'Expected to be enclosed by {{kind}}.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const double = context.options[0] !== 'single'
+ const avoidEscape =
+ context.options[1] && context.options[1].avoidEscape === true
+ const quoteChar = double ? '"' : "'"
+ const quoteName = double ? 'double quotes' : 'single quotes'
+ /** @type {boolean} */
+ let hasInvalidEOF
+
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {
+ 'VAttribute[value!=null]'(node) {
+ if (hasInvalidEOF) {
+ return
+ }
+
+ if (utils.isVBindSameNameShorthand(node)) {
+ // v-bind same-name shorthand (Vue 3.4+)
+ return
+ }
+
+ const text = sourceCode.getText(node.value)
+ const firstChar = text[0]
+
+ if (firstChar !== quoteChar) {
+ const quoted = firstChar === "'" || firstChar === '"'
+ if (avoidEscape && quoted) {
+ const contentText = text.slice(1, -1)
+ if (contentText.includes(quoteChar)) {
+ return
+ }
+ }
+
+ context.report({
+ node: node.value,
+ loc: node.value.loc,
+ messageId: 'expected',
+ data: { kind: quoteName },
+ fix(fixer) {
+ const contentText = quoted ? text.slice(1, -1) : text
+
+ let fixToDouble = double
+ if (avoidEscape && !quoted && contentText.includes(quoteChar)) {
+ fixToDouble = double
+ ? contentText.includes("'")
+ : !contentText.includes('"')
+ }
+
+ const quotePattern = fixToDouble ? /"/g : /'/g
+ const quoteEscaped = fixToDouble ? '"' : '''
+ const fixQuoteChar = fixToDouble ? '"' : "'"
+
+ const replacement =
+ fixQuoteChar +
+ contentText.replace(quotePattern, quoteEscaped) +
+ fixQuoteChar
+ return fixer.replaceText(node.value, replacement)
+ }
+ })
+ }
+ }
+ },
+ {
+ Program(node) {
+ hasInvalidEOF = utils.hasInvalidEOF(node)
+ }
+ }
+ )
}
}
diff --git a/lib/rules/html-self-closing.js b/lib/rules/html-self-closing.js
index b3c5a7a90..c31c9ab70 100644
--- a/lib/rules/html-self-closing.js
+++ b/lib/rules/html-self-closing.js
@@ -5,64 +5,68 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
/**
* These strings wil be displayed in error messages.
*/
-const ELEMENT_TYPE = Object.freeze({
+const ELEMENT_TYPE_MESSAGES = Object.freeze({
NORMAL: 'HTML elements',
VOID: 'HTML void elements',
COMPONENT: 'Vue.js custom components',
SVG: 'SVG elements',
- MATH: 'MathML elements'
+ MATH: 'MathML elements',
+ UNKNOWN: 'unknown elements'
})
+/**
+ * @typedef {object} Options
+ * @property {'always' | 'never'} NORMAL
+ * @property {'always' | 'never'} VOID
+ * @property {'always' | 'never'} COMPONENT
+ * @property {'always' | 'never'} SVG
+ * @property {'always' | 'never'} MATH
+ * @property {null} UNKNOWN
+ */
+
/**
* Normalize the given options.
- * @param {Object|undefined} options The raw options object.
- * @returns {Object} Normalized options.
+ * @param {any} options The raw options object.
+ * @returns {Options} Normalized options.
*/
-function parseOptions (options) {
+function parseOptions(options) {
return {
- [ELEMENT_TYPE.NORMAL]: (options && options.html && options.html.normal) || 'always',
- [ELEMENT_TYPE.VOID]: (options && options.html && options.html.void) || 'never',
- [ELEMENT_TYPE.COMPONENT]: (options && options.html && options.html.component) || 'always',
- [ELEMENT_TYPE.SVG]: (options && options.svg) || 'always',
- [ELEMENT_TYPE.MATH]: (options && options.math) || 'always'
+ NORMAL: (options && options.html && options.html.normal) || 'always',
+ VOID: (options && options.html && options.html.void) || 'never',
+ COMPONENT: (options && options.html && options.html.component) || 'always',
+ SVG: (options && options.svg) || 'always',
+ MATH: (options && options.math) || 'always',
+ UNKNOWN: null
}
}
/**
* Get the elementType of the given element.
* @param {VElement} node The element node to get.
- * @returns {string} The elementType of the element.
+ * @returns {keyof Options} The elementType of the element.
*/
-function getElementType (node) {
+function getElementType(node) {
if (utils.isCustomComponent(node)) {
- return ELEMENT_TYPE.COMPONENT
+ return 'COMPONENT'
}
if (utils.isHtmlElementNode(node)) {
if (utils.isHtmlVoidElementName(node.name)) {
- return ELEMENT_TYPE.VOID
+ return 'VOID'
}
- return ELEMENT_TYPE.NORMAL
+ return 'NORMAL'
}
if (utils.isSvgElementNode(node)) {
- return ELEMENT_TYPE.SVG
+ return 'SVG'
}
- if (utils.isMathMLElementNode(node)) {
- return ELEMENT_TYPE.MATH
+ if (utils.isMathElementNode(node)) {
+ return 'MATH'
}
- return 'unknown elements'
+ return 'UNKNOWN'
}
/**
@@ -72,88 +76,20 @@ function getElementType (node) {
* @param {SourceCode} sourceCode The source code object of the current context.
* @returns {boolean} `true` if the element is empty.
*/
-function isEmpty (node, sourceCode) {
+function isEmpty(node, sourceCode) {
const start = node.startTag.range[1]
- const end = (node.endTag != null) ? node.endTag.range[0] : node.range[1]
+ const end = node.endTag == null ? node.range[1] : node.endTag.range[0]
return sourceCode.text.slice(start, end).trim() === ''
}
-/**
- * Creates AST event handlers for html-self-closing.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {object} AST event handlers.
- */
-function create (context) {
- const sourceCode = context.getSourceCode()
- const options = parseOptions(context.options[0])
- let hasInvalidEOF = false
-
- return utils.defineTemplateBodyVisitor(context, {
- 'VElement' (node) {
- if (hasInvalidEOF) {
- return
- }
-
- const elementType = getElementType(node)
- const mode = options[elementType]
-
- if (mode === 'always' && !node.startTag.selfClosing && isEmpty(node, sourceCode)) {
- context.report({
- node,
- loc: node.loc,
- message: 'Require self-closing on {{elementType}} (<{{name}}>).',
- data: { elementType, name: node.rawName },
- fix: (fixer) => {
- const tokens = context.parserServices.getTemplateBodyTokenStore()
- const close = tokens.getLastToken(node.startTag)
- if (close.type !== 'HTMLTagClose') {
- return null
- }
- return fixer.replaceTextRange([close.range[0], node.range[1]], '/>')
- }
- })
- }
-
- if (mode === 'never' && node.startTag.selfClosing) {
- context.report({
- node,
- loc: node.loc,
- message: 'Disallow self-closing on {{elementType}} (<{{name}}/>).',
- data: { elementType, name: node.rawName },
- fix: (fixer) => {
- const tokens = context.parserServices.getTemplateBodyTokenStore()
- const close = tokens.getLastToken(node.startTag)
- if (close.type !== 'HTMLSelfClosingTagClose') {
- return null
- }
- if (elementType === ELEMENT_TYPE.VOID) {
- return fixer.replaceText(close, '>')
- }
- return fixer.replaceText(close, `>${node.rawName}>`)
- }
- })
- }
- }
- }, {
- Program (node) {
- hasInvalidEOF = utils.hasInvalidEOF(node)
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'layout',
docs: {
description: 'enforce self-closing style',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/html-self-closing.md'
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/html-self-closing.html'
},
fixable: 'code',
schema: {
@@ -163,24 +99,124 @@ module.exports = {
}
},
type: 'array',
- items: [{
- type: 'object',
- properties: {
- html: {
- type: 'object',
- properties: {
- normal: { $ref: '#/definitions/optionValue' },
- void: { $ref: '#/definitions/optionValue' },
- component: { $ref: '#/definitions/optionValue' }
+ items: [
+ {
+ type: 'object',
+ properties: {
+ html: {
+ type: 'object',
+ properties: {
+ normal: { $ref: '#/definitions/optionValue' },
+ void: { $ref: '#/definitions/optionValue' },
+ component: { $ref: '#/definitions/optionValue' }
+ },
+ additionalProperties: false
},
- additionalProperties: false
+ svg: { $ref: '#/definitions/optionValue' },
+ math: { $ref: '#/definitions/optionValue' }
},
- svg: { $ref: '#/definitions/optionValue' },
- math: { $ref: '#/definitions/optionValue' }
- },
- additionalProperties: false
- }],
+ additionalProperties: false
+ }
+ ],
maxItems: 1
+ },
+ messages: {
+ requireSelfClosing:
+ 'Require self-closing on {{elementType}} (<{{name}}>).',
+ disallowSelfClosing:
+ 'Disallow self-closing on {{elementType}} (<{{name}}/>).'
}
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const options = parseOptions(context.options[0])
+ let hasInvalidEOF = false
+
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {
+ VElement(node) {
+ if (hasInvalidEOF || node.parent.type === 'VDocumentFragment') {
+ return
+ }
+
+ const elementType = getElementType(node)
+ const mode = options[elementType]
+
+ if (
+ mode === 'always' &&
+ !node.startTag.selfClosing &&
+ isEmpty(node, sourceCode)
+ ) {
+ context.report({
+ node: node.endTag || node,
+ messageId: 'requireSelfClosing',
+ data: {
+ elementType: ELEMENT_TYPE_MESSAGES[elementType],
+ name: node.rawName
+ },
+ fix(fixer) {
+ const tokens =
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+ const close = tokens.getLastToken(node.startTag)
+ if (close.type !== 'HTMLTagClose') {
+ return null
+ }
+ return fixer.replaceTextRange(
+ [close.range[0], node.range[1]],
+ '/>'
+ )
+ }
+ })
+ }
+
+ if (mode === 'never' && node.startTag.selfClosing) {
+ context.report({
+ node,
+ loc: {
+ start: {
+ line: node.loc.end.line,
+ column: node.loc.end.column - 2
+ },
+ end: node.loc.end
+ },
+ messageId: 'disallowSelfClosing',
+ data: {
+ elementType: ELEMENT_TYPE_MESSAGES[elementType],
+ name: node.rawName
+ },
+ fix(fixer) {
+ const tokens =
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+ const close = tokens.getLastToken(node.startTag)
+ if (close.type !== 'HTMLSelfClosingTagClose') {
+ return null
+ }
+ if (elementType === 'VOID') {
+ return fixer.replaceText(close, '>')
+ }
+ // If only `close` is targeted for replacement, it conflicts with `component-name-in-template-casing`,
+ // so replace the entire element.
+ // return fixer.replaceText(close, `>${node.rawName}>`)
+ const elementPart = sourceCode.text.slice(
+ node.range[0],
+ close.range[0]
+ )
+ return fixer.replaceText(
+ node,
+ `${elementPart}>${node.rawName}>`
+ )
+ }
+ })
+ }
+ }
+ },
+ {
+ Program(node) {
+ hasInvalidEOF = utils.hasInvalidEOF(node)
+ }
+ }
+ )
}
}
diff --git a/lib/rules/jsx-uses-vars.js b/lib/rules/jsx-uses-vars.js
index a254c1093..d9f46972a 100644
--- a/lib/rules/jsx-uses-vars.js
+++ b/lib/rules/jsx-uses-vars.js
@@ -30,31 +30,34 @@ SOFTWARE.
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
+const utils = require('../utils')
module.exports = {
+ // eslint-disable-next-line eslint-plugin/prefer-message-ids
meta: {
+ type: 'problem',
docs: {
- description: 'prevent variables used in JSX to be marked as unused', // eslint-disable-line consistent-docs-description
- category: 'base',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/jsx-uses-vars.md'
+ description: 'prevent variables used in JSX to be marked as unused', // eslint-disable-line eslint-plugin/require-meta-docs-description
+ categories: ['base'],
+ url: 'https://eslint.vuejs.org/rules/jsx-uses-vars.html'
},
schema: []
},
-
- create (context) {
+ /**
+ * @param {RuleContext} context - The rule context.
+ * @returns {RuleListener} AST event handlers.
+ */
+ create(context) {
return {
- JSXOpeningElement (node) {
+ JSXOpeningElement(node) {
let name
- if (node.name.name) {
+ if (node.name.type === 'JSXIdentifier') {
//
name = node.name.name
- } else if (node.name.object) {
+ } else if (node.name.type === 'JSXMemberExpression') {
//
let parent = node.name.object
- while (parent.object) {
+ while (parent.type === 'JSXMemberExpression') {
parent = parent.object
}
name = parent.name
@@ -62,7 +65,7 @@ module.exports = {
return
}
- context.markVariableAsUsed(name)
+ utils.markVariableAsUsed(context, name, node)
}
}
}
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
new file mode 100644
index 000000000..a70a81420
--- /dev/null
+++ b/lib/rules/key-spacing.js
@@ -0,0 +1,11 @@
+/**
+ * @author Toru Nagashima
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('key-spacing', {
+ skipDynamicArguments: true
+})
diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js
new file mode 100644
index 000000000..9cb4fd3fc
--- /dev/null
+++ b/lib/rules/keyword-spacing.js
@@ -0,0 +1,11 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('keyword-spacing', {
+ skipDynamicArguments: true
+})
diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js
new file mode 100644
index 000000000..181d88f1a
--- /dev/null
+++ b/lib/rules/match-component-file-name.js
@@ -0,0 +1,164 @@
+/**
+ * @fileoverview Require component name property to match its file name
+ * @author Rodrigo Pedra Brum
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+const path = require('path')
+
+/**
+ * @param {Expression | SpreadElement} node
+ * @returns {node is (Literal | TemplateLiteral)}
+ */
+function canVerify(node) {
+ return (
+ node.type === 'Literal' ||
+ (node.type === 'TemplateLiteral' &&
+ node.expressions.length === 0 &&
+ node.quasis.length === 1)
+ )
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'require component name property to match its file name',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/match-component-file-name.html'
+ },
+ fixable: null,
+ hasSuggestions: true,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ extensions: {
+ type: 'array',
+ items: {
+ type: 'string'
+ },
+ uniqueItems: true,
+ additionalItems: false
+ },
+ shouldMatchCase: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ shouldMatchFileName:
+ 'Component name `{{name}}` should match file name `{{filename}}`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = context.options[0]
+ const shouldMatchCase = (options && options.shouldMatchCase) || false
+ const extensionsArray = options && options.extensions
+ const allowedExtensions = Array.isArray(extensionsArray)
+ ? extensionsArray
+ : ['jsx']
+
+ const extension = path.extname(context.getFilename())
+ const filename = path.basename(context.getFilename(), extension)
+
+ /** @type {Rule.ReportDescriptor[]} */
+ const errors = []
+ let componentCount = 0
+
+ if (!allowedExtensions.includes(extension.replace(/^\./, ''))) {
+ return {}
+ }
+
+ /**
+ * @param {string} name
+ * @param {string} filename
+ */
+ function compareNames(name, filename) {
+ if (shouldMatchCase) {
+ return name === filename
+ }
+
+ return (
+ casing.pascalCase(name) === filename ||
+ casing.kebabCase(name) === filename
+ )
+ }
+
+ /**
+ * @param {Literal | TemplateLiteral} node
+ */
+ function verifyName(node) {
+ let name
+ if (node.type === 'TemplateLiteral') {
+ const quasis = node.quasis[0]
+ name = quasis.value.cooked
+ } else {
+ name = `${node.value}`
+ }
+
+ if (!compareNames(name, filename)) {
+ errors.push({
+ node,
+ messageId: 'shouldMatchFileName',
+ data: { filename, name },
+ suggest: [
+ {
+ desc: 'Rename component to match file name.',
+ fix(fixer) {
+ const quote =
+ node.type === 'TemplateLiteral' ? '`' : node.raw[0]
+ return fixer.replaceText(node, `${quote}${filename}${quote}`)
+ }
+ }
+ ]
+ })
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.executeOnCallVueComponent(context, (node) => {
+ if (node.arguments.length === 2) {
+ const argument = node.arguments[0]
+
+ if (canVerify(argument)) {
+ verifyName(argument)
+ }
+ }
+ }),
+ utils.executeOnVue(context, (object) => {
+ const node = utils.findProperty(object, 'name')
+
+ componentCount++
+
+ if (!node) return
+ if (!canVerify(node.value)) return
+ verifyName(node.value)
+ }),
+ utils.defineScriptSetupVisitor(context, {
+ onDefineOptionsEnter(node) {
+ componentCount++
+ if (node.arguments.length === 0) return
+ const define = node.arguments[0]
+ if (define.type !== 'ObjectExpression') return
+ const nameNode = utils.findProperty(define, 'name')
+ if (!nameNode) return
+ if (!canVerify(nameNode.value)) return
+ verifyName(nameNode.value)
+ }
+ }),
+ {
+ 'Program:exit'() {
+ if (componentCount > 1) return
+
+ for (const error of errors) context.report(error)
+ }
+ }
+ )
+ }
+}
diff --git a/lib/rules/match-component-import-name.js b/lib/rules/match-component-import-name.js
new file mode 100644
index 000000000..344661005
--- /dev/null
+++ b/lib/rules/match-component-import-name.js
@@ -0,0 +1,73 @@
+/**
+ * @author Doug Wade
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+
+/**
+ * @param {Identifier} identifier
+ * @return {Array}
+ */
+function getExpectedNames(identifier) {
+ return [casing.pascalCase(identifier.name), casing.kebabCase(identifier.name)]
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'require the registered component name to match the imported component name',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/match-component-import-name.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpected:
+ 'Component alias {{importedName}} should be one of: {{expectedName}}.'
+ }
+ },
+ /**
+ * @param {RuleContext} context
+ * @returns {RuleListener}
+ */
+ create(context) {
+ return utils.executeOnVueComponent(context, (obj) => {
+ const components = utils.findProperty(obj, 'components')
+ if (
+ !components ||
+ !components.value ||
+ components.value.type !== 'ObjectExpression'
+ ) {
+ return
+ }
+
+ for (const property of components.value.properties) {
+ if (
+ property.type === 'SpreadElement' ||
+ property.value.type !== 'Identifier' ||
+ property.computed === true
+ ) {
+ continue
+ }
+
+ const importedName = utils.getStaticPropertyName(property) || ''
+ const expectedNames = getExpectedNames(property.value)
+ if (!expectedNames.includes(importedName)) {
+ context.report({
+ node: property,
+ messageId: 'unexpected',
+ data: {
+ importedName,
+ expectedName: expectedNames.join(', ')
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/max-attributes-per-line.js b/lib/rules/max-attributes-per-line.js
index 1ee73a549..8a9de1349 100644
--- a/lib/rules/max-attributes-per-line.js
+++ b/lib/rules/max-attributes-per-line.js
@@ -4,25 +4,77 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
const utils = require('../utils')
+/**
+ * @param {any} options
+ */
+function parseOptions(options) {
+ const defaults = {
+ singleline: 1,
+ multiline: 1
+ }
+
+ if (options) {
+ if (typeof options.singleline === 'number') {
+ defaults.singleline = options.singleline
+ } else if (
+ typeof options.singleline === 'object' &&
+ typeof options.singleline.max === 'number'
+ ) {
+ defaults.singleline = options.singleline.max
+ }
+
+ if (options.multiline) {
+ if (typeof options.multiline === 'number') {
+ defaults.multiline = options.multiline
+ } else if (
+ typeof options.multiline === 'object' &&
+ typeof options.multiline.max === 'number'
+ ) {
+ defaults.multiline = options.multiline.max
+ }
+ }
+ }
+
+ return defaults
+}
+
+/**
+ * @param {(VDirective | VAttribute)[]} attributes
+ */
+function groupAttrsByLine(attributes) {
+ const propsPerLine = [[attributes[0]]]
+
+ for (let index = 1; index < attributes.length; index++) {
+ const previous = attributes[index - 1]
+ const current = attributes[index]
+
+ if (previous.loc.end.line === current.loc.start.line) {
+ propsPerLine[propsPerLine.length - 1].push(current)
+ } else {
+ propsPerLine.push([current])
+ }
+ }
+
+ return propsPerLine
+}
+
module.exports = {
meta: {
+ type: 'layout',
docs: {
description: 'enforce the maximum number of attributes per line',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/max-attributes-per-line.md'
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/max-attributes-per-line.html'
},
- fixable: null,
+ fixable: 'whitespace',
schema: [
{
type: 'object',
properties: {
singleline: {
- anyOf: [
+ oneOf: [
{
type: 'number',
minimum: 1
@@ -40,7 +92,7 @@ module.exports = {
]
},
multiline: {
- anyOf: [
+ oneOf: [
{
type: 'number',
minimum: 1
@@ -51,109 +103,81 @@ module.exports = {
max: {
type: 'number',
minimum: 1
- },
- allowFirstLine: {
- type: 'boolean'
}
},
additionalProperties: false
}
]
}
- }
+ },
+ additionalProperties: false
}
- ]
+ ],
+ messages: {
+ shouldBeOnNewLine: "'{{name}}' should be on a new line."
+ }
},
-
- create: function (context) {
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
const configuration = parseOptions(context.options[0])
const multilineMaximum = configuration.multiline
const singlelinemMaximum = configuration.singleline
- const canHaveFirstLine = configuration.allowFirstLine
+ const template =
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
return utils.defineTemplateBodyVisitor(context, {
- 'VStartTag' (node) {
+ VStartTag(node) {
const numberOfAttributes = node.attributes.length
if (!numberOfAttributes) return
- if (utils.isSingleLine(node) && numberOfAttributes > singlelinemMaximum) {
- showErrors(node.attributes.slice(singlelinemMaximum), node)
+ if (
+ utils.isSingleLine(node) &&
+ numberOfAttributes > singlelinemMaximum
+ ) {
+ showErrors(node.attributes.slice(singlelinemMaximum))
}
if (!utils.isSingleLine(node)) {
- if (!canHaveFirstLine && node.attributes[0].loc.start.line === node.loc.start.line) {
- showErrors([node.attributes[0]], node)
- }
-
- groupAttrsByLine(node.attributes)
- .filter(attrs => attrs.length > multilineMaximum)
- .forEach(attrs => showErrors(attrs.splice(multilineMaximum), node))
- }
- }
- })
-
- // ----------------------------------------------------------------------
- // Helpers
- // ----------------------------------------------------------------------
- function parseOptions (options) {
- const defaults = {
- singleline: 1,
- multiline: 1,
- allowFirstLine: false
- }
-
- if (options) {
- if (typeof options.singleline === 'number') {
- defaults.singleline = options.singleline
- } else if (options.singleline && options.singleline.max) {
- defaults.singleline = options.singleline.max
- }
-
- if (options.multiline) {
- if (typeof options.multiline === 'number') {
- defaults.multiline = options.multiline
- } else if (typeof options.multiline === 'object') {
- if (options.multiline.max) {
- defaults.multiline = options.multiline.max
- }
-
- if (options.multiline.allowFirstLine) {
- defaults.allowFirstLine = options.multiline.allowFirstLine
+ for (const attrs of groupAttrsByLine(node.attributes)) {
+ if (attrs.length > multilineMaximum) {
+ showErrors(attrs.splice(multilineMaximum))
}
}
}
}
+ })
- return defaults
- }
-
- function showErrors (attributes, node) {
- attributes.forEach((prop) => {
+ /**
+ * @param {(VDirective | VAttribute)[]} attributes
+ */
+ function showErrors(attributes) {
+ for (const [i, prop] of attributes.entries()) {
context.report({
node: prop,
loc: prop.loc,
- message: 'Attribute "{{propName}}" should be on a new line.',
- data: {
- propName: prop.key.name
+ messageId: 'shouldBeOnNewLine',
+ data: { name: sourceCode.getText(prop.key) },
+ fix(fixer) {
+ if (i !== 0) return null
+
+ // Find the closest token before the current prop
+ // that is not a white space
+ const prevToken = /** @type {Token} */ (
+ template.getTokenBefore(prop, {
+ filter: (token) => token.type !== 'HTMLWhitespace'
+ })
+ )
+
+ /** @type {Range} */
+ const range = [prevToken.range[1], prop.range[0]]
+
+ return fixer.replaceTextRange(range, '\n')
}
})
- })
- }
-
- function groupAttrsByLine (attributes) {
- const propsPerLine = [[attributes[0]]]
-
- attributes.reduce((previous, current) => {
- if (previous.loc.end.line === current.loc.start.line) {
- propsPerLine[propsPerLine.length - 1].push(current)
- } else {
- propsPerLine.push([current])
- }
- return current
- })
-
- return propsPerLine
+ }
}
}
}
diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js
new file mode 100644
index 000000000..b5dce5eb1
--- /dev/null
+++ b/lib/rules/max-len.js
@@ -0,0 +1,516 @@
+/**
+ * @author Yosuke Ota
+ * @fileoverview Rule to check for max length on a line of Vue file.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+const OPTIONS_SCHEMA = {
+ type: 'object',
+ properties: {
+ code: {
+ type: 'integer',
+ minimum: 0
+ },
+ template: {
+ type: 'integer',
+ minimum: 0
+ },
+ comments: {
+ type: 'integer',
+ minimum: 0
+ },
+ tabWidth: {
+ type: 'integer',
+ minimum: 0
+ },
+ ignorePattern: {
+ type: 'string'
+ },
+ ignoreComments: {
+ type: 'boolean'
+ },
+ ignoreTrailingComments: {
+ type: 'boolean'
+ },
+ ignoreUrls: {
+ type: 'boolean'
+ },
+ ignoreStrings: {
+ type: 'boolean'
+ },
+ ignoreTemplateLiterals: {
+ type: 'boolean'
+ },
+ ignoreRegExpLiterals: {
+ type: 'boolean'
+ },
+ ignoreHTMLAttributeValues: {
+ type: 'boolean'
+ },
+ ignoreHTMLTextContents: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+}
+
+const OPTIONS_OR_INTEGER_SCHEMA = {
+ oneOf: [
+ OPTIONS_SCHEMA,
+ {
+ type: 'integer',
+ minimum: 0
+ }
+ ]
+}
+
+/**
+ * Computes the length of a line that may contain tabs. The width of each
+ * tab will be the number of spaces to the next tab stop.
+ * @param {string} line The line.
+ * @param {number} tabWidth The width of each tab stop in spaces.
+ * @returns {number} The computed line length.
+ * @private
+ */
+function computeLineLength(line, tabWidth) {
+ let extraCharacterCount = 0
+
+ const re = /\t/gu
+ let ret
+ while ((ret = re.exec(line))) {
+ const offset = ret.index
+ const totalOffset = offset + extraCharacterCount
+ const previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0
+ const spaceCount = tabWidth - previousTabStopOffset
+ extraCharacterCount += spaceCount - 1 // -1 for the replaced tab
+ }
+
+ return [...line].length + extraCharacterCount
+}
+
+/**
+ * Tells if a given comment is trailing: it starts on the current line and
+ * extends to or past the end of the current line.
+ * @param {string} line The source line we want to check for a trailing comment on
+ * @param {number} lineNumber The one-indexed line number for line
+ * @param {Token | null} comment The comment to inspect
+ * @returns {comment is Token} If the comment is trailing on the given line
+ */
+function isTrailingComment(line, lineNumber, comment) {
+ return Boolean(
+ comment &&
+ comment.loc.start.line === lineNumber &&
+ lineNumber <= comment.loc.end.line &&
+ (comment.loc.end.line > lineNumber ||
+ comment.loc.end.column === line.length)
+ )
+}
+
+/**
+ * Tells if a comment encompasses the entire line.
+ * @param {string} line The source line with a trailing comment
+ * @param {number} lineNumber The one-indexed line number this is on
+ * @param {Token | null} comment The comment to remove
+ * @returns {boolean} If the comment covers the entire line
+ */
+function isFullLineComment(line, lineNumber, comment) {
+ if (!comment) {
+ return false
+ }
+ const start = comment.loc.start
+ const end = comment.loc.end
+ const isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim()
+
+ return (
+ comment &&
+ (start.line < lineNumber ||
+ (start.line === lineNumber && isFirstTokenOnLine)) &&
+ (end.line > lineNumber ||
+ (end.line === lineNumber && end.column === line.length))
+ )
+}
+
+/**
+ * Gets the line after the comment and any remaining trailing whitespace is
+ * stripped.
+ * @param {string} line The source line with a trailing comment
+ * @param {Token} comment The comment to remove
+ * @returns {string} Line without comment and trailing whitepace
+ */
+function stripTrailingComment(line, comment) {
+ // loc.column is zero-indexed
+ return line.slice(0, comment.loc.start.column).replace(/\s+$/u, '')
+}
+
+/**
+ * Group AST nodes by line number, both start and end.
+ *
+ * @param {Token[]} nodes the AST nodes in question
+ * @returns { { [key: number]: Token[] } } the grouped nodes
+ * @private
+ */
+function groupByLineNumber(nodes) {
+ /** @type { { [key: number]: Token[] } } */
+ const grouped = {}
+ for (const node of nodes) {
+ for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) {
+ if (!Array.isArray(grouped[i])) {
+ grouped[i] = []
+ }
+ grouped[i].push(node)
+ }
+ }
+ return grouped
+}
+
+module.exports = {
+ meta: {
+ type: 'layout',
+
+ docs: {
+ description: 'enforce a maximum line length in `.vue` files',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/max-len.html',
+ extensionSource: {
+ url: 'https://eslint.org/docs/rules/max-len',
+ name: 'ESLint core'
+ }
+ },
+
+ schema: [
+ OPTIONS_OR_INTEGER_SCHEMA,
+ OPTIONS_OR_INTEGER_SCHEMA,
+ OPTIONS_SCHEMA
+ ],
+ messages: {
+ max: 'This line has a length of {{lineLength}}. Maximum allowed is {{maxLength}}.',
+ maxComment:
+ 'This line has a comment length of {{lineLength}}. Maximum allowed is {{maxCommentLength}}.'
+ }
+ },
+ /**
+ * @param {RuleContext} context - The rule context.
+ * @returns {RuleListener} AST event handlers.
+ */
+ create(context) {
+ /*
+ * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however:
+ * - They're matching an entire string that we know is a URI
+ * - We're matching part of a string where we think there *might* be a URL
+ * - We're only concerned about URLs, as picking out any URI would cause
+ * too many false positives
+ * - We don't care about matching the entire URL, any small segment is fine
+ */
+ const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u
+
+ const sourceCode = context.getSourceCode()
+ /** @type {Token[]} */
+ const tokens = []
+ /** @type {(HTMLComment | HTMLBogusComment | Comment)[]} */
+ const comments = []
+ /** @type {VLiteral[]} */
+ const htmlAttributeValues = []
+
+ // The options object must be the last option specified…
+ const options = Object.assign(
+ {},
+ context.options[context.options.length - 1]
+ )
+
+ // …but max code length…
+ if (typeof context.options[0] === 'number') {
+ options.code = context.options[0]
+ }
+
+ // …and tabWidth can be optionally specified directly as integers.
+ if (typeof context.options[1] === 'number') {
+ options.tabWidth = context.options[1]
+ }
+ /** @type {number} */
+ const scriptMaxLength = typeof options.code === 'number' ? options.code : 80
+ /** @type {number} */
+ const tabWidth = typeof options.tabWidth === 'number' ? options.tabWidth : 2 // default value of `vue/html-indent`
+ /** @type {number} */
+ const templateMaxLength =
+ typeof options.template === 'number' ? options.template : scriptMaxLength
+ const ignoreComments = !!options.ignoreComments
+ const ignoreStrings = !!options.ignoreStrings
+ const ignoreTemplateLiterals = !!options.ignoreTemplateLiterals
+ const ignoreRegExpLiterals = !!options.ignoreRegExpLiterals
+ const ignoreTrailingComments =
+ !!options.ignoreTrailingComments || !!options.ignoreComments
+ const ignoreUrls = !!options.ignoreUrls
+ const ignoreHTMLAttributeValues = !!options.ignoreHTMLAttributeValues
+ const ignoreHTMLTextContents = !!options.ignoreHTMLTextContents
+ /** @type {number} */
+ const maxCommentLength = options.comments
+ /** @type {RegExp} */
+ let ignorePattern = options.ignorePattern || null
+
+ if (ignorePattern) {
+ ignorePattern = new RegExp(ignorePattern, 'u')
+ }
+
+ /**
+ * Retrieves an array containing all strings (" or ') in the source code.
+ *
+ * @returns {Token[]} An array of string nodes.
+ */
+ function getAllStrings() {
+ return tokens.filter(
+ (token) =>
+ token.type === 'String' ||
+ (token.type === 'JSXText' &&
+ sourceCode.getNodeByRangeIndex(token.range[0] - 1).type ===
+ 'JSXAttribute')
+ )
+ }
+
+ /**
+ * Retrieves an array containing all template literals in the source code.
+ *
+ * @returns {Token[]} An array of template literal nodes.
+ */
+ function getAllTemplateLiterals() {
+ return tokens.filter((token) => token.type === 'Template')
+ }
+
+ /**
+ * Retrieves an array containing all RegExp literals in the source code.
+ *
+ * @returns {Token[]} An array of RegExp literal nodes.
+ */
+ function getAllRegExpLiterals() {
+ return tokens.filter((token) => token.type === 'RegularExpression')
+ }
+
+ /**
+ * Retrieves an array containing all HTML texts in the source code.
+ *
+ * @returns {Token[]} An array of HTML text nodes.
+ */
+ function getAllHTMLTextContents() {
+ return tokens.filter((token) => token.type === 'HTMLText')
+ }
+
+ /**
+ * Check the program for max length
+ * @param {Program} node Node to examine
+ * @returns {void}
+ * @private
+ */
+ function checkProgramForMaxLength(node) {
+ const programNode = node
+ const templateBody = node.templateBody
+
+ // setup tokens
+ const scriptTokens = sourceCode.ast.tokens
+ const scriptComments = sourceCode.getAllComments()
+
+ if (sourceCode.parserServices.getTemplateBodyTokenStore && templateBody) {
+ const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore()
+
+ const templateTokens = tokenStore.getTokens(templateBody, {
+ includeComments: true
+ })
+
+ if (templateBody.range[0] < programNode.range[0]) {
+ tokens.push(...templateTokens, ...scriptTokens)
+ } else {
+ tokens.push(...scriptTokens, ...templateTokens)
+ }
+ } else {
+ tokens.push(...scriptTokens)
+ }
+
+ if (ignoreComments || maxCommentLength || ignoreTrailingComments) {
+ // list of comments to ignore
+ if (templateBody) {
+ if (templateBody.range[0] < programNode.range[0]) {
+ comments.push(...templateBody.comments, ...scriptComments)
+ } else {
+ comments.push(...scriptComments, ...templateBody.comments)
+ }
+ } else {
+ comments.push(...scriptComments)
+ }
+ }
+
+ /** @type {Range | undefined} */
+ let scriptLinesRange
+ if (scriptTokens.length > 0) {
+ scriptLinesRange =
+ scriptComments.length > 0
+ ? [
+ Math.min(
+ scriptTokens[0].loc.start.line,
+ scriptComments[0].loc.start.line
+ ),
+ Math.max(
+ scriptTokens[scriptTokens.length - 1].loc.end.line,
+ scriptComments[scriptComments.length - 1].loc.end.line
+ )
+ ]
+ : [
+ scriptTokens[0].loc.start.line,
+ scriptTokens[scriptTokens.length - 1].loc.end.line
+ ]
+ } else if (scriptComments.length > 0) {
+ scriptLinesRange = [
+ scriptComments[0].loc.start.line,
+ scriptComments[scriptComments.length - 1].loc.end.line
+ ]
+ }
+ const templateLinesRange = templateBody && [
+ templateBody.loc.start.line,
+ templateBody.loc.end.line
+ ]
+
+ // split (honors line-ending)
+ const lines = sourceCode.lines
+
+ const strings = getAllStrings()
+ const stringsByLine = groupByLineNumber(strings)
+
+ const templateLiterals = getAllTemplateLiterals()
+ const templateLiteralsByLine = groupByLineNumber(templateLiterals)
+
+ const regExpLiterals = getAllRegExpLiterals()
+ const regExpLiteralsByLine = groupByLineNumber(regExpLiterals)
+
+ const htmlAttributeValuesByLine = groupByLineNumber(htmlAttributeValues)
+
+ const htmlTextContents = getAllHTMLTextContents()
+ const htmlTextContentsByLine = groupByLineNumber(htmlTextContents)
+
+ const commentsByLine = groupByLineNumber(comments)
+
+ for (const [i, line] of lines.entries()) {
+ // i is zero-indexed, line numbers are one-indexed
+ const lineNumber = i + 1
+
+ const inScript =
+ scriptLinesRange &&
+ scriptLinesRange[0] <= lineNumber &&
+ lineNumber <= scriptLinesRange[1]
+ const inTemplate =
+ templateLinesRange &&
+ templateLinesRange[0] <= lineNumber &&
+ lineNumber <= templateLinesRange[1]
+ // check if line is inside a script or template.
+ if (!inScript && !inTemplate) {
+ // out of range.
+ continue
+ }
+ const maxLength = Math.max(
+ inScript ? scriptMaxLength : 0,
+ inTemplate ? templateMaxLength : 0
+ )
+
+ if (
+ (ignoreStrings && stringsByLine[lineNumber]) ||
+ (ignoreTemplateLiterals && templateLiteralsByLine[lineNumber]) ||
+ (ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber]) ||
+ (ignoreHTMLAttributeValues &&
+ htmlAttributeValuesByLine[lineNumber]) ||
+ (ignoreHTMLTextContents && htmlTextContentsByLine[lineNumber])
+ ) {
+ // ignore this line
+ continue
+ }
+
+ /*
+ * if we're checking comment length; we need to know whether this
+ * line is a comment
+ */
+ let lineIsComment = false
+ let textToMeasure
+
+ /*
+ * comments to check.
+ */
+ if (commentsByLine[lineNumber]) {
+ const commentList = [...commentsByLine[lineNumber]]
+
+ let comment = commentList.pop() || null
+
+ if (isFullLineComment(line, lineNumber, comment)) {
+ lineIsComment = true
+ textToMeasure = line
+ } else if (
+ ignoreTrailingComments &&
+ isTrailingComment(line, lineNumber, comment)
+ ) {
+ textToMeasure = stripTrailingComment(line, comment)
+
+ // ignore multiple trailing comments in the same line
+ comment = commentList.pop() || null
+
+ while (isTrailingComment(textToMeasure, lineNumber, comment)) {
+ textToMeasure = stripTrailingComment(textToMeasure, comment)
+ }
+ } else {
+ textToMeasure = line
+ }
+ } else {
+ textToMeasure = line
+ }
+
+ if (
+ (ignorePattern && ignorePattern.test(textToMeasure)) ||
+ (ignoreUrls && URL_REGEXP.test(textToMeasure))
+ ) {
+ // ignore this line
+ continue
+ }
+
+ const lineLength = computeLineLength(textToMeasure, tabWidth)
+ const commentLengthApplies = lineIsComment && maxCommentLength
+
+ if (lineIsComment && ignoreComments) {
+ continue
+ }
+
+ if (commentLengthApplies) {
+ if (lineLength > maxCommentLength) {
+ context.report({
+ node,
+ loc: { line: lineNumber, column: 0 },
+ messageId: 'maxComment',
+ data: {
+ lineLength,
+ maxCommentLength
+ }
+ })
+ }
+ } else if (lineLength > maxLength) {
+ context.report({
+ node,
+ loc: { line: lineNumber, column: 0 },
+ messageId: 'max',
+ data: {
+ lineLength,
+ maxLength
+ }
+ })
+ }
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.defineTemplateBodyVisitor(context, {
+ /** @param {VLiteral} node */
+ 'VAttribute[directive=false] > VLiteral'(node) {
+ htmlAttributeValues.push(node)
+ }
+ }),
+ {
+ 'Program:exit'(node) {
+ checkProgramForMaxLength(node)
+ }
+ }
+ )
+ }
+}
diff --git a/lib/rules/max-lines-per-block.js b/lib/rules/max-lines-per-block.js
new file mode 100644
index 000000000..c778ef40b
--- /dev/null
+++ b/lib/rules/max-lines-per-block.js
@@ -0,0 +1,111 @@
+/**
+ * @author lsdsjy
+ * @fileoverview Rule for checking the maximum number of lines in Vue SFC blocks.
+ */
+'use strict'
+
+const { SourceCode } = require('eslint')
+const utils = require('../utils')
+
+/**
+ * @param {string} text
+ */
+function isEmptyLine(text) {
+ return !text.trim()
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce maximum number of lines in Vue SFC blocks',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/max-lines-per-block.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ style: {
+ type: 'integer',
+ minimum: 1
+ },
+ template: {
+ type: 'integer',
+ minimum: 1
+ },
+ script: {
+ type: 'integer',
+ minimum: 1
+ },
+ skipBlankLines: {
+ type: 'boolean',
+ minimum: 0
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ tooManyLines:
+ 'Block has too many lines ({{lineCount}}). Maximum allowed is {{limit}}.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const option = context.options[0] || {}
+ /**
+ * @type {Record}
+ */
+ const limits = {
+ template: option.template,
+ script: option.script,
+ style: option.style
+ }
+
+ const code = context.getSourceCode()
+ const sourceCode = context.getSourceCode()
+ const documentFragment =
+ sourceCode.parserServices.getDocumentFragment &&
+ sourceCode.parserServices.getDocumentFragment()
+
+ function getTopLevelHTMLElements() {
+ if (documentFragment) {
+ return documentFragment.children.filter(utils.isVElement)
+ }
+ return []
+ }
+
+ return {
+ /** @param {Program} node */
+ Program(node) {
+ if (utils.hasInvalidEOF(node)) {
+ return
+ }
+ for (const block of getTopLevelHTMLElements()) {
+ if (limits[block.name]) {
+ // We suppose the start tag and end tag occupy one single line respectively
+ let lineCount = block.loc.end.line - block.loc.start.line - 1
+
+ if (option.skipBlankLines) {
+ const lines = SourceCode.splitLines(code.getText(block))
+ lineCount -= lines.filter(isEmptyLine).length
+ }
+
+ if (lineCount > limits[block.name]) {
+ context.report({
+ node: block,
+ messageId: 'tooManyLines',
+ data: {
+ limit: limits[block.name],
+ lineCount
+ }
+ })
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/max-props.js b/lib/rules/max-props.js
new file mode 100644
index 000000000..0dc3043bf
--- /dev/null
+++ b/lib/rules/max-props.js
@@ -0,0 +1,81 @@
+/**
+ * @author kevsommer Kevin Sommer
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce maximum number of props in Vue component',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/max-props.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ maxProps: {
+ type: 'integer',
+ minimum: 1
+ }
+ },
+ additionalProperties: false,
+ minProperties: 1
+ }
+ ],
+ messages: {
+ tooManyProps:
+ 'Component has too many props ({{propCount}}). Maximum allowed is {{limit}}.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {Record} */
+ const option = context.options[0] || {}
+
+ /**
+ * @param {import('../utils').ComponentProp[]} props
+ * @param {CallExpression | Property} node
+ */
+ function checkMaxNumberOfProps(props, node) {
+ const uniqueProps = new Set(props.map((prop) => prop.propName))
+ const propCount = uniqueProps.size
+ if (propCount > option.maxProps && props[0].node) {
+ context.report({
+ node,
+ messageId: 'tooManyProps',
+ data: {
+ propCount,
+ limit: option.maxProps
+ }
+ })
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.executeOnVue(context, (node) => {
+ const propsNode = node.properties.find(
+ /** @returns {p is Property} */
+ (p) =>
+ p.type === 'Property' && utils.getStaticPropertyName(p) === 'props'
+ )
+
+ if (!propsNode) return
+
+ checkMaxNumberOfProps(
+ utils.getComponentPropsFromOptions(node),
+ propsNode
+ )
+ }),
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(node, props) {
+ checkMaxNumberOfProps(props, node)
+ }
+ })
+ )
+ }
+}
diff --git a/lib/rules/max-template-depth.js b/lib/rules/max-template-depth.js
new file mode 100644
index 000000000..899ec75ce
--- /dev/null
+++ b/lib/rules/max-template-depth.js
@@ -0,0 +1,82 @@
+/**
+ * @author kevsommer Kevin Sommer
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce maximum depth of template',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/max-template-depth.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ maxDepth: {
+ type: 'integer',
+ minimum: 1
+ }
+ },
+ additionalProperties: false,
+ minProperties: 1
+ }
+ ],
+ messages: {
+ templateTooDeep:
+ 'Element is nested too deeply (depth of {{depth}}, maximum allowed is {{limit}}).'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const option = context.options[0] || {}
+
+ /**
+ * @param {VElement} element
+ * @param {number} curDepth
+ */
+ function checkMaxDepth(element, curDepth) {
+ if (curDepth > option.maxDepth) {
+ context.report({
+ node: element,
+ messageId: 'templateTooDeep',
+ data: {
+ depth: curDepth,
+ limit: option.maxDepth
+ }
+ })
+ }
+
+ if (!element.children) {
+ return
+ }
+
+ for (const child of element.children) {
+ if (child.type === 'VElement') {
+ checkMaxDepth(child, curDepth + 1)
+ }
+ }
+ }
+
+ return {
+ /** @param {Program} program */
+ Program(program) {
+ const element = program.templateBody
+
+ if (element == null) {
+ return
+ }
+
+ if (element.type !== 'VElement') {
+ return
+ }
+
+ checkMaxDepth(element, 0)
+ }
+ }
+ }
+}
diff --git a/lib/rules/multi-word-component-names.js b/lib/rules/multi-word-component-names.js
new file mode 100644
index 000000000..60f70a99e
--- /dev/null
+++ b/lib/rules/multi-word-component-names.js
@@ -0,0 +1,135 @@
+/**
+ * @author Marton Csordas
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const path = require('path')
+const casing = require('../utils/casing')
+const utils = require('../utils')
+
+const RESERVED_NAMES_IN_VUE3 = new Set(
+ require('../utils/vue3-builtin-components')
+)
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'require component names to be always multi-word',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
+ },
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ ignores: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true,
+ additionalItems: false
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ unexpected: 'Component name "{{value}}" should always be multi-word.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {Set} */
+ const ignores = new Set()
+ ignores.add('App')
+ ignores.add('app')
+ for (const ignore of (context.options[0] && context.options[0].ignores) ||
+ []) {
+ ignores.add(ignore)
+ if (casing.isPascalCase(ignore)) {
+ // PascalCase
+ ignores.add(casing.kebabCase(ignore))
+ }
+ }
+ let hasVue = utils.isScriptSetup(context)
+ let hasName = false
+
+ /**
+ * Returns true if the given component name is valid, otherwise false.
+ * @param {string} name
+ * */
+ function isValidComponentName(name) {
+ if (ignores.has(name) || RESERVED_NAMES_IN_VUE3.has(name)) {
+ return true
+ }
+ const elements = casing.kebabCase(name).split('-')
+ return elements.length > 1
+ }
+
+ /**
+ * @param {Expression | SpreadElement} nameNode
+ */
+ function validateName(nameNode) {
+ if (nameNode.type !== 'Literal') return
+ const componentName = `${nameNode.value}`
+ if (!isValidComponentName(componentName)) {
+ context.report({
+ node: nameNode,
+ messageId: 'unexpected',
+ data: {
+ value: componentName
+ }
+ })
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.executeOnCallVueComponent(context, (node) => {
+ hasVue = true
+ if (node.arguments.length !== 2) return
+ hasName = true
+ validateName(node.arguments[0])
+ }),
+ utils.executeOnVue(context, (obj) => {
+ hasVue = true
+ const node = utils.findProperty(obj, 'name')
+ if (!node) return
+ hasName = true
+ validateName(node.value)
+ }),
+ utils.defineScriptSetupVisitor(context, {
+ onDefineOptionsEnter(node) {
+ if (node.arguments.length === 0) return
+ const define = node.arguments[0]
+ if (define.type !== 'ObjectExpression') return
+ const nameNode = utils.findProperty(define, 'name')
+ if (!nameNode) return
+ hasName = true
+ validateName(nameNode.value)
+ }
+ }),
+ {
+ /** @param {Program} node */
+ 'Program:exit'(node) {
+ if (hasName) return
+ if (!hasVue && node.body.length > 0) return
+ const fileName = context.getFilename()
+ const componentName = path.basename(fileName, path.extname(fileName))
+ if (
+ utils.isVueFile(fileName) &&
+ !isValidComponentName(componentName)
+ ) {
+ context.report({
+ messageId: 'unexpected',
+ data: {
+ value: componentName
+ },
+ loc: { line: 1, column: 0 }
+ })
+ }
+ }
+ }
+ )
+ }
+}
diff --git a/lib/rules/multiline-html-element-content-newline.js b/lib/rules/multiline-html-element-content-newline.js
new file mode 100644
index 000000000..afe89bd2f
--- /dev/null
+++ b/lib/rules/multiline-html-element-content-newline.js
@@ -0,0 +1,229 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+const INLINE_ELEMENTS = require('../utils/inline-non-void-elements.json')
+
+/**
+ * @param {VElement & { endTag: VEndTag }} element
+ */
+function isMultilineElement(element) {
+ return element.loc.start.line < element.endTag.loc.start.line
+}
+
+/**
+ * @param {any} options
+ */
+function parseOptions(options) {
+ return Object.assign(
+ {
+ ignores: ['pre', 'textarea', ...INLINE_ELEMENTS],
+ ignoreWhenEmpty: true,
+ allowEmptyLines: false
+ },
+ options
+ )
+}
+
+/**
+ * @param {number} lineBreaks
+ */
+function getPhrase(lineBreaks) {
+ switch (lineBreaks) {
+ case 0: {
+ return 'no'
+ }
+ default: {
+ return `${lineBreaks}`
+ }
+ }
+}
+/**
+ * Check whether the given element is empty or not.
+ * This ignores whitespaces, doesn't ignore comments.
+ * @param {VElement & { endTag: VEndTag }} node The element node to check.
+ * @param {SourceCode} sourceCode The source code object of the current context.
+ * @returns {boolean} `true` if the element is empty.
+ */
+function isEmpty(node, sourceCode) {
+ const start = node.startTag.range[1]
+ const end = node.endTag.range[0]
+ return sourceCode.text.slice(start, end).trim() === ''
+}
+
+module.exports = {
+ meta: {
+ type: 'layout',
+ docs: {
+ description:
+ 'require a line break before and after the contents of a multiline element',
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/multiline-html-element-content-newline.html'
+ },
+ fixable: 'whitespace',
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ ignoreWhenEmpty: {
+ type: 'boolean'
+ },
+ ignores: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true,
+ additionalItems: false
+ },
+ allowEmptyLines: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ unexpectedAfterClosingBracket:
+ 'Expected 1 line break after opening tag (`<{{name}}>`), but {{actual}} line breaks found.',
+ unexpectedBeforeOpeningBracket:
+ 'Expected 1 line break before closing tag (`{{name}}>`), but {{actual}} line breaks found.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = parseOptions(context.options[0])
+ const ignores = options.ignores
+ const ignoreWhenEmpty = options.ignoreWhenEmpty
+ const allowEmptyLines = options.allowEmptyLines
+ const sourceCode = context.getSourceCode()
+ const template =
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+
+ /** @type {VElement | null} */
+ let inIgnoreElement = null
+
+ /**
+ * @param {VElement} node
+ */
+ function isIgnoredElement(node) {
+ return (
+ ignores.includes(node.name) ||
+ ignores.includes(casing.pascalCase(node.rawName)) ||
+ ignores.includes(casing.kebabCase(node.rawName))
+ )
+ }
+
+ /**
+ * @param {number} lineBreaks
+ */
+ function isInvalidLineBreaks(lineBreaks) {
+ return allowEmptyLines ? lineBreaks === 0 : lineBreaks !== 1
+ }
+
+ return utils.defineTemplateBodyVisitor(context, {
+ VElement(node) {
+ if (inIgnoreElement) {
+ return
+ }
+ if (isIgnoredElement(node)) {
+ // ignore element name
+ inIgnoreElement = node
+ return
+ }
+ if (node.startTag.selfClosing || !node.endTag) {
+ // self closing
+ return
+ }
+
+ const element = /** @type {VElement & { endTag: VEndTag }} */ (node)
+
+ if (!isMultilineElement(element)) {
+ return
+ }
+
+ /**
+ * @type {SourceCode.CursorWithCountOptions}
+ */
+ const getTokenOption = {
+ includeComments: true,
+ filter: (token) => token.type !== 'HTMLWhitespace'
+ }
+ if (
+ ignoreWhenEmpty &&
+ element.children.length === 0 &&
+ template.getFirstTokensBetween(
+ element.startTag,
+ element.endTag,
+ getTokenOption
+ ).length === 0
+ ) {
+ return
+ }
+
+ const contentFirst = /** @type {Token} */ (
+ template.getTokenAfter(element.startTag, getTokenOption)
+ )
+ const contentLast = /** @type {Token} */ (
+ template.getTokenBefore(element.endTag, getTokenOption)
+ )
+
+ const beforeLineBreaks =
+ contentFirst.loc.start.line - element.startTag.loc.end.line
+ const afterLineBreaks =
+ element.endTag.loc.start.line - contentLast.loc.end.line
+ if (isInvalidLineBreaks(beforeLineBreaks)) {
+ context.report({
+ node: template.getLastToken(element.startTag),
+ loc: {
+ start: element.startTag.loc.end,
+ end: contentFirst.loc.start
+ },
+ messageId: 'unexpectedAfterClosingBracket',
+ data: {
+ name: element.rawName,
+ actual: getPhrase(beforeLineBreaks)
+ },
+ fix(fixer) {
+ /** @type {Range} */
+ const range = [element.startTag.range[1], contentFirst.range[0]]
+ return fixer.replaceTextRange(range, '\n')
+ }
+ })
+ }
+
+ if (isEmpty(element, sourceCode)) {
+ return
+ }
+
+ if (isInvalidLineBreaks(afterLineBreaks)) {
+ context.report({
+ node: template.getFirstToken(element.endTag),
+ loc: {
+ start: contentLast.loc.end,
+ end: element.endTag.loc.start
+ },
+ messageId: 'unexpectedBeforeOpeningBracket',
+ data: {
+ name: element.name,
+ actual: getPhrase(afterLineBreaks)
+ },
+ fix(fixer) {
+ /** @type {Range} */
+ const range = [contentLast.range[1], element.endTag.range[0]]
+ return fixer.replaceTextRange(range, '\n')
+ }
+ })
+ }
+ },
+ 'VElement:exit'(node) {
+ if (inIgnoreElement === node) {
+ inIgnoreElement = null
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/multiline-ternary.js b/lib/rules/multiline-ternary.js
new file mode 100644
index 000000000..84691627e
--- /dev/null
+++ b/lib/rules/multiline-ternary.js
@@ -0,0 +1,13 @@
+/**
+ * @author dev1437
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapStylisticOrCoreRule('multiline-ternary', {
+ skipDynamicArguments: true,
+ applyDocument: true
+})
diff --git a/lib/rules/mustache-interpolation-spacing.js b/lib/rules/mustache-interpolation-spacing.js
index b7b491a18..304b50c70 100644
--- a/lib/rules/mustache-interpolation-spacing.js
+++ b/lib/rules/mustache-interpolation-spacing.js
@@ -4,43 +4,40 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
meta: {
+ type: 'layout',
docs: {
description: 'enforce unified spacing in mustache interpolations',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/mustache-interpolation-spacing.md'
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/mustache-interpolation-spacing.html'
},
fixable: 'whitespace',
schema: [
{
enum: ['always', 'never']
}
- ]
+ ],
+ messages: {
+ expectedSpaceAfter: "Expected 1 space after '{{', but not found.",
+ expectedSpaceBefore: "Expected 1 space before '}}', but not found.",
+ unexpectedSpaceAfter: "Expected no space after '{{', but found.",
+ unexpectedSpaceBefore: "Expected no space before '}}', but found."
+ }
},
-
- create (context) {
+ /** @param {RuleContext} context */
+ create(context) {
const options = context.options[0] || 'always'
+ const sourceCode = context.getSourceCode()
const template =
- context.parserServices.getTemplateBodyTokenStore &&
- context.parserServices.getTemplateBodyTokenStore()
-
- // ----------------------------------------------------------------------
- // Public
- // ----------------------------------------------------------------------
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
return utils.defineTemplateBodyVisitor(context, {
- 'VExpressionContainer[expression!=null]' (node) {
+ /** @param {VExpressionContainer} node */
+ 'VExpressionContainer[expression!=null]'(node) {
const openBrace = template.getFirstToken(node)
const closeBrace = template.getLastToken(node)
@@ -53,21 +50,25 @@ module.exports = {
return
}
- const firstToken = template.getTokenAfter(openBrace, { includeComments: true })
- const lastToken = template.getTokenBefore(closeBrace, { includeComments: true })
+ const firstToken = template.getTokenAfter(openBrace, {
+ includeComments: true
+ })
+ const lastToken = template.getTokenBefore(closeBrace, {
+ includeComments: true
+ })
if (options === 'always') {
if (openBrace.range[1] === firstToken.range[0]) {
context.report({
node: openBrace,
- message: "Expected 1 space after '{{', but not found.",
+ messageId: 'expectedSpaceAfter',
fix: (fixer) => fixer.insertTextAfter(openBrace, ' ')
})
}
if (closeBrace.range[0] === lastToken.range[1]) {
context.report({
node: closeBrace,
- message: "Expected 1 space before '}}', but not found.",
+ messageId: 'expectedSpaceBefore',
fix: (fixer) => fixer.insertTextBefore(closeBrace, ' ')
})
}
@@ -78,8 +79,9 @@ module.exports = {
start: openBrace.loc.start,
end: firstToken.loc.start
},
- message: "Expected no space after '{{', but found.",
- fix: (fixer) => fixer.removeRange([openBrace.range[1], firstToken.range[0]])
+ messageId: 'unexpectedSpaceAfter',
+ fix: (fixer) =>
+ fixer.removeRange([openBrace.range[1], firstToken.range[0]])
})
}
if (closeBrace.range[0] !== lastToken.range[1]) {
@@ -88,8 +90,9 @@ module.exports = {
start: lastToken.loc.end,
end: closeBrace.loc.end
},
- message: "Expected no space before '}}', but found.",
- fix: (fixer) => fixer.removeRange([lastToken.range[1], closeBrace.range[0]])
+ messageId: 'unexpectedSpaceBefore',
+ fix: (fixer) =>
+ fixer.removeRange([lastToken.range[1], closeBrace.range[0]])
})
}
}
diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js
deleted file mode 100644
index e9422eb0e..000000000
--- a/lib/rules/name-property-casing.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @fileoverview Requires specific casing for the name property in Vue components
- * @author Armano
- */
-'use strict'
-
-const utils = require('../utils')
-const casing = require('../utils/casing')
-const allowedCaseOptions = ['PascalCase', 'kebab-case']
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
-function create (context) {
- const options = context.options[0]
- const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase'
-
- // ----------------------------------------------------------------------
- // Public
- // ----------------------------------------------------------------------
-
- return utils.executeOnVue(context, (obj) => {
- const node = obj.properties
- .find(item => (
- item.type === 'Property' &&
- item.key.name === 'name' &&
- item.value.type === 'Literal'
- ))
-
- if (!node) return
-
- const value = casing.getConverter(caseType)(node.value.value)
- if (value !== node.value.value) {
- context.report({
- node: node.value,
- message: 'Property name "{{value}}" is not {{caseType}}.',
- data: {
- value: node.value.value,
- caseType: caseType
- },
- fix: fixer => fixer.replaceText(node.value, node.value.raw.replace(node.value.value, value))
- })
- }
- })
-}
-
-module.exports = {
- meta: {
- docs: {
- description: 'enforce specific casing for the name property in Vue components',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/name-property-casing.md'
- },
- fixable: 'code', // or "code" or "whitespace"
- schema: [
- {
- enum: allowedCaseOptions
- }
- ]
- },
-
- create
-}
diff --git a/lib/rules/new-line-between-multi-line-property.js b/lib/rules/new-line-between-multi-line-property.js
new file mode 100644
index 000000000..54812869b
--- /dev/null
+++ b/lib/rules/new-line-between-multi-line-property.js
@@ -0,0 +1,154 @@
+/**
+ * @fileoverview Enforce new lines between multi-line properties in Vue components.
+ * @author IWANABETHATGUY
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @param {Token} node
+ */
+function isComma(node) {
+ return node.type === 'Punctuator' && node.value === ','
+}
+
+/**
+ * Check whether the between given nodes has empty line.
+ * @param {SourceCode} sourceCode
+ * @param {ASTNode} pre
+ * @param {ASTNode} cur
+ */
+function* iterateBetweenTokens(sourceCode, pre, cur) {
+ yield sourceCode.getLastToken(pre)
+ yield* sourceCode.getTokensBetween(pre, cur, {
+ includeComments: true
+ })
+ yield sourceCode.getFirstToken(cur)
+}
+
+/**
+ * Check whether the between given nodes has empty line.
+ * @param {SourceCode} sourceCode
+ * @param {ASTNode} pre
+ * @param {ASTNode} cur
+ */
+function hasEmptyLine(sourceCode, pre, cur) {
+ /** @type {Token|null} */
+ let preToken = null
+ for (const token of iterateBetweenTokens(sourceCode, pre, cur)) {
+ if (preToken && token.loc.start.line - preToken.loc.end.line >= 2) {
+ return true
+ }
+ preToken = token
+ }
+ return false
+}
+
+module.exports = {
+ meta: {
+ type: 'layout',
+ docs: {
+ description:
+ 'enforce new lines between multi-line properties in Vue components',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/new-line-between-multi-line-property.html'
+ },
+ fixable: 'whitespace',
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ // number of line you want to insert after multi-line property
+ minLineOfMultilineProperty: {
+ type: 'number',
+ minimum: 2
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ missingEmptyLine:
+ 'Enforce new lines between multi-line properties in Vue components.'
+ }
+ },
+
+ /** @param {RuleContext} context */
+ create(context) {
+ let minLineOfMultilineProperty = 2
+ if (
+ context.options &&
+ context.options[0] &&
+ context.options[0].minLineOfMultilineProperty
+ ) {
+ minLineOfMultilineProperty = context.options[0].minLineOfMultilineProperty
+ }
+
+ /** @type {CallExpression[]} */
+ const callStack = []
+ const sourceCode = context.getSourceCode()
+ return Object.assign(
+ utils.defineVueVisitor(context, {
+ CallExpression(node) {
+ callStack.push(node)
+ },
+ 'CallExpression:exit'() {
+ callStack.pop()
+ },
+
+ /**
+ * @param {ObjectExpression} node
+ */
+ ObjectExpression(node) {
+ if (callStack.length > 0) {
+ return
+ }
+ const properties = node.properties
+ for (let i = 1; i < properties.length; i++) {
+ const cur = properties[i]
+ const pre = properties[i - 1]
+
+ const lineCountOfPreProperty =
+ pre.loc.end.line - pre.loc.start.line + 1
+ if (lineCountOfPreProperty < minLineOfMultilineProperty) {
+ continue
+ }
+
+ if (hasEmptyLine(sourceCode, pre, cur)) {
+ continue
+ }
+
+ context.report({
+ node: pre,
+ loc: {
+ start: pre.loc.end,
+ end: cur.loc.start
+ },
+ messageId: 'missingEmptyLine',
+ fix(fixer) {
+ /** @type {Token|null} */
+ let preToken = null
+ for (const token of iterateBetweenTokens(
+ sourceCode,
+ pre,
+ cur
+ )) {
+ if (
+ preToken &&
+ preToken.loc.end.line < token.loc.start.line
+ ) {
+ return fixer.insertTextAfter(preToken, '\n')
+ }
+ preToken = token
+ }
+ const commaToken = sourceCode.getTokenAfter(pre, isComma)
+ return fixer.insertTextAfter(commaToken || pre, '\n\n')
+ }
+ })
+ }
+ }
+ })
+ )
+ }
+}
diff --git a/lib/rules/next-tick-style.js b/lib/rules/next-tick-style.js
new file mode 100644
index 000000000..08cc85a1b
--- /dev/null
+++ b/lib/rules/next-tick-style.js
@@ -0,0 +1,141 @@
+/**
+ * @fileoverview enforce Promise or callback style in `nextTick`
+ * @author Flo Edelmann
+ * @copyright 2020 Flo Edelmann. All rights reserved.
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const { findVariable } = require('@eslint-community/eslint-utils')
+
+/**
+ * @param {Identifier} identifier
+ * @param {RuleContext} context
+ * @returns {CallExpression|undefined}
+ */
+function getVueNextTickCallExpression(identifier, context) {
+ // Instance API: this.$nextTick()
+ if (
+ identifier.name === '$nextTick' &&
+ identifier.parent.type === 'MemberExpression' &&
+ utils.isThis(identifier.parent.object, context) &&
+ identifier.parent.parent.type === 'CallExpression' &&
+ identifier.parent.parent.callee === identifier.parent
+ ) {
+ return identifier.parent.parent
+ }
+
+ // Vue 2 Global API: Vue.nextTick()
+ if (
+ identifier.name === 'nextTick' &&
+ identifier.parent.type === 'MemberExpression' &&
+ identifier.parent.object.type === 'Identifier' &&
+ identifier.parent.object.name === 'Vue' &&
+ identifier.parent.parent.type === 'CallExpression' &&
+ identifier.parent.parent.callee === identifier.parent
+ ) {
+ return identifier.parent.parent
+ }
+
+ // Vue 3 Global API: import { nextTick as nt } from 'vue'; nt()
+ if (
+ identifier.parent.type === 'CallExpression' &&
+ identifier.parent.callee === identifier
+ ) {
+ const variable = findVariable(
+ utils.getScope(context, identifier),
+ identifier
+ )
+
+ if (variable != null && variable.defs.length === 1) {
+ const def = variable.defs[0]
+ if (
+ def.type === 'ImportBinding' &&
+ def.node.type === 'ImportSpecifier' &&
+ def.node.imported.type === 'Identifier' &&
+ def.node.imported.name === 'nextTick' &&
+ def.node.parent.type === 'ImportDeclaration' &&
+ def.node.parent.source.value === 'vue'
+ ) {
+ return identifier.parent
+ }
+ }
+ }
+
+ return undefined
+}
+
+/**
+ * @param {CallExpression} callExpression
+ * @returns {boolean}
+ */
+function isAwaitedPromise(callExpression) {
+ return (
+ callExpression.parent.type === 'AwaitExpression' ||
+ (callExpression.parent.type === 'MemberExpression' &&
+ callExpression.parent.property.type === 'Identifier' &&
+ callExpression.parent.property.name === 'then')
+ )
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce Promise or callback style in `nextTick`',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/next-tick-style.html'
+ },
+ fixable: 'code',
+ schema: [{ enum: ['promise', 'callback'] }],
+ messages: {
+ usePromise:
+ 'Use the Promise returned by `nextTick` instead of passing a callback function.',
+ useCallback:
+ 'Pass a callback function to `nextTick` instead of using the returned Promise.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const preferredStyle =
+ /** @type {string|undefined} */ (context.options[0]) || 'promise'
+
+ return utils.defineVueVisitor(context, {
+ /** @param {Identifier} node */
+ Identifier(node) {
+ const callExpression = getVueNextTickCallExpression(node, context)
+ if (!callExpression) {
+ return
+ }
+
+ if (preferredStyle === 'callback') {
+ if (
+ callExpression.arguments.length !== 1 ||
+ isAwaitedPromise(callExpression)
+ ) {
+ context.report({
+ node,
+ messageId: 'useCallback'
+ })
+ }
+
+ return
+ }
+
+ if (
+ callExpression.arguments.length > 0 ||
+ !isAwaitedPromise(callExpression)
+ ) {
+ context.report({
+ node,
+ messageId: 'usePromise',
+ fix(fixer) {
+ return fixer.insertTextAfter(node, '().then')
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-arrow-functions-in-watch.js b/lib/rules/no-arrow-functions-in-watch.js
new file mode 100644
index 000000000..b16b267f5
--- /dev/null
+++ b/lib/rules/no-arrow-functions-in-watch.js
@@ -0,0 +1,50 @@
+/**
+ * @author Sosuke Suzuki
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow using arrow functions to define watcher',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-arrow-functions-in-watch.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ noArrowFunctionsInWatch:
+ 'You should not use an arrow function to define a watcher.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.executeOnVue(context, (obj) => {
+ const watchNode = utils.findProperty(obj, 'watch')
+ if (watchNode == null) {
+ return
+ }
+ const watchValue = watchNode.value
+ if (watchValue.type !== 'ObjectExpression') {
+ return
+ }
+ for (const property of watchValue.properties) {
+ if (property.type !== 'Property') {
+ continue
+ }
+
+ for (const handler of utils.iterateWatchHandlerValues(property)) {
+ if (handler.type === 'ArrowFunctionExpression') {
+ context.report({
+ node: handler,
+ messageId: 'noArrowFunctionsInWatch'
+ })
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js
index 5327f82b0..e8ed02f0e 100644
--- a/lib/rules/no-async-in-computed-properties.js
+++ b/lib/rules/no-async-in-computed-properties.js
@@ -3,155 +3,290 @@
* @author Armano
*/
'use strict'
-
+const { ReferenceTracker } = require('@eslint-community/eslint-utils')
const utils = require('../utils')
-const PROMISE_FUNCTIONS = [
- 'then',
- 'catch',
- 'finally'
-]
+/**
+ * @typedef {import('../utils').VueObjectData} VueObjectData
+ * @typedef {import('../utils').VueVisitor} VueVisitor
+ * @typedef {import('../utils').ComponentComputedProperty} ComponentComputedProperty
+ */
+
+const PROMISE_FUNCTIONS = new Set(['then', 'catch', 'finally'])
-const PROMISE_METHODS = [
- 'all',
- 'race',
- 'reject',
- 'resolve'
-]
+const PROMISE_METHODS = new Set(['all', 'race', 'reject', 'resolve'])
-const TIMED_FUNCTIONS = [
+const TIMED_FUNCTIONS = new Set([
'setTimeout',
'setInterval',
'setImmediate',
'requestAnimationFrame'
-]
+])
-function isTimedFunction (node) {
+/**
+ * @param {CallExpression} node
+ */
+function isTimedFunction(node) {
+ const callee = utils.skipChainExpression(node.callee)
return (
- node.type === 'CallExpression' &&
- node.callee.type === 'Identifier' &&
- TIMED_FUNCTIONS.indexOf(node.callee.name) !== -1
- ) || (
- node.type === 'CallExpression' &&
- node.callee.type === 'MemberExpression' &&
- node.callee.object.type === 'Identifier' &&
- node.callee.object.name === 'window' && (
- TIMED_FUNCTIONS.indexOf(node.callee.property.name) !== -1
+ ((callee.type === 'Identifier' && TIMED_FUNCTIONS.has(callee.name)) ||
+ (callee.type === 'MemberExpression' &&
+ callee.object.type === 'Identifier' &&
+ callee.object.name === 'window' &&
+ TIMED_FUNCTIONS.has(utils.getStaticPropertyName(callee) || ''))) &&
+ node.arguments.length > 0
+ )
+}
+
+/**
+ * @param {CallExpression} node
+ */
+function isPromise(node) {
+ const callee = utils.skipChainExpression(node.callee)
+ if (callee.type === 'MemberExpression') {
+ const name = utils.getStaticPropertyName(callee)
+ return (
+ name &&
+ // hello.PROMISE_FUNCTION()
+ (PROMISE_FUNCTIONS.has(name) ||
+ // Promise.PROMISE_METHOD()
+ (callee.object.type === 'Identifier' &&
+ callee.object.name === 'Promise' &&
+ PROMISE_METHODS.has(name)))
)
- ) && node.arguments.length
+ }
+ return false
}
-function isPromise (node) {
- if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {
- return ( // hello.PROMISE_FUNCTION()
- node.callee.property.type === 'Identifier' &&
- PROMISE_FUNCTIONS.indexOf(node.callee.property.name) !== -1
- ) || ( // Promise.PROMISE_METHOD()
- node.callee.object.type === 'Identifier' &&
- node.callee.object.name === 'Promise' &&
- PROMISE_METHODS.indexOf(node.callee.property.name) !== -1
+/**
+ * @param {CallExpression} node
+ * @param {RuleContext} context
+ */
+function isNextTick(node, context) {
+ const callee = utils.skipChainExpression(node.callee)
+ if (callee.type === 'MemberExpression') {
+ const name = utils.getStaticPropertyName(callee)
+ return (
+ (utils.isThis(callee.object, context) && name === '$nextTick') ||
+ (callee.object.type === 'Identifier' &&
+ callee.object.name === 'Vue' &&
+ name === 'nextTick')
)
}
return false
}
-function create (context) {
- const forbiddenNodes = []
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow asynchronous actions in computed properties',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-async-in-computed-properties.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpectedInFunction:
+ 'Unexpected {{expressionName}} in computed function.',
+ unexpectedInProperty:
+ 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {Map} */
+ const computedPropertiesMap = new Map()
+ /** @type {(FunctionExpression | ArrowFunctionExpression)[]} */
+ const computedFunctionNodes = []
- const expressionTypes = {
- promise: 'asynchronous action',
- await: 'await operator',
- async: 'async function declaration',
- new: 'Promise object',
- timed: 'timed function'
- }
+ /**
+ * @typedef {object} ScopeStack
+ * @property {ScopeStack | null} upper
+ * @property {BlockStatement | Expression} body
+ */
+ /** @type {ScopeStack | null} */
+ let scopeStack = null
- function onFunctionEnter (node) {
- if (node.async) {
- forbiddenNodes.push({
- node: node,
- type: 'async'
- })
+ const expressionTypes = {
+ promise: 'asynchronous action',
+ nextTick: 'asynchronous action',
+ await: 'await operator',
+ async: 'async function declaration',
+ new: 'Promise object',
+ timed: 'timed function'
}
- }
- return Object.assign({},
- {
- FunctionDeclaration: onFunctionEnter,
+ /**
+ * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+ * @param {VueObjectData|undefined} [info]
+ */
+ function onFunctionEnter(node, info) {
+ if (node.async) {
+ verify(
+ node,
+ node.body,
+ 'async',
+ info ? computedPropertiesMap.get(info.node) : null
+ )
+ }
- FunctionExpression: onFunctionEnter,
+ scopeStack = {
+ upper: scopeStack,
+ body: node.body
+ }
+ }
- ArrowFunctionExpression: onFunctionEnter,
+ function onFunctionExit() {
+ scopeStack = scopeStack && scopeStack.upper
+ }
- NewExpression (node) {
- if (node.callee.name === 'Promise') {
- forbiddenNodes.push({
- node: node,
- type: 'new'
+ /**
+ * @param {ESNode} node
+ * @param {BlockStatement | Expression} targetBody
+ * @param {keyof expressionTypes} type
+ * @param {ComponentComputedProperty[]|undefined|null} computedProperties
+ */
+ function verify(node, targetBody, type, computedProperties) {
+ for (const cp of computedProperties || []) {
+ if (
+ cp.value &&
+ node.loc.start.line >= cp.value.loc.start.line &&
+ node.loc.end.line <= cp.value.loc.end.line &&
+ targetBody === cp.value
+ ) {
+ context.report({
+ node,
+ messageId: 'unexpectedInProperty',
+ data: {
+ expressionName: expressionTypes[type],
+ propertyName: cp.key || 'unknown'
+ }
})
+ return
}
- },
+ }
- CallExpression (node) {
- if (isPromise(node)) {
- forbiddenNodes.push({
- node: node,
- type: 'promise'
+ for (const cf of computedFunctionNodes) {
+ if (
+ node.loc.start.line >= cf.body.loc.start.line &&
+ node.loc.end.line <= cf.body.loc.end.line &&
+ targetBody === cf.body
+ ) {
+ context.report({
+ node,
+ messageId: 'unexpectedInFunction',
+ data: {
+ expressionName: expressionTypes[type]
+ }
})
+ return
}
- if (isTimedFunction(node)) {
- forbiddenNodes.push({
- node: node,
- type: 'timed'
- })
+ }
+ }
+ const nodeVisitor = {
+ ':function': onFunctionEnter,
+ ':function:exit': onFunctionExit,
+
+ /**
+ * @param {NewExpression} node
+ * @param {VueObjectData|undefined} [info]
+ */
+ NewExpression(node, info) {
+ if (!scopeStack) {
+ return
+ }
+ if (
+ node.callee.type === 'Identifier' &&
+ node.callee.name === 'Promise'
+ ) {
+ verify(
+ node,
+ scopeStack.body,
+ 'new',
+ info ? computedPropertiesMap.get(info.node) : null
+ )
}
},
- AwaitExpression (node) {
- forbiddenNodes.push({
- node: node,
- type: 'await'
- })
+ /**
+ * @param {CallExpression} node
+ * @param {VueObjectData|undefined} [info]
+ */
+ CallExpression(node, info) {
+ if (!scopeStack) {
+ return
+ }
+ if (isPromise(node)) {
+ verify(
+ node,
+ scopeStack.body,
+ 'promise',
+ info ? computedPropertiesMap.get(info.node) : null
+ )
+ } else if (isTimedFunction(node)) {
+ verify(
+ node,
+ scopeStack.body,
+ 'timed',
+ info ? computedPropertiesMap.get(info.node) : null
+ )
+ } else if (isNextTick(node, context)) {
+ verify(
+ node,
+ scopeStack.body,
+ 'nextTick',
+ info ? computedPropertiesMap.get(info.node) : null
+ )
+ }
+ },
+
+ /**
+ * @param {AwaitExpression} node
+ * @param {VueObjectData|undefined} [info]
+ */
+ AwaitExpression(node, info) {
+ if (!scopeStack) {
+ return
+ }
+ verify(
+ node,
+ scopeStack.body,
+ 'await',
+ info ? computedPropertiesMap.get(info.node) : null
+ )
}
- },
- utils.executeOnVue(context, (obj) => {
- const computedProperties = utils.getComputedProperties(obj)
-
- computedProperties.forEach(cp => {
- forbiddenNodes.forEach(el => {
- if (
- cp.value &&
- el.node.loc.start.line >= cp.value.loc.start.line &&
- el.node.loc.end.line <= cp.value.loc.end.line
- ) {
- context.report({
- node: el.node,
- message: 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.',
- data: {
- expressionName: expressionTypes[el.type],
- propertyName: cp.key
- }
- })
- }
- })
- })
- })
- )
-}
+ }
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
+ return utils.compositingVisitors(
+ {
+ /** @param {Program} program */
+ Program(program) {
+ const tracker = new ReferenceTracker(utils.getScope(context, program))
+ for (const { node } of utils.iterateReferencesTraceMap(tracker, {
+ computed: {
+ [ReferenceTracker.CALL]: true
+ }
+ })) {
+ if (node.type !== 'CallExpression') {
+ continue
+ }
-module.exports = {
- create,
- meta: {
- docs: {
- description: 'disallow asynchronous actions in computed properties',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-async-in-computed-properties.md'
- },
- fixable: null,
- schema: []
+ const getter = utils.getGetterBodyFromComputedFunction(node)
+ if (getter) {
+ computedFunctionNodes.push(getter)
+ }
+ }
+ }
+ },
+ utils.isScriptSetup(context)
+ ? utils.defineScriptSetupVisitor(context, nodeVisitor)
+ : utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ computedPropertiesMap.set(node, utils.getComputedProperties(node))
+ },
+ ...nodeVisitor
+ })
+ )
}
}
diff --git a/lib/rules/no-bare-strings-in-template.js b/lib/rules/no-bare-strings-in-template.js
new file mode 100644
index 000000000..8e4acc96d
--- /dev/null
+++ b/lib/rules/no-bare-strings-in-template.js
@@ -0,0 +1,293 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const regexp = require('../utils/regexp')
+const casing = require('../utils/casing')
+
+/**
+ * @typedef { { names: { [tagName in string]: Set }, regexps: { name: RegExp, attrs: Set }[], cache: { [tagName in string]: Set } } } TargetAttrs
+ */
+
+// https://dev.w3.org/html5/html-author/charref
+const DEFAULT_ALLOWLIST = [
+ '(',
+ ')',
+ ',',
+ '.',
+ '&',
+ '+',
+ '-',
+ '=',
+ '*',
+ '/',
+ '#',
+ '%',
+ '!',
+ '?',
+ ':',
+ '[',
+ ']',
+ '{',
+ '}',
+ '<',
+ '>',
+ '\u00B7', // "·"
+ '\u2022', // "•"
+ '\u2010', // "‐"
+ '\u2013', // "–"
+ '\u2014', // "—"
+ '\u2212', // "−"
+ '|'
+]
+
+const DEFAULT_ATTRIBUTES = {
+ '/.+/': [
+ 'title',
+ 'aria-label',
+ 'aria-placeholder',
+ 'aria-roledescription',
+ 'aria-valuetext'
+ ],
+ input: ['placeholder'],
+ img: ['alt']
+}
+
+const DEFAULT_DIRECTIVES = ['v-text']
+
+/**
+ * Parse attributes option
+ * @param {any} options
+ * @returns {TargetAttrs}
+ */
+function parseTargetAttrs(options) {
+ /** @type {TargetAttrs} */
+ const result = { names: {}, regexps: [], cache: {} }
+ for (const tagName of Object.keys(options)) {
+ /** @type { Set } */
+ const attrs = new Set(options[tagName])
+ if (regexp.isRegExp(tagName)) {
+ result.regexps.push({
+ name: regexp.toRegExp(tagName),
+ attrs
+ })
+ } else {
+ result.names[tagName] = attrs
+ }
+ }
+ return result
+}
+
+/**
+ * Get a string from given expression container node
+ * @param {VExpressionContainer} value
+ * @returns { string | null }
+ */
+function getStringValue(value) {
+ const expression = value.expression
+ if (!expression) {
+ return null
+ }
+ if (expression.type !== 'Literal') {
+ return null
+ }
+ if (typeof expression.value === 'string') {
+ return expression.value
+ }
+ return null
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow the use of bare strings in ``',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-bare-strings-in-template.html'
+ },
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ allowlist: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true
+ },
+ attributes: {
+ type: 'object',
+ patternProperties: {
+ '^(?:\\S+|/.*/[a-z]*)$': {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true
+ }
+ },
+ additionalProperties: false
+ },
+ directives: {
+ type: 'array',
+ items: { type: 'string', pattern: '^v-' },
+ uniqueItems: true
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ unexpected: 'Unexpected non-translated string used.',
+ unexpectedInAttr: 'Unexpected non-translated string used in `{{attr}}`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /**
+ * @typedef { { upper: ElementStack | null, name: string, attrs: Set } } ElementStack
+ */
+ const opts = context.options[0] || {}
+ /** @type {string[]} */
+ const rawAllowlist = opts.allowlist || DEFAULT_ALLOWLIST
+ const attributes = parseTargetAttrs(opts.attributes || DEFAULT_ATTRIBUTES)
+ const directives = opts.directives || DEFAULT_DIRECTIVES
+
+ /** @type {string[]} */
+ const stringAllowlist = []
+ /** @type {RegExp[]} */
+ const regexAllowlist = []
+
+ for (const item of rawAllowlist) {
+ if (regexp.isRegExp(item)) {
+ regexAllowlist.push(regexp.toRegExp(item))
+ } else {
+ stringAllowlist.push(item)
+ }
+ }
+
+ const allowlistRe =
+ stringAllowlist.length > 0
+ ? new RegExp(
+ stringAllowlist
+ .map((w) => regexp.escape(w))
+ .sort((a, b) => b.length - a.length)
+ .join('|'),
+ 'gu'
+ )
+ : null
+
+ /** @type {ElementStack | null} */
+ let elementStack = null
+ /**
+ * Gets the bare string from given string
+ * @param {string} str
+ */
+ function getBareString(str) {
+ let result = str.trim()
+
+ if (allowlistRe) {
+ result = result.replace(allowlistRe, '')
+ }
+
+ for (const regex of regexAllowlist) {
+ const flags = regex.flags.includes('g')
+ ? regex.flags
+ : `${regex.flags}g`
+ const globalRegex = new RegExp(regex.source, flags)
+ result = result.replace(globalRegex, '')
+ }
+
+ return result.trim()
+ }
+
+ /**
+ * Get the attribute to be verified from the element name.
+ * @param {string} tagName
+ * @returns {Set}
+ */
+ function getTargetAttrs(tagName) {
+ if (attributes.cache[tagName]) {
+ return attributes.cache[tagName]
+ }
+ /** @type {string[]} */
+ const result = []
+ if (attributes.names[tagName]) {
+ result.push(...attributes.names[tagName])
+ }
+ for (const { name, attrs } of attributes.regexps) {
+ name.lastIndex = 0
+ if (name.test(tagName)) {
+ result.push(...attrs)
+ }
+ }
+ if (casing.isKebabCase(tagName)) {
+ result.push(...getTargetAttrs(casing.pascalCase(tagName)))
+ }
+
+ return (attributes.cache[tagName] = new Set(result))
+ }
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VText} node */
+ VText(node) {
+ if (getBareString(node.value)) {
+ context.report({
+ node,
+ messageId: 'unexpected'
+ })
+ }
+ },
+ /**
+ * @param {VElement} node
+ */
+ VElement(node) {
+ elementStack = {
+ upper: elementStack,
+ name: node.rawName,
+ attrs: getTargetAttrs(node.rawName)
+ }
+ },
+ 'VElement:exit'() {
+ elementStack = elementStack && elementStack.upper
+ },
+ /** @param {VAttribute|VDirective} node */
+ VAttribute(node) {
+ if (!node.value || !elementStack) {
+ return
+ }
+ if (node.directive === false) {
+ const attrs = elementStack.attrs
+ if (!attrs.has(node.key.rawName)) {
+ return
+ }
+
+ if (getBareString(node.value.value)) {
+ context.report({
+ node: node.value,
+ messageId: 'unexpectedInAttr',
+ data: {
+ attr: node.key.rawName
+ }
+ })
+ }
+ } else {
+ const directive = `v-${node.key.name.name}`
+ if (!directives.includes(directive)) {
+ return
+ }
+ const str = getStringValue(node.value)
+ if (str && getBareString(str)) {
+ context.report({
+ node: node.value,
+ messageId: 'unexpectedInAttr',
+ data: {
+ attr: directive
+ }
+ })
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-boolean-default.js b/lib/rules/no-boolean-default.js
new file mode 100644
index 000000000..2ff6a2d06
--- /dev/null
+++ b/lib/rules/no-boolean-default.js
@@ -0,0 +1,148 @@
+/**
+ * @fileoverview Prevents boolean defaults from being set
+ * @author Hiroki Osame
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef {import('../utils').ComponentProp} ComponentProp
+ * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
+ */
+
+/**
+ * @param {Expression|undefined} node
+ */
+function isBooleanIdentifier(node) {
+ return Boolean(node && node.type === 'Identifier' && node.name === 'Boolean')
+}
+
+/**
+ * Detects whether given prop node is a Boolean
+ * @param {ComponentObjectProp} prop
+ * @return {Boolean}
+ */
+function isBooleanProp(prop) {
+ const value = utils.skipTSAsExpression(prop.value)
+ return (
+ isBooleanIdentifier(value) ||
+ (value.type === 'ObjectExpression' &&
+ isBooleanIdentifier(utils.findProperty(value, 'type')?.value))
+ )
+}
+
+/**
+ * @param {ObjectExpression} propDefValue
+ */
+function getDefaultNode(propDefValue) {
+ return utils.findProperty(propDefValue, 'default')
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow boolean defaults',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-boolean-default.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ enum: ['default-false', 'no-default']
+ }
+ ],
+ messages: {
+ noBooleanDefault:
+ 'Boolean prop should not set a default (Vue defaults it to false).',
+ defaultFalse: 'Boolean prop should only be defaulted to false.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const booleanType = context.options[0] || 'no-default'
+ /**
+ * @param {ComponentProp} prop
+ * @param {(propName: string) => Expression[]} otherDefaultProvider
+ */
+ function processProp(prop, otherDefaultProvider) {
+ if (prop.type === 'object') {
+ if (!isBooleanProp(prop)) {
+ return
+ }
+ if (prop.value.type === 'ObjectExpression') {
+ const defaultNode = getDefaultNode(prop.value)
+ if (defaultNode) {
+ verifyDefaultExpression(defaultNode.value)
+ }
+ }
+ if (prop.propName != null) {
+ for (const defaultNode of otherDefaultProvider(prop.propName)) {
+ verifyDefaultExpression(defaultNode)
+ }
+ }
+ } else if (prop.type === 'type') {
+ if (prop.types.length !== 1 || prop.types[0] !== 'Boolean') {
+ return
+ }
+ for (const defaultNode of otherDefaultProvider(prop.propName)) {
+ verifyDefaultExpression(defaultNode)
+ }
+ }
+ }
+ /**
+ * @param {ComponentProp[]} props
+ * @param {(propName: string) => Expression[]} otherDefaultProvider
+ */
+ function processProps(props, otherDefaultProvider) {
+ for (const prop of props) {
+ processProp(prop, otherDefaultProvider)
+ }
+ }
+
+ /**
+ * @param {Expression} defaultNode
+ */
+ function verifyDefaultExpression(defaultNode) {
+ switch (booleanType) {
+ case 'no-default': {
+ context.report({
+ node: defaultNode,
+ messageId: 'noBooleanDefault'
+ })
+ break
+ }
+
+ case 'default-false': {
+ if (defaultNode.type !== 'Literal' || defaultNode.value !== false) {
+ context.report({
+ node: defaultNode,
+ messageId: 'defaultFalse'
+ })
+ }
+ break
+ }
+ }
+ }
+ return utils.compositingVisitors(
+ utils.executeOnVueComponent(context, (obj) => {
+ processProps(utils.getComponentPropsFromOptions(obj), () => [])
+ }),
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(node, props) {
+ const defaultsByWithDefaults =
+ utils.getWithDefaultsPropExpressions(node)
+ const defaultsByAssignmentPatterns =
+ utils.getDefaultPropExpressionsForPropsDestructure(node)
+ processProps(props, (propName) =>
+ [
+ defaultsByWithDefaults[propName],
+ defaultsByAssignmentPatterns[propName]?.expression
+ ].filter(utils.isDef)
+ )
+ }
+ })
+ )
+ }
+}
diff --git a/lib/rules/no-child-content.js b/lib/rules/no-child-content.js
new file mode 100644
index 000000000..df39c758e
--- /dev/null
+++ b/lib/rules/no-child-content.js
@@ -0,0 +1,163 @@
+/**
+ * @author Flo Edelmann
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+const { defineTemplateBodyVisitor } = require('../utils')
+
+/**
+ * @typedef {object} RuleOption
+ * @property {string[]} additionalDirectives
+ */
+
+/**
+ * @param {VNode | Token} node
+ * @returns {boolean}
+ */
+function isWhiteSpaceTextNode(node) {
+ return node.type === 'VText' && node.value.trim() === ''
+}
+
+/**
+ * @param {Position} pos1
+ * @param {Position} pos2
+ * @returns {'less' | 'equal' | 'greater'}
+ */
+function comparePositions(pos1, pos2) {
+ if (
+ pos1.line < pos2.line ||
+ (pos1.line === pos2.line && pos1.column < pos2.column)
+ ) {
+ return 'less'
+ }
+
+ if (
+ pos1.line > pos2.line ||
+ (pos1.line === pos2.line && pos1.column > pos2.column)
+ ) {
+ return 'greater'
+ }
+
+ return 'equal'
+}
+
+/**
+ * @param {(VNode | Token)[]} nodes
+ * @returns {SourceLocation | undefined}
+ */
+function getLocationRange(nodes) {
+ /** @type {Position | undefined} */
+ let start
+ /** @type {Position | undefined} */
+ let end
+
+ for (const node of nodes) {
+ if (!start || comparePositions(node.loc.start, start) === 'less') {
+ start = node.loc.start
+ }
+
+ if (!end || comparePositions(node.loc.end, end) === 'greater') {
+ end = node.loc.end
+ }
+ }
+
+ if (start === undefined || end === undefined) {
+ return undefined
+ }
+
+ return { start, end }
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ "disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`",
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-child-content.html'
+ },
+ fixable: null,
+ hasSuggestions: true,
+ schema: [
+ {
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ additionalDirectives: {
+ type: 'array',
+ uniqueItems: true,
+ minItems: 1,
+ items: {
+ type: 'string'
+ }
+ }
+ },
+ required: ['additionalDirectives']
+ }
+ ],
+ messages: {
+ disallowedChildContent:
+ 'Child content is disallowed because it will be overwritten by the v-{{ directiveName }} directive.',
+ removeChildContent: 'Remove child content.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const directives = new Set(['html', 'text'])
+
+ /** @type {RuleOption | undefined} */
+ const option = context.options[0]
+ if (option !== undefined) {
+ for (const directive of option.additionalDirectives) {
+ directives.add(directive)
+ }
+ }
+
+ return defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} directiveNode */
+ 'VAttribute[directive=true]'(directiveNode) {
+ const directiveName = directiveNode.key.name.name
+ const elementNode = directiveNode.parent.parent
+
+ if (elementNode.endTag === null) {
+ return
+ }
+ const sourceCode = context.getSourceCode()
+ const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore()
+ const elementComments = tokenStore.getTokensBetween(
+ elementNode.startTag,
+ elementNode.endTag,
+ {
+ includeComments: true,
+ filter: (token) => token.type === 'HTMLComment'
+ }
+ )
+
+ const childNodes = [...elementNode.children, ...elementComments]
+
+ if (
+ directives.has(directiveName) &&
+ childNodes.some((childNode) => !isWhiteSpaceTextNode(childNode))
+ ) {
+ context.report({
+ node: elementNode,
+ loc: getLocationRange(childNodes),
+ messageId: 'disallowedChildContent',
+ data: { directiveName },
+ suggest: [
+ {
+ messageId: 'removeChildContent',
+ *fix(fixer) {
+ for (const childNode of childNodes) {
+ yield fixer.remove(childNode)
+ }
+ }
+ }
+ ]
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-computed-properties-in-data.js b/lib/rules/no-computed-properties-in-data.js
new file mode 100644
index 000000000..472d45f4d
--- /dev/null
+++ b/lib/rules/no-computed-properties-in-data.js
@@ -0,0 +1,100 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef {import('../utils').VueObjectData} VueObjectData
+ */
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow accessing computed properties in `data`',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-computed-properties-in-data.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ cannotBeUsed:
+ 'The computed property cannot be used in `data()` because it is before initialization.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {Map}>} */
+ const contextMap = new Map()
+
+ /**
+ * @typedef {object} ScopeStack
+ * @property {ScopeStack | null} upper
+ * @property {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+ */
+ /** @type {ScopeStack | null} */
+ let scopeStack = null
+
+ return utils.compositingVisitors(
+ {
+ /**
+ * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+ */
+ ':function'(node) {
+ scopeStack = {
+ upper: scopeStack,
+ node
+ }
+ },
+ ':function:exit'() {
+ scopeStack = scopeStack && scopeStack.upper
+ }
+ },
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ const dataProperty = utils.findProperty(node, 'data')
+ if (
+ !dataProperty ||
+ (dataProperty.value.type !== 'FunctionExpression' &&
+ dataProperty.value.type !== 'ArrowFunctionExpression')
+ ) {
+ return
+ }
+ const computedNames = new Set()
+ for (const computed of utils.iterateProperties(
+ node,
+ new Set(['computed'])
+ )) {
+ computedNames.add(computed.name)
+ }
+
+ contextMap.set(node, { data: dataProperty.value, computedNames })
+ },
+ /**
+ * @param {MemberExpression} node
+ * @param {VueObjectData} vueData
+ */
+ MemberExpression(node, vueData) {
+ if (!scopeStack || !utils.isThis(node.object, context)) {
+ return
+ }
+ const ctx = contextMap.get(vueData.node)
+ if (!ctx || ctx.data !== scopeStack.node) {
+ return
+ }
+ const name = utils.getStaticPropertyName(node)
+ if (!name || !ctx.computedNames.has(name)) {
+ return
+ }
+ context.report({
+ node,
+ messageId: 'cannotBeUsed'
+ })
+ }
+ })
+ )
+ }
+}
diff --git a/lib/rules/no-confusing-v-for-v-if.js b/lib/rules/no-confusing-v-for-v-if.js
deleted file mode 100644
index e931be723..000000000
--- a/lib/rules/no-confusing-v-for-v-if.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @author Toru Nagashima
- * @copyright 2017 Toru Nagashima. All rights reserved.
- * See LICENSE file in root directory for full license.
- */
-'use strict'
-
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
-const utils = require('../utils')
-
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Check whether the given `v-if` node is using the variable which is defined by the `v-for` directive.
- * @param {ASTNode} vIf The `v-if` attribute node to check.
- * @returns {boolean} `true` if the `v-if` is using the variable which is defined by the `v-for` directive.
- */
-function isUsingIterationVar (vIf) {
- const element = vIf.parent.parent
- return vIf.value.references.some(reference =>
- element.variables.some(variable =>
- variable.id.name === reference.id.name &&
- variable.kind === 'v-for'
- )
- )
-}
-
-/**
- * Creates AST event handlers for no-confusing-v-for-v-if.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='if']" (node) {
- const element = node.parent.parent
-
- if (utils.hasDirective(element, 'for') && !isUsingIterationVar(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "This 'v-if' should be moved to the wrapper element."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
-module.exports = {
- create,
- meta: {
- docs: {
- description: 'disallow confusing `v-for` and `v-if` on the same element',
- category: 'recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-confusing-v-for-v-if.md'
- },
- fixable: false,
- schema: []
- }
-}
diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js
new file mode 100644
index 000000000..caabebc4d
--- /dev/null
+++ b/lib/rules/no-console.js
@@ -0,0 +1,45 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = utils.wrapCoreRule('no-console', {
+ skipBaseHandlers: true,
+ create(context) {
+ const options = context.options[0] || {}
+ const allowed = options.allow || []
+
+ /**
+ * Copied from the core rule `no-console`.
+ * Checks whether the property name of the given MemberExpression node
+ * is allowed by options or not.
+ * @param {MemberExpression} node The MemberExpression node to check.
+ * @returns {boolean} `true` if the property name of the node is allowed.
+ */
+ function isAllowed(node) {
+ const propertyName = utils.getStaticPropertyName(node)
+
+ return propertyName && allowed.includes(propertyName)
+ }
+
+ return {
+ MemberExpression(node) {
+ if (
+ node.object.type === 'Identifier' &&
+ node.object.name === 'console' &&
+ !isAllowed(node)
+ ) {
+ context.report({
+ node: node.object,
+ loc: node.object.loc,
+ messageId: 'unexpected'
+ })
+ }
+ }
+ }
+ }
+})
diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js
new file mode 100644
index 000000000..fc7e83946
--- /dev/null
+++ b/lib/rules/no-constant-condition.js
@@ -0,0 +1,29 @@
+/**
+ * @author Flo Edelmann
+ */
+'use strict'
+
+const { wrapCoreRule } = require('../utils')
+
+const conditionalDirectiveNames = new Set(['v-show', 'v-if', 'v-else-if'])
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapCoreRule('no-constant-condition', {
+ create(_context, { baseHandlers }) {
+ return {
+ VDirectiveKey(node) {
+ if (
+ conditionalDirectiveNames.has(`v-${node.name.name}`) &&
+ node.parent.value &&
+ node.parent.value.expression &&
+ baseHandlers.IfStatement
+ ) {
+ baseHandlers.IfStatement({
+ // @ts-expect-error -- Process expression of VExpressionContainer as IfStatement.
+ test: node.parent.value.expression
+ })
+ }
+ }
+ }
+ }
+})
diff --git a/lib/rules/no-custom-modifiers-on-v-model.js b/lib/rules/no-custom-modifiers-on-v-model.js
new file mode 100644
index 000000000..cef3dfaef
--- /dev/null
+++ b/lib/rules/no-custom-modifiers-on-v-model.js
@@ -0,0 +1,47 @@
+/**
+ * @author Przemyslaw Falowski (@przemkow)
+ * @fileoverview This rule checks whether v-model used on the component do not have custom modifiers
+ */
+'use strict'
+
+const utils = require('../utils')
+
+const VALID_MODIFIERS = new Set(['lazy', 'number', 'trim'])
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow custom modifiers on v-model used on the component',
+ categories: ['vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-custom-modifiers-on-v-model.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ notSupportedModifier:
+ "'v-model' directives don't support the modifier '{{name}}'."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ "VAttribute[directive=true][key.name.name='model']"(node) {
+ const element = node.parent.parent
+
+ if (utils.isCustomComponent(element)) {
+ for (const modifier of node.key.modifiers) {
+ if (!VALID_MODIFIERS.has(modifier.name)) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'notSupportedModifier',
+ data: { name: modifier.name }
+ })
+ }
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-data-object-declaration.js b/lib/rules/no-deprecated-data-object-declaration.js
new file mode 100644
index 000000000..1a9f846c5
--- /dev/null
+++ b/lib/rules/no-deprecated-data-object-declaration.js
@@ -0,0 +1,86 @@
+/**
+ * @fileoverview disallow using deprecated object declaration on data
+ * @author yoyo930021
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/** @param {Token} token */
+function isOpenParen(token) {
+ return token.type === 'Punctuator' && token.value === '('
+}
+
+/** @param {Token} token */
+function isCloseParen(token) {
+ return token.type === 'Punctuator' && token.value === ')'
+}
+
+/**
+ * @param {Expression} node
+ * @param {SourceCode} sourceCode
+ */
+function getFirstAndLastTokens(node, sourceCode) {
+ let first = sourceCode.getFirstToken(node)
+ let last = sourceCode.getLastToken(node)
+
+ // If the value enclosed by parentheses, update the 'first' and 'last' by the parentheses.
+ while (true) {
+ const prev = sourceCode.getTokenBefore(first)
+ const next = sourceCode.getTokenAfter(last)
+ if (isOpenParen(prev) && isCloseParen(next)) {
+ first = prev
+ last = next
+ } else {
+ return { first, last }
+ }
+ }
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow using deprecated object declaration on data (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-data-object-declaration.html'
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ objectDeclarationIsDeprecated:
+ "Object declaration on 'data' property is deprecated. Using function declaration instead."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+
+ return utils.executeOnVue(context, (obj) => {
+ const invalidData = utils.findProperty(
+ obj,
+ 'data',
+ (p) =>
+ p.value.type !== 'FunctionExpression' &&
+ p.value.type !== 'ArrowFunctionExpression' &&
+ p.value.type !== 'Identifier'
+ )
+
+ if (invalidData) {
+ context.report({
+ node: invalidData,
+ messageId: 'objectDeclarationIsDeprecated',
+ fix(fixer) {
+ const tokens = getFirstAndLastTokens(invalidData.value, sourceCode)
+
+ return [
+ fixer.insertTextBefore(tokens.first, 'function() {\nreturn '),
+ fixer.insertTextAfter(tokens.last, ';\n}')
+ ]
+ }
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-delete-set.js b/lib/rules/no-deprecated-delete-set.js
new file mode 100644
index 000000000..699c928e1
--- /dev/null
+++ b/lib/rules/no-deprecated-delete-set.js
@@ -0,0 +1,132 @@
+/**
+ * @author Wayne Zhang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const { ReferenceTracker } = require('@eslint-community/eslint-utils')
+
+/**
+ * @typedef {import('@eslint-community/eslint-utils').TYPES.TraceMap} TraceMap
+ */
+
+/** @type {TraceMap} */
+const deletedImportApisMap = {
+ set: {
+ [ReferenceTracker.CALL]: true
+ },
+ del: {
+ [ReferenceTracker.CALL]: true
+ }
+}
+const deprecatedApis = new Set(['set', 'delete'])
+const deprecatedDollarApis = new Set(['$set', '$delete'])
+
+/**
+ * @param {Expression|Super} node
+ */
+function isVue(node) {
+ return node.type === 'Identifier' && node.name === 'Vue'
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-delete-set.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ deprecated: 'The `$delete`, `$set` is deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /**
+ * @param {Identifier} identifier
+ * @param {RuleContext} context
+ * @returns {CallExpression|undefined}
+ */
+ function getVueDeprecatedCallExpression(identifier, context) {
+ // Instance API: this.$set()
+ if (
+ deprecatedDollarApis.has(identifier.name) &&
+ identifier.parent.type === 'MemberExpression' &&
+ utils.isThis(identifier.parent.object, context) &&
+ identifier.parent.parent.type === 'CallExpression' &&
+ identifier.parent.parent.callee === identifier.parent
+ ) {
+ return identifier.parent.parent
+ }
+
+ // Vue 2 Global API: Vue.set()
+ if (
+ deprecatedApis.has(identifier.name) &&
+ identifier.parent.type === 'MemberExpression' &&
+ isVue(identifier.parent.object) &&
+ identifier.parent.parent.type === 'CallExpression' &&
+ identifier.parent.parent.callee === identifier.parent
+ ) {
+ return identifier.parent.parent
+ }
+
+ return undefined
+ }
+
+ const nodeVisitor = {
+ /** @param {Identifier} node */
+ Identifier(node) {
+ const callExpression = getVueDeprecatedCallExpression(node, context)
+ if (!callExpression) {
+ return
+ }
+
+ context.report({
+ node,
+ messageId: 'deprecated'
+ })
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.defineVueVisitor(context, nodeVisitor),
+ utils.defineScriptSetupVisitor(context, nodeVisitor),
+ {
+ /** @param {Program} node */
+ Program(node) {
+ const tracker = new ReferenceTracker(utils.getScope(context, node))
+
+ // import { set } from 'vue'; set()
+ const esmTraceMap = {
+ vue: {
+ [ReferenceTracker.ESM]: true,
+ ...deletedImportApisMap
+ }
+ }
+ // const { set } = require('vue'); set()
+ const cjsTraceMap = {
+ vue: {
+ ...deletedImportApisMap
+ }
+ }
+
+ for (const { node } of [
+ ...tracker.iterateEsmReferences(esmTraceMap),
+ ...tracker.iterateCjsReferences(cjsTraceMap)
+ ]) {
+ const refNode = /** @type {CallExpression} */ (node)
+ context.report({
+ node: refNode.callee,
+ messageId: 'deprecated'
+ })
+ }
+ }
+ }
+ )
+ }
+}
diff --git a/lib/rules/no-deprecated-destroyed-lifecycle.js b/lib/rules/no-deprecated-destroyed-lifecycle.js
new file mode 100644
index 000000000..98986cd2e
--- /dev/null
+++ b/lib/rules/no-deprecated-destroyed-lifecycle.js
@@ -0,0 +1,78 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @param {RuleFixer} fixer
+ * @param {Property} property
+ * @param {string} newName
+ */
+function fix(fixer, property, newName) {
+ if (property.computed) {
+ if (
+ property.key.type === 'Literal' ||
+ property.key.type === 'TemplateLiteral'
+ ) {
+ return fixer.replaceTextRange(
+ [property.key.range[0] + 1, property.key.range[1] - 1],
+ newName
+ )
+ }
+ return null
+ }
+ if (property.shorthand) {
+ return fixer.insertTextBefore(property.key, `${newName}:`)
+ }
+ return fixer.replaceText(property.key, newName)
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow using deprecated `destroyed` and `beforeDestroy` lifecycle hooks (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-destroyed-lifecycle.html'
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ deprecatedDestroyed:
+ 'The `destroyed` lifecycle hook is deprecated. Use `unmounted` instead.',
+ deprecatedBeforeDestroy:
+ 'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.executeOnVue(context, (obj) => {
+ const destroyed = utils.findProperty(obj, 'destroyed')
+
+ if (destroyed) {
+ context.report({
+ node: destroyed.key,
+ messageId: 'deprecatedDestroyed',
+ fix(fixer) {
+ return fix(fixer, destroyed, 'unmounted')
+ }
+ })
+ }
+
+ const beforeDestroy = utils.findProperty(obj, 'beforeDestroy')
+ if (beforeDestroy) {
+ context.report({
+ node: beforeDestroy.key,
+ messageId: 'deprecatedBeforeDestroy',
+ fix(fixer) {
+ return fix(fixer, beforeDestroy, 'beforeUnmount')
+ }
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-dollar-listeners-api.js b/lib/rules/no-deprecated-dollar-listeners-api.js
new file mode 100644
index 000000000..2db80a116
--- /dev/null
+++ b/lib/rules/no-deprecated-dollar-listeners-api.js
@@ -0,0 +1,63 @@
+/**
+ * @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: 'disallow using deprecated `$listeners` (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-dollar-listeners-api.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ deprecated: 'The `$listeners` is deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {
+ VExpressionContainer(node) {
+ for (const reference of node.references) {
+ if (reference.variable != null) {
+ // Not vm reference
+ continue
+ }
+ if (reference.id.name === '$listeners') {
+ context.report({
+ node: reference.id,
+ messageId: 'deprecated'
+ })
+ }
+ }
+ }
+ },
+ utils.defineVueVisitor(context, {
+ MemberExpression(node) {
+ if (
+ node.property.type !== 'Identifier' ||
+ node.property.name !== '$listeners'
+ ) {
+ return
+ }
+ if (!utils.isThis(node.object, context)) {
+ return
+ }
+
+ context.report({
+ node: node.property,
+ messageId: 'deprecated'
+ })
+ }
+ })
+ )
+ }
+}
diff --git a/lib/rules/no-deprecated-dollar-scopedslots-api.js b/lib/rules/no-deprecated-dollar-scopedslots-api.js
new file mode 100644
index 000000000..cca25942c
--- /dev/null
+++ b/lib/rules/no-deprecated-dollar-scopedslots-api.js
@@ -0,0 +1,70 @@
+/**
+ * @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:
+ 'disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-dollar-scopedslots-api.html'
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ deprecated: 'The `$scopedSlots` is deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(
+ context,
+ {
+ VExpressionContainer(node) {
+ for (const reference of node.references) {
+ if (reference.variable != null) {
+ // Not vm reference
+ continue
+ }
+ if (reference.id.name === '$scopedSlots') {
+ context.report({
+ node: reference.id,
+ messageId: 'deprecated',
+ fix(fixer) {
+ return fixer.replaceText(reference.id, '$slots')
+ }
+ })
+ }
+ }
+ }
+ },
+ utils.defineVueVisitor(context, {
+ MemberExpression(node) {
+ if (
+ node.property.type !== 'Identifier' ||
+ node.property.name !== '$scopedSlots'
+ ) {
+ return
+ }
+ if (!utils.isThis(node.object, context)) {
+ return
+ }
+
+ context.report({
+ node: node.property,
+ messageId: 'deprecated',
+ fix(fixer) {
+ return fixer.replaceText(node.property, '$slots')
+ }
+ })
+ }
+ })
+ )
+ }
+}
diff --git a/lib/rules/no-deprecated-events-api.js b/lib/rules/no-deprecated-events-api.js
new file mode 100644
index 000000000..fa602f335
--- /dev/null
+++ b/lib/rules/no-deprecated-events-api.js
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview disallow using deprecated events api
+ * @author yoyo930021
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow using deprecated events api (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-events-api.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ noDeprecatedEventsApi:
+ 'The Events api `$on`, `$off` `$once` is deprecated. Using external library instead, for example mitt.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineVueVisitor(context, {
+ /** @param {MemberExpression & ({parent: CallExpression} | {parent: ChainExpression & {parent: CallExpression}})} node */
+ 'CallExpression > MemberExpression, CallExpression > ChainExpression > MemberExpression'(
+ node
+ ) {
+ const call =
+ node.parent.type === 'ChainExpression'
+ ? node.parent.parent
+ : node.parent
+
+ if (call.optional) {
+ // It is OK because checking whether it is deprecated.
+ // e.g. `this.$on?.()`
+ return
+ }
+
+ if (
+ utils.skipChainExpression(call.callee) !== node ||
+ !['$on', '$off', '$once'].includes(
+ utils.getStaticPropertyName(node) || ''
+ )
+ ) {
+ return
+ }
+ if (!utils.isThis(node.object, context)) {
+ return
+ }
+
+ context.report({
+ node: node.property,
+ messageId: 'noDeprecatedEventsApi'
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-filter.js b/lib/rules/no-deprecated-filter.js
new file mode 100644
index 000000000..5e7967385
--- /dev/null
+++ b/lib/rules/no-deprecated-filter.js
@@ -0,0 +1,36 @@
+/**
+ * @author Przemyslaw Falowski (@przemkow)
+ * @fileoverview disallow using deprecated filters syntax
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow using deprecated filters syntax (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-filter.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ noDeprecatedFilter: 'Filters are deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ VFilterSequenceExpression(node) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'noDeprecatedFilter'
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-functional-template.js b/lib/rules/no-deprecated-functional-template.js
new file mode 100644
index 000000000..5b3b9dea1
--- /dev/null
+++ b/lib/rules/no-deprecated-functional-template.js
@@ -0,0 +1,47 @@
+/**
+ * @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:
+ 'disallow using deprecated the `functional` template (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-functional-template.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpected: 'The `functional` template are deprecated.'
+ }
+ },
+ /**
+ * @param {RuleContext} context - The rule context.
+ * @returns {RuleListener} AST event handlers.
+ */
+ create(context) {
+ return {
+ Program(program) {
+ const element = program.templateBody
+ if (element == null) {
+ return
+ }
+
+ const functional = utils.getAttribute(element, 'functional')
+
+ if (functional) {
+ context.report({
+ node: functional,
+ messageId: 'unexpected'
+ })
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/no-deprecated-html-element-is.js b/lib/rules/no-deprecated-html-element-is.js
new file mode 100644
index 000000000..5253ef2b7
--- /dev/null
+++ b/lib/rules/no-deprecated-html-element-is.js
@@ -0,0 +1,65 @@
+/**
+ * @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:
+ 'disallow using deprecated the `is` attribute on HTML elements (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-html-element-is.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpected: 'The `is` attribute on HTML element are deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @param {VElement} node */
+ function isValidElement(node) {
+ return (
+ !utils.isHtmlWellKnownElementName(node.rawName) &&
+ !utils.isSvgWellKnownElementName(node.rawName) &&
+ !utils.isMathWellKnownElementName(node.rawName)
+ )
+ }
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='is']"(
+ node
+ ) {
+ if (isValidElement(node.parent.parent)) {
+ return
+ }
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpected'
+ })
+ },
+ /** @param {VAttribute} node */
+ "VAttribute[directive=false][key.name='is']"(node) {
+ if (isValidElement(node.parent.parent)) {
+ return
+ }
+ if (node.value && node.value.value.startsWith('vue:')) {
+ // Usage on native elements 3.1+
+ return
+ }
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpected'
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-inline-template.js b/lib/rules/no-deprecated-inline-template.js
new file mode 100644
index 000000000..36923f2c2
--- /dev/null
+++ b/lib/rules/no-deprecated-inline-template.js
@@ -0,0 +1,39 @@
+/**
+ * @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:
+ 'disallow using deprecated `inline-template` attribute (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-inline-template.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpected: '`inline-template` are deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VIdentifier} node */
+ "VAttribute[directive=false] > VIdentifier[rawName='inline-template']"(
+ node
+ ) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpected'
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-model-definition.js b/lib/rules/no-deprecated-model-definition.js
new file mode 100644
index 000000000..6c7e4dd8d
--- /dev/null
+++ b/lib/rules/no-deprecated-model-definition.js
@@ -0,0 +1,133 @@
+/**
+ * @author Flo Edelmann
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+const allowedPropNames = new Set(['modelValue', 'model-value'])
+const allowedEventNames = new Set(['update:modelValue', 'update:model-value'])
+
+/**
+ * @param {ObjectExpression} node
+ * @param {string} key
+ * @returns {Literal | TemplateLiteral | undefined}
+ */
+function findPropertyValue(node, key) {
+ const property = node.properties.find(
+ (property) =>
+ property.type === 'Property' &&
+ property.key.type === 'Identifier' &&
+ property.key.name === key
+ )
+ if (
+ !property ||
+ property.type !== 'Property' ||
+ !utils.isStringLiteral(property.value)
+ ) {
+ return undefined
+ }
+ return property.value
+}
+
+/**
+ * @param {RuleFixer} fixer
+ * @param {Literal | TemplateLiteral} node
+ * @param {string} text
+ */
+function replaceLiteral(fixer, node, text) {
+ return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], text)
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow deprecated `model` definition (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-model-definition.html'
+ },
+ fixable: null,
+ hasSuggestions: true,
+ schema: [
+ {
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ allowVue3Compat: {
+ type: 'boolean'
+ }
+ }
+ }
+ ],
+ messages: {
+ deprecatedModel: '`model` definition is deprecated.',
+ vue3Compat:
+ '`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
+ changeToModelValue: 'Change to `modelValue`/`update:modelValue`.',
+ changeToKebabModelValue: 'Change to `model-value`/`update:model-value`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const allowVue3Compat = Boolean(context.options[0]?.allowVue3Compat)
+
+ return utils.executeOnVue(context, (obj) => {
+ const modelProperty = utils.findProperty(obj, 'model')
+ if (!modelProperty || modelProperty.value.type !== 'ObjectExpression') {
+ return
+ }
+
+ if (!allowVue3Compat) {
+ context.report({
+ node: modelProperty,
+ messageId: 'deprecatedModel'
+ })
+ return
+ }
+
+ const propName = findPropertyValue(modelProperty.value, 'prop')
+ const eventName = findPropertyValue(modelProperty.value, 'event')
+
+ if (
+ !propName ||
+ !eventName ||
+ !allowedPropNames.has(
+ utils.getStringLiteralValue(propName, true) ?? ''
+ ) ||
+ !allowedEventNames.has(
+ utils.getStringLiteralValue(eventName, true) ?? ''
+ )
+ ) {
+ context.report({
+ node: modelProperty,
+ messageId: 'vue3Compat',
+ suggest:
+ propName && eventName
+ ? [
+ {
+ messageId: 'changeToModelValue',
+ *fix(fixer) {
+ const newPropName = 'modelValue'
+ const newEventName = 'update:modelValue'
+ yield replaceLiteral(fixer, propName, newPropName)
+ yield replaceLiteral(fixer, eventName, newEventName)
+ }
+ },
+ {
+ messageId: 'changeToKebabModelValue',
+ *fix(fixer) {
+ const newPropName = 'model-value'
+ const newEventName = 'update:model-value'
+ yield replaceLiteral(fixer, propName, newPropName)
+ yield replaceLiteral(fixer, eventName, newEventName)
+ }
+ }
+ ]
+ : []
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-props-default-this.js b/lib/rules/no-deprecated-props-default-this.js
new file mode 100644
index 000000000..5583b12ad
--- /dev/null
+++ b/lib/rules/no-deprecated-props-default-this.js
@@ -0,0 +1,126 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @param {Expression|SpreadElement|null} node
+ */
+function isFunctionIdentifier(node) {
+ return node && node.type === 'Identifier' && node.name === 'Function'
+}
+
+/**
+ * @param {Expression} node
+ * @returns {boolean}
+ */
+function hasFunctionType(node) {
+ if (isFunctionIdentifier(node)) {
+ return true
+ }
+ if (node.type === 'ArrayExpression') {
+ return node.elements.some(isFunctionIdentifier)
+ }
+ return false
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow deprecated `this` access in props default function (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-props-default-this.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ deprecated:
+ 'Props default value factory functions no longer have access to `this`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /**
+ * @typedef {object} ScopeStack
+ * @property {ScopeStack | null} upper
+ * @property {FunctionExpression | FunctionDeclaration} node
+ * @property {boolean} propDefault
+ */
+ /** @type {Set} */
+ const propsDefault = new Set()
+ /** @type {ScopeStack | null} */
+ let scopeStack = null
+
+ /**
+ * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+ */
+ function onFunctionEnter(node) {
+ if (node.type === 'ArrowFunctionExpression') {
+ return
+ }
+ if (scopeStack) {
+ scopeStack = {
+ upper: scopeStack,
+ node,
+ propDefault: false
+ }
+ } else if (node.type === 'FunctionExpression' && propsDefault.has(node)) {
+ scopeStack = {
+ upper: scopeStack,
+ node,
+ propDefault: true
+ }
+ }
+ }
+
+ /**
+ * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+ */
+ function onFunctionExit(node) {
+ if (scopeStack && scopeStack.node === node) {
+ scopeStack = scopeStack.upper
+ }
+ }
+
+ return utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ for (const prop of utils.getComponentPropsFromOptions(node)) {
+ if (prop.type !== 'object') {
+ continue
+ }
+ if (prop.value.type !== 'ObjectExpression') {
+ continue
+ }
+ const def = utils.findProperty(prop.value, 'default')
+ if (!def) {
+ continue
+ }
+ const type = utils.findProperty(prop.value, 'type')
+ if (type && hasFunctionType(type.value)) {
+ // ignore function type
+ continue
+ }
+ if (def.value.type !== 'FunctionExpression') {
+ continue
+ }
+ propsDefault.add(def.value)
+ }
+ },
+ ':function': onFunctionEnter,
+ ':function:exit': onFunctionExit,
+ ThisExpression(node) {
+ if (scopeStack && scopeStack.propDefault) {
+ context.report({
+ node,
+ messageId: 'deprecated'
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-router-link-tag-prop.js b/lib/rules/no-deprecated-router-link-tag-prop.js
new file mode 100644
index 000000000..e2853f2af
--- /dev/null
+++ b/lib/rules/no-deprecated-router-link-tag-prop.js
@@ -0,0 +1,93 @@
+/**
+ * @author Marton Csordas
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+
+/** @param {RuleContext} context */
+function getComponentNames(context) {
+ let components = ['RouterLink']
+
+ if (context.options[0] && context.options[0].components) {
+ components = context.options[0].components
+ }
+
+ return new Set(
+ components.flatMap((component) => [
+ casing.kebabCase(component),
+ casing.pascalCase(component)
+ ])
+ )
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow using deprecated `tag` property on `RouterLink` (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-router-link-tag-prop.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ components: {
+ type: 'array',
+ items: {
+ type: 'string'
+ },
+ uniqueItems: true,
+ minItems: 1
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ deprecated:
+ "'tag' property on '{{element}}' component is deprecated. Use scoped slots instead."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const components = getComponentNames(context)
+
+ return utils.defineTemplateBodyVisitor(context, {
+ VElement(node) {
+ if (!components.has(node.rawName)) return
+
+ /** @type VIdentifier | null */
+ let tagKey = null
+
+ const tagAttr = utils.getAttribute(node, 'tag')
+ if (tagAttr) {
+ tagKey = tagAttr.key
+ } else {
+ const directive = utils.getDirective(node, 'bind', 'tag')
+ if (directive) {
+ const arg = directive.key.argument
+ if (arg && arg.type === 'VIdentifier') {
+ tagKey = arg
+ }
+ }
+ }
+
+ if (tagKey) {
+ context.report({
+ node: tagKey,
+ messageId: 'deprecated',
+ data: {
+ element: node.rawName
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-scope-attribute.js b/lib/rules/no-deprecated-scope-attribute.js
new file mode 100644
index 000000000..80bcc02bd
--- /dev/null
+++ b/lib/rules/no-deprecated-scope-attribute.js
@@ -0,0 +1,31 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const scopeAttribute = require('./syntaxes/scope-attribute')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow deprecated `scope` attribute (in Vue.js 2.5.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-scope-attribute.html'
+ },
+ // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
+ fixable: 'code',
+ schema: [],
+ messages: {
+ forbiddenScopeAttribute: '`scope` attributes are deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const templateBodyVisitor =
+ scopeAttribute.createTemplateBodyVisitor(context)
+ return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
+ }
+}
diff --git a/lib/rules/no-deprecated-slot-attribute.js b/lib/rules/no-deprecated-slot-attribute.js
new file mode 100644
index 000000000..664c14353
--- /dev/null
+++ b/lib/rules/no-deprecated-slot-attribute.js
@@ -0,0 +1,42 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const slotAttribute = require('./syntaxes/slot-attribute')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow deprecated `slot` attribute (in Vue.js 2.6.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-slot-attribute.html'
+ },
+ // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
+ fixable: 'code',
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ ignore: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ forbiddenSlotAttribute: '`slot` attributes are deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const templateBodyVisitor = slotAttribute.createTemplateBodyVisitor(context)
+ return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
+ }
+}
diff --git a/lib/rules/no-deprecated-slot-scope-attribute.js b/lib/rules/no-deprecated-slot-scope-attribute.js
new file mode 100644
index 000000000..20a4f00bd
--- /dev/null
+++ b/lib/rules/no-deprecated-slot-scope-attribute.js
@@ -0,0 +1,34 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const slotScopeAttribute = require('./syntaxes/slot-scope-attribute')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description:
+ 'disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-slot-scope-attribute.html'
+ },
+ // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
+ fixable: 'code',
+ schema: [],
+ messages: {
+ forbiddenSlotScopeAttribute: '`slot-scope` are deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const templateBodyVisitor = slotScopeAttribute.createTemplateBodyVisitor(
+ context,
+ { fixToUpgrade: true }
+ )
+ return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
+ }
+}
diff --git a/lib/rules/no-deprecated-v-bind-sync.js b/lib/rules/no-deprecated-v-bind-sync.js
new file mode 100644
index 000000000..f52c1aead
--- /dev/null
+++ b/lib/rules/no-deprecated-v-bind-sync.js
@@ -0,0 +1,54 @@
+/**
+ * @author Przemyslaw Falowski (@przemkow)
+ * @fileoverview Disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-v-bind-sync.html'
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ syncModifierIsDeprecated:
+ "'.sync' modifier on 'v-bind' directive is deprecated. Use 'v-model:propName' instead."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ "VAttribute[directive=true][key.name.name='bind']"(node) {
+ if (node.key.modifiers.map((mod) => mod.name).includes('sync')) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'syncModifierIsDeprecated',
+ fix(fixer) {
+ if (node.key.argument == null) {
+ // is using spread syntax
+ return null
+ }
+ if (node.key.modifiers.length > 1) {
+ // has multiple modifiers
+ return null
+ }
+
+ const bindArgument = context
+ .getSourceCode()
+ .getText(node.key.argument)
+ return fixer.replaceText(node.key, `v-model:${bindArgument}`)
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-v-is.js b/lib/rules/no-deprecated-v-is.js
new file mode 100644
index 000000000..c497b0120
--- /dev/null
+++ b/lib/rules/no-deprecated-v-is.js
@@ -0,0 +1,29 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const vIs = require('./syntaxes/v-is')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow deprecated `v-is` directive (in Vue.js 3.1.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-v-is.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ forbiddenVIs: '`v-is` directive is deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const templateBodyVisitor = vIs.createTemplateBodyVisitor(context)
+ return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
+ }
+}
diff --git a/lib/rules/no-deprecated-v-on-native-modifier.js b/lib/rules/no-deprecated-v-on-native-modifier.js
new file mode 100644
index 000000000..929f8d1d8
--- /dev/null
+++ b/lib/rules/no-deprecated-v-on-native-modifier.js
@@ -0,0 +1,41 @@
+/**
+ * @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:
+ 'disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-v-on-native-modifier.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ deprecated: "'.native' modifier on 'v-on' directive is deprecated."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VIdentifier & {parent:VDirectiveKey} } node */
+ "VAttribute[directive=true][key.name.name='on'] > VDirectiveKey > VIdentifier[name='native']"(
+ node
+ ) {
+ const key = node.parent
+ if (!key.modifiers.includes(node)) return
+
+ context.report({
+ node,
+ messageId: 'deprecated'
+ })
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-v-on-number-modifiers.js b/lib/rules/no-deprecated-v-on-number-modifiers.js
new file mode 100644
index 000000000..1fdcdeaac
--- /dev/null
+++ b/lib/rules/no-deprecated-v-on-number-modifiers.js
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview disallow using deprecated number (keycode) modifiers
+ * @author yoyo930021
+ */
+'use strict'
+
+const utils = require('../utils')
+const keyCodeToKey = require('../utils/keycode-to-key')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-v-on-number-modifiers.html'
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ numberModifierIsDeprecated:
+ "'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirectiveKey} node */
+ "VAttribute[directive=true][key.name.name='on'] > VDirectiveKey"(node) {
+ const modifier = node.modifiers.find((mod) =>
+ Number.isInteger(Number.parseInt(mod.name, 10))
+ )
+ if (!modifier) return
+
+ const keyCodes = Number.parseInt(modifier.name, 10)
+ if (keyCodes > 9 || keyCodes < 0) {
+ context.report({
+ node: modifier,
+ messageId: 'numberModifierIsDeprecated',
+ fix(fixer) {
+ const key = keyCodeToKey[keyCodes]
+ if (!key) return null
+
+ return fixer.replaceText(modifier, `${key}`)
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-deprecated-vue-config-keycodes.js b/lib/rules/no-deprecated-vue-config-keycodes.js
new file mode 100644
index 000000000..68af8430a
--- /dev/null
+++ b/lib/rules/no-deprecated-vue-config-keycodes.js
@@ -0,0 +1,48 @@
+/**
+ * @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:
+ 'disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+)',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-vue-config-keycodes.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpected: '`Vue.config.keyCodes` are deprecated.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return {
+ /** @param {MemberExpression} node */
+ "MemberExpression[property.type='Identifier'][property.name='keyCodes']"(
+ node
+ ) {
+ const config = utils.skipChainExpression(node.object)
+ if (
+ config.type !== 'MemberExpression' ||
+ config.property.type !== 'Identifier' ||
+ config.property.name !== 'config' ||
+ config.object.type !== 'Identifier' ||
+ config.object.name !== 'Vue'
+ ) {
+ return
+ }
+ context.report({
+ node,
+ messageId: 'unexpected'
+ })
+ }
+ }
+ }
+}
diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js
index eccb1a9dd..ecfa787cf 100644
--- a/lib/rules/no-dupe-keys.js
+++ b/lib/rules/no-dupe-keys.js
@@ -4,54 +4,96 @@
*/
'use strict'
+const { findVariable } = require('@eslint-community/eslint-utils')
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
+/**
+ * @typedef {import('../utils').GroupName} GroupName
+ * @typedef {import('eslint').Scope.Variable} Variable
+ * @typedef {import('../utils').ComponentProp} ComponentProp
+ */
+
+/** @type {GroupName[]} */
+const GROUP_NAMES = ['props', 'computed', 'data', 'methods', 'setup']
+
+/**
+ * Gets the props pattern node from given `defineProps()` node
+ * @param {CallExpression} node
+ * @returns {Pattern|null}
+ */
+function getPropsPattern(node) {
+ let target = node
+ if (
+ target.parent &&
+ target.parent.type === 'CallExpression' &&
+ target.parent.arguments[0] === target &&
+ target.parent.callee.type === 'Identifier' &&
+ target.parent.callee.name === 'withDefaults'
+ ) {
+ target = target.parent
+ }
-const GROUP_NAMES = ['props', 'computed', 'data', 'methods']
+ if (
+ !target.parent ||
+ target.parent.type !== 'VariableDeclarator' ||
+ target.parent.init !== target
+ ) {
+ return null
+ }
+ return target.parent.id
+}
-function create (context) {
- const options = context.options[0] || {}
- const groups = new Set(GROUP_NAMES.concat(options.groups || []))
+/**
+ * Checks whether the initialization of the given variable declarator node contains one of the references.
+ * @param {VariableDeclarator} node
+ * @param {ESNode[]} references
+ */
+function isInsideInitializer(node, references) {
+ const init = node.init
+ if (!init) {
+ return false
+ }
+ return references.some(
+ (id) => init.range[0] <= id.range[0] && id.range[1] <= init.range[1]
+ )
+}
- // ----------------------------------------------------------------------
- // Public
- // ----------------------------------------------------------------------
+/**
+ * Collects all renamed props from a pattern
+ * @param {Pattern | null} pattern - The destructuring pattern
+ * @returns {Set} - Set of prop names that have been renamed
+ */
+function collectRenamedProps(pattern) {
+ const renamedProps = new Set()
- return utils.executeOnVue(context, (obj) => {
- const usedNames = []
- const properties = utils.iterateProperties(obj, groups)
+ if (!pattern || pattern.type !== 'ObjectPattern') {
+ return renamedProps
+ }
- for (const o of properties) {
- if (usedNames.indexOf(o.name) !== -1) {
- context.report({
- node: o.node,
- message: "Duplicated key '{{name}}'.",
- data: {
- name: o.name
- }
- })
- }
+ for (const prop of pattern.properties) {
+ if (prop.type !== 'Property') continue
- usedNames.push(o.name)
+ if (
+ prop.key.type === 'Identifier' &&
+ prop.value.type === 'Identifier' &&
+ prop.key.name !== prop.value.name
+ ) {
+ renamedProps.add(prop.key.name)
}
- })
-}
+ }
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
+ return renamedProps
+}
module.exports = {
meta: {
+ type: 'problem',
docs: {
description: 'disallow duplication of field names',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-dupe-keys.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-dupe-keys.html'
},
- fixable: null, // or "code" or "whitespace"
+ fixable: null,
schema: [
{
type: 'object',
@@ -62,8 +104,105 @@ module.exports = {
},
additionalProperties: false
}
- ]
+ ],
+ messages: {
+ duplicateKey:
+ "Duplicate key '{{name}}'. May cause name collision in script or template tag."
+ }
},
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = context.options[0] || {}
+ const groups = new Set([...GROUP_NAMES, ...(options.groups || [])])
+
+ return utils.compositingVisitors(
+ utils.executeOnVue(context, (obj) => {
+ const properties = utils.iterateProperties(obj, groups)
+ /** @type {Set} */
+ const usedNames = new Set()
+ for (const o of properties) {
+ if (usedNames.has(o.name)) {
+ context.report({
+ node: o.node,
+ messageId: 'duplicateKey',
+ data: {
+ name: o.name
+ }
+ })
+ }
+
+ usedNames.add(o.name)
+ }
+ }),
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(node, props) {
+ const propsNode = getPropsPattern(node)
+ const propReferences = [
+ ...(propsNode ? extractReferences(propsNode) : []),
+ node
+ ]
+
+ const renamedProps = collectRenamedProps(propsNode)
+
+ for (const prop of props) {
+ if (!prop.propName) continue
+
+ if (renamedProps.has(prop.propName)) {
+ continue
+ }
+
+ const variable = findVariable(
+ utils.getScope(context, node),
+ prop.propName
+ )
+ if (!variable || variable.defs.length === 0) continue
- create
+ if (
+ variable.defs.some((def) => {
+ if (def.type !== 'Variable') return false
+ return isInsideInitializer(def.node, propReferences)
+ })
+ ) {
+ continue
+ }
+
+ context.report({
+ node: variable.defs[0].node,
+ messageId: 'duplicateKey',
+ data: {
+ name: prop.propName
+ }
+ })
+ }
+ }
+ })
+ )
+
+ /**
+ * Extracts references from the given node.
+ * @param {Pattern} node
+ * @returns {Identifier[]} References
+ */
+ function extractReferences(node) {
+ if (node.type === 'Identifier') {
+ const variable = findVariable(utils.getScope(context, node), node)
+ if (!variable) {
+ return []
+ }
+ return variable.references.map((ref) => ref.identifier)
+ }
+ if (node.type === 'ObjectPattern') {
+ return node.properties.flatMap((prop) =>
+ extractReferences(prop.type === 'Property' ? prop.value : prop)
+ )
+ }
+ if (node.type === 'AssignmentPattern') {
+ return extractReferences(node.left)
+ }
+ if (node.type === 'RestElement') {
+ return extractReferences(node.argument)
+ }
+ return []
+ }
+ }
}
diff --git a/lib/rules/no-dupe-v-else-if.js b/lib/rules/no-dupe-v-else-if.js
new file mode 100644
index 000000000..a20d5256b
--- /dev/null
+++ b/lib/rules/no-dupe-v-else-if.js
@@ -0,0 +1,181 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef {NonNullable} VExpression
+ */
+/**
+ * @typedef {object} OrOperands
+ * @property {VExpression} OrOperands.node
+ * @property {AndOperands[]} OrOperands.operands
+ *
+ * @typedef {object} AndOperands
+ * @property {VExpression} AndOperands.node
+ * @property {VExpression[]} AndOperands.operands
+ */
+/**
+ * Splits the given node by the given logical operator.
+ * @param {string} operator Logical operator `||` or `&&`.
+ * @param {VExpression} node The node to split.
+ * @returns {VExpression[]} Array of conditions that makes the node when joined by the operator.
+ */
+function splitByLogicalOperator(operator, node) {
+ if (node.type === 'LogicalExpression' && node.operator === operator) {
+ return [
+ ...splitByLogicalOperator(operator, node.left),
+ ...splitByLogicalOperator(operator, node.right)
+ ]
+ }
+ return [node]
+}
+
+/**
+ * @param {VExpression} node
+ */
+function splitByOr(node) {
+ return splitByLogicalOperator('||', node)
+}
+/**
+ * @param {VExpression} node
+ */
+function splitByAnd(node) {
+ return splitByLogicalOperator('&&', node)
+}
+
+/**
+ * @param {VExpression} node
+ * @returns {OrOperands}
+ */
+function buildOrOperands(node) {
+ const orOperands = splitByOr(node)
+ return {
+ node,
+ operands: orOperands.map((orOperand) => {
+ const andOperands = splitByAnd(orOperand)
+ return {
+ node: orOperand,
+ operands: andOperands
+ }
+ })
+ }
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow duplicate conditions in `v-if` / `v-else-if` chains',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-dupe-v-else-if.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpected:
+ 'This branch can never execute. Its condition is a duplicate or covered by previous conditions in the `v-if` / `v-else-if` chain.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const tokenStore =
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+ /**
+ * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes
+ * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators.
+ * @param {VExpression} a First node.
+ * @param {VExpression} 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
+ }
+
+ if (
+ a.type === 'LogicalExpression' &&
+ b.type === 'LogicalExpression' &&
+ (a.operator === '||' || a.operator === '&&') &&
+ a.operator === b.operator
+ ) {
+ return (
+ (equal(a.left, b.left) && equal(a.right, b.right)) ||
+ (equal(a.left, b.right) && equal(a.right, b.left))
+ )
+ }
+
+ return utils.equalTokens(a, b, tokenStore)
+ }
+
+ /**
+ * Determines whether the first given AndOperands is a subset of the second given AndOperands.
+ *
+ * e.g. A: (a && b), B: (a && b && c): B is a subset of A.
+ *
+ * @param {AndOperands} operandsA The AndOperands to compare from.
+ * @param {AndOperands} operandsB The AndOperands to compare against.
+ * @returns {boolean} `true` if the `andOperandsA` is a subset of the `andOperandsB`.
+ */
+ function isSubset(operandsA, operandsB) {
+ return operandsA.operands.every((operandA) =>
+ operandsB.operands.some((operandB) => equal(operandA, operandB))
+ )
+ }
+
+ return utils.defineTemplateBodyVisitor(context, {
+ "VAttribute[directive=true][key.name.name='else-if']"(node) {
+ if (!node.value || !node.value.expression) {
+ return
+ }
+ const test = node.value.expression
+ const conditionsToCheck =
+ test.type === 'LogicalExpression' && test.operator === '&&'
+ ? [...splitByAnd(test), test]
+ : [test]
+ const listToCheck = conditionsToCheck.map(buildOrOperands)
+
+ /** @type {VElement | null} */
+ let current = node.parent.parent
+ while (current && (current = utils.prevSibling(current))) {
+ const vIf = utils.getDirective(current, 'if')
+ const currentTestDir = vIf || utils.getDirective(current, 'else-if')
+ if (!currentTestDir) {
+ return
+ }
+ if (currentTestDir.value && currentTestDir.value.expression) {
+ const currentOrOperands = buildOrOperands(
+ currentTestDir.value.expression
+ )
+
+ for (const condition of listToCheck) {
+ const operands = (condition.operands = condition.operands.filter(
+ (orOperand) =>
+ !currentOrOperands.operands.some((currentOrOperand) =>
+ isSubset(currentOrOperand, orOperand)
+ )
+ ))
+ if (operands.length === 0) {
+ context.report({
+ node: condition.node,
+ messageId: 'unexpected'
+ })
+ return
+ }
+ }
+ }
+
+ if (vIf) {
+ return
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/no-duplicate-attr-inheritance.js b/lib/rules/no-duplicate-attr-inheritance.js
new file mode 100644
index 000000000..31aef7e44
--- /dev/null
+++ b/lib/rules/no-duplicate-attr-inheritance.js
@@ -0,0 +1,134 @@
+/**
+ * @fileoverview Disable inheritAttrs when using v-bind="$attrs"
+ * @author Hiroki Osame
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/** @param {VElement[]} elements */
+function isConditionalGroup(elements) {
+ if (elements.length < 2) {
+ return false
+ }
+
+ const firstElement = elements[0]
+ const lastElement = elements[elements.length - 1]
+ const inBetweenElements = elements.slice(1, -1)
+
+ return (
+ utils.hasDirective(firstElement, 'if') &&
+ (utils.hasDirective(lastElement, 'else-if') ||
+ utils.hasDirective(lastElement, 'else')) &&
+ inBetweenElements.every((element) => utils.hasDirective(element, 'else-if'))
+ )
+}
+
+/** @param {VElement[]} elements */
+function isMultiRootNodes(elements) {
+ if (elements.length > 1 && !isConditionalGroup(elements)) {
+ return true
+ }
+
+ return false
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description:
+ 'enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"`',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-duplicate-attr-inheritance.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ checkMultiRootNodes: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ noDuplicateAttrInheritance: 'Set "inheritAttrs" to false.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = context.options[0] || {}
+ const checkMultiRootNodes = options.checkMultiRootNodes === true
+
+ /** @type {Literal['value']} */
+ let inheritsAttrs = true
+ /** @type {VReference[]} */
+ const attrsRefs = []
+
+ /** @param {ObjectExpression} node */
+ function processOptions(node) {
+ const inheritAttrsProp = utils.findProperty(node, 'inheritAttrs')
+
+ if (inheritAttrsProp && inheritAttrsProp.value.type === 'Literal') {
+ inheritsAttrs = inheritAttrsProp.value.value
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.executeOnVue(context, processOptions),
+ utils.defineScriptSetupVisitor(context, {
+ onDefineOptionsEnter(node) {
+ if (node.arguments.length === 0) return
+ const define = node.arguments[0]
+ if (define.type !== 'ObjectExpression') return
+ processOptions(define)
+ }
+ }),
+ utils.defineTemplateBodyVisitor(context, {
+ /** @param {VExpressionContainer} node */
+ "VAttribute[directive=true][key.name.name='bind'][key.argument=null] > VExpressionContainer"(
+ node
+ ) {
+ if (!inheritsAttrs) {
+ return
+ }
+ const reference = node.references.find((reference) => {
+ if (reference.variable != null) {
+ // Not vm reference
+ return false
+ }
+ return reference.id.name === '$attrs'
+ })
+
+ if (reference) {
+ attrsRefs.push(reference)
+ }
+ }
+ }),
+ {
+ 'Program:exit'(program) {
+ const element = program.templateBody
+ if (element == null) {
+ return
+ }
+
+ const rootElements = element.children.filter(utils.isVElement)
+
+ if (!checkMultiRootNodes && isMultiRootNodes(rootElements)) return
+
+ if (attrsRefs.length > 0) {
+ for (const attrsRef of attrsRefs) {
+ context.report({
+ node: attrsRef.id,
+ messageId: 'noDuplicateAttrInheritance'
+ })
+ }
+ }
+ }
+ }
+ )
+ }
+}
diff --git a/lib/rules/no-duplicate-attributes.js b/lib/rules/no-duplicate-attributes.js
index cab14a6c8..0c7d3eb55 100644
--- a/lib/rules/no-duplicate-attributes.js
+++ b/lib/rules/no-duplicate-attributes.js
@@ -5,94 +5,37 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
/**
* Get the name of the given attribute node.
- * @param {ASTNode} attribute The attribute node to get.
- * @returns {string} The name of the attribute.
+ * @param {VAttribute | VDirective} attribute The attribute node to get.
+ * @returns {string | null} The name of the attribute.
*/
-function getName (attribute) {
+function getName(attribute) {
if (!attribute.directive) {
return attribute.key.name
}
- if (attribute.key.name === 'bind') {
- return attribute.key.argument || null
+ if (attribute.key.name.name === 'bind') {
+ return (
+ (attribute.key.argument &&
+ attribute.key.argument.type === 'VIdentifier' &&
+ attribute.key.argument.name) ||
+ null
+ )
}
return null
}
-/**
- * Creates AST event handlers for no-duplicate-attributes.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- const options = context.options[0] || {}
- const allowCoexistStyle = options.allowCoexistStyle !== false
- const allowCoexistClass = options.allowCoexistClass !== false
-
- const directiveNames = new Set()
- const attributeNames = new Set()
-
- function isDuplicate (name, isDirective) {
- if ((allowCoexistStyle && name === 'style') || (allowCoexistClass && name === 'class')) {
- return isDirective ? directiveNames.has(name) : attributeNames.has(name)
- }
- return directiveNames.has(name) || attributeNames.has(name)
- }
-
- return utils.defineTemplateBodyVisitor(context, {
- 'VStartTag' () {
- directiveNames.clear()
- attributeNames.clear()
- },
- 'VAttribute' (node) {
- const name = getName(node)
- if (name == null) {
- return
- }
-
- if (isDuplicate(name, node.directive)) {
- context.report({
- node,
- loc: node.loc,
- message: "Duplicate attribute '{{name}}'.",
- data: { name }
- })
- }
-
- if (node.directive) {
- directiveNames.add(name)
- } else {
- attributeNames.add(name)
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'disallow duplication of attributes',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-duplicate-attributes.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-duplicate-attributes.html'
},
- fixable: false,
+ fixable: null,
schema: [
{
@@ -104,8 +47,65 @@ module.exports = {
allowCoexistStyle: {
type: 'boolean'
}
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ duplicateAttribute: "Duplicate attribute '{{name}}'."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = context.options[0] || {}
+ const allowCoexistStyle = options.allowCoexistStyle !== false
+ const allowCoexistClass = options.allowCoexistClass !== false
+
+ /** @type {Set} */
+ const directiveNames = new Set()
+ /** @type {Set} */
+ const attributeNames = new Set()
+
+ /**
+ * @param {string} name
+ * @param {boolean} isDirective
+ */
+ function isDuplicate(name, isDirective) {
+ if (
+ (allowCoexistStyle && name === 'style') ||
+ (allowCoexistClass && name === 'class')
+ ) {
+ return isDirective ? directiveNames.has(name) : attributeNames.has(name)
+ }
+ return directiveNames.has(name) || attributeNames.has(name)
+ }
+
+ return utils.defineTemplateBodyVisitor(context, {
+ VStartTag() {
+ directiveNames.clear()
+ attributeNames.clear()
+ },
+ VAttribute(node) {
+ const name = getName(node)
+ if (name == null) {
+ return
+ }
+
+ if (isDuplicate(name, node.directive)) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'duplicateAttribute',
+ data: { name }
+ })
+ }
+
+ if (node.directive) {
+ directiveNames.add(name)
+ } else {
+ attributeNames.add(name)
}
}
- ]
+ })
}
}
diff --git a/lib/rules/no-empty-component-block.js b/lib/rules/no-empty-component-block.js
new file mode 100644
index 000000000..e7afc59f6
--- /dev/null
+++ b/lib/rules/no-empty-component-block.js
@@ -0,0 +1,104 @@
+/**
+ * @author tyankatsu
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const { isVElement } = require('../utils')
+
+/**
+ * check whether has attribute `src`
+ * @param {VElement} componentBlock
+ */
+function hasAttributeSrc(componentBlock) {
+ const hasAttribute = componentBlock.startTag.attributes.length > 0
+
+ const hasSrc = componentBlock.startTag.attributes.some(
+ (attribute) =>
+ !attribute.directive &&
+ attribute.key.name === 'src' &&
+ attribute.value &&
+ attribute.value.value !== ''
+ )
+
+ return hasAttribute && hasSrc
+}
+
+/**
+ * check whether value under the component block is only whitespaces or break lines
+ * @param {VElement} componentBlock
+ */
+function isValueOnlyWhiteSpacesOrLineBreaks(componentBlock) {
+ return (
+ componentBlock.children.length === 1 &&
+ componentBlock.children[0].type === 'VText' &&
+ !componentBlock.children[0].value.trim()
+ )
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description:
+ 'disallow the `` `\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
new file mode 100644
index 000000000..e670fa3fc
--- /dev/null
+++ b/lib/rules/syntaxes/dynamic-directive-arguments.js
@@ -0,0 +1,27 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+module.exports = {
+ supported: '>=2.6.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ /**
+ * Reports dynamic argument node
+ * @param {VExpressionContainer} dynamicArgument node of dynamic argument
+ * @returns {void}
+ */
+ function reportDynamicArgument(dynamicArgument) {
+ context.report({
+ node: dynamicArgument,
+ messageId: 'forbiddenDynamicDirectiveArguments'
+ })
+ }
+
+ return {
+ '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
new file mode 100644
index 000000000..af7dbcd2f
--- /dev/null
+++ b/lib/rules/syntaxes/scope-attribute.js
@@ -0,0 +1,30 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+module.exports = {
+ deprecated: '2.5.0',
+ supported: '<3.0.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ /**
+ * Reports `scope` node
+ * @param {VDirectiveKey} scopeKey node of `scope`
+ * @returns {void}
+ */
+ function reportScope(scopeKey) {
+ context.report({
+ node: scopeKey,
+ messageId: 'forbiddenScopeAttribute',
+ // fix to use `slot-scope`
+ fix: (fixer) => fixer.replaceText(scopeKey, 'slot-scope')
+ })
+ }
+
+ return {
+ "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
new file mode 100644
index 000000000..27087cb37
--- /dev/null
+++ b/lib/rules/syntaxes/slot-attribute.js
@@ -0,0 +1,175 @@
+/**
+ * @author Yosuke Ota
+ * 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`.
+ * @param {VAttribute} slotAttr node of `slot`
+ * @returns {boolean} `true` if the given node can convert to the `v-slot`
+ */
+ function canConvertFromSlotToVSlot(slotAttr) {
+ if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
+ return false
+ }
+ if (!slotAttr.value) {
+ return true
+ }
+ const slotName = slotAttr.value.value
+ // If other than alphanumeric, underscore and hyphen characters are included it can not be converted.
+ return !/[^\w\-]/u.test(slotName)
+ }
+
+ /**
+ * Checks whether the given node can convert to the `v-slot`.
+ * @param {VDirective} slotAttr node of `v-bind:slot`
+ * @returns {boolean} `true` if the given node can convert to the `v-slot`
+ */
+ function canConvertFromVBindSlotToVSlot(slotAttr) {
+ if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
+ return false
+ }
+
+ if (!slotAttr.value) {
+ return true
+ }
+
+ if (!slotAttr.value.expression) {
+ // parse error or empty expression
+ return false
+ }
+
+ return slotAttr.value.expression.type === 'Identifier'
+ }
+
+ /**
+ * Convert to `v-slot`.
+ * @param {RuleFixer} fixer fixer
+ * @param {VAttribute|VDirective} slotAttr node of `slot`
+ * @param {string | null} slotName name of `slot`
+ * @param {boolean} vBind `true` if `slotAttr` is `v-bind:slot`
+ * @returns {IterableIterator} fix data
+ */
+ function* fixSlotToVSlot(fixer, slotAttr, slotName, vBind) {
+ 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')
+ )
+ let nameArgument = ''
+ if (slotName) {
+ nameArgument = vBind ? `:[${slotName}]` : `:${slotName}`
+ }
+ const scopeValue =
+ scopeAttr && scopeAttr.value
+ ? `=${sourceCode.getText(scopeAttr.value)}`
+ : ''
+
+ const replaceText = `v-slot${nameArgument}${scopeValue}`
+
+ 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`)
+ }
+ }
+ /**
+ * Reports `slot` node
+ * @param {VAttribute} slotAttr node of `slot`
+ * @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',
+ // fix to use `v-slot`
+ *fix(fixer) {
+ if (!canConvertFromSlotToVSlot(slotAttr)) {
+ return
+ }
+ const slotName = slotAttr.value && slotAttr.value.value
+ yield* fixSlotToVSlot(fixer, slotAttr, slotName, false)
+ }
+ })
+ }
+ /**
+ * Reports `v-bind:slot` node
+ * @param {VDirective} slotAttr node of `v-bind:slot`
+ * @returns {void}
+ */
+ function reportVBindSlot(slotAttr) {
+ context.report({
+ node: slotAttr.key,
+ messageId: 'forbiddenSlotAttribute',
+ // fix to use `v-slot`
+ *fix(fixer) {
+ if (!canConvertFromVBindSlotToVSlot(slotAttr)) {
+ return
+ }
+ const slotName =
+ slotAttr.value &&
+ slotAttr.value.expression &&
+ sourceCode.getText(slotAttr.value.expression).trim()
+ yield* fixSlotToVSlot(fixer, slotAttr, slotName, true)
+ }
+ })
+ }
+
+ return {
+ "VAttribute[directive=false][key.name='slot']": reportSlot,
+ "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
new file mode 100644
index 000000000..5f8070498
--- /dev/null
+++ b/lib/rules/syntaxes/slot-scope-attribute.js
@@ -0,0 +1,113 @@
+/**
+ * @author Yosuke Ota
+ * 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',
+ /**
+ * @param {RuleContext} context
+ * @param {object} option
+ * @param {boolean} [option.fixToUpgrade]
+ * @returns {TemplateListener}
+ */
+ 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`.
+ * @param {VStartTag} startTag node of ``
+ * @returns {boolean} `true` if the given node can convert to the `v-slot`
+ */
+ function canConvertToVSlot(startTag) {
+ if (
+ !canConvertToVSlotForElement(startTag.parent, sourceCode, tokenStore)
+ ) {
+ return false
+ }
+
+ const slotAttr = startTag.attributes.find(
+ (attr) => attr.directive === false && attr.key.name === 'slot'
+ )
+ if (slotAttr) {
+ // if the element have `slot` it can not be converted.
+ // Conversion of `slot` is done with `vue/no-deprecated-slot-attribute`.
+ return false
+ }
+
+ const vBindSlotAttr = startTag.attributes.find(
+ (attr) =>
+ attr.directive === true &&
+ attr.key.name.name === 'bind' &&
+ attr.key.argument &&
+ attr.key.argument.type === 'VIdentifier' &&
+ attr.key.argument.name === 'slot'
+ )
+ if (vBindSlotAttr) {
+ // if the element have `v-bind:slot` it can not be converted.
+ // Conversion of `v-bind:slot` is done with `vue/no-deprecated-slot-attribute`.
+ return false
+ }
+ return true
+ }
+
+ /**
+ * Convert to `v-slot`.
+ * @param {RuleFixer} fixer fixer
+ * @param {VDirective} scopeAttr node of `slot-scope`
+ * @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}`
+ 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
+ * @param {VDirective} scopeAttr node of `slot-scope`
+ * @returns {void}
+ */
+ function reportSlotScope(scopeAttr) {
+ context.report({
+ node: scopeAttr.key,
+ messageId: 'forbiddenSlotScopeAttribute',
+ fix(fixer) {
+ if (!fixToUpgrade) {
+ return null
+ }
+ // fix to use `v-slot`
+ const startTag = scopeAttr.parent
+ if (!canConvertToVSlot(startTag)) {
+ return null
+ }
+ return fixSlotScopeToVSlot(fixer, scopeAttr)
+ }
+ })
+ }
+
+ return {
+ "VAttribute[directive=true][key.name.name='slot-scope']": reportSlotScope
+ }
+ }
+}
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
new file mode 100644
index 000000000..645ed375d
--- /dev/null
+++ b/lib/rules/syntaxes/v-bind-prop-modifier-shorthand.js
@@ -0,0 +1,34 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+module.exports = {
+ supported: '>=3.2.0 || >=2.6.0-beta.1 <=2.6.0-beta.3',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ /**
+ * Reports `.prop` shorthand node
+ * @param { VDirectiveKey & { argument: VIdentifier } } bindPropKey node of `.prop` shorthand
+ * @returns {void}
+ */
+ function reportPropModifierShorthand(bindPropKey) {
+ context.report({
+ node: bindPropKey,
+ messageId: 'forbiddenVBindPropModifierShorthand',
+ // fix to use `:x.prop` (downgrade)
+ fix: (fixer) =>
+ fixer.replaceText(
+ bindPropKey,
+ `:${bindPropKey.argument.rawName}.prop`
+ )
+ })
+ }
+
+ return {
+ "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
new file mode 100644
index 000000000..7fb1f862e
--- /dev/null
+++ b/lib/rules/syntaxes/v-is.js
@@ -0,0 +1,27 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'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} vIsAttr node of `v-is`
+ * @returns {void}
+ */
+ function reportVIs(vIsAttr) {
+ context.report({
+ node: vIsAttr.key,
+ messageId: 'forbiddenVIs'
+ })
+ }
+
+ return {
+ "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-argument.js b/lib/rules/syntaxes/v-model-argument.js
new file mode 100644
index 000000000..d1bd59738
--- /dev/null
+++ b/lib/rules/syntaxes/v-model-argument.js
@@ -0,0 +1,23 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+module.exports = {
+ supported: '>=3.0.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ return {
+ /** @param {VDirectiveKey & { argument: VExpressionContainer | VIdentifier }} node */
+ "VAttribute[directive=true] > VDirectiveKey[name.name='model'][argument!=null]"(
+ node
+ ) {
+ context.report({
+ node: node.argument,
+ messageId: 'forbiddenVModelArgument'
+ })
+ }
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/v-model-custom-modifiers.js b/lib/rules/syntaxes/v-model-custom-modifiers.js
new file mode 100644
index 000000000..4b17b47d6
--- /dev/null
+++ b/lib/rules/syntaxes/v-model-custom-modifiers.js
@@ -0,0 +1,29 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const BUILTIN_MODIFIERS = new Set(['lazy', 'number', 'trim'])
+
+module.exports = {
+ supported: '>=3.0.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ return {
+ /** @param {VDirectiveKey} node */
+ "VAttribute[directive=true] > VDirectiveKey[name.name='model'][modifiers.length>0]"(
+ node
+ ) {
+ for (const modifier of node.modifiers) {
+ if (!BUILTIN_MODIFIERS.has(modifier.name)) {
+ context.report({
+ node: modifier,
+ messageId: 'forbiddenVModelCustomModifiers'
+ })
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/v-slot.js b/lib/rules/syntaxes/v-slot.js
new file mode 100644
index 000000000..a6a4b4ebd
--- /dev/null
+++ b/lib/rules/syntaxes/v-slot.js
@@ -0,0 +1,84 @@
+/**
+ * @author Yosuke Ota
+ * 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()
+
+ /**
+ * Convert to `slot` and `slot-scope`.
+ * @param {RuleFixer} fixer fixer
+ * @param {VDirective} vSlotAttr node of `v-slot`
+ * @returns {null|Fix} fix data
+ */
+ function fixVSlotToSlot(fixer, vSlotAttr) {
+ const key = vSlotAttr.key
+ if (key.modifiers.length > 0) {
+ // unknown modifiers
+ return null
+ }
+
+ const attrs = []
+ const argument = key.argument
+ if (argument) {
+ if (argument.type === 'VIdentifier') {
+ const name = argument.rawName
+ attrs.push(`slot="${name}"`)
+ } else if (
+ argument.type === 'VExpressionContainer' &&
+ argument.expression
+ ) {
+ const expression = sourceCode.getText(argument.expression)
+ attrs.push(`:slot="${expression}"`)
+ } else {
+ // unknown or syntax error
+ return null
+ }
+ }
+ const scopedValueNode = vSlotAttr.value
+ if (scopedValueNode) {
+ attrs.push(`slot-scope=${sourceCode.getText(scopedValueNode)}`)
+ }
+ if (attrs.length === 0) {
+ attrs.push('slot') // useless
+ }
+ return fixer.replaceText(vSlotAttr, attrs.join(' '))
+ }
+ /**
+ * Reports `v-slot` node
+ * @param {VDirective} vSlotAttr node of `v-slot`
+ * @returns {void}
+ */
+ function reportVSlot(vSlotAttr) {
+ context.report({
+ node: vSlotAttr.key,
+ messageId: 'forbiddenVSlot',
+ // fix to use `slot` (downgrade)
+ fix(fixer) {
+ if (!canConvertToSlot(vSlotAttr)) {
+ return null
+ }
+ return fixVSlotToSlot(fixer, vSlotAttr)
+ }
+ })
+ }
+
+ return {
+ "VAttribute[directive=true][key.name.name='slot']": reportVSlot
+ }
+ }
+}
diff --git a/lib/rules/template-curly-spacing.js b/lib/rules/template-curly-spacing.js
new file mode 100644
index 000000000..e215108e9
--- /dev/null
+++ b/lib/rules/template-curly-spacing.js
@@ -0,0 +1,12 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const { wrapStylisticOrCoreRule } = require('../utils')
+
+// 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 ed5ae1197..a664a8edf 100644
--- a/lib/rules/this-in-template.js
+++ b/lib/rules/this-in-template.js
@@ -1,33 +1,30 @@
/**
- * @fileoverview enforce usage of `this` in template.
+ * @fileoverview disallow usage of `this` in template.
* @author Armano
*/
'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: 'enforce usage of `this` in template',
- category: 'recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/this-in-template.md'
+ description: 'disallow usage of `this` in template',
+ 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'."
+ }
},
/**
@@ -36,66 +33,99 @@ module.exports = {
* @param {RuleContext} context - The rule context.
* @returns {Object} AST event handlers.
*/
- create (context) {
- const options = context.options[0] !== 'always' ? 'never' : 'always'
- let scope = {
- parent: null,
- nodes: []
- }
+ create(context) {
+ const options = context.options[0] === 'always' ? 'always' : 'never'
+ /**
+ * @typedef {object} ScopeStack
+ * @property {ScopeStack | null} parent
+ * @property {Identifier[]} nodes
+ */
+
+ /** @type {ScopeStack | null} */
+ let scopeStack = null
- return utils.defineTemplateBodyVisitor(context, Object.assign({
- VElement (node) {
- scope = {
- parent: scope,
- nodes: scope.nodes.slice() // make copy
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VElement} node */
+ VElement(node) {
+ scopeStack = {
+ parent: scopeStack,
+ nodes: scopeStack
+ ? [...scopeStack.nodes] // make copy
+ : []
}
if (node.variables) {
for (const variable of node.variables) {
const varNode = variable.id
const name = varNode.name
- if (!scope.nodes.some(node => node.name === name)) { // Prevent adding duplicates
- scope.nodes.push(varNode)
+ if (!scopeStack.nodes.some((node) => node.name === name)) {
+ // Prevent adding duplicates
+ scopeStack.nodes.push(varNode)
}
}
}
},
- 'VElement:exit' (node) {
- scope = scope.parent
- }
- }, options === 'never'
- ? {
- 'VExpressionContainer MemberExpression > ThisExpression' (node) {
- const propertyName = utils.getStaticPropertyName(node.parent.property)
- if (!propertyName ||
- scope.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']
- ) {
- return
- }
-
- context.report({
- node,
- loc: node.loc,
- message: "Unexpected usage of 'this'."
- })
- }
- }
- : {
- 'VExpressionContainer' (node) {
- if (node.references) {
- for (const reference of node.references) {
- if (!scope.nodes.some(el => el.name === reference.id.name)) {
- context.report({
- node: reference.id,
- loc: reference.id.loc,
- message: "Expected 'this'."
- })
+ 'VElement:exit'() {
+ scopeStack = scopeStack && scopeStack.parent
+ },
+ ...(options === 'never'
+ ? {
+ /** @param { ThisExpression & { parent: MemberExpression } } node */
+ 'VExpressionContainer MemberExpression > ThisExpression'(node) {
+ if (!scopeStack) {
+ return
+ }
+ const propertyName = utils.getStaticPropertyName(node.parent)
+ if (
+ !propertyName ||
+ scopeStack.nodes.some((el) => el.name === propertyName) ||
+ RESERVED_NAMES.has(propertyName) || // this.class | this['class']
+ /^\d.*$|[^\w$]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas']
+ ) {
+ return
}
+
+ context.report({
+ node,
+ loc: node.loc,
+ fix(fixer) {
+ // node.parent should be some code like `this.test`, `this?.['result']`
+ return fixer.replaceText(node.parent, propertyName)
+ },
+ messageId: 'unexpected'
+ })
}
}
- }
- }
- ))
+ : {
+ /** @param {VExpressionContainer} node */
+ VExpressionContainer(node) {
+ if (!scopeStack) {
+ return
+ }
+ if (node.parent.type === 'VDirectiveKey') {
+ // We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier.
+ // For example, In `:[this.prop]` case, `:[this` is an argument and `prop]` is a modifier.
+ return
+ }
+ if (node.references) {
+ for (const reference of node.references) {
+ if (
+ !scopeStack.nodes.some(
+ (el) => el.name === reference.id.name
+ )
+ ) {
+ context.report({
+ node: reference.id,
+ loc: reference.id.loc,
+ 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
new file mode 100644
index 000000000..f048b70ef
--- /dev/null
+++ b/lib/rules/use-v-on-exact.js
@@ -0,0 +1,222 @@
+/**
+ * @fileoverview enforce usage of `exact` modifier on `v-on`.
+ * @author Armano
+ */
+'use strict'
+
+/**
+ * @typedef { {name: string, node: VDirectiveKey, modifiers: string[] } } EventDirective
+ */
+
+const utils = require('../utils')
+
+const SYSTEM_MODIFIERS = new Set(['ctrl', 'shift', 'alt', 'meta'])
+const GLOBAL_MODIFIERS = new Set([
+ 'stop',
+ 'prevent',
+ 'capture',
+ 'self',
+ 'once',
+ 'passive',
+ 'native'
+])
+
+/**
+ * Finds and returns all keys for event directives
+ *
+ * @param {VStartTag} startTag Element startTag
+ * @param {SourceCode} sourceCode The source code object.
+ * @returns {EventDirective[]} [{ name, node, modifiers }]
+ */
+function getEventDirectives(startTag, sourceCode) {
+ return utils.getDirectives(startTag, 'on').map((attribute) => ({
+ name: attribute.key.argument
+ ? sourceCode.getText(attribute.key.argument)
+ : '',
+ node: attribute.key,
+ modifiers: attribute.key.modifiers.map((modifier) => modifier.name)
+ }))
+}
+
+/**
+ * Checks whether given modifier is key modifier
+ *
+ * @param {string} modifier
+ * @returns {boolean}
+ */
+function isKeyModifier(modifier) {
+ return !GLOBAL_MODIFIERS.has(modifier) && !SYSTEM_MODIFIERS.has(modifier)
+}
+
+/**
+ * Checks whether given modifier is system one
+ *
+ * @param {string} modifier
+ * @returns {boolean}
+ */
+function isSystemModifier(modifier) {
+ return SYSTEM_MODIFIERS.has(modifier)
+}
+
+/**
+ * Checks whether given any of provided modifiers
+ * has system modifier
+ *
+ * @param {string[]} modifiers
+ * @returns {boolean}
+ */
+function hasSystemModifier(modifiers) {
+ return modifiers.some(isSystemModifier)
+}
+
+/**
+ * Groups all events in object,
+ * with keys represinting each event name
+ *
+ * @param {EventDirective[]} events
+ * @returns { { [key: string]: EventDirective[] } } { click: [], keypress: [] }
+ */
+function groupEvents(events) {
+ /** @type { { [key: string]: EventDirective[] } } */
+ const grouped = {}
+ for (const event of events) {
+ if (!grouped[event.name]) {
+ grouped[event.name] = []
+ }
+ grouped[event.name].push(event)
+ }
+ return grouped
+}
+
+/**
+ * Creates alphabetically sorted string with system modifiers
+ *
+ * @param {string[]} modifiers
+ * @returns {string} e.g. "alt,ctrl,del,shift"
+ */
+function getSystemModifiersString(modifiers) {
+ return modifiers.filter(isSystemModifier).sort().join(',')
+}
+
+/**
+ * Creates alphabetically sorted string with key modifiers
+ *
+ * @param {string[]} modifiers
+ * @returns {string} e.g. "enter,tab"
+ */
+function getKeyModifiersString(modifiers) {
+ return modifiers.filter(isKeyModifier).sort().join(',')
+}
+
+/**
+ * Compares two events based on their modifiers
+ * to detect possible event leakeage
+ *
+ * @param {EventDirective} baseEvent
+ * @param {EventDirective} event
+ * @returns {boolean}
+ */
+function hasConflictedModifiers(baseEvent, event) {
+ if (event.node === baseEvent.node || event.modifiers.includes('exact'))
+ return false
+
+ const eventKeyModifiers = getKeyModifiersString(event.modifiers)
+ const baseEventKeyModifiers = getKeyModifiersString(baseEvent.modifiers)
+
+ if (
+ eventKeyModifiers &&
+ baseEventKeyModifiers &&
+ eventKeyModifiers !== baseEventKeyModifiers
+ )
+ return false
+
+ const eventSystemModifiers = getSystemModifiersString(event.modifiers)
+ const baseEventSystemModifiers = getSystemModifiersString(baseEvent.modifiers)
+
+ return (
+ baseEvent.modifiers.length > 0 &&
+ baseEventSystemModifiers !== eventSystemModifiers &&
+ baseEventSystemModifiers.includes(eventSystemModifiers)
+ )
+}
+
+/**
+ * Searches for events that might conflict with each other
+ *
+ * @param {EventDirective[]} events
+ * @returns {EventDirective[]} conflicted events, without duplicates
+ */
+function findConflictedEvents(events) {
+ /** @type {EventDirective[]} */
+ const conflictedEvents = []
+ for (const event of events) {
+ conflictedEvents.push(
+ ...events
+ .filter((evt) => !conflictedEvents.includes(evt)) // No duplicates
+ .filter(hasConflictedModifiers.bind(null, event))
+ )
+ }
+ return conflictedEvents
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce usage of `exact` modifier on `v-on`',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/use-v-on-exact.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ considerExact: "Consider to use '.exact' modifier."
+ }
+ },
+
+ /**
+ * Creates AST event handlers for use-v-on-exact.
+ *
+ * @param {RuleContext} context - The rule context.
+ * @returns {Object} AST event handlers.
+ */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VStartTag} node */
+ VStartTag(node) {
+ if (node.attributes.length === 0) return
+
+ const isCustomComponent = utils.isCustomComponent(node.parent)
+ let events = getEventDirectives(node, sourceCode)
+
+ if (isCustomComponent) {
+ // For components consider only events with `native` modifier
+ events = events.filter((event) => event.modifiers.includes('native'))
+ }
+
+ const grouppedEvents = groupEvents(events)
+
+ for (const eventName of Object.keys(grouppedEvents)) {
+ const eventsInGroup = grouppedEvents[eventName]
+ const hasEventWithKeyModifier = eventsInGroup.some((event) =>
+ hasSystemModifier(event.modifiers)
+ )
+
+ if (!hasEventWithKeyModifier) continue
+
+ const conflictedEvents = findConflictedEvents(eventsInGroup)
+
+ for (const e of conflictedEvents) {
+ context.report({
+ node: e.node,
+ loc: e.node.loc,
+ messageId: 'considerExact'
+ })
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/v-bind-style.js b/lib/rules/v-bind-style.js
index deafc6e8a..752a8fdf0 100644
--- a/lib/rules/v-bind-style.js
+++ b/lib/rules/v-bind-style.js
@@ -5,60 +5,167 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
+const casing = require('../utils/casing')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
+/**
+ * @typedef { VDirectiveKey & { name: VIdentifier & { name: 'bind' }, argument: VExpressionContainer | VIdentifier } } VBindDirectiveKey
+ * @typedef { VDirective & { key: VBindDirectiveKey } } VBindDirective
+ */
/**
- * Creates AST event handlers for v-bind-style.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
+ * @param {string} name
+ * @returns {string}
*/
-function create (context) {
- const shorthand = context.options[0] !== 'longform'
+function kebabCaseToCamelCase(name) {
+ return casing.isKebabCase(name) ? casing.camelCase(name) : name
+}
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='bind'][key.argument!=null]" (node) {
- if (node.key.shorthand === shorthand) {
- return
- }
+/**
+ * @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
- context.report({
- node,
- loc: node.loc,
- message: shorthand
- ? "Unexpected 'v-bind' before ':'."
- : "Expected 'v-bind' before ':'.",
- fix: (fixer) => shorthand
- ? fixer.removeRange([node.range[0], node.range[0] + 6])
- : fixer.insertTextBefore(node, 'v-bind')
- })
- }
- })
+ if (!attrName || !valueName) return false
+
+ return kebabCaseToCamelCase(attrName) === kebabCaseToCamelCase(valueName)
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
+/**
+ * @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 = {
- create,
meta: {
+ type: 'suggestion',
docs: {
description: 'enforce `v-bind` directive style',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/v-bind-style.md'
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/v-bind-style.html'
},
fixable: 'code',
schema: [
- { enum: ['shorthand', 'longform'] }
- ]
+ { 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'
+
+ /** @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')
+
+ 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
new file mode 100644
index 000000000..2b20cfd0a
--- /dev/null
+++ b/lib/rules/v-for-delimiter-style.js
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview enforce `v-for` directive's delimiter style
+ * @author Flo Edelmann
+ * @copyright 2020 Flo Edelmann. All rights reserved.
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'layout',
+ docs: {
+ description: "enforce `v-for` directive's delimiter style",
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/v-for-delimiter-style.html'
+ },
+ fixable: 'code',
+ schema: [{ enum: ['in', 'of'] }],
+ messages: {
+ expected:
+ "Expected '{{preferredDelimiter}}' instead of '{{usedDelimiter}}' in 'v-for'."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const preferredDelimiter =
+ /** @type {string|undefined} */ (context.options[0]) || 'in'
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VForExpression} node */
+ VForExpression(node) {
+ const sourceCode = context.getSourceCode()
+ const tokenStore =
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+
+ 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
+ }
+
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'expected',
+ data: {
+ preferredDelimiter,
+ usedDelimiter: delimiterToken.value
+ },
+ *fix(fixer) {
+ yield fixer.replaceText(delimiterToken, preferredDelimiter)
+ }
+ })
+ }
+ })
+ }
+}
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
new file mode 100644
index 000000000..c9fac76e8
--- /dev/null
+++ b/lib/rules/v-on-event-hyphenation.js
@@ -0,0 +1,138 @@
+'use strict'
+
+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',
+ categories: ['vue3-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html',
+ defaultOptions: {
+ vue3: ['always', { autofix: true }]
+ }
+ },
+ fixable: 'code',
+ schema: [
+ {
+ enum: ['always', 'never']
+ },
+ {
+ type: 'object',
+ properties: {
+ autofix: { type: 'boolean' },
+ ignore: {
+ type: 'array',
+ items: {
+ allOf: [
+ { type: 'string' },
+ { not: { type: 'string', pattern: ':exit$' } },
+ { not: { type: 'string', pattern: String.raw`^\s*$` } }
+ ]
+ },
+ uniqueItems: true,
+ additionalItems: false
+ },
+ ignoreTags: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true,
+ additionalItems: false
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ 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 */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const option = context.options[0]
+ const optionsPayload = context.options[1]
+ 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.getConverter(
+ useHyphenated ? 'kebab-case' : 'camelCase'
+ )
+
+ /**
+ * @param {VDirective} node
+ * @param {VIdentifier} argument
+ * @param {string} name
+ */
+ function reportIssue(node, argument, name) {
+ const text = sourceCode.getText(node.key)
+
+ context.report({
+ node: node.key,
+ loc: node.loc,
+ messageId: useHyphenated ? 'mustBeHyphenated' : 'cannotBeHyphenated',
+ data: {
+ text
+ },
+ fix:
+ autofix &&
+ // It cannot be converted in snake_case.
+ !name.includes('_')
+ ? (fixer) => fixer.replaceText(argument, caseConverter(name))
+ : null
+ })
+ }
+
+ /**
+ * @param {string} value
+ */
+ function isIgnoredAttribute(value) {
+ const isIgnored = ignoredAttributes.some((attr) => value.includes(attr))
+
+ if (isIgnored) {
+ return true
+ }
+
+ 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) {
+ 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, node.key.argument, 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 fe83564df..eb5950026 100644
--- a/lib/rules/v-on-style.js
+++ b/lib/rules/v-on-style.js
@@ -5,61 +5,48 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for v-on-style.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- const shorthand = context.options[0] !== 'longform'
-
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='on'][key.argument!=null]" (node) {
- if (node.key.shorthand === shorthand) {
- return
- }
-
- const pos = node.range[0]
- context.report({
- node,
- loc: node.loc,
- message: shorthand
- ? "Expected '@' instead of 'v-on:'."
- : "Expected 'v-on:' instead of '@'.",
- fix: (fixer) => shorthand
- ? fixer.replaceTextRange([pos, pos + 5], '@')
- : fixer.replaceTextRange([pos, pos + 1], 'v-on:')
- })
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'suggestion',
docs: {
description: 'enforce `v-on` directive style',
- category: 'strongly-recommended',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/v-on-style.md'
+ 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) {
+ const preferShorthand = context.options[0] !== 'longform'
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='on'][key.argument!=null]"(
+ node
+ ) {
+ const shorthand = node.key.name.rawName === '@'
+ if (shorthand === preferShorthand) {
+ return
+ }
+
+ const pos = node.range[0]
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: preferShorthand ? 'expectedShorthand' : 'expectedLonghand',
+ fix: (fixer) =>
+ preferShorthand
+ ? fixer.replaceTextRange([pos, pos + 5], '@')
+ : fixer.replaceTextRange([pos, pos + 1], 'v-on:')
+ })
+ }
+ })
}
}
diff --git a/lib/rules/v-slot-style.js b/lib/rules/v-slot-style.js
new file mode 100644
index 000000000..964550b98
--- /dev/null
+++ b/lib/rules/v-slot-style.js
@@ -0,0 +1,162 @@
+/**
+ * @author Toru Nagashima
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const { pascalCase } = require('../utils/casing')
+const utils = require('../utils')
+
+/**
+ * @typedef {Object} Options
+ * @property {"shorthand" | "longform" | "v-slot"} atComponent The style for the default slot at a custom component directly.
+ * @property {"shorthand" | "longform" | "v-slot"} default The style for the default slot at a template wrapper.
+ * @property {"shorthand" | "longform"} named The style for named slots at a template wrapper.
+ */
+
+/**
+ * Normalize options.
+ * @param {any} options The raw options to normalize.
+ * @returns {Options} The normalized options.
+ */
+function normalizeOptions(options) {
+ /** @type {Options} */
+ const normalized = {
+ atComponent: 'v-slot',
+ default: 'shorthand',
+ named: 'shorthand'
+ }
+
+ if (typeof options === 'string') {
+ normalized.atComponent =
+ normalized.default =
+ normalized.named =
+ /** @type {"shorthand" | "longform"} */ (options)
+ } else if (options != null) {
+ /** @type {(keyof Options)[]} */
+ const keys = ['atComponent', 'default', 'named']
+ for (const key of keys) {
+ if (options[key] != null) {
+ normalized[key] = options[key]
+ }
+ }
+ }
+
+ return normalized
+}
+
+/**
+ * Get the expected style.
+ * @param {Options} options The options that defined expected types.
+ * @param {VDirective} node The `v-slot` node to check.
+ * @returns {"shorthand" | "longform" | "v-slot"} The expected style.
+ */
+function getExpectedStyle(options, node) {
+ const { argument } = node.key
+
+ if (
+ argument == null ||
+ (argument.type === 'VIdentifier' && argument.name === 'default')
+ ) {
+ const element = node.parent.parent
+ return element.name === 'template' ? options.default : options.atComponent
+ }
+ return options.named
+}
+
+/**
+ * Get the expected style.
+ * @param {VDirective} node The `v-slot` node to check.
+ * @returns {"shorthand" | "longform" | "v-slot"} The expected style.
+ */
+function getActualStyle(node) {
+ const { name, argument } = node.key
+
+ if (name.rawName === '#') {
+ return 'shorthand'
+ }
+ if (argument != null) {
+ return 'longform'
+ }
+ return 'v-slot'
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce `v-slot` directive style',
+ categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
+ url: 'https://eslint.vuejs.org/rules/v-slot-style.html'
+ },
+ fixable: 'code',
+ schema: [
+ {
+ oneOf: [
+ { enum: ['shorthand', 'longform'] },
+ {
+ type: 'object',
+ properties: {
+ atComponent: { enum: ['shorthand', 'longform', 'v-slot'] },
+ default: { enum: ['shorthand', 'longform', 'v-slot'] },
+ named: { enum: ['shorthand', 'longform'] }
+ },
+ additionalProperties: false
+ }
+ ]
+ }
+ ],
+ messages: {
+ expectedShorthand: "Expected '#{{argument}}' instead of '{{actual}}'.",
+ expectedLongform:
+ "Expected 'v-slot:{{argument}}' instead of '{{actual}}'.",
+ expectedVSlot: "Expected 'v-slot' instead of '{{actual}}'."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const options = normalizeOptions(context.options[0])
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='slot']"(node) {
+ const expected = getExpectedStyle(options, node)
+ const actual = getActualStyle(node)
+ if (actual === expected) {
+ return
+ }
+
+ const { name, argument } = node.key
+ /** @type {Range} */
+ const range = [name.range[0], (argument || name).range[1]]
+ const argumentText = argument ? sourceCode.getText(argument) : 'default'
+ context.report({
+ node,
+ messageId: `expected${pascalCase(expected)}`,
+ data: {
+ actual: sourceCode.text.slice(range[0], range[1]),
+ argument: argumentText
+ },
+
+ fix(fixer) {
+ switch (expected) {
+ case 'shorthand': {
+ return fixer.replaceTextRange(range, `#${argumentText}`)
+ }
+ case 'longform': {
+ return fixer.replaceTextRange(range, `v-slot:${argumentText}`)
+ }
+ case 'v-slot': {
+ return fixer.replaceTextRange(range, 'v-slot')
+ }
+ 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
new file mode 100644
index 000000000..c384f6ac0
--- /dev/null
+++ b/lib/rules/valid-next-tick.js
@@ -0,0 +1,211 @@
+/**
+ * @fileoverview enforce valid `nextTick` function calls
+ * @author Flo Edelmann
+ * @copyright 2021 Flo Edelmann. All rights reserved.
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const { findVariable } = require('@eslint-community/eslint-utils')
+
+/**
+ * @param {Identifier} identifier
+ * @param {RuleContext} context
+ * @returns {ASTNode|undefined}
+ */
+function getVueNextTickNode(identifier, context) {
+ // Instance API: this.$nextTick()
+ if (
+ identifier.name === '$nextTick' &&
+ identifier.parent.type === 'MemberExpression' &&
+ utils.isThis(identifier.parent.object, context)
+ ) {
+ return identifier.parent
+ }
+
+ // Vue 2 Global API: Vue.nextTick()
+ if (
+ identifier.name === 'nextTick' &&
+ identifier.parent.type === 'MemberExpression' &&
+ identifier.parent.object.type === 'Identifier' &&
+ identifier.parent.object.name === 'Vue'
+ ) {
+ return identifier.parent
+ }
+
+ // Vue 3 Global API: import { nextTick as nt } from 'vue'; nt()
+ const variable = findVariable(utils.getScope(context, identifier), identifier)
+
+ if (variable != null && variable.defs.length === 1) {
+ const def = variable.defs[0]
+ if (
+ def.type === 'ImportBinding' &&
+ def.node.type === 'ImportSpecifier' &&
+ def.node.imported.type === 'Identifier' &&
+ def.node.imported.name === 'nextTick' &&
+ def.node.parent.type === 'ImportDeclaration' &&
+ def.node.parent.source.value === 'vue'
+ ) {
+ return identifier
+ }
+ }
+
+ return undefined
+}
+
+/**
+ * @param {CallExpression} callExpression
+ * @returns {boolean}
+ */
+function isAwaitedPromise(callExpression) {
+ if (callExpression.parent.type === 'AwaitExpression') {
+ // cases like `await nextTick()`
+ return true
+ }
+
+ if (callExpression.parent.type === 'ReturnStatement') {
+ // 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' &&
+ callExpression.parent.property.type === 'Identifier' &&
+ callExpression.parent.property.name === 'then'
+ ) {
+ // cases like `nextTick().then()`
+ return true
+ }
+
+ if (
+ callExpression.parent.type === 'VariableDeclarator' ||
+ callExpression.parent.type === 'AssignmentExpression'
+ ) {
+ // cases like `let foo = nextTick()` or `foo = nextTick()`
+ return true
+ }
+
+ if (
+ callExpression.parent.type === 'ArrayExpression' &&
+ callExpression.parent.parent.type === 'CallExpression' &&
+ callExpression.parent.parent.callee.type === 'MemberExpression' &&
+ callExpression.parent.parent.callee.object.type === 'Identifier' &&
+ callExpression.parent.parent.callee.object.name === 'Promise' &&
+ callExpression.parent.parent.callee.property.type === 'Identifier'
+ ) {
+ // cases like `Promise.all([nextTick()])`
+ return true
+ }
+
+ return false
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `nextTick` function calls',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-next-tick.html'
+ },
+ fixable: 'code',
+ 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) {
+ return utils.defineVueVisitor(context, {
+ /** @param {Identifier} node */
+ Identifier(node) {
+ const nextTickNode = getVueNextTickNode(node, context)
+ if (!nextTickNode || !nextTickNode.parent) {
+ return
+ }
+
+ let parentNode = nextTickNode.parent
+
+ // skip conditional expressions like `foo ? nextTick : bar`
+ if (parentNode.type === 'ConditionalExpression') {
+ parentNode = parentNode.parent
+ }
+
+ if (
+ parentNode.type === 'CallExpression' &&
+ parentNode.callee !== nextTickNode
+ ) {
+ // cases like `foo.then(nextTick)` are allowed
+ return
+ }
+
+ if (
+ parentNode.type === 'VariableDeclarator' ||
+ parentNode.type === 'AssignmentExpression'
+ ) {
+ // cases like `let foo = nextTick` or `foo = nextTick` are allowed
+ return
+ }
+
+ if (parentNode.type !== 'CallExpression') {
+ context.report({
+ node,
+ messageId: 'shouldBeFunction',
+ fix(fixer) {
+ return fixer.insertTextAfter(node, '()')
+ }
+ })
+ return
+ }
+
+ if (parentNode.arguments.length === 0) {
+ if (!isAwaitedPromise(parentNode)) {
+ context.report({
+ node,
+ messageId: 'missingCallbackOrAwait',
+ suggest: [
+ {
+ messageId: 'addAwait',
+ fix(fixer) {
+ return fixer.insertTextBefore(parentNode, 'await ')
+ }
+ }
+ ]
+ })
+ }
+ return
+ }
+
+ if (parentNode.arguments.length > 1) {
+ context.report({
+ node,
+ messageId: 'tooManyParameters'
+ })
+ return
+ }
+
+ if (isAwaitedPromise(parentNode)) {
+ context.report({
+ node,
+ messageId: 'eitherAwaitOrCallback'
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/valid-template-root.js b/lib/rules/valid-template-root.js
index 756ffecdd..c604a43a8 100644
--- a/lib/rules/valid-template-root.js
+++ b/lib/rules/valid-template-root.js
@@ -5,118 +5,61 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid template root',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-template-root.html'
+ },
+ fixable: null,
+ 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) {
+ const sourceCode = context.getSourceCode()
-/**
- * Creates AST event handlers for valid-template-root.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- const sourceCode = context.getSourceCode()
+ return {
+ /** @param {Program} program */
+ Program(program) {
+ const element = program.templateBody
+ if (element == null) {
+ return
+ }
- return {
- Program (program) {
- const element = program.templateBody
- if (element == null) {
- return
- }
+ const hasSrc = utils.hasAttribute(element, 'src')
+ const rootElements = []
- const hasSrc = utils.hasAttribute(element, 'src')
- const rootElements = []
- let extraText = null
- let extraElement = null
- let vIf = false
- for (const child of element.children) {
- if (child.type === 'VElement') {
- if (rootElements.length === 0 && !hasSrc) {
+ for (const child of element.children) {
+ if (sourceCode.getText(child).trim() !== '') {
rootElements.push(child)
- vIf = utils.hasDirective(child, 'if')
- } else if (vIf && utils.hasDirective(child, 'else-if')) {
- rootElements.push(child)
- } else if (vIf && utils.hasDirective(child, 'else')) {
- rootElements.push(child)
- vIf = false
- } else {
- extraElement = child
}
- } else if (sourceCode.getText(child).trim() !== '') {
- extraText = child
}
- }
- if (hasSrc && (extraText != null || extraElement != null)) {
- context.report({
- node: extraText || extraElement,
- loc: (extraText || extraElement).loc,
- message: "The template root with 'src' attribute is required to be empty."
- })
- } else if (extraText != null) {
- context.report({
- node: extraText,
- loc: extraText.loc,
- message: 'The template root requires an element rather than texts.'
- })
- } else if (extraElement != null) {
- context.report({
- node: extraElement,
- loc: extraElement.loc,
- message: 'The template root requires exactly one element.'
- })
- } else if (rootElements.length === 0 && !hasSrc) {
- context.report({
- node: element,
- loc: element.loc,
- message: 'The template root requires exactly one element.'
- })
- } else {
- for (const element of rootElements) {
- const tag = element.startTag
- const name = element.name
-
- if (name === 'template' || name === 'slot') {
- context.report({
- node: tag,
- loc: tag.loc,
- message: "The template root disallows '<{{name}}>' elements.",
- data: { name }
- })
- }
- if (utils.hasDirective(element, 'for')) {
+ if (hasSrc && rootElements.length > 0) {
+ for (const element of rootElements) {
context.report({
- node: tag,
- loc: tag.loc,
- message: "The template root disallows 'v-for' directives."
+ node: element,
+ loc: element.loc,
+ messageId: 'emptySrc'
})
}
+ } else if (rootElements.length === 0 && !hasSrc) {
+ context.report({
+ node: element,
+ loc: element.loc,
+ messageId: 'noChild'
+ })
}
}
}
}
}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
-module.exports = {
- create,
- meta: {
- docs: {
- description: 'enforce valid template root',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-template-root.md'
- },
- fixable: false,
- schema: []
- }
-}
diff --git a/lib/rules/valid-v-bind-sync.js b/lib/rules/valid-v-bind-sync.js
new file mode 100644
index 000000000..ba04d6117
--- /dev/null
+++ b/lib/rules/valid-v-bind-sync.js
@@ -0,0 +1,159 @@
+/**
+ * @fileoverview enforce valid `.sync` modifier on `v-bind` directives
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * Check whether the given node is valid or not.
+ * @param {VElement} node The element node to check.
+ * @returns {boolean} `true` if the node is valid.
+ */
+function isValidElement(node) {
+ if (!utils.isCustomComponent(node)) {
+ // non Vue-component
+ return false
+ }
+ return true
+}
+
+/**
+ * Check whether the given node is a MemberExpression containing an optional chaining.
+ * e.g.
+ * - `a?.b`
+ * - `a?.b.c`
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a MemberExpression containing an optional chaining.
+ */
+function isOptionalChainingMemberExpression(node) {
+ return (
+ node.type === 'ChainExpression' &&
+ node.expression.type === 'MemberExpression'
+ )
+}
+
+/**
+ * Check whether the given node can be LHS (left-hand side).
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node can be LHS.
+ */
+function isLhs(node) {
+ return node.type === 'Identifier' || node.type === 'MemberExpression'
+}
+
+/**
+ * Check whether the given node is a MemberExpression of a possibly null object.
+ * e.g.
+ * - `(a?.b).c`
+ * - `(null).foo`
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a MemberExpression of a possibly null object.
+ */
+function maybeNullObjectMemberExpression(node) {
+ if (node.type !== 'MemberExpression') {
+ return false
+ }
+ const { object } = node
+ if (object.type === 'ChainExpression') {
+ // `(a?.b).c`
+ return true
+ }
+ if (object.type === 'Literal' && object.value === null && !object.bigint) {
+ // `(null).foo`
+ return true
+ }
+ if (object.type === 'MemberExpression') {
+ return maybeNullObjectMemberExpression(object)
+ }
+ return false
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `.sync` modifier on `v-bind` directives',
+ categories: ['vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-bind-sync.html'
+ },
+ fixable: null,
+ deprecated: true,
+ schema: [],
+ messages: {
+ unexpectedInvalidElement:
+ "'.sync' modifiers aren't supported on <{{name}}> non Vue-components.",
+ unexpectedOptionalChaining:
+ "Optional chaining cannot appear in 'v-bind' with '.sync' modifiers.",
+ unexpectedNonLhsExpression:
+ "'.sync' modifiers require the attribute value which is valid as LHS.",
+ unexpectedNullObject:
+ "'.sync' modifier has potential null object property access.",
+ unexpectedUpdateIterationVariable:
+ "'.sync' modifiers cannot update the iteration variable '{{varName}}' itself."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='bind']"(node) {
+ if (!node.key.modifiers.map((mod) => mod.name).includes('sync')) {
+ return
+ }
+ const element = node.parent.parent
+ const name = element.name
+
+ if (!isValidElement(element)) {
+ context.report({
+ node,
+ messageId: 'unexpectedInvalidElement',
+ data: { name }
+ })
+ }
+
+ if (!node.value) {
+ return
+ }
+
+ const expression = node.value.expression
+ if (!expression) {
+ // Parsing error
+ return
+ }
+ if (isOptionalChainingMemberExpression(expression)) {
+ context.report({
+ node: expression,
+ messageId: 'unexpectedOptionalChaining'
+ })
+ } else if (!isLhs(expression)) {
+ context.report({
+ node: expression,
+ messageId: 'unexpectedNonLhsExpression'
+ })
+ } else if (maybeNullObjectMemberExpression(expression)) {
+ context.report({
+ node: expression,
+ messageId: 'unexpectedNullObject'
+ })
+ }
+
+ for (const reference of node.value.references) {
+ const id = reference.id
+ if (id.parent.type !== 'VExpressionContainer') {
+ continue
+ }
+ const variable = reference.variable
+ if (variable) {
+ context.report({
+ 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 0437dfccf..0f44dad26 100644
--- a/lib/rules/valid-v-bind.js
+++ b/lib/rules/valid-v-bind.js
@@ -5,62 +5,48 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
+const VALID_MODIFIERS = new Set(['prop', 'camel', 'sync', 'attr'])
-const VALID_MODIFIERS = new Set(['prop', 'camel', 'sync'])
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `v-bind` directives',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-bind.html'
+ },
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='bind']"(node) {
+ for (const modifier of node.key.modifiers) {
+ if (!VALID_MODIFIERS.has(modifier.name)) {
+ context.report({
+ node: modifier,
+ messageId: 'unsupportedModifier',
+ data: { name: modifier.name }
+ })
+ }
+ }
-/**
- * Creates AST event handlers for valid-v-bind.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='bind']" (node) {
- for (const modifier of node.key.modifiers) {
- if (!VALID_MODIFIERS.has(modifier)) {
+ if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.key.loc,
- message: "'v-bind' directives don't support the modifier '{{name}}'.",
- data: { name: modifier }
+ messageId: 'expectedValue'
})
}
}
-
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-bind' directives require an attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
-module.exports = {
- create,
- meta: {
- docs: {
- description: 'enforce valid `v-bind` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-bind.md'
- },
- fixable: false,
- schema: []
+ })
}
}
diff --git a/lib/rules/valid-v-cloak.js b/lib/rules/valid-v-cloak.js
index 3d1ce77a9..5ff956818 100644
--- a/lib/rules/valid-v-cloak.js
+++ b/lib/rules/valid-v-cloak.js
@@ -5,63 +5,52 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-cloak.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='cloak']" (node) {
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-cloak' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-cloak' directives require no modifier."
- })
- }
- if (node.value) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-cloak' directives require no attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-cloak` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-cloak.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-cloak.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='cloak']"(node) {
+ 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) {
+ context.report({
+ node: node.value,
+ messageId: 'unexpectedValue'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-else-if.js b/lib/rules/valid-v-else-if.js
index 4af050f5b..b94437466 100644
--- a/lib/rules/valid-v-else-if.js
+++ b/lib/rules/valid-v-else-if.js
@@ -5,86 +5,78 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-else-if.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='else-if']" (node) {
- const element = node.parent.parent
-
- 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."
- })
- }
- 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."
- })
- }
- 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."
- })
- }
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-else-if' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-else-if' directives require no modifier."
- })
- }
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-else-if' directives require that attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-else-if` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-else-if.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-else-if.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='else-if']"(node) {
+ const element = node.parent.parent
+
+ if (!utils.prevElementHasIf(element)) {
+ context.report({
+ node,
+ messageId: 'missingVIf'
+ })
+ }
+ if (utils.hasDirective(element, 'if')) {
+ context.report({
+ node,
+ messageId: 'withVIf'
+ })
+ }
+ if (utils.hasDirective(element, 'else')) {
+ context.report({
+ node,
+ messageId: 'withVElse'
+ })
+ }
+ 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'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-else.js b/lib/rules/valid-v-else.js
index b4eef35a8..f35ac661d 100644
--- a/lib/rules/valid-v-else.js
+++ b/lib/rules/valid-v-else.js
@@ -5,86 +5,78 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-else.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='else']" (node) {
- const element = node.parent.parent
-
- 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' directive."
- })
- }
- 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."
- })
- }
- 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."
- })
- }
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-else' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-else' directives require no modifier."
- })
- }
- if (utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-else' directives require no attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-else` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-else.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-else.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='else']"(node) {
+ const element = node.parent.parent
+
+ if (!utils.prevElementHasIf(element)) {
+ context.report({
+ node,
+ messageId: 'missingVIf'
+ })
+ }
+ if (utils.hasDirective(element, 'if')) {
+ context.report({
+ node,
+ messageId: 'withVIf'
+ })
+ }
+ if (utils.hasDirective(element, 'else-if')) {
+ context.report({
+ node,
+ messageId: 'withVElseIf'
+ })
+ }
+ 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) {
+ context.report({
+ node: node.value,
+ messageId: 'unexpectedValue'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-for.js b/lib/rules/valid-v-for.js
index 62e05d5a4..05183dd7e 100644
--- a/lib/rules/valid-v-for.js
+++ b/lib/rules/valid-v-for.js
@@ -5,32 +5,24 @@
*/
'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 {ASTNode} vFor The attribute node of `v-for` to check.
- * @param {ASTNode} vBindKey The attribute node of `v-bind:key` to check.
+ * @param {VDirective} vFor The attribute node of `v-for` to check.
+ * @param {VDirective} vBindKey The attribute node of `v-bind:key` to check.
* @returns {boolean} `true` if the node is using the variables which are defined by `v-for` directives.
*/
-function isUsingIterationVar (vFor, vBindKey) {
+function isUsingIterationVar(vFor, vBindKey) {
if (vBindKey.value == null) {
return false
}
const references = vBindKey.value.references
const variables = vFor.parent.parent.variables
- return references.some(reference =>
- variables.some(variable =>
- variable.id.name === reference.id.name &&
- variable.kind === 'v-for'
+ return references.some((reference) =>
+ variables.some(
+ (variable) =>
+ variable.id.name === reference.id.name && variable.kind === 'v-for'
)
)
}
@@ -38,19 +30,19 @@ function isUsingIterationVar (vFor, vBindKey) {
/**
* Check the child element in tempalte v-for about `v-bind:key` attributes.
* @param {RuleContext} context The rule context to report.
- * @param {ASTNode} vFor The attribute node of `v-for` to check.
- * @param {ASTNode} child The child node to check.
+ * @param {VDirective} vFor The attribute node of `v-for` to check.
+ * @param {VElement} child The child node to check.
*/
-function checkChildKey (context, vFor, child) {
+function checkChildKey(context, vFor, child) {
const childFor = utils.getDirective(child, 'for')
// if child has v-for, check if parent iterator is used in v-for
if (childFor != null) {
- const childForRefs = childFor.value.references
+ const childForRefs = (childFor.value && childFor.value.references) || []
const variables = vFor.parent.parent.variables
- const usedInFor = childForRefs.some(cref =>
- variables.some(variable =>
- cref.id.name === variable.id.name &&
- variable.kind === 'v-for'
+ const usedInFor = childForRefs.some((cref) =>
+ variables.some(
+ (variable) =>
+ cref.id.name === variable.id.name && variable.kind === 'v-for'
)
)
// if parent iterator is used, skip other checks
@@ -66,11 +58,13 @@ function checkChildKey (context, vFor, child) {
/**
* Check the given element about `v-bind:key` attributes.
* @param {RuleContext} context The rule context to report.
- * @param {ASTNode} vFor The attribute node of `v-for` to check.
- * @param {ASTNode} element The element node to check.
+ * @param {VDirective} vFor The attribute node of `v-for` to check.
+ * @param {VElement} element The element node to check.
*/
-function checkKey (context, vFor, element) {
- if (element.name === 'template') {
+function checkKey(context, vFor, element) {
+ const vBindKey = utils.getDirective(element, 'bind', 'key')
+
+ if (vBindKey == null && element.name === 'template') {
for (const child of element.children) {
if (child.type === 'VElement') {
checkChildKey(context, vFor, child)
@@ -79,120 +73,117 @@ function checkKey (context, vFor, element) {
return
}
- const vBindKey = utils.getDirective(element, 'bind', 'key')
-
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'
})
}
}
-/**
- * Creates AST event handlers for valid-v-for.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- const sourceCode = context.getSourceCode()
-
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='for']" (node) {
- const element = node.parent.parent
-
- checkKey(context, node, element)
-
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-for' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-for' directives require no modifier."
- })
- }
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-for' directives require that attribute value."
- })
- return
- }
-
- const expr = node.value.expression
- if (expr == null) {
- return
- }
- if (expr.type !== 'VForExpression') {
- context.report({
- node: node.value,
- loc: node.value.loc,
- message: "'v-for' directives require the special syntax ' in '."
- })
- return
- }
-
- const lhs = expr.left
- const value = lhs[0]
- const key = lhs[1]
- const index = lhs[2]
-
- if (value === null) {
- context.report({
- node: value || expr,
- loc: value && value.loc,
- message: "Invalid alias ''."
- })
- }
- if (key !== undefined && (!key || key.type !== 'Identifier')) {
- context.report({
- node: key || expr,
- loc: key && key.loc,
- message: "Invalid alias '{{text}}'.",
- 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}}'.",
- data: { text: index ? sourceCode.getText(index) : '' }
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-for` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-for.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-for.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ const sourceCode = context.getSourceCode()
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='for']"(node) {
+ const element = node.parent.parent
+
+ checkKey(context, node, element)
+
+ 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
+ }
+
+ const expr = node.value.expression
+ if (expr == null) {
+ return
+ }
+ if (expr.type !== 'VForExpression') {
+ context.report({
+ node: node.value,
+ messageId: 'unexpectedExpression'
+ })
+ return
+ }
+
+ const lhs = expr.left
+ const value = lhs[0]
+ const key = lhs[1]
+ const index = lhs[2]
+
+ if (value === null) {
+ context.report({
+ node: expr,
+ messageId: 'invalidEmptyAlias'
+ })
+ }
+ if (key !== undefined && (!key || key.type !== 'Identifier')) {
+ context.report({
+ node: key || expr,
+ messageId: 'invalidAlias',
+ data: { text: key ? sourceCode.getText(key) : '' }
+ })
+ }
+ if (index !== undefined && (!index || index.type !== 'Identifier')) {
+ context.report({
+ node: index || expr,
+ 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 427b9b3b1..1df254033 100644
--- a/lib/rules/valid-v-html.js
+++ b/lib/rules/valid-v-html.js
@@ -5,63 +5,52 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-html.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='html']" (node) {
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-html' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-html' directives require no modifier."
- })
- }
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-html' directives require that attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-html` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-html.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-html.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='html']"(node) {
+ 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'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-if.js b/lib/rules/valid-v-if.js
index 18cbf56a9..2e8651b44 100644
--- a/lib/rules/valid-v-if.js
+++ b/lib/rules/valid-v-if.js
@@ -5,79 +5,70 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-if.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='if']" (node) {
- const element = node.parent.parent
-
- 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."
- })
- }
- 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."
- })
- }
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-if' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-if' directives require no modifier."
- })
- }
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-if' directives require that attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-if` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-if.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-if.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='if']"(node) {
+ const element = node.parent.parent
+
+ if (utils.hasDirective(element, 'else')) {
+ context.report({
+ node,
+ messageId: 'withVElse'
+ })
+ }
+ if (utils.hasDirective(element, 'else-if')) {
+ context.report({
+ node,
+ messageId: 'withVElseIf'
+ })
+ }
+ 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'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-is.js b/lib/rules/valid-v-is.js
new file mode 100644
index 000000000..2f97d1fd9
--- /dev/null
+++ b/lib/rules/valid-v-is.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview enforce valid `v-is` directives
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * Check whether the given node is valid or not.
+ * @param {VElement} node The element node to check.
+ * @returns {boolean} `true` if the node is valid.
+ */
+function isValidElement(node) {
+ if (
+ utils.isHtmlElementNode(node) &&
+ !utils.isHtmlWellKnownElementName(node.rawName)
+ ) {
+ // Vue-component
+ return false
+ }
+ return true
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `v-is` directives',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-is.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-is' directives require no argument.",
+ unexpectedModifier: "'v-is' directives require no modifier.",
+ expectedValue: "'v-is' directives require that attribute value.",
+ ownerMustBeHTMLElement:
+ "'v-is' directive must be owned by a native HTML element, but '{{name}}' is not."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ "VAttribute[directive=true][key.name.name='is']"(node) {
+ 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'
+ })
+ }
+
+ const element = node.parent.parent
+
+ if (!isValidElement(element)) {
+ const name = element.name
+ context.report({
+ node,
+ 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 b46d827c4..ca2830dc6 100644
--- a/lib/rules/valid-v-model.js
+++ b/lib/rules/valid-v-model.js
@@ -5,171 +5,229 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
const VALID_MODIFIERS = new Set(['lazy', 'number', 'trim'])
/**
* Check whether the given node is valid or not.
- * @param {ASTNode} node The element node to check.
+ * @param {VElement} node The element node to check.
* @returns {boolean} `true` if the node is valid.
*/
-function isValidElement (node) {
+function isValidElement(node) {
const name = node.name
return (
name === 'input' ||
name === 'select' ||
name === 'textarea' ||
- (
- name !== 'keep-alive' &&
+ (name !== 'keep-alive' &&
name !== 'slot' &&
name !== 'transition' &&
name !== 'transition-group' &&
- utils.isCustomComponent(node)
- )
+ utils.isCustomComponent(node))
)
}
/**
- * Check whether the given node can be LHS.
+ * Check whether the given node is a MemberExpression containing an optional chaining.
+ * e.g.
+ * - `a?.b`
+ * - `a?.b.c`
* @param {ASTNode} node The node to check.
- * @returns {boolean} `true` if the node can be LHS.
+ * @returns {boolean} `true` if the node is a MemberExpression containing an optional chaining.
*/
-function isLhs (node) {
- return node != null && (
- node.type === 'Identifier' ||
- node.type === 'MemberExpression'
+function isOptionalChainingMemberExpression(node) {
+ return (
+ node.type === 'ChainExpression' &&
+ node.expression.type === 'MemberExpression'
)
}
+/**
+ * Check whether the given node can be LHS (left-hand side).
+ * @param {ASTNode} node The node to check.
+ * @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'
+}
+
+/**
+ * Check whether the given node is a MemberExpression of a possibly null object.
+ * e.g.
+ * - `(a?.b).c`
+ * - `(null).foo`
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a MemberExpression of a possibly null object.
+ */
+function maybeNullObjectMemberExpression(node) {
+ if (node.type !== 'MemberExpression') {
+ return false
+ }
+ const { object } = node
+ if (object.type === 'ChainExpression') {
+ // `(a?.b).c`
+ return true
+ }
+ if (object.type === 'Literal' && object.value === null && !object.bigint) {
+ // `(null).foo`
+ return true
+ }
+ if (object.type === 'MemberExpression') {
+ return maybeNullObjectMemberExpression(object)
+ }
+ return false
+}
+
/**
* Get the variable by names.
* @param {string} name The variable name to find.
- * @param {ASTNode} leafNode The node to look up.
- * @returns {Variable|null} The found variable or null.
+ * @param {VElement} leafNode The node to look up.
+ * @returns {VVariable|null} The found variable or null.
*/
-function getVariable (name, leafNode) {
+function getVariable(name, leafNode) {
let node = leafNode
while (node != null) {
const variables = node.variables
- const variable = variables && variables.find(v => v.id.name === name)
+ const variable = variables && variables.find((v) => v.id.name === name)
if (variable != null) {
return variable
}
+ if (node.parent.type === 'VDocumentFragment') {
+ break
+ }
+
node = node.parent
}
return null
}
-/**
- * Creates AST event handlers for valid-v-model.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='model']" (node) {
- const element = node.parent.parent
- const name = element.name
-
- if (!isValidElement(element)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-model' directives aren't supported on <{{name}}> elements.",
- data: { name }
- })
- }
+/** @type {RuleModule} */
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `v-model` directives',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-model.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpectedInvalidElement:
+ "'v-model' directives aren't supported on <{{name}}> elements.",
+ unexpectedInputFile:
+ "'v-model' directives don't support 'file' input type.",
+ unexpectedArgument: "'v-model' directives require no argument.",
+ unexpectedModifier:
+ "'v-model' directives don't support the modifier '{{name}}'.",
+ missingValue: "'v-model' directives require that attribute value.",
+ unexpectedOptionalChaining:
+ "Optional chaining cannot appear in 'v-model' directives.",
+ unexpectedNonLhsExpression:
+ "'v-model' directives require the attribute value which is valid as LHS.",
+ unexpectedNullObject:
+ "'v-model' directive has potential null object property access.",
+ unexpectedUpdateIterationVariable:
+ "'v-model' directives cannot update the iteration variable '{{varName}}' itself."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='model']"(node) {
+ const element = node.parent.parent
+ const name = element.name
+
+ if (!isValidElement(element)) {
+ context.report({
+ node,
+ messageId: 'unexpectedInvalidElement',
+ data: { name }
+ })
+ }
- if (name === 'input' && utils.hasAttribute(element, 'type', 'file')) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-model' directives don't support 'file' input type.",
- data: { name }
- })
- }
+ if (name === 'input' && utils.hasAttribute(element, 'type', 'file')) {
+ context.report({
+ node,
+ messageId: 'unexpectedInputFile'
+ })
+ }
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-model' directives require no argument."
- })
- }
+ if (!utils.isCustomComponent(element)) {
+ if (node.key.argument) {
+ context.report({
+ node: node.key.argument,
+ messageId: 'unexpectedArgument'
+ })
+ }
+
+ for (const modifier of node.key.modifiers) {
+ if (!VALID_MODIFIERS.has(modifier.name)) {
+ context.report({
+ node: modifier,
+ messageId: 'unexpectedModifier',
+ data: { name: modifier.name }
+ })
+ }
+ }
+ }
- for (const modifier of node.key.modifiers) {
- if (!VALID_MODIFIERS.has(modifier)) {
+ if (!node.value || utils.isEmptyValueDirective(node, context)) {
context.report({
node,
- loc: node.loc,
- message: "'v-model' directives don't support the modifier '{{name}}'.",
- data: { name: modifier }
+ messageId: 'missingValue'
})
+ return
}
- }
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-model' directives require that attribute value."
- })
- }
- if (node.value) {
- if (!isLhs(node.value.expression)) {
+ const expression = node.value.expression
+ if (!expression) {
+ // Parsing error
+ return
+ }
+ if (isOptionalChainingMemberExpression(expression)) {
context.report({
- node,
- loc: node.loc,
- message: "'v-model' directives require the attribute value which is valid as LHS."
+ node: expression,
+ messageId: 'unexpectedOptionalChaining'
+ })
+ } else if (!isLhs(expression)) {
+ context.report({
+ node: expression,
+ messageId: 'unexpectedNonLhsExpression'
+ })
+ } else if (maybeNullObjectMemberExpression(expression)) {
+ context.report({
+ node: expression,
+ messageId: 'unexpectedNullObject'
})
}
for (const reference of node.value.references) {
const id = reference.id
- if (id.parent.type === 'MemberExpression' || id.parent.type === 'BinaryExpression') {
+ if (id.parent.type !== 'VExpressionContainer') {
continue
}
const variable = getVariable(id.name, element)
if (variable != null) {
context.report({
- node,
- loc: node.loc,
- message: "'v-model' directives cannot update the iteration variable 'x' itself."
+ node: expression,
+ messageId: 'unexpectedUpdateIterationVariable',
+
+ data: { varName: id.name }
})
}
}
}
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
-module.exports = {
- create,
- meta: {
- docs: {
- description: 'enforce valid `v-model` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-model.md'
- },
- fixable: false,
- schema: []
+ })
}
}
diff --git a/lib/rules/valid-v-on.js b/lib/rules/valid-v-on.js
index d44fc7200..82612d496 100644
--- a/lib/rules/valid-v-on.js
+++ b/lib/rules/valid-v-on.js
@@ -5,68 +5,139 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
+const keyAliases = require('../utils/key-aliases.json')
const VALID_MODIFIERS = new Set([
- 'stop', 'prevent', 'capture', 'self', 'ctrl', 'shift', 'alt', 'meta',
- 'native', 'once', 'left', 'right', 'middle', 'passive', 'esc', 'tab',
- 'enter', 'space', 'up', 'left', 'right', 'down', 'delete', 'exact'
-])
-const VERB_MODIFIERS = new Set([
- 'stop', 'prevent'
+ 'stop',
+ 'prevent',
+ 'capture',
+ 'self',
+ 'ctrl',
+ 'shift',
+ 'alt',
+ 'meta',
+ 'native',
+ 'once',
+ 'left',
+ 'right',
+ 'middle',
+ 'passive',
+ 'esc',
+ 'tab',
+ 'enter',
+ 'space',
+ 'up',
+ 'left',
+ 'right',
+ 'down',
+ 'delete',
+ 'exact'
])
+const VERB_MODIFIERS = new Set(['stop', 'prevent'])
+// https://www.w3.org/TR/uievents-key/
+const KEY_ALIASES = new Set(keyAliases)
/**
- * Creates AST event handlers for valid-v-on.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
+ * @param {VIdentifier} modifierNode
+ * @param {Set} customModifiers
*/
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='on']" (node) {
- for (const modifier of node.key.modifiers) {
- if (!VALID_MODIFIERS.has(modifier) && !Number.isInteger(parseInt(modifier, 10))) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-on' directives don't support the modifier '{{modifier}}'.",
- data: { modifier }
- })
- }
- }
- if (!utils.hasAttributeValue(node) && !node.key.modifiers.some(VERB_MODIFIERS.has, VERB_MODIFIERS)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-on' directives require that attribute value or verb modifiers."
- })
- }
- }
- })
+function isValidModifier(modifierNode, customModifiers) {
+ const modifier = modifierNode.name
+ return (
+ // built-in aliases
+ VALID_MODIFIERS.has(modifier) ||
+ // keyCode
+ Number.isInteger(Number.parseInt(modifier, 10)) ||
+ // keyAlias (an Unicode character)
+ [...modifier].length === 1 ||
+ // keyAlias (special keys)
+ KEY_ALIASES.has(modifier) ||
+ // custom modifiers
+ customModifiers.has(modifier)
+ )
}
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-on` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-on.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-on.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ modifiers: {
+ type: 'array'
+ }
+ },
+ 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) {
+ const options = context.options[0] || {}
+ /** @type {Set} */
+ const customModifiers = new Set(options.modifiers || [])
+ const sourceCode = context.getSourceCode()
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='on']"(node) {
+ for (const modifier of node.key.modifiers) {
+ if (!isValidModifier(modifier, customModifiers)) {
+ context.report({
+ node: modifier,
+ messageId: 'unsupportedModifier',
+ data: { modifier: modifier.name }
+ })
+ }
+ }
+
+ if (
+ (!node.value || !node.value.expression) &&
+ !node.key.modifiers.some((modifier) =>
+ VERB_MODIFIERS.has(modifier.name)
+ )
+ ) {
+ if (node.value && !utils.isEmptyValueDirective(node, context)) {
+ const valueText = sourceCode.getText(node.value)
+ let innerText = valueText
+ if (
+ (valueText[0] === '"' || valueText[0] === "'") &&
+ valueText[0] === valueText[valueText.length - 1]
+ ) {
+ // quoted
+ innerText = valueText.slice(1, -1)
+ }
+ if (/^\w+$/.test(innerText)) {
+ context.report({
+ node: node.value,
+ messageId: 'avoidKeyword',
+ data: { value: valueText }
+ })
+ }
+ } else {
+ context.report({
+ node,
+ messageId: 'expectedValueOrVerb'
+ })
+ }
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-once.js b/lib/rules/valid-v-once.js
index 19aa48239..9d3d8bf8f 100644
--- a/lib/rules/valid-v-once.js
+++ b/lib/rules/valid-v-once.js
@@ -5,63 +5,52 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-once.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='once']" (node) {
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-once' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-once' directives require no modifier."
- })
- }
- if (node.value) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-once' directives require no attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-once` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-once.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-once.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='once']"(node) {
+ 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) {
+ context.report({
+ node: node.value,
+ messageId: 'unexpectedValue'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-pre.js b/lib/rules/valid-v-pre.js
index 4ff08c32b..0505b9cb5 100644
--- a/lib/rules/valid-v-pre.js
+++ b/lib/rules/valid-v-pre.js
@@ -5,63 +5,52 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-pre.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='pre']" (node) {
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-pre' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-pre' directives require no modifier."
- })
- }
- if (node.value) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-pre' directives require no attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-pre` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-pre.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-pre.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='pre']"(node) {
+ 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) {
+ context.report({
+ node: node.value,
+ messageId: 'unexpectedValue'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-show.js b/lib/rules/valid-v-show.js
index 96d43cd3a..1da617806 100644
--- a/lib/rules/valid-v-show.js
+++ b/lib/rules/valid-v-show.js
@@ -5,63 +5,60 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-show.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='show']" (node) {
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-show' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-show' directives require no modifier."
- })
- }
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-show' directives require that attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-show` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-show.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-show.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='show']"(node) {
+ 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'
+ })
+ }
+ if (node.parent.parent.name === 'template') {
+ context.report({
+ node,
+ messageId: 'unexpectedTemplate'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js
new file mode 100644
index 000000000..d5c0efd1e
--- /dev/null
+++ b/lib/rules/valid-v-slot.js
@@ -0,0 +1,402 @@
+/**
+ * @author Toru Nagashima
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef { { expr: VForExpression, variables: VVariable[] } } VSlotVForVariables
+ */
+
+/**
+ * Get all `v-slot` directives on a given element.
+ * @param {VElement} node The VElement node to check.
+ * @returns {VDirective[]} The array of `v-slot` directives.
+ */
+function getSlotDirectivesOnElement(node) {
+ return utils.getDirectives(node, 'slot')
+}
+
+/**
+ * Get all `v-slot` directives on the children of a given element.
+ * @param {VElement} node The VElement node to check.
+ * @returns {VDirective[][]}
+ * The array of the group of `v-slot` directives.
+ * The group bundles `v-slot` directives of element sequence which is connected
+ * by `v-if`/`v-else-if`/`v-else`.
+ */
+function getSlotDirectivesOnChildren(node) {
+ /** @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)
+ }
+ }
+
+ return groups
+}
+
+/**
+ * Get the normalized name of a given `v-slot` directive node with modifiers after `v-slot:` directive.
+ * @param {VDirective} node The `v-slot` directive node.
+ * @param {SourceCode} sourceCode The source code.
+ * @returns {string} The normalized name.
+ */
+function getNormalizedName(node, sourceCode) {
+ if (node.key.argument == null) {
+ return 'default'
+ }
+ return node.key.modifiers.length === 0
+ ? sourceCode.getText(node.key.argument)
+ : sourceCode.text.slice(node.key.argument.range[0], node.key.range[1])
+}
+
+/**
+ * Get all `v-slot` directives which are distributed to the same slot as a given `v-slot` directive node.
+ * @param {VDirective[][]} vSlotGroups The result of `getAllNamedSlotElements()`.
+ * @param {VDirective} currentVSlot The current `v-slot` directive node.
+ * @param {VSlotVForVariables | null} currentVSlotVForVars The current `v-for` variables.
+ * @param {SourceCode} sourceCode The source code.
+ * @param {ParserServices.TokenStore} tokenStore The token store.
+ * @returns {VDirective[][]} The array of the group of `v-slot` directives.
+ */
+function filterSameSlot(
+ vSlotGroups,
+ currentVSlot,
+ currentVSlotVForVars,
+ sourceCode,
+ tokenStore
+) {
+ const currentName = getNormalizedName(currentVSlot, sourceCode)
+ return vSlotGroups
+ .map((vSlots) =>
+ vSlots.filter((vSlot) => {
+ if (getNormalizedName(vSlot, sourceCode) !== currentName) {
+ return false
+ }
+ const vForExpr = getVSlotVForVariableIfUsingIterationVars(
+ vSlot,
+ utils.getDirective(vSlot.parent.parent, 'for')
+ )
+ if (!currentVSlotVForVars || !vForExpr) {
+ return !currentVSlotVForVars && !vForExpr
+ }
+ if (
+ !equalVSlotVForVariables(currentVSlotVForVars, vForExpr, tokenStore)
+ ) {
+ return false
+ }
+ //
+ return true
+ })
+ )
+ .filter((slots) => slots.length > 0)
+}
+
+/**
+ * Determines whether the two given `v-slot` variables are considered to be equal.
+ * @param {VSlotVForVariables} a First element.
+ * @param {VSlotVForVariables} b Second element.
+ * @param {ParserServices.TokenStore} tokenStore The token store.
+ * @returns {boolean} `true` if the elements are considered to be equal.
+ */
+function equalVSlotVForVariables(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)
+ }
+}
+
+/**
+ * Gets the `v-for` directive and variable that provide the variables used by the given` v-slot` directive.
+ * @param {VDirective} vSlot The current `v-slot` directive node.
+ * @param {VDirective | null} [vFor] The current `v-for` directive node.
+ * @returns { VSlotVForVariables | null } The VSlotVForVariable.
+ */
+function getVSlotVForVariableIfUsingIterationVars(vSlot, vFor) {
+ const expr =
+ vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
+ const variables =
+ expr && getUsingIterationVars(vSlot.key.argument, vSlot.parent.parent)
+ return expr && variables && variables.length > 0 ? { expr, variables } : null
+}
+
+/**
+ * Gets iterative variables if a given argument node is using iterative variables that the element defined.
+ * @param {VExpressionContainer|VIdentifier|null} argument The argument node to check.
+ * @param {VElement} element The element node which has the argument.
+ * @returns {VVariable[]} The argument node is using iteration variables.
+ */
+function getUsingIterationVars(argument, element) {
+ const vars = []
+ if (argument && argument.type === 'VExpressionContainer') {
+ for (const { variable } of argument.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
+}
+
+/**
+ * Check whether a given argument node is using an scope variable that the directive defined.
+ * @param {VDirective} vSlot The `v-slot` directive to check.
+ * @returns {boolean} `true` if that argument node is using a scope variable the directive defined.
+ */
+function isUsingScopeVar(vSlot) {
+ const argument = vSlot.key.argument
+ const value = vSlot.value
+
+ if (argument && value && argument.type === 'VExpressionContainer') {
+ for (const { variable } of argument.references) {
+ if (
+ variable != null &&
+ variable.kind === 'scope' &&
+ variable.id.range[0] > value.range[0] &&
+ variable.id.range[1] < value.range[1]
+ ) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+/**
+ * If `allowModifiers` option is set to `true`, check whether a given argument node has invalid modifiers like `v-slot.foo`.
+ * Otherwise, check whether a given argument node has at least one modifier.
+ * @param {VDirective} vSlot The `v-slot` directive to check.
+ * @param {boolean} allowModifiers `allowModifiers` option in context.
+ * @return {boolean} `true` if that argument node has invalid modifiers like `v-slot.foo`.
+ */
+function hasInvalidModifiers(vSlot, allowModifiers) {
+ return allowModifiers
+ ? vSlot.key.argument == null && vSlot.key.modifiers.length > 0
+ : vSlot.key.modifiers.length > 0
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `v-slot` directives',
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-slot.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ allowModifiers: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ ownerMustBeCustomElement:
+ "'v-slot' directive must be owned by a custom element, but '{{name}}' is not.",
+ namedSlotMustBeOnTemplate:
+ "Named slots must use '' on a custom element.",
+ defaultSlotMustBeOnTemplate:
+ "Default slot must use '' on a custom element when there are other named slots.",
+ disallowDuplicateSlotsOnElement:
+ "An element cannot have multiple 'v-slot' directives.",
+ disallowDuplicateSlotsOnChildren:
+ "An element cannot have multiple '' elements which are distributed to the same slot.",
+ disallowArgumentUseSlotParams:
+ "Dynamic argument of 'v-slot' directive cannot use that slot parameter.",
+ disallowAnyModifier: "'v-slot' directive doesn't support any modifier.",
+ requireAttributeValue:
+ "'v-slot' directive on a custom element requires that attribute value."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const tokenStore =
+ sourceCode.parserServices.getTemplateBodyTokenStore &&
+ sourceCode.parserServices.getTemplateBodyTokenStore()
+ const options = context.options[0] || {}
+ const allowModifiers = options.allowModifiers === true
+
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='slot']"(node) {
+ const isDefaultSlot =
+ node.key.argument == null ||
+ (node.key.argument.type === 'VIdentifier' &&
+ node.key.argument.name === 'default')
+ const element = node.parent.parent
+ const parentElement = element.parent
+ const ownerElement =
+ element.name === 'template' ? parentElement : element
+ if (ownerElement.type === 'VDocumentFragment') {
+ return
+ }
+ const vSlotsOnElement = getSlotDirectivesOnElement(element)
+ const vSlotGroupsOnChildren = getSlotDirectivesOnChildren(ownerElement)
+
+ // Verify location.
+ if (!utils.isCustomComponent(ownerElement)) {
+ context.report({
+ node,
+ messageId: 'ownerMustBeCustomElement',
+ data: { name: ownerElement.rawName }
+ })
+ }
+ if (!isDefaultSlot && element.name !== 'template') {
+ context.report({
+ node,
+ messageId: 'namedSlotMustBeOnTemplate'
+ })
+ }
+ if (ownerElement === element && vSlotGroupsOnChildren.length > 0) {
+ context.report({
+ node,
+ messageId: 'defaultSlotMustBeOnTemplate'
+ })
+ }
+
+ // Verify duplication.
+ if (vSlotsOnElement.length >= 2 && vSlotsOnElement[0] !== node) {
+ // E.g.,
+ context.report({
+ node,
+ messageId: 'disallowDuplicateSlotsOnElement'
+ })
+ }
+ if (ownerElement === parentElement) {
+ const vFor = utils.getDirective(element, 'for')
+ const vSlotVForVar = getVSlotVForVariableIfUsingIterationVars(
+ node,
+ vFor
+ )
+ const vSlotGroupsOfSameSlot = filterSameSlot(
+ vSlotGroupsOnChildren,
+ node,
+ vSlotVForVar,
+ sourceCode,
+ tokenStore
+ )
+ if (
+ vSlotGroupsOfSameSlot.length >= 2 &&
+ !vSlotGroupsOfSameSlot[0].includes(node)
+ ) {
+ // E.g.,
+ //
+ context.report({
+ node,
+ messageId: 'disallowDuplicateSlotsOnChildren'
+ })
+ }
+ if (vFor && !vSlotVForVar) {
+ // E.g.,
+ context.report({
+ node,
+ messageId: 'disallowDuplicateSlotsOnChildren'
+ })
+ }
+ }
+
+ // Verify argument.
+ if (isUsingScopeVar(node)) {
+ context.report({
+ node,
+ messageId: 'disallowArgumentUseSlotParams'
+ })
+ }
+
+ // Verify modifiers.
+ if (hasInvalidModifiers(node, allowModifiers)) {
+ // 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'
+ })
+ }
+
+ // Verify value.
+ if (
+ ownerElement === element &&
+ isDefaultSlot &&
+ (!node.value ||
+ utils.isEmptyValueDirective(node, context) ||
+ utils.isEmptyExpressionValueDirective(node, context))
+ ) {
+ context.report({
+ node,
+ messageId: 'requireAttributeValue'
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/valid-v-text.js b/lib/rules/valid-v-text.js
index 9e897e100..b41e7f2b3 100644
--- a/lib/rules/valid-v-text.js
+++ b/lib/rules/valid-v-text.js
@@ -5,63 +5,52 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
const utils = require('../utils')
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Creates AST event handlers for valid-v-text.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
-function create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name='text']" (node) {
- if (node.key.argument) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-text' directives require no argument."
- })
- }
- if (node.key.modifiers.length > 0) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-text' directives require no modifier."
- })
- }
- if (!utils.hasAttributeValue(node)) {
- context.report({
- node,
- loc: node.loc,
- message: "'v-text' directives require that attribute value."
- })
- }
- }
- })
-}
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
module.exports = {
- create,
meta: {
+ type: 'problem',
docs: {
description: 'enforce valid `v-text` directives',
- category: 'essential',
- url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/valid-v-text.md'
+ categories: ['vue3-essential', 'vue2-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-text.html'
},
- fixable: false,
- schema: []
+ fixable: null,
+ 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) {
+ return utils.defineTemplateBodyVisitor(context, {
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='text']"(node) {
+ 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'
+ })
+ }
+ }
+ })
}
}
diff --git a/lib/utils/casing.js b/lib/utils/casing.js
index 2baca2356..a47e978cd 100644
--- a/lib/utils/casing.js
+++ b/lib/utils/casing.js
@@ -1,31 +1,88 @@
-const assert = require('assert')
-
-const invalidChars = /[^a-zA-Z0-9:]+/g
+/**
+ * Capitalize a string.
+ * @param {string} str
+ */
+function capitalize(str) {
+ return str.charAt(0).toUpperCase() + str.slice(1)
+}
+/**
+ * Checks whether the given string has symbols.
+ * @param {string} str
+ */
+function hasSymbols(str) {
+ return /[!"#%&'()*+,./:;<=>?@[\\\]^`{|}]/u.exec(str) // without " ", "$", "-" and "_"
+}
+/**
+ * Checks whether the given string has upper.
+ * @param {string} str
+ */
+function hasUpper(str) {
+ return /[A-Z]/u.exec(str)
+}
/**
* Convert text to kebab-case
* @param {string} str Text to be converted
* @return {string}
*/
-function kebabCase (str) {
+function kebabCase(str) {
+ return str
+ .replace(/_/gu, '-')
+ .replace(/\B([A-Z])/gu, '-$1')
+ .toLowerCase()
+}
+
+/**
+ * Checks whether the given string is kebab-case.
+ * @param {string} str
+ */
+function isKebabCase(str) {
+ return (
+ !hasUpper(str) &&
+ !hasSymbols(str) &&
+ !str.startsWith('-') && // starts with hyphen is not kebab-case
+ !/_|--|\s/u.test(str)
+ )
+}
+
+/**
+ * Convert text to snake_case
+ * @param {string} str Text to be converted
+ * @return {string}
+ */
+function snakeCase(str) {
return str
- .replace(/([a-z])([A-Z])/g, match => match[0] + '-' + match[1])
- .replace(invalidChars, '-')
+ .replace(/\B([A-Z])/gu, '_$1')
+ .replace(/-/gu, '_')
.toLowerCase()
}
+/**
+ * Checks whether the given string is snake_case.
+ * @param {string} str
+ */
+function isSnakeCase(str) {
+ return !hasUpper(str) && !hasSymbols(str) && !/-|__|\s/u.test(str)
+}
+
/**
* Convert text to camelCase
* @param {string} str Text to be converted
* @return {string} Converted string
*/
-function camelCase (str) {
- return str
- .replace(/_/g, (_, index) => index === 0 ? _ : '-')
- .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) =>
- index === 0 ? letter.toLowerCase() : letter.toUpperCase()
- )
- .replace(invalidChars, '')
+function camelCase(str) {
+ if (isPascalCase(str)) {
+ return str.charAt(0).toLowerCase() + str.slice(1)
+ }
+ return str.replace(/[-_](\w)/gu, (_, c) => (c ? c.toUpperCase() : ''))
+}
+
+/**
+ * Checks whether the given string is camelCase.
+ * @param {string} str
+ */
+function isCamelCase(str) {
+ return !hasSymbols(str) && !/^[A-Z]/u.test(str) && !/-|_|\s/u.test(str)
}
/**
@@ -33,34 +90,90 @@ function camelCase (str) {
* @param {string} str Text to be converted
* @return {string} Converted string
*/
-function pascalCase (str) {
- return str
- .replace(/_/g, (_, index) => index === 0 ? _ : '-')
- .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => letter.toUpperCase())
- .replace(invalidChars, '')
+function pascalCase(str) {
+ return capitalize(camelCase(str))
+}
+
+/**
+ * Checks whether the given string is PascalCase.
+ * @param {string} str
+ */
+function isPascalCase(str) {
+ return !hasSymbols(str) && !/^[a-z]/u.test(str) && !/-|_|\s/u.test(str)
}
const convertersMap = {
'kebab-case': kebabCase,
- 'camelCase': camelCase,
- 'PascalCase': pascalCase
+ snake_case: snakeCase,
+ camelCase,
+ PascalCase: pascalCase
+}
+
+const checkersMap = {
+ 'kebab-case': isKebabCase,
+ snake_case: isSnakeCase,
+ camelCase: isCamelCase,
+ PascalCase: isPascalCase
+}
+/**
+ * Return case checker
+ * @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of checker to return ('camelCase', 'kebab-case', 'PascalCase')
+ * @return {isKebabCase|isCamelCase|isPascalCase|isSnakeCase}
+ */
+function getChecker(name) {
+ return checkersMap[name] || isPascalCase
+}
+
+/**
+ * Return case converter
+ * @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
+ * @return {kebabCase|camelCase|pascalCase|snakeCase}
+ */
+function getConverter(name) {
+ return convertersMap[name] || pascalCase
}
module.exports = {
- allowedCaseOptions: [
- 'camelCase',
- 'kebab-case',
- 'PascalCase'
- ],
+ allowedCaseOptions: ['camelCase', 'kebab-case', 'PascalCase'],
/**
* Return case converter
* @param {string} name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
- * @return {kebabCase|camelCase|pascalCase}
+ * @return {kebabCase|camelCase|pascalCase|snakeCase}
*/
- getConverter (name) {
- assert(typeof name === 'string')
+ getConverter,
- return convertersMap[name] || pascalCase
- }
+ /**
+ * Return case checker
+ * @param {string} name type of checker to return ('camelCase', 'kebab-case', 'PascalCase')
+ * @return {isKebabCase|isCamelCase|isPascalCase|isSnakeCase}
+ */
+ getChecker,
+
+ /**
+ * Return case exact converter.
+ * If the converted result is not the correct case, the original value is returned.
+ * @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
+ * @return {kebabCase|camelCase|pascalCase|snakeCase}
+ */
+ getExactConverter(name) {
+ const converter = getConverter(name)
+ const checker = getChecker(name)
+ return (str) => {
+ const result = converter(str)
+ return checker(result) ? result : str /* cannot convert */
+ }
+ },
+
+ camelCase,
+ pascalCase,
+ kebabCase,
+ snakeCase,
+
+ isCamelCase,
+ isPascalCase,
+ isKebabCase,
+ isSnakeCase,
+
+ capitalize
}
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
new file mode 100644
index 000000000..63a3f7162
--- /dev/null
+++ b/lib/utils/deprecated-html-elements.json
@@ -0,0 +1,31 @@
+[
+ "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
new file mode 100644
index 000000000..53f7df5ae
--- /dev/null
+++ b/lib/utils/html-comments.js
@@ -0,0 +1,251 @@
+/**
+ * @typedef { { exceptions?: string[] } } CommentParserConfig
+ * @typedef { (comment: ParsedHTMLComment) => void } HTMLCommentVisitor
+ * @typedef { { includeDirectives?: boolean } } CommentVisitorOption
+ *
+ * @typedef { Token & { type: 'HTMLCommentOpen' } } HTMLCommentOpen
+ * @typedef { Token & { type: 'HTMLCommentOpenDecoration' } } HTMLCommentOpenDecoration
+ * @typedef { Token & { type: 'HTMLCommentValue' } } HTMLCommentValue
+ * @typedef { Token & { type: 'HTMLCommentClose' } } HTMLCommentClose
+ * @typedef { Token & { type: 'HTMLCommentCloseDecoration' } } HTMLCommentCloseDecoration
+ * @typedef { { open: HTMLCommentOpen, openDecoration: HTMLCommentOpenDecoration | null, value: HTMLCommentValue | null, closeDecoration: HTMLCommentCloseDecoration | null, close: HTMLCommentClose } } ParsedHTMLComment
+ */
+const utils = require('./')
+
+const COMMENT_DIRECTIVE = /^\s*eslint-(?:en|dis)able/
+const IE_CONDITIONAL_IF = /^\[if\s+/
+const IE_CONDITIONAL_ENDIF = /\[endif]$/
+
+/** @type { 'HTMLCommentOpen' } */
+const TYPE_HTML_COMMENT_OPEN = 'HTMLCommentOpen'
+/** @type { 'HTMLCommentOpenDecoration' } */
+const TYPE_HTML_COMMENT_OPEN_DECORATION = 'HTMLCommentOpenDecoration'
+/** @type { 'HTMLCommentValue' } */
+const TYPE_HTML_COMMENT_VALUE = 'HTMLCommentValue'
+/** @type { 'HTMLCommentClose' } */
+const TYPE_HTML_COMMENT_CLOSE = 'HTMLCommentClose'
+/** @type { 'HTMLCommentCloseDecoration' } */
+const TYPE_HTML_COMMENT_CLOSE_DECORATION = 'HTMLCommentCloseDecoration'
+
+/**
+ * @param {HTMLComment} comment
+ * @returns {boolean}
+ */
+function isCommentDirective(comment) {
+ return COMMENT_DIRECTIVE.test(comment.value)
+}
+
+/**
+ * @param {HTMLComment} comment
+ * @returns {boolean}
+ */
+function isIEConditionalComment(comment) {
+ return (
+ IE_CONDITIONAL_IF.test(comment.value) ||
+ IE_CONDITIONAL_ENDIF.test(comment.value)
+ )
+}
+
+/**
+ * Define HTML comment parser
+ *
+ * @param {SourceCode} sourceCode The source code instance.
+ * @param {CommentParserConfig | null} config The config.
+ * @returns { (node: Token) => (ParsedHTMLComment | null) } HTML comment parser.
+ */
+function defineParser(sourceCode, config) {
+ config = config || {}
+
+ const exceptions = config.exceptions || []
+
+ /**
+ * Get a open decoration string from comment contents.
+ * @param {string} contents comment contents
+ * @returns {string} decoration string
+ */
+ function getOpenDecoration(contents) {
+ let decoration = ''
+ for (const exception of exceptions) {
+ const length = exception.length
+ let index = 0
+ while (contents.startsWith(exception, index)) {
+ index += length
+ }
+ const exceptionLength = index
+ if (decoration.length < exceptionLength) {
+ decoration = contents.slice(0, exceptionLength)
+ }
+ }
+ return decoration
+ }
+
+ /**
+ * Get a close decoration string from comment contents.
+ * @param {string} contents comment contents
+ * @returns {string} decoration string
+ */
+ function getCloseDecoration(contents) {
+ let decoration = ''
+ for (const exception of exceptions) {
+ const length = exception.length
+ let index = contents.length
+ while (contents.endsWith(exception, index)) {
+ index -= length
+ }
+ const exceptionLength = contents.length - index
+ if (decoration.length < exceptionLength) {
+ decoration = contents.slice(index)
+ }
+ }
+ return decoration
+ }
+
+ /**
+ * Parse HTMLComment.
+ * @param {Token} node a comment token
+ * @returns {ParsedHTMLComment | null} the result of HTMLComment tokens.
+ */
+ return function parseHTMLComment(node) {
+ if (node.type !== 'HTMLComment') {
+ // Is not HTMLComment
+ return null
+ }
+
+ const htmlCommentText = sourceCode.getText(node)
+
+ if (
+ !htmlCommentText.startsWith('')
+ ) {
+ // Is not normal HTML Comment
+ // e.g. Error Code: "abrupt-closing-of-empty-comment", "incorrectly-closed-comment"
+ return null
+ }
+
+ let valueText = htmlCommentText.slice(4, -3)
+ const openDecorationText = getOpenDecoration(valueText)
+ valueText = valueText.slice(openDecorationText.length)
+ const firstCharIndex = valueText.search(/\S/)
+ const beforeSpace =
+ firstCharIndex >= 0 ? valueText.slice(0, firstCharIndex) : valueText
+ valueText = valueText.slice(beforeSpace.length)
+
+ const closeDecorationText = getCloseDecoration(valueText)
+ if (closeDecorationText) {
+ valueText = valueText.slice(0, -closeDecorationText.length)
+ }
+ const lastCharIndex = valueText.search(/\S\s*$/)
+ const afterSpace =
+ lastCharIndex >= 0 ? valueText.slice(lastCharIndex + 1) : valueText
+ if (afterSpace) {
+ valueText = valueText.slice(0, -afterSpace.length)
+ }
+
+ let tokenIndex = node.range[0]
+ /**
+ * @param {string} type
+ * @param {string} value
+ * @returns {any}
+ */
+ const createToken = (type, value) => {
+ /** @type {Range} */
+ const range = [tokenIndex, tokenIndex + value.length]
+ tokenIndex = range[1]
+ /** @type {SourceLocation} */
+ let loc
+ return {
+ type,
+ value,
+ range,
+ get loc() {
+ if (loc) {
+ return loc
+ }
+ return (loc = {
+ start: sourceCode.getLocFromIndex(range[0]),
+ end: sourceCode.getLocFromIndex(range[1])
+ })
+ }
+ }
+ }
+
+ /** @type {HTMLCommentOpen} */
+ const open = createToken(TYPE_HTML_COMMENT_OPEN, '')
+
+ return {
+ /** HTML comment open (``) */
+ closeDecoration,
+ /** HTML comment close (`-->`) */
+ close
+ }
+ }
+}
+
+/**
+ * Define HTML comment visitor
+ *
+ * @param {RuleContext} context The rule context.
+ * @param {CommentParserConfig | null} config The config.
+ * @param {HTMLCommentVisitor} visitHTMLComment The HTML comment visitor.
+ * @param {CommentVisitorOption} [visitorOption] The option for visitor.
+ * @returns {RuleListener} HTML comment visitor.
+ */
+function defineVisitor(context, config, visitHTMLComment, visitorOption) {
+ return {
+ Program(node) {
+ visitorOption = visitorOption || {}
+ if (utils.hasInvalidEOF(node)) {
+ return
+ }
+ if (!node.templateBody) {
+ return
+ }
+ const parse = defineParser(context.getSourceCode(), config)
+
+ for (const comment of node.templateBody.comments) {
+ if (comment.type !== 'HTMLComment') {
+ continue
+ }
+ if (!visitorOption.includeDirectives && isCommentDirective(comment)) {
+ // ignore directives
+ continue
+ }
+ if (isIEConditionalComment(comment)) {
+ // ignore IE conditional
+ continue
+ }
+
+ const tokens = parse(comment)
+ if (tokens) {
+ visitHTMLComment(tokens)
+ }
+ }
+ }
+ }
+}
+
+module.exports = {
+ defineVisitor
+}
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 1d02ba11b..d2879b120 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -4,57 +4,129 @@
*/
'use strict'
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
+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')
-const assert = require('assert')
-
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
+/**
+ * @typedef {import('../../typings/eslint-plugin-vue/util-types/node').HasLocation} HasLocation
+ * @typedef { { type: string } & HasLocation } MaybeNode
+ */
-const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionExpression', 'AssignmentExpression', 'AssignmentPattern', 'AwaitExpression', 'BinaryExpression', 'BlockStatement', 'BreakStatement', 'CallExpression', 'CatchClause', 'ClassBody', 'ClassDeclaration', 'ClassExpression', 'ConditionalExpression', 'ContinueStatement', 'DebuggerStatement', 'DoWhileStatement', 'EmptyStatement', 'ExperimentalRestProperty', 'ExperimentalSpreadProperty', 'ExportAllDeclaration', 'ExportDefaultDeclaration', 'ExportNamedDeclaration', 'ExportSpecifier', 'ExpressionStatement', 'ForInStatement', 'ForOfStatement', 'ForStatement', 'FunctionDeclaration', 'FunctionExpression', 'Identifier', 'IfStatement', 'ImportDeclaration', 'ImportDefaultSpecifier', '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', 'VForExpression', 'VIdentifier', 'VLiteral', 'VOnExpression', 'VStartTag', 'VText'])
-const LT_CHAR = /[\r\n\u2028\u2029]/
-const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g
+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 = new Set(['pre', 'textarea'])
+/**
+ * @typedef {object} IndentOptions
+ * @property { " " | "\t" } IndentOptions.indentChar
+ * @property {number} IndentOptions.indentSize
+ * @property {number} IndentOptions.baseIndent
+ * @property {number} IndentOptions.attribute
+ * @property {object} IndentOptions.closeBracket
+ * @property {number} IndentOptions.closeBracket.startTag
+ * @property {number} IndentOptions.closeBracket.endTag
+ * @property {number} IndentOptions.closeBracket.selfClosingTag
+ * @property {number} IndentOptions.switchCase
+ * @property {boolean} IndentOptions.alignAttributesVertically
+ * @property {string[]} IndentOptions.ignores
+ */
+/**
+ * @typedef {object} IndentUserOptions
+ * @property { " " | "\t" } [IndentUserOptions.indentChar]
+ * @property {number} [IndentUserOptions.indentSize]
+ * @property {number} [IndentUserOptions.baseIndent]
+ * @property {number} [IndentUserOptions.attribute]
+ * @property {IndentOptions['closeBracket'] | number} [IndentUserOptions.closeBracket]
+ * @property {number} [IndentUserOptions.switchCase]
+ * @property {boolean} [IndentUserOptions.alignAttributesVertically]
+ * @property {string[]} [IndentUserOptions.ignores]
+ */
/**
* Normalize options.
* @param {number|"tab"|undefined} type The type of indentation.
- * @param {Object} options Other options.
- * @param {Object} defaultOptions The default value of options.
- * @returns {{indentChar:" "|"\t",indentSize:number,baseIndent:number,attribute:number,closeBracket:number,switchCase:number,alignAttributesVertically:boolean,ignores:string[]}} Normalized options.
+ * @param {IndentUserOptions} options Other options.
+ * @param {Partial} defaultOptions The default value of options.
+ * @returns {IndentOptions} Normalized options.
*/
-function parseOptions (type, options, defaultOptions) {
- const ret = Object.assign({
- indentChar: ' ',
- indentSize: 2,
- baseIndent: 0,
- attribute: 1,
- closeBracket: 0,
- switchCase: 0,
- alignAttributesVertically: true,
- ignores: []
- }, defaultOptions)
+function parseOptions(type, options, defaultOptions) {
+ /** @type {IndentOptions} */
+ const ret = Object.assign(
+ {
+ indentChar: ' ',
+ indentSize: 2,
+ baseIndent: 0,
+ attribute: 1,
+ closeBracket: {
+ startTag: 0,
+ endTag: 0,
+ selfClosingTag: 0
+ },
+ switchCase: 0,
+ alignAttributesVertically: true,
+ ignores: []
+ },
+ defaultOptions
+ )
if (Number.isSafeInteger(type)) {
- ret.indentSize = type
+ ret.indentSize = Number(type)
} else if (type === 'tab') {
ret.indentChar = '\t'
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)) {
- ret.closeBracket = options.closeBracket
+ const num = Number(options.closeBracket)
+ ret.closeBracket = {
+ startTag: num,
+ endTag: num,
+ selfClosingTag: num
+ }
+ } else if (options.closeBracket) {
+ ret.closeBracket = Object.assign(
+ {
+ startTag: 0,
+ endTag: 0,
+ selfClosingTag: 0
+ },
+ options.closeBracket
+ )
}
- if (Number.isSafeInteger(options.switchCase)) {
+ if (options.switchCase != null && Number.isSafeInteger(options.switchCase)) {
ret.switchCase = options.switchCase
}
@@ -68,149 +140,14 @@ function parseOptions (type, options, defaultOptions) {
return ret
}
-/**
- * Check whether the given token is an arrow.
- * @param {Token} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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 whitespace.
- * @param {Token} 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} 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' || token.type.endsWith('Comment'))
-}
-
-/**
- * Check whether the given token is a comment.
- * @param {Token} 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' && !token.type.endsWith('Comment')
-}
-
-/**
- * Get the last element.
- * @param {Array} xs The array to get the last element.
- * @returns {any|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 {Node} 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 {Node[]} 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) {
+function isBeginningOfLine(node, index, nodes) {
if (node != null) {
for (let i = index - 1; i >= 0; --i) {
const prevNode = nodes[i]
@@ -229,46 +166,67 @@ function isBeginningOfLine (node, index, nodes) {
* @param {Token} token The token to check.
* @returns {boolean} `true` if the token is a closing token.
*/
-function isClosingToken (token) {
- return token != null && (
- token.type === 'HTMLEndTagOpen' ||
- token.type === 'VExpressionEnd' ||
- (
- token.type === 'Punctuator' &&
- (
- token.value === ')' ||
- token.value === '}' ||
- token.value === ']'
- )
- )
+function isClosingToken(token) {
+ return (
+ token != null &&
+ (token.type === 'HTMLEndTagOpen' ||
+ token.type === 'VExpressionEnd' ||
+ (token.type === 'Punctuator' &&
+ (token.value === ')' || token.value === '}' || token.value === ']')))
)
}
+/**
+ * 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.
*
* @param {RuleContext} context The rule context.
- * @param {TokenStore} tokenStore The token store object to get tokens.
- * @param {Object} defaultOptions The default value of options.
- * @returns {object} AST event handlers.
+ * @param {ParserServices.TokenStore | SourceCode} tokenStore The token store object to get tokens.
+ * @param {Partial} defaultOptions The default value of options.
+ * @returns {NodeListener} AST event handlers.
*/
-module.exports.defineVisitor = function create (context, tokenStore, defaultOptions) {
- const options = parseOptions(context.options[0], context.options[1] || {}, defaultOptions)
+module.exports.defineVisitor = function create(
+ context,
+ tokenStore,
+ defaultOptions
+) {
+ if (!context.getFilename().endsWith('.vue')) return {}
+
+ const options = parseOptions(
+ context.options[0],
+ context.options[1] || {},
+ 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()
/**
* Set offset to the given tokens.
- * @param {Token|Token[]} token The token to set.
+ * @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}
*/
- function setOffset (token, offset, baseToken) {
- assert(baseToken != null, "'baseToken' should not be null or undefined.")
-
+ function setOffset(token, offset, baseToken) {
+ if (!token || token === baseToken) {
+ return
+ }
if (Array.isArray(token)) {
for (const t of token) {
+ if (!t || t === baseToken) continue
offsets.set(t, {
baseToken,
offset,
@@ -286,34 +244,98 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
}
}
+ /**
+ * 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.
* @returns {void}
*/
- function setBaseline (token, hardTabAdditional) {
+ function setBaseline(token) {
const offsetInfo = offsets.get(token)
if (offsetInfo != null) {
offsetInfo.baseline = true
}
}
+ /**
+ * Sets preformatted tokens to the given element node.
+ * @param {VElement} node The node to set.
+ * @returns {void}
+ */
+ function setPreformattedTokens(node) {
+ const endToken =
+ (node.endTag && tokenStore.getFirstToken(node.endTag)) ||
+ tokenStore.getTokenAfter(node)
+
+ /** @type {SourceCode.CursorWithSkipOptions} */
+ const cursorOptions = {
+ includeComments: true,
+ filter: (token) =>
+ token != null &&
+ (token.type === 'HTMLText' ||
+ token.type === 'HTMLRCDataText' ||
+ token.type === 'HTMLTagOpen' ||
+ token.type === 'HTMLEndTagOpen' ||
+ token.type === 'HTMLComment')
+ }
+ 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)
+ }
+
/**
* Get the first and last tokens of the given node.
* If the node is parenthesized, this gets the outermost parentheses.
- * @param {Node} 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) {
- borderOffset |= 0
+ function getFirstAndLastTokens(node, borderOffset = 0) {
+ borderOffset = Math.trunc(borderOffset)
let firstToken = tokenStore.getFirstToken(node)
let lastToken = tokenStore.getLastToken(node)
// Get the outermost left parenthesis if it's parenthesized.
let t, u
- while ((t = tokenStore.getTokenBefore(firstToken)) != null && (u = tokenStore.getTokenAfter(lastToken)) != null && isLeftParen(t) && isRightParen(u) && t.range[0] >= borderOffset) {
+ while (
+ (t = tokenStore.getTokenBefore(firstToken)) != null &&
+ (u = tokenStore.getTokenAfter(lastToken)) != null &&
+ isOpeningParenToken(t) &&
+ isClosingParenToken(u) &&
+ t.range[0] >= borderOffset
+ ) {
firstToken = t
lastToken = u
}
@@ -325,54 +347,78 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
* Process the given node list.
* The first node is offsetted from the given left token.
* Rest nodes are adjusted to the first node.
- * @param {Node[]} nodeList The node to process.
- * @param {Node|null} leftToken The left parenthesis token.
- * @param {Node|null} rightToken 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 {Node} [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.
+ * @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}
*/
- function processNodeList (nodeList, leftToken, rightToken, offset, alignVertically) {
+ function processNodeList(nodeList, left, right, offset, alignVertically) {
let t
+ const leftToken = left && tokenStore.getFirstToken(left)
+ const rightToken = right && tokenStore.getFirstToken(right)
- if (nodeList.length >= 1) {
- let lastToken = leftToken
+ 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)
+ const elementTokens = getFirstAndLastTokens(
+ node,
+ lastToken == null ? 0 : lastToken.range[1]
+ )
- // Collect related tokens.
- // Commas between this and the previous, and the first token of this node.
+ // Collect comma/comment tokens between the last token of the previous node and the first token of this node.
if (lastToken != null) {
t = lastToken
- while ((t = tokenStore.getTokenAfter(t)) != null && t.range[1] <= elementTokens.firstToken.range[0]) {
- if (lastToken.loc.end.line !== t.loc.start.line) {
+ while (
+ (t = tokenStore.getTokenAfter(t, ITERATION_OPTS)) != null &&
+ t.range[1] <= elementTokens.firstToken.range[0]
+ ) {
+ if (baseToken == null) {
+ alignTokensBeforeBaseToken.push(t)
+ } else {
alignTokens.push(t)
}
}
}
- alignTokens.push(elementTokens.firstToken)
- // Save the last token to find tokens between the next token.
+ if (baseToken == null) {
+ baseToken = elementTokens.firstToken
+ } else {
+ alignTokens.push(elementTokens.firstToken)
+ }
+
+ // Save the last token to find tokens between this node and the next node.
lastToken = elementTokens.lastToken
}
- // Check trailing commas.
+ // Check trailing commas and comments.
if (rightToken != null && lastToken != null) {
t = lastToken
- while ((t = tokenStore.getTokenAfter(t)) != null && t.range[1] <= rightToken.range[0]) {
- alignTokens.push(t)
+ while (
+ (t = tokenStore.getTokenAfter(t, ITERATION_OPTS)) != null &&
+ t.range[1] <= rightToken.range[0]
+ ) {
+ if (baseToken == null) {
+ alignTokensBeforeBaseToken.push(t)
+ } else {
+ alignTokens.push(t)
+ }
}
}
// Set offsets.
- const baseToken = alignTokens.shift()
+ if (leftToken != null) {
+ setOffset(alignTokensBeforeBaseToken, offset, leftToken)
+ }
if (baseToken != null) {
// Set offset to the first token.
if (leftToken != null) {
@@ -384,7 +430,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
setBaseline(baseToken)
}
- if (alignVertically === false) {
+ if (alignVertically === false && leftToken != null) {
// Align tokens relatively to the left token.
setOffset(alignTokens, offset, leftToken)
} else {
@@ -394,7 +440,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
}
}
- if (rightToken != null) {
+ if (rightToken != null && leftToken != null) {
setOffset(rightToken, 0, leftToken)
}
}
@@ -402,43 +448,56 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
/**
* Process the given node as body.
* The body node maybe a block statement or an expression node.
- * @param {Node} node The body node to process.
+ * @param {ASTNode} node The body node to process.
* @param {Token} baseToken The base token.
* @returns {void}
*/
- function processMaybeBlock (node, baseToken) {
+ 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 = []
-
- 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)
+ }
}
/**
* Find the head of chaining nodes.
- * @param {Node} node The start node to find the head.
+ * @param {ASTNode} node The start node to find the head.
* @returns {Token} The head token of the chain.
*/
- function getChainHeadToken (node) {
+ function getChainHeadToken(node) {
const type = node.type
- while (node.parent.type === type) {
+ while (node.parent && node.parent.type === type) {
+ const prevToken = tokenStore.getTokenBefore(node)
+ if (isOpeningParenToken(prevToken)) {
+ // The chaining is broken by parentheses.
+ break
+ }
node = node.parent
}
return tokenStore.getFirstToken(node)
@@ -454,36 +513,53 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
* - An expression of SequenceExpression
*
* @param {Token} token The token to check.
- * @param {Node} belongingNode The node that the token is belonging to.
+ * @param {ASTNode} 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) {
+ function isBeginningOfElement(token, belongingNode) {
let node = belongingNode
- while (node != null) {
+ while (node != null && node.parent != null) {
const parent = node.parent
- const t = parent && parent.type
- if (t != null && (t.endsWith('Statement') || t.endsWith('Declaration'))) {
+ if (
+ parent.type.endsWith('Statement') ||
+ parent.type.endsWith('Declaration')
+ ) {
return parent.range[0] === token.range[0]
}
- if (t === 'VExpressionContainer') {
- return node.range[0] === token.range[0]
+ if (parent.type === 'VExpressionContainer') {
+ if (node.range[0] !== token.range[0]) {
+ return false
+ }
+ const prevToken = tokenStore.getTokenBefore(belongingNode)
+ if (isOpeningParenToken(prevToken)) {
+ // It is not the first token because it is enclosed in parentheses.
+ return false
+ }
+ return true
}
- if (t === 'CallExpression' || t === 'NewExpression') {
- const openParen = tokenStore.getTokenAfter(parent.callee, isNotRightParen)
- return parent.arguments.some(param =>
- getFirstAndLastTokens(param, openParen.range[1]).firstToken.range[0] === token.range[0]
+ if (parent.type === 'CallExpression' || parent.type === 'NewExpression') {
+ const openParen = /** @type {Token} */ (
+ tokenStore.getTokenAfter(parent.callee, isNotClosingParenToken)
+ )
+ return parent.arguments.some(
+ (param) =>
+ getFirstAndLastTokens(param, openParen.range[1]).firstToken
+ .range[0] === token.range[0]
)
}
- if (t === 'ArrayExpression') {
- return parent.elements.some(element =>
- element != null &&
- getFirstAndLastTokens(element).firstToken.range[0] === token.range[0]
+ if (parent.type === 'ArrayExpression') {
+ return parent.elements.some(
+ (element) =>
+ element != null &&
+ getFirstAndLastTokens(element).firstToken.range[0] ===
+ token.range[0]
)
}
- if (t === 'SequenceExpression') {
- return parent.expressions.some(expr =>
- getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0]
+ if (parent.type === 'SequenceExpression') {
+ return parent.expressions.some(
+ (expr) =>
+ getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0]
)
}
@@ -495,44 +571,51 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
/**
* Set the base indentation to a given top-level AST node.
- * @param {Node} node The node to set.
+ * @param {ASTNode} node The node to set.
* @param {number} expectedIndent The number of expected indent.
* @returns {void}
*/
- function processTopLevelNode (node, expectedIndent) {
+ function processTopLevelNode(node, expectedIndent) {
const token = tokenStore.getFirstToken(node)
const offsetInfo = offsets.get(token)
- if (offsetInfo != null) {
- offsetInfo.expectedIndent = expectedIndent
+ if (offsetInfo == null) {
+ offsets.set(token, {
+ baseToken: null,
+ offset: 0,
+ baseline: false,
+ expectedIndent
+ })
} else {
- offsets.set(token, { baseToken: null, offset: 0, baseline: false, expectedIndent })
+ offsetInfo.expectedIndent = expectedIndent
}
}
/**
* Ignore all tokens of the given node.
- * @param {Node} node The node to ignore.
+ * @param {ASTNode} node The node to ignore.
* @returns {void}
*/
- function ignore (node) {
+ function ignore(node) {
for (const token of tokenStore.getTokens(node)) {
offsets.delete(token)
+ ignoreTokens.add(token)
}
}
/**
* Define functions to ignore nodes into the given visitor.
- * @param {Object} visitor The visitor to define functions to ignore nodes.
- * @returns {Object} The visitor.
+ * @param {NodeListener} visitor The visitor to define functions to ignore nodes.
+ * @returns {NodeListener} The visitor.
*/
- function processIgnores (visitor) {
+ function processIgnores(visitor) {
for (const ignorePattern of options.ignores) {
const key = `${ignorePattern}:exit`
if (visitor.hasOwnProperty(key)) {
const handler = visitor[key]
- visitor[key] = function (node) {
- const ret = handler.apply(this, arguments)
+ visitor[key] = function (node, ...args) {
+ // @ts-expect-error
+ const ret = handler.call(this, node, ...args)
ignore(node)
return ret
}
@@ -547,31 +630,43 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
/**
* Calculate correct indentation of the line of the given tokens.
* @param {Token[]} tokens Tokens which are on the same line.
- * @returns {number} Correct indentation. If it failed to calculate then `Number.MAX_SAFE_INTEGER`.
+ * @returns { { expectedIndent: number, expectedBaseIndent: number } |null } Correct indentation. If it failed to calculate then `null`.
*/
- function getExpectedIndent (tokens) {
- let expectedIndent = Number.MAX_SAFE_INTEGER
+ 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) {
- expectedIndent = Math.min(expectedIndent, offsetInfo.expectedIndent)
- } else {
+ if (offsetInfo.expectedIndent == null) {
const baseOffsetInfo = offsets.get(offsetInfo.baseToken)
- if (baseOffsetInfo != null && baseOffsetInfo.expectedIndent != null && (i === 0 || !baseOffsetInfo.baseline)) {
- expectedIndent = Math.min(expectedIndent, baseOffsetInfo.expectedIndent + offsetInfo.offset * options.indentSize)
+ if (
+ baseOffsetInfo != null &&
+ baseOffsetInfo.expectedIndent != null &&
+ (i === 0 || !baseOffsetInfo.baseline)
+ ) {
+ expectedIndents.push(
+ baseOffsetInfo.expectedIndent +
+ offsetInfo.offset * options.indentSize
+ )
if (baseOffsetInfo.baseline) {
break
}
}
+ } else {
+ expectedIndents.push(offsetInfo.expectedIndent)
}
}
}
+ if (expectedIndents.length === 0) {
+ return null
+ }
- return expectedIndent
+ return {
+ expectedIndent: expectedIndents[0],
+ expectedBaseIndent: Math.min(...expectedIndents)
+ }
}
/**
@@ -579,7 +674,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
* @param {Token} firstToken The first token on a line.
* @returns {string} The text of indentation part.
*/
- function getIndentText (firstToken) {
+ function getIndentText(firstToken) {
const text = sourceCode.text
let i = firstToken.range[0] - 1
@@ -593,29 +688,33 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
/**
* Define the function which fixes the problem.
* @param {Token} token The token to fix.
- * @param {number} actualIndent The number of actual indentaion.
+ * @param {number} actualIndent The number of actual indentation.
* @param {number} expectedIndent The number of expected indentation.
- * @returns {Function} The defined function.
+ * @returns { (fixer: RuleFixer) => Fix } The defined function.
*/
- function defineFix (token, actualIndent, expectedIndent) {
+ function defineFix(token, actualIndent, expectedIndent) {
if (token.type === 'Block' && token.loc.start.line !== token.loc.end.line) {
// Fix indentation in multiline block comments.
- const lines = sourceCode.getText(token).match(LINES)
+ const lines = sourceCode.getText(token).match(LINES) || []
const firstLine = lines.shift()
- if (lines.every(l => BLOCK_COMMENT_PREFIX.test(l))) {
- return fixer => {
+ if (lines.every((l) => BLOCK_COMMENT_PREFIX.test(l))) {
+ return (fixer) => {
+ /** @type {Range} */
const range = [token.range[0] - actualIndent, token.range[1]]
const indent = options.indentChar.repeat(expectedIndent)
return fixer.replaceTextRange(
range,
- `${indent}${firstLine}${lines.map(l => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`)).join('')}`
+ `${indent}${firstLine}${lines
+ .map((l) => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`))
+ .join('')}`
)
}
}
}
- return fixer => {
+ return (fixer) => {
+ /** @type {Range} */
const range = [token.range[0] - actualIndent, token.range[0]]
const indent = options.indentChar.repeat(expectedIndent)
return fixer.replaceTextRange(range, indent)
@@ -626,26 +725,35 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
* 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|undefined} optionalExpectedIndent The optional expected indentation.
+ * @param {[number, number?]} [optionalExpectedIndents] The optional expected indentation.
* @returns {void}
*/
- function validateCore (token, expectedIndent, optionalExpectedIndent) {
+ function validateCore(token, expectedIndent, optionalExpectedIndents) {
const line = token.loc.start.line
- const actualIndent = token.loc.start.column
const indentText = getIndentText(token)
- const unit = (options.indentChar === '\t' ? 'tab' : 'space')
- for (let i = 0; i < indentText.length; ++i) {
- if (indentText[i] !== options.indentChar) {
+ // If there is no line terminator after the `
diff --git a/tests/fixtures/script-indent/array-expression-03.vue b/tests/fixtures/script-indent/array-expression-03.vue
new file mode 100644
index 000000000..0e15d8aad
--- /dev/null
+++ b/tests/fixtures/script-indent/array-expression-03.vue
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/array-expression-04.vue b/tests/fixtures/script-indent/array-expression-04.vue
new file mode 100644
index 000000000..1530c24b5
--- /dev/null
+++ b/tests/fixtures/script-indent/array-expression-04.vue
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/bigint-01.vue b/tests/fixtures/script-indent/bigint-01.vue
new file mode 100644
index 000000000..4e8e6c20b
--- /dev/null
+++ b/tests/fixtures/script-indent/bigint-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/class-fields-private-methods-01.vue b/tests/fixtures/script-indent/class-fields-private-methods-01.vue
new file mode 100644
index 000000000..46c6ae67c
--- /dev/null
+++ b/tests/fixtures/script-indent/class-fields-private-methods-01.vue
@@ -0,0 +1,64 @@
+
+
diff --git a/tests/fixtures/script-indent/class-fields-private-properties-01.vue b/tests/fixtures/script-indent/class-fields-private-properties-01.vue
new file mode 100644
index 000000000..c935f1904
--- /dev/null
+++ b/tests/fixtures/script-indent/class-fields-private-properties-01.vue
@@ -0,0 +1,22 @@
+
+
diff --git a/tests/fixtures/script-indent/class-fields-properties-01.vue b/tests/fixtures/script-indent/class-fields-properties-01.vue
new file mode 100644
index 000000000..ef193d1fe
--- /dev/null
+++ b/tests/fixtures/script-indent/class-fields-properties-01.vue
@@ -0,0 +1,16 @@
+
+
diff --git a/tests/fixtures/script-indent/class-fields-properties-02.vue b/tests/fixtures/script-indent/class-fields-properties-02.vue
new file mode 100644
index 000000000..3efe3bd25
--- /dev/null
+++ b/tests/fixtures/script-indent/class-fields-properties-02.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/export-all-declaration-02.vue b/tests/fixtures/script-indent/export-all-declaration-02.vue
new file mode 100644
index 000000000..b85213a6b
--- /dev/null
+++ b/tests/fixtures/script-indent/export-all-declaration-02.vue
@@ -0,0 +1,16 @@
+
+
diff --git a/tests/fixtures/script-indent/export-named-declaration-02.vue b/tests/fixtures/script-indent/export-named-declaration-02.vue
index 32c52653e..00dbf3f35 100644
--- a/tests/fixtures/script-indent/export-named-declaration-02.vue
+++ b/tests/fixtures/script-indent/export-named-declaration-02.vue
@@ -1,5 +1,6 @@
diff --git a/tests/fixtures/script-indent/function-declaration-04.vue b/tests/fixtures/script-indent/function-declaration-04.vue
new file mode 100644
index 000000000..d4ce7897b
--- /dev/null
+++ b/tests/fixtures/script-indent/function-declaration-04.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/if-statement-04.vue b/tests/fixtures/script-indent/if-statement-04.vue
new file mode 100644
index 000000000..e6ba11eb4
--- /dev/null
+++ b/tests/fixtures/script-indent/if-statement-04.vue
@@ -0,0 +1,7 @@
+
+
+
diff --git a/tests/fixtures/script-indent/ignore-01.vue b/tests/fixtures/script-indent/ignore-01.vue
new file mode 100644
index 000000000..e323cdc08
--- /dev/null
+++ b/tests/fixtures/script-indent/ignore-01.vue
@@ -0,0 +1,19 @@
+
+
diff --git a/tests/fixtures/script-indent/ignore-02.vue b/tests/fixtures/script-indent/ignore-02.vue
new file mode 100644
index 000000000..18c557476
--- /dev/null
+++ b/tests/fixtures/script-indent/ignore-02.vue
@@ -0,0 +1,19 @@
+
+
diff --git a/tests/fixtures/script-indent/import-declaration-11.vue b/tests/fixtures/script-indent/import-declaration-11.vue
new file mode 100644
index 000000000..83289b1f4
--- /dev/null
+++ b/tests/fixtures/script-indent/import-declaration-11.vue
@@ -0,0 +1,7 @@
+
+
+
diff --git a/tests/fixtures/script-indent/import-declaration-12.vue b/tests/fixtures/script-indent/import-declaration-12.vue
new file mode 100644
index 000000000..fa622a696
--- /dev/null
+++ b/tests/fixtures/script-indent/import-declaration-12.vue
@@ -0,0 +1,11 @@
+
+
+
diff --git a/tests/fixtures/script-indent/import-expression-01.vue b/tests/fixtures/script-indent/import-expression-01.vue
new file mode 100644
index 000000000..3f7c7cb61
--- /dev/null
+++ b/tests/fixtures/script-indent/import-expression-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/import-expression-02.vue b/tests/fixtures/script-indent/import-expression-02.vue
new file mode 100644
index 000000000..8144c3ffa
--- /dev/null
+++ b/tests/fixtures/script-indent/import-expression-02.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/import-expression-03.vue b/tests/fixtures/script-indent/import-expression-03.vue
new file mode 100644
index 000000000..e82a6df42
--- /dev/null
+++ b/tests/fixtures/script-indent/import-expression-03.vue
@@ -0,0 +1,11 @@
+
+
diff --git a/tests/fixtures/script-indent/indent-valid-fixture-01.vue b/tests/fixtures/script-indent/indent-valid-fixture-01.vue
new file mode 100644
index 000000000..c1423bf9b
--- /dev/null
+++ b/tests/fixtures/script-indent/indent-valid-fixture-01.vue
@@ -0,0 +1,541 @@
+
+
+
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/issue407.vue b/tests/fixtures/script-indent/issue407.vue
new file mode 100644
index 000000000..40ecda9d4
--- /dev/null
+++ b/tests/fixtures/script-indent/issue407.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/issue441.vue b/tests/fixtures/script-indent/issue441.vue
new file mode 100644
index 000000000..a6738f2a3
--- /dev/null
+++ b/tests/fixtures/script-indent/issue441.vue
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/issue443.vue b/tests/fixtures/script-indent/issue443.vue
new file mode 100644
index 000000000..4a8c356e7
--- /dev/null
+++ b/tests/fixtures/script-indent/issue443.vue
@@ -0,0 +1,27 @@
+
+
\ No newline at end of file
diff --git a/tests/fixtures/script-indent/issue625.vue b/tests/fixtures/script-indent/issue625.vue
new file mode 100644
index 000000000..6c205ee83
--- /dev/null
+++ b/tests/fixtures/script-indent/issue625.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/jsx-01.vue b/tests/fixtures/script-indent/jsx-01.vue
new file mode 100644
index 000000000..d5e73c650
--- /dev/null
+++ b/tests/fixtures/script-indent/jsx-01.vue
@@ -0,0 +1,16 @@
+
+
diff --git a/tests/fixtures/script-indent/jsx-02.vue b/tests/fixtures/script-indent/jsx-02.vue
new file mode 100644
index 000000000..a7656d430
--- /dev/null
+++ b/tests/fixtures/script-indent/jsx-02.vue
@@ -0,0 +1,23 @@
+
+
diff --git a/tests/fixtures/script-indent/logical-expression-01.vue b/tests/fixtures/script-indent/logical-expression-01.vue
new file mode 100644
index 000000000..087e00753
--- /dev/null
+++ b/tests/fixtures/script-indent/logical-expression-01.vue
@@ -0,0 +1,16 @@
+
+
diff --git a/tests/fixtures/script-indent/logical-expression-02.vue b/tests/fixtures/script-indent/logical-expression-02.vue
new file mode 100644
index 000000000..ccba0b7c0
--- /dev/null
+++ b/tests/fixtures/script-indent/logical-expression-02.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/logical-expression-03.vue b/tests/fixtures/script-indent/logical-expression-03.vue
new file mode 100644
index 000000000..9b5ccb8ca
--- /dev/null
+++ b/tests/fixtures/script-indent/logical-expression-03.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/method-definition-02.vue b/tests/fixtures/script-indent/method-definition-02.vue
new file mode 100644
index 000000000..d5c1d3725
--- /dev/null
+++ b/tests/fixtures/script-indent/method-definition-02.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/no-linebreak-script.vue b/tests/fixtures/script-indent/no-linebreak-script.vue
new file mode 100644
index 000000000..b8555f2df
--- /dev/null
+++ b/tests/fixtures/script-indent/no-linebreak-script.vue
@@ -0,0 +1,3 @@
+
+
diff --git a/tests/fixtures/script-indent/nullish-coalescing-operator-01.vue b/tests/fixtures/script-indent/nullish-coalescing-operator-01.vue
new file mode 100644
index 000000000..22427c7f6
--- /dev/null
+++ b/tests/fixtures/script-indent/nullish-coalescing-operator-01.vue
@@ -0,0 +1,6 @@
+
+
diff --git a/tests/fixtures/script-indent/object-expression-02.vue b/tests/fixtures/script-indent/object-expression-02.vue
new file mode 100644
index 000000000..5ff0e46c2
--- /dev/null
+++ b/tests/fixtures/script-indent/object-expression-02.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/object-expression-03.vue b/tests/fixtures/script-indent/object-expression-03.vue
new file mode 100644
index 000000000..f65063ecc
--- /dev/null
+++ b/tests/fixtures/script-indent/object-expression-03.vue
@@ -0,0 +1,5 @@
+
+
diff --git a/tests/fixtures/script-indent/object-expression-04.vue b/tests/fixtures/script-indent/object-expression-04.vue
new file mode 100644
index 000000000..25e8c2077
--- /dev/null
+++ b/tests/fixtures/script-indent/object-expression-04.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-01.vue b/tests/fixtures/script-indent/optional-chaining-01.vue
new file mode 100644
index 000000000..f0ea36ee4
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-02.vue b/tests/fixtures/script-indent/optional-chaining-02.vue
new file mode 100644
index 000000000..11c77d518
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-02.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-03.vue b/tests/fixtures/script-indent/optional-chaining-03.vue
new file mode 100644
index 000000000..01626acf0
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-03.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-04.vue b/tests/fixtures/script-indent/optional-chaining-04.vue
new file mode 100644
index 000000000..65a42a74d
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-04.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-05.vue b/tests/fixtures/script-indent/optional-chaining-05.vue
new file mode 100644
index 000000000..0e926b497
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-05.vue
@@ -0,0 +1,11 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-06.vue b/tests/fixtures/script-indent/optional-chaining-06.vue
new file mode 100644
index 000000000..47898015b
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-06.vue
@@ -0,0 +1,25 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-07.vue b/tests/fixtures/script-indent/optional-chaining-07.vue
new file mode 100644
index 000000000..1ba9fb0fe
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-07.vue
@@ -0,0 +1,25 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-08.vue b/tests/fixtures/script-indent/optional-chaining-08.vue
new file mode 100644
index 000000000..61d071887
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-08.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-09.vue b/tests/fixtures/script-indent/optional-chaining-09.vue
new file mode 100644
index 000000000..c17562c0c
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-09.vue
@@ -0,0 +1,32 @@
+
+
diff --git a/tests/fixtures/script-indent/opts-baseindent1.js b/tests/fixtures/script-indent/opts-baseindent1.js
deleted file mode 100644
index e5456c834..000000000
--- a/tests/fixtures/script-indent/opts-baseindent1.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/*{"options":[2,{"baseIndent":1}]}*/
-var aaa = {
- test: 1
-}
-var bbb = {
- test: 1
- },
- ccc = {
- test: 1
- }
-const ddd = {
- test: 1
- },
- eee = (a) => {
- foo(a)
- }
diff --git a/tests/fixtures/script-indent/property-01.vue b/tests/fixtures/script-indent/property-01.vue
index 6bc2a8e94..0f1a45071 100644
--- a/tests/fixtures/script-indent/property-01.vue
+++ b/tests/fixtures/script-indent/property-01.vue
@@ -4,37 +4,5 @@
aaa
:
1
- ,
- bbb
- (
- a
- ,
- b
- )
- {
- ;
- }
- ,
- get
- ccc
- (
- )
- {
- ;
- },
- [
- d
- ]
- :
- 1,
- get
- [
- e
- ]
- (
- )
- {
- ;
- }
}
diff --git a/tests/fixtures/script-indent/property-02.vue b/tests/fixtures/script-indent/property-02.vue
new file mode 100644
index 000000000..b1f4d933f
--- /dev/null
+++ b/tests/fixtures/script-indent/property-02.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/fixtures/script-indent/property-03.vue b/tests/fixtures/script-indent/property-03.vue
new file mode 100644
index 000000000..4ebc00e9a
--- /dev/null
+++ b/tests/fixtures/script-indent/property-03.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/property-04.vue b/tests/fixtures/script-indent/property-04.vue
new file mode 100644
index 000000000..a28cfaad7
--- /dev/null
+++ b/tests/fixtures/script-indent/property-04.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/property-05.vue b/tests/fixtures/script-indent/property-05.vue
new file mode 100644
index 000000000..73ec6cd17
--- /dev/null
+++ b/tests/fixtures/script-indent/property-05.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/fixtures/script-indent/property-06.vue b/tests/fixtures/script-indent/property-06.vue
new file mode 100644
index 000000000..acf1644a2
--- /dev/null
+++ b/tests/fixtures/script-indent/property-06.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/rest-properties-01.vue b/tests/fixtures/script-indent/rest-properties-01.vue
new file mode 100644
index 000000000..87ab2ab9b
--- /dev/null
+++ b/tests/fixtures/script-indent/rest-properties-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/sequence-expression-04.vue b/tests/fixtures/script-indent/sequence-expression-04.vue
new file mode 100644
index 000000000..f263bf523
--- /dev/null
+++ b/tests/fixtures/script-indent/sequence-expression-04.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/static-block-01.vue b/tests/fixtures/script-indent/static-block-01.vue
new file mode 100644
index 000000000..87bfc6b34
--- /dev/null
+++ b/tests/fixtures/script-indent/static-block-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/try-statement-04.vue b/tests/fixtures/script-indent/try-statement-04.vue
new file mode 100644
index 000000000..4b3d9e866
--- /dev/null
+++ b/tests/fixtures/script-indent/try-statement-04.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-abstract-accessor-property-01.vue b/tests/fixtures/script-indent/ts-abstract-accessor-property-01.vue
new file mode 100644
index 000000000..01068057e
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-abstract-accessor-property-01.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-abstract-class-property-01.vue b/tests/fixtures/script-indent/ts-abstract-class-property-01.vue
new file mode 100644
index 000000000..500b4c493
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-abstract-class-property-01.vue
@@ -0,0 +1,22 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-abstract-class-property-02.vue b/tests/fixtures/script-indent/ts-abstract-class-property-02.vue
new file mode 100644
index 000000000..47e4aba99
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-abstract-class-property-02.vue
@@ -0,0 +1,19 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-abstract-method-definition-01.vue b/tests/fixtures/script-indent/ts-abstract-method-definition-01.vue
new file mode 100644
index 000000000..cd700f1a9
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-abstract-method-definition-01.vue
@@ -0,0 +1,30 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-accessor-property-01.vue b/tests/fixtures/script-indent/ts-accessor-property-01.vue
new file mode 100644
index 000000000..97928b6b6
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-accessor-property-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-accessor-property-02.vue b/tests/fixtures/script-indent/ts-accessor-property-02.vue
new file mode 100644
index 000000000..da749a32e
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-accessor-property-02.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-accessor-property-03.vue b/tests/fixtures/script-indent/ts-accessor-property-03.vue
new file mode 100644
index 000000000..993e52527
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-accessor-property-03.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-accessor-property-04.vue b/tests/fixtures/script-indent/ts-accessor-property-04.vue
new file mode 100644
index 000000000..e69e294fc
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-accessor-property-04.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-accessor-property-05.vue b/tests/fixtures/script-indent/ts-accessor-property-05.vue
new file mode 100644
index 000000000..dc3503ca7
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-accessor-property-05.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-as-expression-01.vue b/tests/fixtures/script-indent/ts-as-expression-01.vue
new file mode 100644
index 000000000..377dd3436
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-as-expression-01.vue
@@ -0,0 +1,6 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-call-expression-01.vue b/tests/fixtures/script-indent/ts-call-expression-01.vue
new file mode 100644
index 000000000..a12e28f7d
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-call-expression-01.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-call-signature-declaration-01.vue b/tests/fixtures/script-indent/ts-call-signature-declaration-01.vue
new file mode 100644
index 000000000..b734c69c6
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-call-signature-declaration-01.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-call-signature-declaration-02.vue b/tests/fixtures/script-indent/ts-call-signature-declaration-02.vue
new file mode 100644
index 000000000..e2b6cd0b7
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-call-signature-declaration-02.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-declaration-01.vue b/tests/fixtures/script-indent/ts-class-declaration-01.vue
new file mode 100644
index 000000000..776e38aa3
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-declaration-01.vue
@@ -0,0 +1,11 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-declaration-02.vue b/tests/fixtures/script-indent/ts-class-declaration-02.vue
new file mode 100644
index 000000000..4b4eafbf1
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-declaration-02.vue
@@ -0,0 +1,16 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-declaration-03.vue b/tests/fixtures/script-indent/ts-class-declaration-03.vue
new file mode 100644
index 000000000..c1697f273
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-declaration-03.vue
@@ -0,0 +1,26 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-declaration-04.vue b/tests/fixtures/script-indent/ts-class-declaration-04.vue
new file mode 100644
index 000000000..c1697f273
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-declaration-04.vue
@@ -0,0 +1,26 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-declaration-05.vue b/tests/fixtures/script-indent/ts-class-declaration-05.vue
new file mode 100644
index 000000000..893584928
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-declaration-05.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-declaration-06.vue b/tests/fixtures/script-indent/ts-class-declaration-06.vue
new file mode 100644
index 000000000..06e42f0c6
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-declaration-06.vue
@@ -0,0 +1,28 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-declaration-07.vue b/tests/fixtures/script-indent/ts-class-declaration-07.vue
new file mode 100644
index 000000000..dfca03b8a
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-declaration-07.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-fields-private-methods-01.vue b/tests/fixtures/script-indent/ts-class-fields-private-methods-01.vue
new file mode 100644
index 000000000..ac0eda090
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-fields-private-methods-01.vue
@@ -0,0 +1,64 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-fields-private-properties-01.vue b/tests/fixtures/script-indent/ts-class-fields-private-properties-01.vue
new file mode 100644
index 000000000..26006fb4a
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-fields-private-properties-01.vue
@@ -0,0 +1,22 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-property-01.vue b/tests/fixtures/script-indent/ts-class-property-01.vue
new file mode 100644
index 000000000..a0e8b8583
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-property-01.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-property-02.vue b/tests/fixtures/script-indent/ts-class-property-02.vue
new file mode 100644
index 000000000..f2ae2e508
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-property-02.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-class-property-03.vue b/tests/fixtures/script-indent/ts-class-property-03.vue
new file mode 100644
index 000000000..dfd437f38
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-class-property-03.vue
@@ -0,0 +1,36 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-conditional-type-01.vue b/tests/fixtures/script-indent/ts-conditional-type-01.vue
new file mode 100644
index 000000000..ca1334b18
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-conditional-type-01.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-conditional-type-02.vue b/tests/fixtures/script-indent/ts-conditional-type-02.vue
new file mode 100644
index 000000000..1e8d68b76
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-conditional-type-02.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-conditional-type-03.vue b/tests/fixtures/script-indent/ts-conditional-type-03.vue
new file mode 100644
index 000000000..e3230e948
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-conditional-type-03.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-constructor-type-01.vue b/tests/fixtures/script-indent/ts-constructor-type-01.vue
new file mode 100644
index 000000000..97cc1a1d8
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-constructor-type-01.vue
@@ -0,0 +1,21 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-declare-function-01.vue b/tests/fixtures/script-indent/ts-declare-function-01.vue
new file mode 100644
index 000000000..2d1fa1f19
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-declare-function-01.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-declare-function-02.vue b/tests/fixtures/script-indent/ts-declare-function-02.vue
new file mode 100644
index 000000000..1450b1b32
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-declare-function-02.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-declare-function-03.vue b/tests/fixtures/script-indent/ts-declare-function-03.vue
new file mode 100644
index 000000000..7dabfe206
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-declare-function-03.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-declare-function-04.vue b/tests/fixtures/script-indent/ts-declare-function-04.vue
new file mode 100644
index 000000000..7ae12d894
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-declare-function-04.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-decorator-01.vue b/tests/fixtures/script-indent/ts-decorator-01.vue
new file mode 100644
index 000000000..c6f39dc35
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-decorator-01.vue
@@ -0,0 +1,205 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-decorator-02.vue b/tests/fixtures/script-indent/ts-decorator-02.vue
new file mode 100644
index 000000000..a314771ad
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-decorator-02.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-01.vue b/tests/fixtures/script-indent/ts-enum-01.vue
new file mode 100644
index 000000000..b6b9ccae0
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-01.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-02.vue b/tests/fixtures/script-indent/ts-enum-02.vue
new file mode 100644
index 000000000..a0e25f12b
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-02.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-03.vue b/tests/fixtures/script-indent/ts-enum-03.vue
new file mode 100644
index 000000000..f46b6b179
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-03.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-04.vue b/tests/fixtures/script-indent/ts-enum-04.vue
new file mode 100644
index 000000000..141e8d7f9
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-04.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-05.vue b/tests/fixtures/script-indent/ts-enum-05.vue
new file mode 100644
index 000000000..d40883ee9
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-05.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-06.vue b/tests/fixtures/script-indent/ts-enum-06.vue
new file mode 100644
index 000000000..b4d02a8f3
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-06.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-member-01.vue b/tests/fixtures/script-indent/ts-enum-member-01.vue
new file mode 100644
index 000000000..a646bda9b
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-member-01.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-enum-member-02.vue b/tests/fixtures/script-indent/ts-enum-member-02.vue
new file mode 100644
index 000000000..0dfad0857
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-enum-member-02.vue
@@ -0,0 +1,26 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-export-assignment-01.vue b/tests/fixtures/script-indent/ts-export-assignment-01.vue
new file mode 100644
index 000000000..ee028cb48
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-export-assignment-01.vue
@@ -0,0 +1,5 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-export-assignment-02.vue b/tests/fixtures/script-indent/ts-export-assignment-02.vue
new file mode 100644
index 000000000..0d167f0df
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-export-assignment-02.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-export-namespace-01.vue b/tests/fixtures/script-indent/ts-export-namespace-01.vue
new file mode 100644
index 000000000..098f4b3cb
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-export-namespace-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-function-type-01.vue b/tests/fixtures/script-indent/ts-function-type-01.vue
new file mode 100644
index 000000000..1cd005f49
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-function-type-01.vue
@@ -0,0 +1,6 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-function-type-02.vue b/tests/fixtures/script-indent/ts-function-type-02.vue
new file mode 100644
index 000000000..bd5c27cb3
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-function-type-02.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-assertion-01.vue b/tests/fixtures/script-indent/ts-import-assertion-01.vue
new file mode 100644
index 000000000..04cd9c99b
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-assertion-01.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-assertion-02.vue b/tests/fixtures/script-indent/ts-import-assertion-02.vue
new file mode 100644
index 000000000..c6e0cb866
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-assertion-02.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-assertion-03.vue b/tests/fixtures/script-indent/ts-import-assertion-03.vue
new file mode 100644
index 000000000..7cdf02501
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-assertion-03.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-assertion-04.vue b/tests/fixtures/script-indent/ts-import-assertion-04.vue
new file mode 100644
index 000000000..fc1bee6ee
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-assertion-04.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-equal-01.vue b/tests/fixtures/script-indent/ts-import-equal-01.vue
new file mode 100644
index 000000000..b35c28cd0
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-equal-01.vue
@@ -0,0 +1,6 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-equal-02.vue b/tests/fixtures/script-indent/ts-import-equal-02.vue
new file mode 100644
index 000000000..5d55cab24
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-equal-02.vue
@@ -0,0 +1,19 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-equal-03.vue b/tests/fixtures/script-indent/ts-import-equal-03.vue
new file mode 100644
index 000000000..d532454be
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-equal-03.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-type-01.vue b/tests/fixtures/script-indent/ts-import-type-01.vue
new file mode 100644
index 000000000..d8790ed57
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-type-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-type-02.vue b/tests/fixtures/script-indent/ts-import-type-02.vue
new file mode 100644
index 000000000..f19a97d89
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-type-02.vue
@@ -0,0 +1,18 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-import-type-03.vue b/tests/fixtures/script-indent/ts-import-type-03.vue
new file mode 100644
index 000000000..2724f2911
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-import-type-03.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-indexed-access-type-01.vue b/tests/fixtures/script-indent/ts-indexed-access-type-01.vue
new file mode 100644
index 000000000..f95b266fd
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-indexed-access-type-01.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-indexed-access-type-02.vue b/tests/fixtures/script-indent/ts-indexed-access-type-02.vue
new file mode 100644
index 000000000..6c641d0bf
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-indexed-access-type-02.vue
@@ -0,0 +1,11 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-infer-01.vue b/tests/fixtures/script-indent/ts-infer-01.vue
new file mode 100644
index 000000000..7cd8cb660
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-infer-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-instantiation-expression-01.vue b/tests/fixtures/script-indent/ts-instantiation-expression-01.vue
new file mode 100644
index 000000000..64e1d1073
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-instantiation-expression-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-01.vue b/tests/fixtures/script-indent/ts-interface-declaration-01.vue
new file mode 100644
index 000000000..6dc683226
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-02.vue b/tests/fixtures/script-indent/ts-interface-declaration-02.vue
new file mode 100644
index 000000000..99d53c153
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-02.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-03.vue b/tests/fixtures/script-indent/ts-interface-declaration-03.vue
new file mode 100644
index 000000000..6490c79ec
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-03.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-04.vue b/tests/fixtures/script-indent/ts-interface-declaration-04.vue
new file mode 100644
index 000000000..bb770b382
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-04.vue
@@ -0,0 +1,19 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-05.vue b/tests/fixtures/script-indent/ts-interface-declaration-05.vue
new file mode 100644
index 000000000..1c00cab83
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-05.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-06.vue b/tests/fixtures/script-indent/ts-interface-declaration-06.vue
new file mode 100644
index 000000000..3390cb963
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-06.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-07.vue b/tests/fixtures/script-indent/ts-interface-declaration-07.vue
new file mode 100644
index 000000000..3dbcc88d3
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-07.vue
@@ -0,0 +1,25 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-interface-declaration-08.vue b/tests/fixtures/script-indent/ts-interface-declaration-08.vue
new file mode 100644
index 000000000..0048fe129
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-interface-declaration-08.vue
@@ -0,0 +1,27 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-mapped-type-01.vue b/tests/fixtures/script-indent/ts-mapped-type-01.vue
new file mode 100644
index 000000000..6f7016231
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-mapped-type-01.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-module-declaration-01.vue b/tests/fixtures/script-indent/ts-module-declaration-01.vue
new file mode 100644
index 000000000..ef89078fc
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-module-declaration-01.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-new-expression-01.vue b/tests/fixtures/script-indent/ts-new-expression-01.vue
new file mode 100644
index 000000000..3dd190fa0
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-new-expression-01.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-qualified-name-01.vue b/tests/fixtures/script-indent/ts-qualified-name-01.vue
new file mode 100644
index 000000000..aa8fa0a0f
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-qualified-name-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-satisfies-operators-01.vue b/tests/fixtures/script-indent/ts-satisfies-operators-01.vue
new file mode 100644
index 000000000..858834115
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-satisfies-operators-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-static-block-01.vue b/tests/fixtures/script-indent/ts-static-block-01.vue
new file mode 100644
index 000000000..5d585d6d0
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-static-block-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-template-literal-type-01.vue b/tests/fixtures/script-indent/ts-template-literal-type-01.vue
new file mode 100644
index 000000000..a260ecdae
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-template-literal-type-01.vue
@@ -0,0 +1,40 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-tuple-01.vue b/tests/fixtures/script-indent/ts-tuple-01.vue
new file mode 100644
index 000000000..d5d1bfdb0
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-tuple-01.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-tuple-02.vue b/tests/fixtures/script-indent/ts-tuple-02.vue
new file mode 100644
index 000000000..526755f48
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-tuple-02.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-alias-seclaration-01.vue b/tests/fixtures/script-indent/ts-type-alias-seclaration-01.vue
new file mode 100644
index 000000000..92037fb4c
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-alias-seclaration-01.vue
@@ -0,0 +1,21 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-annotation-01.vue b/tests/fixtures/script-indent/ts-type-annotation-01.vue
new file mode 100644
index 000000000..8d7ac5958
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-annotation-01.vue
@@ -0,0 +1,9 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-annotation-02.vue b/tests/fixtures/script-indent/ts-type-annotation-02.vue
new file mode 100644
index 000000000..5769cfd40
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-annotation-02.vue
@@ -0,0 +1,33 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-annotation-03.vue b/tests/fixtures/script-indent/ts-type-annotation-03.vue
new file mode 100644
index 000000000..2d18debaf
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-annotation-03.vue
@@ -0,0 +1,29 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-annotation-04.vue b/tests/fixtures/script-indent/ts-type-annotation-04.vue
new file mode 100644
index 000000000..015bfb0bb
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-annotation-04.vue
@@ -0,0 +1,25 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-annotation-05.vue b/tests/fixtures/script-indent/ts-type-annotation-05.vue
new file mode 100644
index 000000000..7bdec085b
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-annotation-05.vue
@@ -0,0 +1,21 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-annotation-06.vue b/tests/fixtures/script-indent/ts-type-annotation-06.vue
new file mode 100644
index 000000000..79cbf15d7
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-annotation-06.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-assertion-01.vue b/tests/fixtures/script-indent/ts-type-assertion-01.vue
new file mode 100644
index 000000000..80f9aa78b
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-assertion-01.vue
@@ -0,0 +1,10 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-literal-01.vue b/tests/fixtures/script-indent/ts-type-literal-01.vue
new file mode 100644
index 000000000..61f481f9c
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-literal-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-only-import-export-01.vue b/tests/fixtures/script-indent/ts-type-only-import-export-01.vue
new file mode 100644
index 000000000..dea1377e9
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-only-import-export-01.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-only-import-export-02.vue b/tests/fixtures/script-indent/ts-type-only-import-export-02.vue
new file mode 100644
index 000000000..b2bad678e
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-only-import-export-02.vue
@@ -0,0 +1,23 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-only-import-export-03.vue b/tests/fixtures/script-indent/ts-type-only-import-export-03.vue
new file mode 100644
index 000000000..4c9238dc9
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-only-import-export-03.vue
@@ -0,0 +1,26 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-paramater-01.vue b/tests/fixtures/script-indent/ts-type-paramater-01.vue
new file mode 100644
index 000000000..cbdd01bcf
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-paramater-01.vue
@@ -0,0 +1,24 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-type-parameter-seclaration-01.vue b/tests/fixtures/script-indent/ts-type-parameter-seclaration-01.vue
new file mode 100644
index 000000000..71ca145ab
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-type-parameter-seclaration-01.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-union-intersection-01.vue b/tests/fixtures/script-indent/ts-union-intersection-01.vue
new file mode 100644
index 000000000..a808493e8
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-01.vue
@@ -0,0 +1,28 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-union-intersection-02.vue b/tests/fixtures/script-indent/ts-union-intersection-02.vue
new file mode 100644
index 000000000..62673b0a9
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-02.vue
@@ -0,0 +1,21 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-union-intersection-03.vue b/tests/fixtures/script-indent/ts-union-intersection-03.vue
new file mode 100644
index 000000000..022d2f1da
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-03.vue
@@ -0,0 +1,19 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-union-intersection-04.vue b/tests/fixtures/script-indent/ts-union-intersection-04.vue
new file mode 100644
index 000000000..378c95cdb
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-04.vue
@@ -0,0 +1,17 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-union-intersection-05.vue b/tests/fixtures/script-indent/ts-union-intersection-05.vue
new file mode 100644
index 000000000..f12da960e
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-05.vue
@@ -0,0 +1,19 @@
+
+
diff --git a/tests/fixtures/script-indent/ts-union-intersection-06.vue b/tests/fixtures/script-indent/ts-union-intersection-06.vue
new file mode 100644
index 000000000..8241ae123
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-06.vue
@@ -0,0 +1,27 @@
+
+
diff --git a/tests/fixtures/typescript/src/test.vue b/tests/fixtures/typescript/src/test.vue
new file mode 100644
index 000000000..5bf03f4a7
--- /dev/null
+++ b/tests/fixtures/typescript/src/test.vue
@@ -0,0 +1 @@
+
diff --git a/tests/fixtures/typescript/src/test01.ts b/tests/fixtures/typescript/src/test01.ts
new file mode 100644
index 000000000..d14550843
--- /dev/null
+++ b/tests/fixtures/typescript/src/test01.ts
@@ -0,0 +1,20 @@
+export type Props1 = {
+ foo: string
+ bar?: number
+ baz?: boolean
+}
+export type Emits1 = {
+ (e: 'foo' | 'bar', payload: string): void
+ (e: 'baz', payload: number): void
+}
+export type Props2 = {
+ a: string
+ b?: number
+ c?: boolean
+ d?: boolean
+ e?: number | string
+ f?: () => number
+ g?: { foo?: string }
+ h?: string[]
+ i?: readonly string[]
+}
diff --git a/tests/fixtures/typescript/tsconfig.json b/tests/fixtures/typescript/tsconfig.json
new file mode 100644
index 000000000..c13ef64e3
--- /dev/null
+++ b/tests/fixtures/typescript/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "skipLibCheck": true
+ }
+}
diff --git a/tests/fixtures/utils/html-comments/abrupt-closing-of-empty-comment/comment-tokens.json b/tests/fixtures/utils/html-comments/abrupt-closing-of-empty-comment/comment-tokens.json
new file mode 100644
index 000000000..adf085577
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/abrupt-closing-of-empty-comment/comment-tokens.json
@@ -0,0 +1,43 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 72,
+ 75
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 6
+ },
+ "end": {
+ "line": 4,
+ "column": 1
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/abrupt-closing-of-empty-comment/source.vue b/tests/fixtures/utils/html-comments/abrupt-closing-of-empty-comment/source.vue
new file mode 100644
index 000000000..9276de6b5
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/abrupt-closing-of-empty-comment/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/blank1/comment-tokens.json b/tests/fixtures/utils/html-comments/blank1/comment-tokens.json
new file mode 100644
index 000000000..016703d3b
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/blank1/comment-tokens.json
@@ -0,0 +1,43 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 50,
+ 53
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 9
+ },
+ "end": {
+ "line": 3,
+ "column": 12
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/blank1/source.vue b/tests/fixtures/utils/html-comments/blank1/source.vue
new file mode 100644
index 000000000..71043a9cb
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/blank1/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/blank2/comment-tokens.json b/tests/fixtures/utils/html-comments/blank2/comment-tokens.json
new file mode 100644
index 000000000..2cffdf190
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/blank2/comment-tokens.json
@@ -0,0 +1,43 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 50,
+ 53
+ ],
+ "loc": {
+ "start": {
+ "line": 4,
+ "column": 2
+ },
+ "end": {
+ "line": 4,
+ "column": 5
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/blank2/source.vue b/tests/fixtures/utils/html-comments/blank2/source.vue
new file mode 100644
index 000000000..887603525
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/blank2/source.vue
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/decoration-empty-value/comment-tokens.json b/tests/fixtures/utils/html-comments/decoration-empty-value/comment-tokens.json
new file mode 100644
index 000000000..fff156f17
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration-empty-value/comment-tokens.json
@@ -0,0 +1,60 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 68,
+ 71
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 11
+ },
+ "end": {
+ "line": 3,
+ "column": 14
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/decoration-empty-value/source.vue b/tests/fixtures/utils/html-comments/decoration-empty-value/source.vue
new file mode 100644
index 000000000..14bcb9519
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration-empty-value/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/decoration1/comment-tokens.json b/tests/fixtures/utils/html-comments/decoration1/comment-tokens.json
new file mode 100644
index 000000000..37dda22ae
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration1/comment-tokens.json
@@ -0,0 +1,94 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 69,
+ 72
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 23
+ },
+ "end": {
+ "line": 3,
+ "column": 26
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/decoration1/source.vue b/tests/fixtures/utils/html-comments/decoration1/source.vue
new file mode 100644
index 000000000..c5a6c442c
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration1/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/decoration2/comment-tokens.json b/tests/fixtures/utils/html-comments/decoration2/comment-tokens.json
new file mode 100644
index 000000000..0ad4e3634
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration2/comment-tokens.json
@@ -0,0 +1,94 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 71,
+ 74
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 25
+ },
+ "end": {
+ "line": 3,
+ "column": 28
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/decoration2/source.vue b/tests/fixtures/utils/html-comments/decoration2/source.vue
new file mode 100644
index 000000000..f40c67956
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration2/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/decoration3/comment-tokens.json b/tests/fixtures/utils/html-comments/decoration3/comment-tokens.json
new file mode 100644
index 000000000..ab7f1c333
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration3/comment-tokens.json
@@ -0,0 +1,94 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 77,
+ 80
+ ],
+ "loc": {
+ "start": {
+ "line": 5,
+ "column": 7
+ },
+ "end": {
+ "line": 5,
+ "column": 10
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/decoration3/source.vue b/tests/fixtures/utils/html-comments/decoration3/source.vue
new file mode 100644
index 000000000..2fe37af4a
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/decoration3/source.vue
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/empty/comment-tokens.json b/tests/fixtures/utils/html-comments/empty/comment-tokens.json
new file mode 100644
index 000000000..81d8f4723
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/empty/comment-tokens.json
@@ -0,0 +1,43 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 46,
+ 49
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 6
+ },
+ "end": {
+ "line": 3,
+ "column": 9
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/empty/source.vue b/tests/fixtures/utils/html-comments/empty/source.vue
new file mode 100644
index 000000000..33d7fd240
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/empty/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/ie-conditional-comments1/comment-tokens.json b/tests/fixtures/utils/html-comments/ie-conditional-comments1/comment-tokens.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/ie-conditional-comments1/comment-tokens.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/ie-conditional-comments1/source.vue b/tests/fixtures/utils/html-comments/ie-conditional-comments1/source.vue
new file mode 100644
index 000000000..f8a6a1559
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/ie-conditional-comments1/source.vue
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/ie-conditional-comments2/comment-tokens.json b/tests/fixtures/utils/html-comments/ie-conditional-comments2/comment-tokens.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/ie-conditional-comments2/comment-tokens.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/ie-conditional-comments2/source.vue b/tests/fixtures/utils/html-comments/ie-conditional-comments2/source.vue
new file mode 100644
index 000000000..679eba727
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/ie-conditional-comments2/source.vue
@@ -0,0 +1,6 @@
+
+
+
+
not IE only
+
+
diff --git a/tests/fixtures/utils/html-comments/incorrectly-closed-comment/comment-tokens.json b/tests/fixtures/utils/html-comments/incorrectly-closed-comment/comment-tokens.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/incorrectly-closed-comment/comment-tokens.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/incorrectly-closed-comment/source.vue b/tests/fixtures/utils/html-comments/incorrectly-closed-comment/source.vue
new file mode 100644
index 000000000..edfecdca6
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/incorrectly-closed-comment/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/non-decoration/comment-tokens.json b/tests/fixtures/utils/html-comments/non-decoration/comment-tokens.json
new file mode 100644
index 000000000..4a42c85cc
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/non-decoration/comment-tokens.json
@@ -0,0 +1,60 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 70,
+ 73
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 21
+ },
+ "end": {
+ "line": 3,
+ "column": 24
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/non-decoration/source.vue b/tests/fixtures/utils/html-comments/non-decoration/source.vue
new file mode 100644
index 000000000..464b27799
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/non-decoration/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/script-comment/comment-tokens.json b/tests/fixtures/utils/html-comments/script-comment/comment-tokens.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/script-comment/comment-tokens.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/script-comment/source.vue b/tests/fixtures/utils/html-comments/script-comment/source.vue
new file mode 100644
index 000000000..41aedcefe
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/script-comment/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/test1/comment-tokens.json b/tests/fixtures/utils/html-comments/test1/comment-tokens.json
new file mode 100644
index 000000000..5c53e9ca4
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test1/comment-tokens.json
@@ -0,0 +1,60 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 53,
+ 56
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 13
+ },
+ "end": {
+ "line": 3,
+ "column": 16
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/test1/source.vue b/tests/fixtures/utils/html-comments/test1/source.vue
new file mode 100644
index 000000000..a270969a3
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test1/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/test2/comment-tokens.json b/tests/fixtures/utils/html-comments/test2/comment-tokens.json
new file mode 100644
index 000000000..c414ab176
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test2/comment-tokens.json
@@ -0,0 +1,60 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 55,
+ 58
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 15
+ },
+ "end": {
+ "line": 3,
+ "column": 18
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/test2/source.vue b/tests/fixtures/utils/html-comments/test2/source.vue
new file mode 100644
index 000000000..aed10db97
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test2/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/test3/comment-tokens.json b/tests/fixtures/utils/html-comments/test3/comment-tokens.json
new file mode 100644
index 000000000..40db0b89d
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test3/comment-tokens.json
@@ -0,0 +1,60 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 61,
+ 64
+ ],
+ "loc": {
+ "start": {
+ "line": 3,
+ "column": 21
+ },
+ "end": {
+ "line": 3,
+ "column": 24
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/test3/source.vue b/tests/fixtures/utils/html-comments/test3/source.vue
new file mode 100644
index 000000000..bdb9c48cd
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test3/source.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/tests/fixtures/utils/html-comments/test4/comment-tokens.json b/tests/fixtures/utils/html-comments/test4/comment-tokens.json
new file mode 100644
index 000000000..3513260bd
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test4/comment-tokens.json
@@ -0,0 +1,60 @@
+[
+ {
+ "open": {
+ "type": "HTMLCommentOpen",
+ "value": "",
+ "range": [
+ 73,
+ 76
+ ],
+ "loc": {
+ "start": {
+ "line": 6,
+ "column": 2
+ },
+ "end": {
+ "line": 6,
+ "column": 5
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/utils/html-comments/test4/source.vue b/tests/fixtures/utils/html-comments/test4/source.vue
new file mode 100644
index 000000000..bca201562
--- /dev/null
+++ b/tests/fixtures/utils/html-comments/test4/source.vue
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$-with-destructuring/result.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$-with-destructuring/result.js
new file mode 100644
index 000000000..805890a33
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$-with-destructuring/result.js
@@ -0,0 +1,16 @@
+let { a, b: c, d: [,f], g: [...h], ...i } = $(foo)
+let [ x, y = 42 ] = $(bar)
+
+console.log(
+ /*>*/a/*<{"escape":false,"method":"$"}*/,
+ b,
+ /*>*/c/*<{"escape":false,"method":"$"}*/,
+ d,
+ e,
+ /*>*/f/*<{"escape":false,"method":"$"}*/,
+ g,
+ /*>*/h/*<{"escape":false,"method":"$"}*/,
+ /*>*/i/*<{"escape":false,"method":"$"}*/,
+ /*>*/x/*<{"escape":false,"method":"$"}*/,
+ /*>*/y/*<{"escape":false,"method":"$"}*/
+)
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$-with-destructuring/source.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$-with-destructuring/source.js
new file mode 100644
index 000000000..bfb92574f
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$-with-destructuring/source.js
@@ -0,0 +1,16 @@
+let { a, b: c, d: [,f], g: [...h], ...i } = $(foo)
+let [ x, y = 42 ] = $(bar)
+
+console.log(
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i,
+ x,
+ y
+)
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$/result.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$/result.js
new file mode 100644
index 000000000..80d709dee
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$/result.js
@@ -0,0 +1,6 @@
+let v = $(foo)
+let { x, y } = $(bar)
+console.log(/*>*/v/*<{"escape":false,"method":"$"}*/, /*>*/x/*<{"escape":false,"method":"$"}*/, /*>*/y/*<{"escape":false,"method":"$"}*/)
+let a = /*>*/v/*<{"escape":false,"method":"$"}*/
+console.log(a)
+console.log($$({ /*>*/v/*<{"escape":true,"method":"$"}*/, /*>*/x/*<{"escape":true,"method":"$"}*/ }))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$/source.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$/source.js
new file mode 100644
index 000000000..28c98cbc9
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$/source.js
@@ -0,0 +1,6 @@
+let v = $(foo)
+let { x, y } = $(bar)
+console.log(v, x, y)
+let a = v
+console.log(a)
+console.log($$({ v, x }))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$computed/result.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$computed/result.js
new file mode 100644
index 000000000..a45a0ae6f
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$computed/result.js
@@ -0,0 +1,5 @@
+let v = $computed(() => 42)
+console.log(/*>*/v/*<{"escape":false,"method":"$computed"}*/)
+let a = /*>*/v/*<{"escape":false,"method":"$computed"}*/
+console.log(a)
+console.log($$(/*>*/v/*<{"escape":true,"method":"$computed"}*/))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$computed/source.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$computed/source.js
new file mode 100644
index 000000000..ff0f34766
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$computed/source.js
@@ -0,0 +1,5 @@
+let v = $computed(() => 42)
+console.log(v)
+let a = v
+console.log(a)
+console.log($$(v))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$customRef/result.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$customRef/result.js
new file mode 100644
index 000000000..a8a3ae90a
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$customRef/result.js
@@ -0,0 +1,10 @@
+let v = $customRef((track, trigger) => {
+ return {
+ get() { return count },
+ set(newValue) { count = newValue }
+ }
+})
+console.log(/*>*/v/*<{"escape":false,"method":"$customRef"}*/)
+let a = /*>*/v/*<{"escape":false,"method":"$customRef"}*/
+console.log(a)
+console.log($$(/*>*/v/*<{"escape":true,"method":"$customRef"}*/))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$customRef/source.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$customRef/source.js
new file mode 100644
index 000000000..dd62754bc
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$customRef/source.js
@@ -0,0 +1,10 @@
+let v = $customRef((track, trigger) => {
+ return {
+ get() { return count },
+ set(newValue) { count = newValue }
+ }
+})
+console.log(v)
+let a = v
+console.log(a)
+console.log($$(v))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$ref/result.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$ref/result.js
new file mode 100644
index 000000000..518f0c56b
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$ref/result.js
@@ -0,0 +1,5 @@
+let v = $ref(0)
+console.log(/*>*/v/*<{"escape":false,"method":"$ref"}*/)
+let a = /*>*/v/*<{"escape":false,"method":"$ref"}*/
+console.log(a)
+console.log($$(/*>*/v/*<{"escape":true,"method":"$ref"}*/))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$ref/source.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$ref/source.js
new file mode 100644
index 000000000..5c8d7248e
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$ref/source.js
@@ -0,0 +1,5 @@
+let v = $ref(0)
+console.log(v)
+let a = v
+console.log(a)
+console.log($$(v))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$shallowRef/result.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$shallowRef/result.js
new file mode 100644
index 000000000..dbe345a19
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$shallowRef/result.js
@@ -0,0 +1,5 @@
+let v = $shallowRef({ count: 0 })
+console.log(/*>*/v/*<{"escape":false,"method":"$shallowRef"}*/)
+let a = /*>*/v/*<{"escape":false,"method":"$shallowRef"}*/
+console.log(a)
+console.log($$(/*>*/v/*<{"escape":true,"method":"$shallowRef"}*/))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$shallowRef/source.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$shallowRef/source.js
new file mode 100644
index 000000000..da4c19320
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$shallowRef/source.js
@@ -0,0 +1,5 @@
+let v = $shallowRef({ count: 0 })
+console.log(v)
+let a = v
+console.log(a)
+console.log($$(v))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$toRef/result.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$toRef/result.js
new file mode 100644
index 000000000..a7bff1219
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$toRef/result.js
@@ -0,0 +1,5 @@
+let v = $toRef(foo, 'bar')
+console.log(/*>*/v/*<{"escape":false,"method":"$toRef"}*/)
+let a = /*>*/v/*<{"escape":false,"method":"$toRef"}*/
+console.log(a)
+console.log($$(/*>*/v/*<{"escape":true,"method":"$toRef"}*/))
diff --git a/tests/fixtures/utils/ref-object-references/reactive-vars/$toRef/source.js b/tests/fixtures/utils/ref-object-references/reactive-vars/$toRef/source.js
new file mode 100644
index 000000000..b94ddea93
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/reactive-vars/$toRef/source.js
@@ -0,0 +1,5 @@
+let v = $toRef(foo, 'bar')
+console.log(v)
+let a = v
+console.log(a)
+console.log($$(v))
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/computed/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/computed/result.js
new file mode 100644
index 000000000..2217a8182
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/computed/result.js
@@ -0,0 +1,5 @@
+import { computed } from 'vue'
+let v = computed(() => 42)
+console.log(/*>*/v/*<{"type":"expression","method":"computed"}*/.value)
+let a = v
+console.log(/*>*/a/*<{"type":"expression","method":"computed"}*/.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/computed/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/computed/source.js
new file mode 100644
index 000000000..2b4a6412d
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/computed/source.js
@@ -0,0 +1,5 @@
+import { computed } from 'vue'
+let v = computed(() => 42)
+console.log(v.value)
+let a = v
+console.log(a.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/customRef/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/customRef/result.js
new file mode 100644
index 000000000..e7af0774e
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/customRef/result.js
@@ -0,0 +1,10 @@
+import { customRef } from 'vue'
+let v = customRef((track, trigger) => {
+ return {
+ get() { return count },
+ set(newValue) { count = newValue }
+ }
+})
+console.log(/*>*/v/*<{"type":"expression","method":"customRef"}*/.value)
+let a = v
+console.log(/*>*/a/*<{"type":"expression","method":"customRef"}*/.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/customRef/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/customRef/source.js
new file mode 100644
index 000000000..c94db730f
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/customRef/source.js
@@ -0,0 +1,10 @@
+import { customRef } from 'vue'
+let v = customRef((track, trigger) => {
+ return {
+ get() { return count },
+ set(newValue) { count = newValue }
+ }
+})
+console.log(v.value)
+let a = v
+console.log(a.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/defineModel01/result.vue b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel01/result.vue
new file mode 100644
index 000000000..a5d4293d8
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel01/result.vue
@@ -0,0 +1,24 @@
+
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/defineModel01/source.vue b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel01/source.vue
new file mode 100644
index 000000000..7cca9156c
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel01/source.vue
@@ -0,0 +1,24 @@
+
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/defineModel02/result.vue b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel02/result.vue
new file mode 100644
index 000000000..c2903d01f
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel02/result.vue
@@ -0,0 +1,16 @@
+
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/defineModel02/source.vue b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel02/source.vue
new file mode 100644
index 000000000..012cb9349
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/defineModel02/source.vue
@@ -0,0 +1,16 @@
+
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/ref-to-pattern/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/ref-to-pattern/result.js
new file mode 100644
index 000000000..a0681af4a
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/ref-to-pattern/result.js
@@ -0,0 +1,9 @@
+import { ref } from 'vue'
+let v = ref(0)
+const /*>*/{ value: a }/*<{"type":"pattern","method":"ref"}*/ = v
+const /*>*/{ value = 42 }/*<{"type":"pattern","method":"ref"}*/ = v
+console.log(a)
+console.log(value)
+
+const [x] = v
+console.log(x)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/ref-to-pattern/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/ref-to-pattern/source.js
new file mode 100644
index 000000000..3a2c5c330
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/ref-to-pattern/source.js
@@ -0,0 +1,9 @@
+import { ref } from 'vue'
+let v = ref(0)
+const { value: a } = v
+const { value = 42 } = v
+console.log(a)
+console.log(value)
+
+const [x] = v
+console.log(x)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/ref/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/ref/result.js
new file mode 100644
index 000000000..c356f2c2b
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/ref/result.js
@@ -0,0 +1,5 @@
+import { ref } from 'vue'
+let v = ref(0)
+console.log(/*>*/v/*<{"type":"expression","method":"ref"}*/.value)
+let a = v
+console.log(/*>*/a/*<{"type":"expression","method":"ref"}*/.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/ref/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/ref/source.js
new file mode 100644
index 000000000..836b45777
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/ref/source.js
@@ -0,0 +1,5 @@
+import { ref } from 'vue'
+let v = ref(0)
+console.log(v.value)
+let a = v
+console.log(a.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/shallowRef/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/shallowRef/result.js
new file mode 100644
index 000000000..206f3e88d
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/shallowRef/result.js
@@ -0,0 +1,5 @@
+import { shallowRef } from 'vue'
+let v = shallowRef({ count: 0 })
+console.log(/*>*/v/*<{"type":"expression","method":"shallowRef"}*/.value)
+let a = v
+console.log(/*>*/a/*<{"type":"expression","method":"shallowRef"}*/.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/shallowRef/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/shallowRef/source.js
new file mode 100644
index 000000000..98496ae7f
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/shallowRef/source.js
@@ -0,0 +1,5 @@
+import { shallowRef } from 'vue'
+let v = shallowRef({ count: 0 })
+console.log(v.value)
+let a = v
+console.log(a.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/toRef/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/toRef/result.js
new file mode 100644
index 000000000..28bea16dc
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/toRef/result.js
@@ -0,0 +1,5 @@
+import { toRef } from 'vue'
+let v = toRef(foo, 'bar')
+console.log(/*>*/v/*<{"type":"expression","method":"toRef"}*/.value)
+let a = v
+console.log(/*>*/a/*<{"type":"expression","method":"toRef"}*/.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/toRef/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/toRef/source.js
new file mode 100644
index 000000000..df884f003
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/toRef/source.js
@@ -0,0 +1,5 @@
+import { toRef } from 'vue'
+let v = toRef(foo, 'bar')
+console.log(v.value)
+let a = v
+console.log(a.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/toRefs-to-pattern/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs-to-pattern/result.js
new file mode 100644
index 000000000..527acdc78
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs-to-pattern/result.js
@@ -0,0 +1,5 @@
+import { toRefs } from 'vue'
+let bar = toRefs(foo)
+const { x, y = 42 } = bar
+console.log(/*>*/x/*<{"type":"expression","method":"toRefs"}*/.value)
+console.log(/*>*/y/*<{"type":"expression","method":"toRefs"}*/.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/toRefs-to-pattern/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs-to-pattern/source.js
new file mode 100644
index 000000000..b2c39b4a9
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs-to-pattern/source.js
@@ -0,0 +1,5 @@
+import { toRefs } from 'vue'
+let bar = toRefs(foo)
+const { x, y = 42 } = bar
+console.log(x.value)
+console.log(y.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/toRefs/result.js b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs/result.js
new file mode 100644
index 000000000..809dfc902
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs/result.js
@@ -0,0 +1,18 @@
+import { toRefs } from 'vue'
+let { x, y } = toRefs(foo)
+console.log(/*>*/x/*<{"type":"expression","method":"toRefs"}*/.value)
+let a = y
+console.log(/*>*/a/*<{"type":"expression","method":"toRefs"}*/.value)
+console.log(/*>*/y/*<{"type":"expression","method":"toRefs"}*/.value)
+
+let bar = toRefs(foo)
+console.log(bar)
+console.log(/*>*/bar.x/*<{"type":"expression","method":"toRefs"}*/.value)
+console.log(/*>*/bar.y/*<{"type":"expression","method":"toRefs"}*/.value)
+
+const z = bar.z
+console.log(/*>*/z/*<{"type":"expression","method":"toRefs"}*/.value)
+
+let b;
+/*>*/b/*<{"type":"pattern","method":"toRefs"}*/ = bar.b
+console.log(/*>*/b/*<{"type":"expression","method":"toRefs"}*/.value)
diff --git a/tests/fixtures/utils/ref-object-references/ref-objects/toRefs/source.js b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs/source.js
new file mode 100644
index 000000000..638e69296
--- /dev/null
+++ b/tests/fixtures/utils/ref-object-references/ref-objects/toRefs/source.js
@@ -0,0 +1,18 @@
+import { toRefs } from 'vue'
+let { x, y } = toRefs(foo)
+console.log(x.value)
+let a = y
+console.log(a.value)
+console.log(y.value)
+
+let bar = toRefs(foo)
+console.log(bar)
+console.log(bar.x.value)
+console.log(bar.y.value)
+
+const z = bar.z
+console.log(z.value)
+
+let b;
+b = bar.b
+console.log(b.value)
diff --git a/tests/fixtures/utils/selector/attr-contains/result.json b/tests/fixtures/utils/selector/attr-contains/result.json
new file mode 100644
index 000000000..a2627437e
--- /dev/null
+++ b/tests/fixtures/utils/selector/attr-contains/result.json
@@ -0,0 +1,9 @@
+{
+ "selector": "a[href*=\"example\"]",
+ "matches": [
+ "",
+ "",
+ ""
+ ],
+ "errors": []
+}
\ No newline at end of file
diff --git a/tests/fixtures/utils/selector/attr-contains/source.vue b/tests/fixtures/utils/selector/attr-contains/source.vue
new file mode 100644
index 000000000..6c9a89dde
--- /dev/null
+++ b/tests/fixtures/utils/selector/attr-contains/source.vue
@@ -0,0 +1,10 @@
+
+
+