diff --git a/.circleci/config.yml b/.circleci/config.yml
index e695f422a..0ba3d33dc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,7 +5,6 @@ workflows:
- eslint-v6
- eslint-v7
- ts-eslint-v4
- - node-v12
- node-v14
- node-v16
- lint
@@ -36,7 +35,7 @@ jobs:
eslint-v6:
docker:
- - image: node:12
+ - image: node:14
steps:
- run:
name: Versions
@@ -88,10 +87,6 @@ jobs:
- run:
name: Test
command: npm test
- node-v12:
- <<: *node-base
- docker:
- - image: node:12
node-v14:
<<: *node-base
docker:
diff --git a/.eslintrc.js b/.eslintrc.js
index 3229de98d..b73aa7c94 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,7 +3,7 @@
module.exports = {
root: true,
parserOptions: {
- ecmaVersion: 2018
+ ecmaVersion: 'latest'
},
env: {
es6: true,
@@ -14,9 +14,10 @@ module.exports = {
'plugin:eslint-plugin/recommended',
'prettier',
'plugin:node-dependencies/recommended',
- 'plugin:jsonc/recommended-with-jsonc'
+ 'plugin:jsonc/recommended-with-jsonc',
+ 'plugin:unicorn/recommended'
],
- plugins: ['eslint-plugin', 'prettier'],
+ plugins: ['eslint-plugin', 'prettier', 'unicorn'],
rules: {
'accessor-pairs': 2,
camelcase: [2, { properties: 'never' }],
@@ -117,14 +118,27 @@ module.exports = {
'prefer-arrow-callback': 'error',
'prefer-spread': 'error',
- 'dot-notation': 'error'
+ 'dot-notation': 'error',
+ 'arrow-body-style': 'error',
+
+ 'unicorn/consistent-function-scoping': [
+ 'error',
+ { checkArrowFunctions: false }
+ ],
+ 'unicorn/filename-case': 'off',
+ 'unicorn/no-null': 'off',
+ 'unicorn/no-array-callback-reference': 'off', // doesn't work well with TypeScript's custom type guards
+ 'unicorn/no-useless-undefined': 'off',
+ 'unicorn/prefer-optional-catch-binding': 'off', // not supported by current ESLint parser version
+ 'unicorn/prefer-module': 'off',
+ 'unicorn/prevent-abbreviations': 'off'
},
overrides: [
{
files: ['./**/*.vue'],
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
- ecmaVersion: 2020,
+ ecmaVersion: 'latest',
sourceType: 'module'
}
},
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 15c7d4193..dedfe4110 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -25,7 +25,7 @@ jobs:
name: Test
strategy:
matrix:
- node: [16, 17]
+ node: [17, 18]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
diff --git a/.markdownlint.yml b/.markdownlint.yml
index cb76e21d5..b300725cc 100644
--- a/.markdownlint.yml
+++ b/.markdownlint.yml
@@ -5,6 +5,8 @@ no-inline-html:
- badge
- eslint-code-block
- sup
+ - rules-table
+ - span
# enforce consistency
code-block-style:
diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vuepress/components/eslint-code-block.vue
index 2c6abd2cc..f71e606a3 100644
--- a/docs/.vuepress/components/eslint-code-block.vue
+++ b/docs/.vuepress/components/eslint-code-block.vue
@@ -90,7 +90,7 @@ export default {
rules: this.rules,
parser: 'vue-eslint-parser',
parserOptions: {
- ecmaVersion: 2020,
+ ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true
diff --git a/docs/.vuepress/components/rules-table.vue b/docs/.vuepress/components/rules-table.vue
new file mode 100644
index 000000000..29d31c4c6
--- /dev/null
+++ b/docs/.vuepress/components/rules-table.vue
@@ -0,0 +1,90 @@
+
+
+
+ Highlight:
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js
index dc7e30771..03082d606 100644
--- a/docs/.vuepress/enhanceApp.js
+++ b/docs/.vuepress/enhanceApp.js
@@ -8,21 +8,19 @@ export default (
// siteData, // site metadata
}
) => {
- if (typeof window !== 'undefined') {
- if (typeof window.process === 'undefined') {
- window.process = new Proxy(
- {
- env: {},
- cwd: () => undefined
- },
- {
- get(target, name) {
- // For debug
- // console.log(name)
- return target[name]
- }
+ if (typeof window !== 'undefined' && typeof window.process === 'undefined') {
+ window.process = new Proxy(
+ {
+ env: {},
+ cwd: () => undefined
+ },
+ {
+ get(target, name) {
+ // For debug
+ // console.log(name)
+ return target[name]
}
- )
- }
+ }
+ )
}
}
diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl
index 48680bbf3..724c68247 100644
--- a/docs/.vuepress/styles/index.styl
+++ b/docs/.vuepress/styles/index.styl
@@ -18,3 +18,9 @@
}
}
}
+
+.theme-container.rule-list .theme-default-content
+ max-width: 1280px;
+
+.emoji
+ font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Apple Color Emoji", "Noto Color Emoji", "Noto Emoji", sans-serif
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 26ce0e59d..b72928647 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -1,5 +1,6 @@
---
sidebarDepth: 0
+pageClass: rule-list
---
# Available rules
@@ -12,282 +13,179 @@ sidebarDepth: 0
: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).
:::
-## Base Rules (Enabling Correct ESLint Parsing)
-
-Enforce all the rules in this category, as well as all higher priority rules, with:
-
-```json
-{
- "extends": "plugin:vue/base"
-}
-```
+Mark indicating rule type:
-| Rule ID | Description | |
-|:--------|:------------|:---|
-| [vue/comment-directive](./comment-directive.md) | support comment-directives in `` | |
-| [vue/jsx-uses-vars](./jsx-uses-vars.md) | prevent variables used in JSX to be marked as unused | |
-| [vue/script-setup-uses-vars](./script-setup-uses-vars.md) | prevent `
+```
+
+
+
+
+
+```vue
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+```
+
+
+
## :wrench: Options
```json
@@ -116,6 +157,18 @@ export default {
+
+
+```vue
+
+
+
+```
+
+
+
## :books: Further Reading
- [Style guide - Multi-word component names](https://vuejs.org/style-guide/rules-essential.html#use-multi-word-component-names)
diff --git a/docs/rules/name-property-casing.md b/docs/rules/name-property-casing.md
index 0dedbc1d3..a69855f7e 100644
--- a/docs/rules/name-property-casing.md
+++ b/docs/rules/name-property-casing.md
@@ -9,8 +9,7 @@ since: v3.8.0
> enforce specific casing for the name property in Vue components
-- :warning: This rule was **deprecated** and replaced by [vue/component-definition-name-casing](component-definition-name-casing.md) rule.
-- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+- :no_entry_sign: 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
diff --git a/docs/rules/no-child-content.md b/docs/rules/no-child-content.md
index 295232301..940ea183c 100644
--- a/docs/rules/no-child-content.md
+++ b/docs/rules/no-child-content.md
@@ -9,6 +9,7 @@ since: v8.1.0
> 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/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
## :book: Rule Details
diff --git a/docs/rules/no-confusing-v-for-v-if.md b/docs/rules/no-confusing-v-for-v-if.md
index 9bdfbf3de..c9d72dbb4 100644
--- a/docs/rules/no-confusing-v-for-v-if.md
+++ b/docs/rules/no-confusing-v-for-v-if.md
@@ -9,7 +9,7 @@ since: v3.0.0
> disallow confusing `v-for` and `v-if` on the same element
-- :warning: This rule was **deprecated** and replaced by [vue/no-use-v-if-with-v-for](no-use-v-if-with-v-for.md) rule.
+- :no_entry_sign: 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
diff --git a/docs/rules/no-export-in-script-setup.md b/docs/rules/no-export-in-script-setup.md
index b059305b9..c16899c80 100644
--- a/docs/rules/no-export-in-script-setup.md
+++ b/docs/rules/no-export-in-script-setup.md
@@ -9,7 +9,7 @@ since: v7.13.0
> disallow `export` in `
+```
+
+
+
## :wrench: Options
Nothing.
diff --git a/docs/rules/no-invalid-model-keys.md b/docs/rules/no-invalid-model-keys.md
index 1a46e9a47..34f65d88a 100644
--- a/docs/rules/no-invalid-model-keys.md
+++ b/docs/rules/no-invalid-model-keys.md
@@ -9,6 +9,8 @@ since: v7.9.0
> require valid keys in model option
+- :warning: This rule was **deprecated** 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.
diff --git a/docs/rules/no-ref-as-operand.md b/docs/rules/no-ref-as-operand.md
index 2610d4624..df3549dc6 100644
--- a/docs/rules/no-ref-as-operand.md
+++ b/docs/rules/no-ref-as-operand.md
@@ -9,7 +9,7 @@ since: v7.0.0
> disallow use of value wrapped by `ref()` (Composition API) as an operand
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
## :book: Rule Details
diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md
index a0d9c5f2c..94e761eaa 100644
--- a/docs/rules/no-reserved-component-names.md
+++ b/docs/rules/no-reserved-component-names.md
@@ -9,6 +9,8 @@ since: v6.1.0
> disallow the use of reserved names in component definitions
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+
## :book: Rule Details
This rule prevents name collisions between Vue components and standard HTML elements and built-in components.
diff --git a/docs/rules/no-restricted-class.md b/docs/rules/no-restricted-class.md
index d67e5d8b2..849c71e8c 100644
--- a/docs/rules/no-restricted-class.md
+++ b/docs/rules/no-restricted-class.md
@@ -35,7 +35,7 @@ in the rule configuration.
-
+
diff --git a/docs/rules/no-restricted-custom-event.md b/docs/rules/no-restricted-custom-event.md
index bc48def0a..b77feac14 100644
--- a/docs/rules/no-restricted-custom-event.md
+++ b/docs/rules/no-restricted-custom-event.md
@@ -31,7 +31,7 @@ This rule takes a list of strings, where each string is a custom event name or p
-
+
+```
+
+
+
+
+
+```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/user-guide/README.md b/docs/user-guide/README.md
index b083a6bd3..8129cad21 100644
--- a/docs/user-guide/README.md
+++ b/docs/user-guide/README.md
@@ -23,7 +23,7 @@ yarn add -D eslint eslint-plugin-vue
::: tip Requirements
- ESLint v6.2.0 and above
-- Node.js v12.22.x, v14.17.x, v16.x and above
+- Node.js v14.17.x, v16.x and above
:::
@@ -342,36 +342,15 @@ See also: "[Visual Studio Code](#editor-integrations)" section and [Vetur - Lint
#### The variables used in the `` are warned by `no-unused-vars` rule
-You must use [vue/script-setup-uses-vars](../rules/script-setup-uses-vars.md) rule.
-In your configuration, use the rule set provided by `eslint-plugin-vue` or enable it rule.
+You need to use [vue-eslint-parser] v9.0.0 or later.
-Example **.eslintrc.js**:
-
-```js
-module.exports = {
- // Use the rule set.
- extends: ['plugin:vue/base'],
- rules: {
- // Enable vue/script-setup-uses-vars rule
- 'vue/script-setup-uses-vars': 'error',
- }
-}
-```
+Previously you had to use the [vue/script-setup-uses-vars](../rules/script-setup-uses-vars.md) rule, this is no longer needed.
#### Compiler macros such as `defineProps` and `defineEmits` generate `no-undef` warnings
-You need to enable the compiler macros environment in your ESLint configuration file.
-If you don't want to expose these variables globally, you can use `/* global defineProps, defineEmits */` instead.
+You need to use [vue-eslint-parser] v9.0.0 or later.
-Example **.eslintrc.js**:
-
-```js
-module.exports = {
- env: {
- 'vue/setup-compiler-macros': true
- }
-}
-```
+Previously you had to use the `vue/setup-compiler-macros` environment, this is no longer needed.
#### Parsing error with Top Level `await`
@@ -411,3 +390,5 @@ module.exports = {
Try searching for existing issues.
If it does not exist, you should open a new issue and share your repository to reproduce the issue.
+
+[vue-eslint-parser]: https://github.com/vuejs/vue-eslint-parser
diff --git a/eslint-internal-rules/no-invalid-meta-docs-categories.js b/eslint-internal-rules/no-invalid-meta-docs-categories.js
index 5a86d16ab..c95691c36 100644
--- a/eslint-internal-rules/no-invalid-meta-docs-categories.js
+++ b/eslint-internal-rules/no-invalid-meta-docs-categories.js
@@ -12,17 +12,15 @@
/**
* Gets the property of the Object node passed in that has the name specified.
*
- * @param {string} property Name of the property to return.
+ * @param {string} propertyName Name of the property to return.
* @param {ASTNode} node The ObjectExpression node.
* @returns {ASTNode} The Property node or null if not found.
*/
-function getPropertyFromObject(property, node) {
+function getPropertyFromObject(propertyName, node) {
if (node && node.type === 'ObjectExpression') {
- const properties = node.properties
-
- for (let i = 0; i < properties.length; i++) {
- if (properties[i].key.name === property) {
- return properties[i]
+ for (const property of node.properties) {
+ if (property.type === 'Property' && property.key.name === propertyName) {
+ return property
}
}
}
diff --git a/eslint-internal-rules/no-invalid-meta.js b/eslint-internal-rules/no-invalid-meta.js
index e96435cf9..45057c9bf 100644
--- a/eslint-internal-rules/no-invalid-meta.js
+++ b/eslint-internal-rules/no-invalid-meta.js
@@ -12,17 +12,15 @@
/**
* Gets the property of the Object node passed in that has the name specified.
*
- * @param {string} property Name of the property to return.
+ * @param {string} propertyName Name of the property to return.
* @param {ASTNode} node The ObjectExpression node.
* @returns {ASTNode} The Property node or null if not found.
*/
-function getPropertyFromObject(property, node) {
+function getPropertyFromObject(propertyName, node) {
if (node && node.type === 'ObjectExpression') {
- const properties = node.properties
-
- for (let i = 0; i < properties.length; i++) {
- if (properties[i].key.name === property) {
- return properties[i]
+ for (const property of node.properties) {
+ if (property.type === 'Property' && property.key.name === propertyName) {
+ return property
}
}
}
diff --git a/lib/configs/base.js b/lib/configs/base.js
index 145991976..3724461e2 100644
--- a/lib/configs/base.js
+++ b/lib/configs/base.js
@@ -16,7 +16,6 @@ module.exports = {
plugins: ['vue'],
rules: {
'vue/comment-directive': 'error',
- 'vue/jsx-uses-vars': 'error',
- 'vue/script-setup-uses-vars': 'error'
+ 'vue/jsx-uses-vars': 'error'
}
}
diff --git a/lib/configs/essential.js b/lib/configs/essential.js
index 970473a4d..944a162ea 100644
--- a/lib/configs/essential.js
+++ b/lib/configs/essential.js
@@ -9,14 +9,18 @@ module.exports = {
'vue/multi-word-component-names': 'error',
'vue/no-arrow-functions-in-watch': 'error',
'vue/no-async-in-computed-properties': 'error',
+ 'vue/no-child-content': 'error',
'vue/no-computed-properties-in-data': 'error',
'vue/no-custom-modifiers-on-v-model': 'error',
'vue/no-dupe-keys': 'error',
'vue/no-dupe-v-else-if': 'error',
'vue/no-duplicate-attributes': 'error',
+ 'vue/no-export-in-script-setup': 'error',
'vue/no-multiple-template-root': 'error',
'vue/no-mutating-props': 'error',
'vue/no-parsing-error': 'error',
+ 'vue/no-ref-as-operand': 'error',
+ 'vue/no-reserved-component-names': 'error',
'vue/no-reserved-keys': 'error',
'vue/no-reserved-props': [
'error',
@@ -24,23 +28,31 @@ module.exports = {
vueVersion: 2
}
],
+ 'vue/no-setup-props-destructure': 'error',
'vue/no-shared-component-data': 'error',
'vue/no-side-effects-in-computed-properties': 'error',
'vue/no-template-key': 'error',
'vue/no-textarea-mustache': 'error',
'vue/no-unused-components': 'error',
'vue/no-unused-vars': 'error',
+ 'vue/no-use-computed-property-like-method': 'error',
'vue/no-use-v-if-with-v-for': 'error',
'vue/no-useless-template-attributes': 'error',
'vue/no-v-for-template-key': 'error',
'vue/no-v-model-argument': 'error',
+ 'vue/no-v-text-v-html-on-component': 'error',
'vue/require-component-is': 'error',
'vue/require-prop-type-constructor': 'error',
'vue/require-render-return': 'error',
'vue/require-v-for-key': 'error',
'vue/require-valid-default-prop': 'error',
'vue/return-in-computed-property': 'error',
+ 'vue/return-in-emits-validator': 'error',
'vue/use-v-on-exact': 'error',
+ 'vue/valid-attribute-name': 'error',
+ 'vue/valid-define-emits': 'error',
+ 'vue/valid-define-props': 'error',
+ 'vue/valid-model-definition': 'error',
'vue/valid-next-tick': 'error',
'vue/valid-template-root': 'error',
'vue/valid-v-bind-sync': 'error',
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 155774649..5fe530beb 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -9,6 +9,7 @@ module.exports = {
'vue/multi-word-component-names': 'error',
'vue/no-arrow-functions-in-watch': 'error',
'vue/no-async-in-computed-properties': 'error',
+ 'vue/no-child-content': 'error',
'vue/no-computed-properties-in-data': 'error',
'vue/no-deprecated-data-object-declaration': 'error',
'vue/no-deprecated-destroyed-lifecycle': 'error',
@@ -33,10 +34,12 @@ module.exports = {
'vue/no-dupe-v-else-if': 'error',
'vue/no-duplicate-attributes': 'error',
'vue/no-export-in-script-setup': 'error',
+ 'vue/no-expose-after-await': 'error',
'vue/no-lifecycle-after-await': 'error',
'vue/no-mutating-props': 'error',
'vue/no-parsing-error': 'error',
'vue/no-ref-as-operand': 'error',
+ 'vue/no-reserved-component-names': 'error',
'vue/no-reserved-keys': 'error',
'vue/no-reserved-props': 'error',
'vue/no-setup-props-destructure': 'error',
@@ -46,10 +49,13 @@ module.exports = {
'vue/no-textarea-mustache': 'error',
'vue/no-unused-components': 'error',
'vue/no-unused-vars': 'error',
+ 'vue/no-use-computed-property-like-method': 'error',
'vue/no-use-v-if-with-v-for': 'error',
'vue/no-useless-template-attributes': 'error',
'vue/no-v-for-template-key-on-child': 'error',
+ 'vue/no-v-text-v-html-on-component': 'error',
'vue/no-watch-after-await': 'error',
+ 'vue/prefer-import-from-vue': 'error',
'vue/require-component-is': 'error',
'vue/require-prop-type-constructor': 'error',
'vue/require-render-return': 'error',
@@ -60,6 +66,7 @@ module.exports = {
'vue/return-in-computed-property': 'error',
'vue/return-in-emits-validator': 'error',
'vue/use-v-on-exact': 'error',
+ 'vue/valid-attribute-name': 'error',
'vue/valid-define-emits': 'error',
'vue/valid-define-props': 'error',
'vue/valid-next-tick': 'error',
diff --git a/lib/index.js b/lib/index.js
index 39f91b515..c4979a98c 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -31,7 +31,6 @@ module.exports = {
'dot-location': require('./rules/dot-location'),
'dot-notation': require('./rules/dot-notation'),
eqeqeq: require('./rules/eqeqeq'),
- 'experimental-script-setup-vars': require('./rules/experimental-script-setup-vars'),
'first-attribute-linebreak': require('./rules/first-attribute-linebreak'),
'func-call-spacing': require('./rules/func-call-spacing'),
'html-button-has-type': require('./rules/html-button-has-type'),
@@ -54,7 +53,6 @@ module.exports = {
'multi-word-component-names': require('./rules/multi-word-component-names'),
'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
'mustache-interpolation-spacing': require('./rules/mustache-interpolation-spacing'),
- 'name-property-casing': require('./rules/name-property-casing'),
'new-line-between-multi-line-property': require('./rules/new-line-between-multi-line-property'),
'next-tick-style': require('./rules/next-tick-style'),
'no-arrow-functions-in-watch': require('./rules/no-arrow-functions-in-watch'),
@@ -63,7 +61,6 @@ module.exports = {
'no-boolean-default': require('./rules/no-boolean-default'),
'no-child-content': require('./rules/no-child-content'),
'no-computed-properties-in-data': require('./rules/no-computed-properties-in-data'),
- 'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'),
'no-constant-condition': require('./rules/no-constant-condition'),
'no-custom-modifiers-on-v-model': require('./rules/no-custom-modifiers-on-v-model'),
'no-deprecated-data-object-declaration': require('./rules/no-deprecated-data-object-declaration'),
@@ -133,7 +130,6 @@ module.exports = {
'no-this-in-before-route-enter': require('./rules/no-this-in-before-route-enter'),
'no-undef-components': require('./rules/no-undef-components'),
'no-undef-properties': require('./rules/no-undef-properties'),
- 'no-unregistered-components': require('./rules/no-unregistered-components'),
'no-unsupported-features': require('./rules/no-unsupported-features'),
'no-unused-components': require('./rules/no-unused-components'),
'no-unused-properties': require('./rules/no-unused-properties'),
@@ -200,8 +196,10 @@ module.exports = {
'v-on-function-call': require('./rules/v-on-function-call'),
'v-on-style': require('./rules/v-on-style'),
'v-slot-style': require('./rules/v-slot-style'),
+ 'valid-attribute-name': require('./rules/valid-attribute-name'),
'valid-define-emits': require('./rules/valid-define-emits'),
'valid-define-props': require('./rules/valid-define-props'),
+ 'valid-model-definition': require('./rules/valid-model-definition'),
'valid-next-tick': require('./rules/valid-next-tick'),
'valid-template-root': require('./rules/valid-template-root'),
'valid-v-bind-sync': require('./rules/valid-v-bind-sync'),
@@ -236,6 +234,8 @@ module.exports = {
'.vue': require('./processor')
},
environments: {
+ // TODO Remove in the next major version
+ /** @deprecated */
'setup-compiler-macros': {
globals: {
defineProps: 'readonly',
diff --git a/lib/processor.js b/lib/processor.js
index 81ab94784..e0c10ef72 100644
--- a/lib/processor.js
+++ b/lib/processor.js
@@ -84,10 +84,10 @@ module.exports = {
return false
} else {
const disableDirectiveKeys = []
- if (state.block.disableAllKeys.size) {
+ if (state.block.disableAllKeys.size > 0) {
disableDirectiveKeys.push(...state.block.disableAllKeys)
}
- if (state.line.disableAllKeys.size) {
+ if (state.line.disableAllKeys.size > 0) {
disableDirectiveKeys.push(...state.line.disableAllKeys)
}
if (message.ruleId) {
@@ -101,7 +101,7 @@ module.exports = {
}
}
- if (disableDirectiveKeys.length) {
+ if (disableDirectiveKeys.length > 0) {
// Store used eslint-disable comment key
usedDisableDirectiveKeys.push(...disableDirectiveKeys)
return false
@@ -111,7 +111,7 @@ module.exports = {
}
})
- if (unusedDisableDirectiveReports.size) {
+ if (unusedDisableDirectiveReports.size > 0) {
for (const key of usedDisableDirectiveKeys) {
// Remove used eslint-disable comments
unusedDisableDirectiveReports.delete(key)
diff --git a/lib/removed-rules.js b/lib/removed-rules.js
new file mode 100644
index 000000000..cf2b5e8fa
--- /dev/null
+++ b/lib/removed-rules.js
@@ -0,0 +1,37 @@
+'use strict'
+
+/**
+ * @typedef {object} RemovedRule
+ * @property {string} ruleName
+ * @property {string[]} replacedBy
+ * @property {string} deprecatedInVersion
+ * @property {string} removedInVersion
+ */
+
+/** @type {RemovedRule[]} */
+module.exports = [
+ {
+ ruleName: 'experimental-script-setup-vars',
+ replacedBy: [],
+ deprecatedInVersion: 'v7.13.0',
+ removedInVersion: 'v9.0.0'
+ },
+ {
+ ruleName: 'name-property-casing',
+ replacedBy: ['component-definition-name-casing'],
+ deprecatedInVersion: 'v7.0.0',
+ removedInVersion: 'v9.0.0'
+ },
+ {
+ ruleName: 'no-confusing-v-for-v-if',
+ replacedBy: ['no-use-v-if-with-v-for'],
+ deprecatedInVersion: 'v5.0.0',
+ removedInVersion: 'v9.0.0'
+ },
+ {
+ ruleName: 'no-unregistered-components',
+ replacedBy: ['no-undef-components'],
+ deprecatedInVersion: 'v8.4.0',
+ removedInVersion: 'v9.0.0'
+ }
+]
diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js
index f9126a548..1c097763c 100644
--- a/lib/rules/attribute-hyphenation.js
+++ b/lib/rules/attribute-hyphenation.js
@@ -12,6 +12,26 @@ const svgAttributes = require('../utils/svg-attributes-weird-case.json')
// Rule Definition
// ------------------------------------------------------------------------------
+/**
+ * @param {VDirective | VAttribute} node
+ * @returns {string | null}
+ */
+function getAttributeName(node) {
+ if (!node.directive) {
+ return node.key.rawName
+ }
+
+ if (
+ node.key.name.name === 'bind' &&
+ node.key.argument &&
+ node.key.argument.type === 'VIdentifier'
+ ) {
+ return node.key.argument.rawName
+ }
+
+ return null
+}
+
module.exports = {
meta: {
type: 'suggestion',
@@ -52,12 +72,10 @@ module.exports = {
const option = context.options[0]
const optionsPayload = context.options[1]
const useHyphenated = option !== 'never'
- let ignoredAttributes = ['data-', 'aria-', 'slot-scope'].concat(
- svgAttributes
- )
+ const ignoredAttributes = ['data-', 'aria-', 'slot-scope', ...svgAttributes]
if (optionsPayload && optionsPayload.ignore) {
- ignoredAttributes = ignoredAttributes.concat(optionsPayload.ignore)
+ ignoredAttributes.push(...optionsPayload.ignore)
}
const caseConverter = casing.getExactConverter(
@@ -89,9 +107,7 @@ module.exports = {
* @param {string} value
*/
function isIgnoredAttribute(value) {
- const isIgnored = ignoredAttributes.some((attr) => {
- return value.includes(attr)
- })
+ const isIgnored = ignoredAttributes.some((attr) => value.includes(attr))
if (isIgnored) {
return true
@@ -112,14 +128,8 @@ module.exports = {
)
return
- const name = !node.directive
- ? node.key.rawName
- : node.key.name.name === 'bind'
- ? node.key.argument &&
- node.key.argument.type === 'VIdentifier' &&
- node.key.argument.rawName
- : /* otherwise */ false
- if (!name || isIgnoredAttribute(name)) return
+ const name = getAttributeName(node)
+ if (name === null || isIgnoredAttribute(name)) return
reportIssue(node, name)
}
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
index 0d9ea9ce8..d88e9c934 100644
--- a/lib/rules/attributes-order.js
+++ b/lib/rules/attributes-order.js
@@ -111,30 +111,31 @@ function getAttributeType(attribute) {
if (attribute.directive) {
if (!isVBind(attribute)) {
const name = attribute.key.name.name
- if (name === 'for') {
- return ATTRS.LIST_RENDERING
- } else if (
- name === 'if' ||
- name === 'else-if' ||
- name === 'else' ||
- name === 'show' ||
- name === 'cloak'
- ) {
- return ATTRS.CONDITIONALS
- } else if (name === 'pre' || name === 'once') {
- return ATTRS.RENDER_MODIFIERS
- } else if (name === 'model') {
- return ATTRS.TWO_WAY_BINDING
- } else if (name === 'on') {
- return ATTRS.EVENTS
- } else if (name === 'html' || name === 'text') {
- return ATTRS.CONTENT
- } else if (name === 'slot') {
- return ATTRS.SLOT
- } else if (name === 'is') {
- return ATTRS.DEFINITION
- } else {
- return ATTRS.OTHER_DIRECTIVES
+ switch (name) {
+ case 'for':
+ return ATTRS.LIST_RENDERING
+ case 'if':
+ case 'else-if':
+ case 'else':
+ case 'show':
+ case 'cloak':
+ return ATTRS.CONDITIONALS
+ case 'pre':
+ case 'once':
+ return ATTRS.RENDER_MODIFIERS
+ case 'model':
+ return ATTRS.TWO_WAY_BINDING
+ case 'on':
+ return ATTRS.EVENTS
+ case 'html':
+ case 'text':
+ return ATTRS.CONTENT
+ case 'slot':
+ return ATTRS.SLOT
+ case 'is':
+ return ATTRS.DEFINITION
+ default:
+ return ATTRS.OTHER_DIRECTIVES
}
}
propName =
@@ -144,16 +145,19 @@ function getAttributeType(attribute) {
} else {
propName = attribute.key.name
}
- if (propName === 'is') {
- return ATTRS.DEFINITION
- } else if (propName === 'id') {
- return ATTRS.GLOBAL
- } else if (propName === 'ref' || propName === 'key') {
- return ATTRS.UNIQUE
- } else if (propName === 'slot' || propName === 'slot-scope') {
- return ATTRS.SLOT
- } else {
- return ATTRS.OTHER_ATTR
+ switch (propName) {
+ case 'is':
+ return ATTRS.DEFINITION
+ case 'id':
+ return ATTRS.GLOBAL
+ case 'ref':
+ case 'key':
+ return ATTRS.UNIQUE
+ case 'slot':
+ case 'slot-scope':
+ return ATTRS.SLOT
+ default:
+ return ATTRS.OTHER_ATTR
}
}
@@ -213,13 +217,13 @@ function create(context) {
/** @type { { [key: string]: number } } */
const attributePosition = {}
- attributeOrder.forEach((item, i) => {
+ for (const [i, item] of attributeOrder.entries()) {
if (Array.isArray(item)) {
for (const attr of item) {
attributePosition[attr] = i
}
} else attributePosition[item] = i
- })
+ }
/**
* @param {VAttribute | VDirective} node
@@ -319,8 +323,7 @@ function create(context) {
})
const results = []
- for (let index = 0; index < attributes.length; index++) {
- const attr = attributes[index]
+ for (const [index, attr] of attributes.entries()) {
const position = getPositionFromAttrIndex(index)
if (position == null) {
// The omitted order is skipped.
diff --git a/lib/rules/block-lang.js b/lib/rules/block-lang.js
index f1383c835..c1998eb5f 100644
--- a/lib/rules/block-lang.js
+++ b/lib/rules/block-lang.js
@@ -52,13 +52,21 @@ function getAllowsLangPhrase(lang) {
/**
* Normalizes a given option.
* @param {string} blockName The block name.
- * @param { UserBlockOptions } option An option to parse.
+ * @param {UserBlockOptions} option An option to parse.
* @returns {BlockOptions} Normalized option.
*/
function normalizeOption(blockName, option) {
- const lang = new Set(
- Array.isArray(option.lang) ? option.lang : option.lang ? [option.lang] : []
- )
+ /** @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)) {
@@ -165,8 +173,7 @@ module.exports = {
style: { allowNoLang: true }
}
)
- if (!Object.keys(options).length) {
- // empty
+ if (Object.keys(options).length === 0) {
return {}
}
@@ -198,11 +205,9 @@ module.exports = {
if (!option.allowNoLang) {
messageId = 'expected'
} else if (option.lang.size === 0) {
- if ((DEFAULT_LANGUAGES[tag] || []).includes(lang.value.value)) {
- messageId = 'unexpectedDefault'
- } else {
- messageId = 'unexpected'
- }
+ messageId = (DEFAULT_LANGUAGES[tag] || []).includes(lang.value.value)
+ ? 'unexpectedDefault'
+ : 'unexpected'
} else {
messageId = 'useOrNot'
}
diff --git a/lib/rules/block-tag-newline.js b/lib/rules/block-tag-newline.js
index c88a36c27..8459f5760 100644
--- a/lib/rules/block-tag-newline.js
+++ b/lib/rules/block-tag-newline.js
@@ -257,9 +257,8 @@ module.exports = {
}
const option =
- options.multiline === options.singleline
- ? options.singleline
- : /[\n\r\u2028\u2029]/u.test(text.trim())
+ options.multiline !== options.singleline &&
+ /[\n\r\u2028\u2029]/u.test(text.trim())
? options.multiline
: options.singleline
if (option === 'ignore') {
@@ -348,13 +347,6 @@ module.exports = {
const verify = normalizeOptionValue(context.options[0])
- /**
- * @returns {VElement[]}
- */
- function getTopLevelHTMLElements() {
- return documentFragment.children.filter(utils.isVElement)
- }
-
return utils.defineTemplateBodyVisitor(
context,
{},
@@ -365,8 +357,10 @@ module.exports = {
return
}
- for (const element of getTopLevelHTMLElements()) {
- verify(element)
+ for (const element of documentFragment.children) {
+ if (utils.isVElement(element)) {
+ verify(element)
+ }
}
}
}
diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js
index 942860f60..1f4cdd336 100644
--- a/lib/rules/comment-directive.js
+++ b/lib/rules/comment-directive.js
@@ -51,7 +51,7 @@ function parse(pattern, comment) {
/** @type {RuleAndLocation[]} */
const rules = []
- const rulesRe = /([^,\s]+)[,\s]*/g
+ const rulesRe = /([^\s,]+)[\s,]*/g
let startIndex = match[0].length
rulesRe.lastIndex = startIndex
@@ -126,35 +126,35 @@ function disable(context, loc, group, rule, key) {
*/
function processBlock(context, comment, reportUnusedDisableDirectives) {
const parsed = parse(COMMENT_DIRECTIVE_B, comment.value)
- if (parsed != null) {
- if (parsed.type === 'eslint-disable') {
- if (parsed.rules.length) {
- const rules = reportUnusedDisableDirectives
- ? reportUnusedRules(context, comment, parsed.type, parsed.rules)
- : parsed.rules
- for (const rule of rules) {
- disable(
- context,
- comment.loc.start,
- 'block',
- rule.ruleId,
- rule.key || '*'
- )
- }
- } else {
- const key = reportUnusedDisableDirectives
- ? reportUnused(context, comment, parsed.type)
- : ''
- disable(context, comment.loc.start, 'block', null, key)
+ if (parsed === null) return
+
+ if (parsed.type === 'eslint-disable') {
+ if (parsed.rules.length > 0) {
+ const rules = reportUnusedDisableDirectives
+ ? reportUnusedRules(context, comment, parsed.type, parsed.rules)
+ : parsed.rules
+ for (const rule of rules) {
+ disable(
+ context,
+ comment.loc.start,
+ 'block',
+ rule.ruleId,
+ rule.key || '*'
+ )
}
} else {
- if (parsed.rules.length) {
- for (const rule of parsed.rules) {
- enable(context, comment.loc.start, 'block', rule.ruleId)
- }
- } else {
- enable(context, comment.loc.start, 'block', null)
+ const key = reportUnusedDisableDirectives
+ ? reportUnused(context, comment, parsed.type)
+ : ''
+ disable(context, comment.loc.start, 'block', null, key)
+ }
+ } else {
+ if (parsed.rules.length > 0) {
+ for (const rule of parsed.rules) {
+ enable(context, comment.loc.start, 'block', rule.ruleId)
}
+ } else {
+ enable(context, comment.loc.start, 'block', null)
}
}
}
@@ -173,7 +173,7 @@ function processLine(context, comment, reportUnusedDisableDirectives) {
const line =
comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1)
const column = -1
- if (parsed.rules.length) {
+ if (parsed.rules.length > 0) {
const rules = reportUnusedDisableDirectives
? reportUnusedRules(context, comment, parsed.type, parsed.rules)
: parsed.rules
diff --git a/lib/rules/component-api-style.js b/lib/rules/component-api-style.js
index 9ebf01d68..12ad52e2d 100644
--- a/lib/rules/component-api-style.js
+++ b/lib/rules/component-api-style.js
@@ -43,17 +43,26 @@ function parseOptions(options) {
/** @type {UserPreferOption} */
const preferOptions = options[0] || ['script-setup', 'composition']
for (const prefer of preferOptions) {
- if (prefer === 'script-setup') {
- opts.allowsSFC.scriptSetup = true
- } else if (prefer === 'composition') {
- opts.allowsSFC.composition = true
- opts.allowsOther.composition = true
- } else if (prefer === 'composition-vue2') {
- opts.allowsSFC.compositionVue2 = true
- opts.allowsOther.compositionVue2 = true
- } else if (prefer === 'options') {
- opts.allowsSFC.options = true
- opts.allowsOther.options = true
+ switch (prefer) {
+ case 'script-setup': {
+ opts.allowsSFC.scriptSetup = true
+ break
+ }
+ case 'composition': {
+ opts.allowsSFC.composition = true
+ opts.allowsOther.composition = true
+ break
+ }
+ case 'composition-vue2': {
+ opts.allowsSFC.compositionVue2 = true
+ opts.allowsOther.compositionVue2 = true
+ break
+ }
+ case 'options': {
+ opts.allowsSFC.options = true
+ opts.allowsOther.options = true
+ break
+ }
}
}
@@ -180,9 +189,9 @@ function isPreferScriptSetup(allowsOpt) {
* @param {string} name
*/
function buildOptionPhrase(name) {
- return LIFECYCLE_HOOK_OPTIONS.has(name)
- ? `\`${name}\` lifecycle hook`
- : name === 'setup' || name === 'render'
+ if (LIFECYCLE_HOOK_OPTIONS.has(name)) return `\`${name}\` lifecycle hook`
+
+ return name === 'setup' || name === 'render'
? `\`${name}\` function`
: `\`${name}\` option`
}
diff --git a/lib/rules/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js
index 8e192df5e..103e3feb0 100644
--- a/lib/rules/component-definition-name-casing.js
+++ b/lib/rules/component-definition-name-casing.js
@@ -12,6 +12,19 @@ const allowedCaseOptions = ['PascalCase', 'kebab-case']
// Rule Definition
// ------------------------------------------------------------------------------
+/**
+ * @param {Expression | SpreadElement} node
+ * @returns {node is (Literal | TemplateLiteral)}
+ */
+function canConvert(node) {
+ return (
+ node.type === 'Literal' ||
+ (node.type === 'TemplateLiteral' &&
+ node.expressions.length === 0 &&
+ node.quasis.length === 1)
+ )
+}
+
module.exports = {
meta: {
type: 'suggestion',
@@ -30,8 +43,9 @@ module.exports = {
/** @param {RuleContext} context */
create(context) {
const options = context.options[0]
- const caseType =
- allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase'
+ const caseType = allowedCaseOptions.includes(options)
+ ? options
+ : 'PascalCase'
// ----------------------------------------------------------------------
// Public
@@ -71,19 +85,6 @@ module.exports = {
}
}
- /**
- * @param {Expression | SpreadElement} node
- * @returns {node is (Literal | TemplateLiteral)}
- */
- function canConvert(node) {
- return (
- node.type === 'Literal' ||
- (node.type === 'TemplateLiteral' &&
- node.expressions.length === 0 &&
- node.quasis.length === 1)
- )
- }
-
return Object.assign(
{},
utils.executeOnCallVueComponent(context, (node) => {
diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js
index b2f60a637..017e4cb08 100644
--- a/lib/rules/component-name-in-template-casing.js
+++ b/lib/rules/component-name-in-template-casing.js
@@ -58,8 +58,9 @@ module.exports = {
create(context) {
const caseOption = context.options[0]
const options = context.options[1] || {}
- const caseType =
- allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase
+ const caseType = allowedCaseOptions.includes(caseOption)
+ ? caseOption
+ : defaultCase
/** @type {RegExp[]} */
const ignores = (options.ignores || []).map(toRegExp)
const registeredComponentsOnly = options.registeredComponentsOnly !== false
diff --git a/lib/rules/component-options-name-casing.js b/lib/rules/component-options-name-casing.js
index ad70c1546..37fe47ed7 100644
--- a/lib/rules/component-options-name-casing.js
+++ b/lib/rules/component-options-name-casing.js
@@ -64,14 +64,14 @@ module.exports = {
return
}
- node.value.properties.forEach((property) => {
+ for (const property of node.value.properties) {
if (property.type !== 'Property') {
- return
+ continue
}
const name = getOptionsComponentName(property.key)
if (!name || checkCase(name)) {
- return
+ continue
}
context.report({
@@ -109,7 +109,7 @@ module.exports = {
}
]
})
- })
+ }
})
}
}
diff --git a/lib/rules/component-tags-order.js b/lib/rules/component-tags-order.js
index dfd8482bf..60640dc39 100644
--- a/lib/rules/component-tags-order.js
+++ b/lib/rules/component-tags-order.js
@@ -25,6 +25,26 @@ const DEFAULT_ORDER = Object.freeze([['script', 'template'], 'style'])
// Rule Definition
// ------------------------------------------------------------------------------
+/**
+ * @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',
@@ -74,7 +94,7 @@ module.exports = {
/** @type {(string|string[])[]} */
const orderOptions =
(context.options[0] && context.options[0].order) || DEFAULT_ORDER
- orderOptions.forEach((selectorOrSelectors, index) => {
+ for (const [index, selectorOrSelectors] of orderOptions.entries()) {
if (Array.isArray(selectorOrSelectors)) {
for (const selector of selectorOrSelectors) {
orders.push({
@@ -90,26 +110,6 @@ module.exports = {
index
})
}
- })
-
- /**
- * @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(' ')
}
/**
@@ -136,13 +136,14 @@ module.exports = {
}
const elements = getTopLevelHTMLElements()
- const elementWithOrders = elements.flatMap((element) => {
+ const elementsWithOrder = elements.flatMap((element) => {
const order = getOrderElement(element)
return order ? [{ order, element }] : []
})
const sourceCode = context.getSourceCode()
- elementWithOrders.forEach(({ order: expected, element }, index) => {
- const firstUnordered = elementWithOrders
+ 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]
@@ -188,7 +189,7 @@ module.exports = {
}
})
}
- })
+ }
}
}
}
diff --git a/lib/rules/custom-event-name-casing.js b/lib/rules/custom-event-name-casing.js
index f59fa9c2a..5db15a875 100644
--- a/lib/rules/custom-event-name-casing.js
+++ b/lib/rules/custom-event-name-casing.js
@@ -22,7 +22,7 @@ const { toRegExp } = require('../utils/regexp')
// ------------------------------------------------------------------------------
const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase']
-const DEFAULT_CASE = 'kebab-case'
+const DEFAULT_CASE = 'camelCase'
/**
* Get the name param node from the given CallExpression
diff --git a/lib/rules/define-macros-order.js b/lib/rules/define-macros-order.js
index 9c8ea45e6..007b17027 100644
--- a/lib/rules/define-macros-order.js
+++ b/lib/rules/define-macros-order.js
@@ -19,6 +19,17 @@ const MACROS_PROPS = 'defineProps'
const ORDER = [MACROS_EMITS, MACROS_PROPS]
const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]
+/**
+ * @param {VElement} scriptSetup
+ * @param {ASTNode} node
+ */
+function inScriptSetup(scriptSetup, node) {
+ return (
+ scriptSetup.range[0] <= node.range[0] &&
+ node.range[1] <= scriptSetup.range[1]
+ )
+}
+
/**
* @param {ASTNode} node
*/
@@ -33,9 +44,10 @@ function isUseStrictStatement(node) {
/**
* Get an index of the first statement after imports and interfaces in order
* to place defineEmits and defineProps before this statement
+ * @param {VElement} scriptSetup
* @param {Program} program
*/
-function getTargetStatementPosition(program) {
+function getTargetStatementPosition(scriptSetup, program) {
const skipStatements = new Set([
'ImportDeclaration',
'TSInterfaceDeclaration',
@@ -45,7 +57,11 @@ function getTargetStatementPosition(program) {
])
for (const [index, item] of program.body.entries()) {
- if (!skipStatements.has(item.type) && !isUseStrictStatement(item)) {
+ if (
+ inScriptSetup(scriptSetup, item) &&
+ !skipStatements.has(item.type) &&
+ !isUseStrictStatement(item)
+ ) {
return index
}
}
@@ -104,7 +120,10 @@ function create(context) {
'Program:exit'(program) {
const shouldFirstNode = macrosNodes.get(order[0])
const shouldSecondNode = macrosNodes.get(order[1])
- const firstStatementIndex = getTargetStatementPosition(program)
+ const firstStatementIndex = getTargetStatementPosition(
+ scriptSetup,
+ program
+ )
const firstStatement = program.body[firstStatementIndex]
// have both defineEmits and defineProps
@@ -213,13 +232,12 @@ function create(context) {
const targetComment = sourceCode.getTokenAfter(beforeTargetToken, {
includeComments: true
})
- const textSpace = getTextBetweenTokens(beforeTargetToken, targetComment)
// make insert text: comments + node + space before target
const textNode = sourceCode.getText(
node,
node.range[0] - nodeComment.range[0]
)
- const insertText = textNode + textSpace
+ const insertText = getInsertText(textNode, target)
return [
fixer.insertTextBefore(targetComment, insertText),
@@ -228,17 +246,28 @@ function create(context) {
}
/**
- * @param {ASTNode} tokenBefore
- * @param {ASTNode} tokenAfter
+ * Get result text to insert
+ * @param {string} textNode
+ * @param {ASTNode} target
*/
- function getTextBetweenTokens(tokenBefore, tokenAfter) {
- return sourceCode.text.slice(tokenBefore.range[1], tokenAfter.range[0])
+ function getInsertText(textNode, target) {
+ const afterTargetComment = sourceCode.getTokenAfter(target, {
+ includeComments: true
+ })
+ const afterText = sourceCode.text.slice(
+ target.range[1],
+ afterTargetComment.range[0]
+ )
+ // handle case when a();b() -> b()a();
+ const invalidResult = !textNode.endsWith(';') && !afterText.includes('\n')
+
+ return textNode + afterText + (invalidResult ? ';' : '')
}
/**
* Get position of the beginning of the token's line(or prevToken end if no line)
- * @param {ASTNode} token
- * @param {ASTNode} prevToken
+ * @param {ASTNode|Token} token
+ * @param {ASTNode|Token} prevToken
*/
function getLineStartIndex(token, prevToken) {
// if we have next token on the same line - get index right before that token
diff --git a/lib/rules/experimental-script-setup-vars.js b/lib/rules/experimental-script-setup-vars.js
deleted file mode 100644
index 14c498de5..000000000
--- a/lib/rules/experimental-script-setup-vars.js
+++ /dev/null
@@ -1,230 +0,0 @@
-/**
- * @fileoverview prevent variables defined in `
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.strictEqual(messages.length, 1)
assert.strictEqual(messages[0].ruleId, 'no-unused-vars')
@@ -129,9 +124,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -144,9 +137,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -157,9 +148,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -173,9 +162,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 2)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -191,9 +178,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -205,9 +190,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -222,9 +205,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 2)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -240,9 +221,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 2)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -260,9 +239,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -276,9 +253,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 2)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -296,9 +271,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-duplicate-attributes')
@@ -311,9 +284,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -324,9 +295,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -339,9 +308,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -353,9 +320,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -370,9 +335,7 @@ describe('comment-directive', () => {
Hello
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -387,9 +350,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 2)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
@@ -417,6 +378,12 @@ describe('comment-directive', () => {
},
useEslintrc: false
})
+
+ async function lintMessages(code) {
+ const result = await eslint.lintText(code, { filePath: 'test.vue' })
+ return result[0].messages
+ }
+
it('report unused ', async () => {
const code = `
@@ -425,9 +392,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/comment-directive')
@@ -447,9 +412,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -464,9 +427,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/comment-directive')
@@ -486,9 +447,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 2)
@@ -518,9 +477,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 4)
@@ -554,9 +511,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
@@ -570,9 +525,7 @@ describe('comment-directive', () => {
`
- const messages = (
- await eslint.lintText(code, { filePath: 'test.vue' })
- )[0].messages
+ const messages = await lintMessages(code)
assert.deepEqual(messages.length, 0)
})
diff --git a/tests/lib/rules/custom-event-name-casing.js b/tests/lib/rules/custom-event-name-casing.js
index 00b76e4ed..8ad20377e 100644
--- a/tests/lib/rules/custom-event-name-casing.js
+++ b/tests/lib/rules/custom-event-name-casing.js
@@ -41,7 +41,8 @@ tester.run('custom-event-name-casing', rule, {
}
}
- `
+ `,
+ options: ['kebab-case']
},
{
filename: 'test.vue',
@@ -67,7 +68,8 @@ tester.run('custom-event-name-casing', rule, {
}
}
- `
+ `,
+ options: ['kebab-case']
},
{
filename: 'test.vue',
@@ -92,7 +94,8 @@ tester.run('custom-event-name-casing', rule, {
}
}
- `
+ `,
+ options: ['kebab-case']
},
{
filename: 'test.vue',
@@ -117,7 +120,8 @@ tester.run('custom-event-name-casing', rule, {
}
}
- `
+ `,
+ options: ['kebab-case']
},
{
filename: 'test.vue',
@@ -149,7 +153,8 @@ tester.run('custom-event-name-casing', rule, {
},
}
- `
+ `,
+ options: ['kebab-case']
},
{
filename: 'test.vue',
@@ -165,7 +170,8 @@ tester.run('custom-event-name-casing', rule, {
},
}
- `
+ `,
+ options: ['kebab-case']
},
{
filename: 'test.vue',
@@ -269,6 +275,60 @@ tester.run('custom-event-name-casing', rule, {
`,
options: ['camelCase']
+ },
+ // Default
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+
+ // kebab-case
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: ['kebab-case']
}
],
invalid: [
@@ -296,6 +356,7 @@ tester.run('custom-event-name-casing', rule, {
}
`,
+ options: ['kebab-case'],
errors: [
{
message: "Custom event name 'fooBar' must be kebab-case.",
@@ -344,6 +405,7 @@ tester.run('custom-event-name-casing', rule, {
}
`,
+ options: ['kebab-case'],
errors: [
"Custom event name 'fooBar' must be kebab-case.",
"Custom event name 'barBaz' must be kebab-case.",
@@ -374,6 +436,7 @@ tester.run('custom-event-name-casing', rule, {
}
`,
+ options: ['kebab-case'],
errors: [
"Custom event name 'fooBar' must be kebab-case.",
"Custom event name 'barBaz' must be kebab-case.",
@@ -448,6 +511,7 @@ tester.run('custom-event-name-casing', rule, {
"Custom event name 'click/row' must be kebab-case."
]
},
+ // camelCase
{
filename: 'test.vue',
code: `
@@ -479,6 +543,69 @@ tester.run('custom-event-name-casing', rule, {
"Custom event name 'baz-qux' must be camelCase."
]
},
+ // Default
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ errors: [
+ "Custom event name 'foo-bar' must be camelCase.",
+ "Custom event name 'bar-baz' must be camelCase.",
+ "Custom event name 'baz-qux' must be camelCase."
+ ]
+ },
+ // kebab-case
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ options: ['kebab-case'],
+ errors: [
+ "Custom event name 'fooBar' must be kebab-case.",
+ "Custom event name 'barBaz' must be kebab-case.",
+ "Custom event name 'bazQux' must be kebab-case."
+ ]
+ },
{
filename: 'test.vue',
code: `
@@ -490,8 +617,8 @@ tester.run('custom-event-name-casing', rule, {
`,
errors: [
{
- message: "Custom event name 'fooBar' must be kebab-case.",
- line: 4
+ message: "Custom event name 'foo-bar' must be camelCase.",
+ line: 5
}
]
}
diff --git a/tests/lib/rules/define-macros-order.js b/tests/lib/rules/define-macros-order.js
index ce5f30040..90e6748d6 100644
--- a/tests/lib/rules/define-macros-order.js
+++ b/tests/lib/rules/define-macros-order.js
@@ -155,9 +155,7 @@ tester.run('define-macros-order', rule, {
defineProps({
test: Boolean
})
-
defineEmits(['update:test'])
-
console.log('test1')
console.log('test2')
console.log('test3')
@@ -192,7 +190,6 @@ tester.run('define-macros-order', rule, {
defineProps({
test: Boolean
})
-
defineEmits(['update:test'])
console.log('test1')
@@ -261,7 +258,6 @@ tester.run('define-macros-order', rule, {
}
const emit = defineEmits<{(e: 'update:test'): void}>()
-
const props = withDefaults(defineProps(), {
msg: 'hello',
labels: () => ['one', 'two']
@@ -377,8 +373,7 @@ tester.run('define-macros-order', rule, {
`,
output: `
+ defineEmits(['update:test']);const props = defineProps({ test: Boolean });
`,
options: optionsEmitsFirst,
errors: [
@@ -387,6 +382,56 @@ tester.run('define-macros-order', rule, {
line: 3
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ errors: [
+ {
+ message: message('defineProps'),
+ line: 11
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ errors: [
+ {
+ message: message('defineProps'),
+ line: 2
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/experimental-script-setup-vars.js b/tests/lib/rules/experimental-script-setup-vars.js
deleted file mode 100644
index 089019c7c..000000000
--- a/tests/lib/rules/experimental-script-setup-vars.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * @author Yosuke Ota
- */
-'use strict'
-
-const { RuleTester } = require('eslint')
-const rule = require('../../../lib/rules/experimental-script-setup-vars')
-
-const tester = new RuleTester({
- parser: require.resolve('vue-eslint-parser'),
- parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
-})
-
-tester.run('experimental-script-setup-vars', rule, {
- valid: [
- `
- `,
- `
- `,
- `
- `,
- `
-
-
- `
- ],
- invalid: [
- {
- code: `
-
- `,
- errors: [
- {
- message: 'Parsing error.',
- line: 2
- }
- ]
- }
- ]
-})
diff --git a/tests/lib/rules/html-closing-bracket-newline.js b/tests/lib/rules/html-closing-bracket-newline.js
index 4c07c80db..b72c46b97 100644
--- a/tests/lib/rules/html-closing-bracket-newline.js
+++ b/tests/lib/rules/html-closing-bracket-newline.js
@@ -337,6 +337,156 @@ tester.run('html-closing-bracket-newline', rule, {
'Expected 1 line break before closing bracket, but no line breaks found.',
'Expected 1 line break before closing bracket, but no line breaks found.'
]
+ },
+ {
+ code: `
+
+
+
+
+ `,
+ output: `
+
+
+
+
+ `,
+ options: [
+ {
+ singleline: 'never',
+ multiline: 'never'
+ }
+ ],
+ errors: [
+ {
+ message:
+ 'Expected no line breaks before closing bracket, but 1 line break found.',
+ line: 2,
+ column: 18,
+ endLine: 3,
+ endColumn: 9
+ },
+ {
+ message:
+ 'Expected no line breaks before closing bracket, but 1 line break found.',
+ line: 4,
+ column: 19,
+ endLine: 5,
+ endColumn: 9
+ },
+ {
+ message:
+ 'Expected no line breaks before closing bracket, but 1 line break found.',
+ line: 6,
+ column: 16,
+ endLine: 7,
+ endColumn: 9
+ },
+ {
+ message:
+ 'Expected no line breaks before closing bracket, but 1 line break found.',
+ line: 8,
+ column: 17,
+ endLine: 9,
+ endColumn: 9
+ },
+ {
+ message:
+ 'Expected no line breaks before closing bracket, but 1 line break found.',
+ line: 10,
+ column: 15,
+ endLine: 11,
+ endColumn: 9
+ },
+ {
+ message:
+ 'Expected no line breaks before closing bracket, but 1 line break found.',
+ line: 11,
+ column: 17,
+ endLine: 12,
+ endColumn: 9
+ }
+ ]
+ },
+ {
+ code: `
+
+
+
+
+ `,
+ output: `
+
+
+
+
+ `,
+ options: [
+ {
+ singleline: 'always',
+ multiline: 'always'
+ }
+ ],
+ errors: [
+ {
+ message:
+ 'Expected 1 line break before closing bracket, but no line breaks found.',
+ line: 2,
+ column: 18,
+ endColumn: 18
+ },
+ {
+ message:
+ 'Expected 1 line break before closing bracket, but no line breaks found.',
+ line: 3,
+ column: 19,
+ endColumn: 19
+ },
+ {
+ message:
+ 'Expected 1 line break before closing bracket, but no line breaks found.',
+ line: 4,
+ column: 16,
+ endColumn: 16
+ },
+ {
+ message:
+ 'Expected 1 line break before closing bracket, but no line breaks found.',
+ line: 5,
+ column: 17,
+ endColumn: 17
+ },
+ {
+ message:
+ 'Expected 1 line break before closing bracket, but no line breaks found.',
+ line: 6,
+ column: 15,
+ endColumn: 15
+ },
+ {
+ message:
+ 'Expected 1 line break before closing bracket, but no line breaks found.',
+ line: 6,
+ column: 23,
+ endColumn: 23
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/html-closing-bracket-spacing.js b/tests/lib/rules/html-closing-bracket-spacing.js
index 154ba87b2..af03d0bfd 100644
--- a/tests/lib/rules/html-closing-bracket-spacing.js
+++ b/tests/lib/rules/html-closing-bracket-spacing.js
@@ -114,6 +114,56 @@ ruleTester.run('html-closing-bracket-spacing', rule, {
}
]
},
+ {
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ errors: [
+ {
+ message: "Expected no space before '>', but found.",
+ line: 2,
+ column: 18,
+ endColumn: 20
+ },
+ {
+ message: "Expected no space before '>', but found.",
+ line: 2,
+ column: 30,
+ endColumn: 32
+ },
+ {
+ message: "Expected no space before '>', but found.",
+ line: 3,
+ column: 16,
+ endColumn: 18
+ },
+ {
+ message: "Expected no space before '>', but found.",
+ line: 3,
+ column: 26,
+ endColumn: 28
+ },
+ {
+ message: "Expected no space before '>', but found.",
+ line: 4,
+ column: 15,
+ endColumn: 17
+ },
+ {
+ message: "Expected no space before '>', but found.",
+ line: 4,
+ column: 24,
+ endColumn: 26
+ }
+ ]
+ },
{
code: '\n \n
\n \n',
output: '\n \n
\n \n',
@@ -144,6 +194,63 @@ ruleTester.run('html-closing-bracket-spacing', rule, {
endColumn: 10
}
]
+ },
+ {
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ options: [
+ {
+ startTag: 'always',
+ endTag: 'always',
+ selfClosingTag: 'never'
+ }
+ ],
+ errors: [
+ {
+ message: "Expected a space before '>', but not found.",
+ line: 2,
+ column: 18,
+ endColumn: 19
+ },
+ {
+ message: "Expected a space before '>', but not found.",
+ line: 2,
+ column: 29,
+ endColumn: 30
+ },
+ {
+ message: "Expected a space before '>', but not found.",
+ line: 3,
+ column: 16,
+ endColumn: 17
+ },
+ {
+ message: "Expected a space before '>', but not found.",
+ line: 3,
+ column: 25,
+ endColumn: 26
+ },
+ {
+ message: "Expected a space before '>', but not found.",
+ line: 4,
+ column: 15,
+ endColumn: 16
+ },
+ {
+ message: "Expected a space before '>', but not found.",
+ line: 4,
+ column: 23,
+ endColumn: 24
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js
index 8340f9423..1c817f1a3 100644
--- a/tests/lib/rules/html-indent.js
+++ b/tests/lib/rules/html-indent.js
@@ -58,10 +58,10 @@ function loadPatterns(additionalValid, additionalInvalid) {
const lines = output.split('\n').map((text, number) => ({
number,
text,
- indentSize: (/^[ \t]+/.exec(text) || [''])[0].length
+ indentSize: (/^[\t ]+/.exec(text) || [''])[0].length
}))
const code = lines
- .map((line) => line.text.replace(/^[ \t]+/, ''))
+ .map((line) => line.text.replace(/^[\t ]+/, ''))
.join('\n')
const errors = lines
.map((line) =>
@@ -82,8 +82,8 @@ function loadPatterns(additionalValid, additionalInvalid) {
.filter(Boolean)
return {
- valid: valid.concat(additionalValid),
- invalid: invalid.concat(additionalInvalid)
+ valid: [...valid, ...additionalValid],
+ invalid: [...invalid, ...additionalInvalid]
}
}
diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js
index d1b4c3371..83610962c 100644
--- a/tests/lib/rules/match-component-file-name.js
+++ b/tests/lib/rules/match-component-file-name.js
@@ -564,7 +564,18 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ export default {
+ name: 'MyComponent',
+ render() { return }
+ }
+ `
+ }
+ ]
}
]
},
@@ -581,7 +592,46 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ export default {
+ name: 'MyComponent',
+ render() { return }
+ }
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'MyComponent.jsx',
+ code: `
+ export default {
+ name: "MComponent",
+ render() { return }
+ }
+ `,
+ options: [{ extensions: ['jsx'] }],
+ parserOptions: jsxParserOptions,
+ errors: [
+ {
+ message:
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ export default {
+ name: "MyComponent",
+ render() { return }
+ }
+ `
+ }
+ ]
}
]
},
@@ -598,7 +648,18 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ export default {
+ name: \`MyComponent\`,
+ render() { return }
+ }
+ `
+ }
+ ]
}
]
},
@@ -620,7 +681,53 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'MyComponent.vue',
+ code: `
+
+ `,
+ options: [{ extensions: ['vue'] }],
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions,
+ errors: [
+ {
+ message:
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+
+ `
+ }
+ ]
}
]
},
@@ -640,7 +747,20 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+
+ `
+ }
+ ]
}
]
},
@@ -659,7 +779,46 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ new Vue({
+ name: 'MyComponent',
+ template: ''
+ })
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'MyComponent.js',
+ code: `
+ new Vue({
+ name: "MComponent",
+ template: ''
+ })
+ `,
+ options: [{ extensions: ['js'] }],
+ parserOptions,
+ errors: [
+ {
+ message:
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ new Vue({
+ name: "MyComponent",
+ template: ''
+ })
+ `
+ }
+ ]
}
]
},
@@ -676,7 +835,18 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ new Vue({
+ name: \`MyComponent\`,
+ template: ''
+ })
+ `
+ }
+ ]
}
]
},
@@ -692,7 +862,43 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ Vue.mixin({
+ name: 'MyComponent',
+ })
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'MyComponent.js',
+ code: `
+ Vue.mixin({
+ name: "MComponent",
+ })
+ `,
+ options: [{ extensions: ['js'] }],
+ parserOptions,
+ errors: [
+ {
+ message:
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ Vue.mixin({
+ name: "MyComponent",
+ })
+ `
+ }
+ ]
}
]
},
@@ -708,7 +914,17 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ Vue.mixin({
+ name: \`MyComponent\`,
+ })
+ `
+ }
+ ]
}
]
},
@@ -724,7 +940,43 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ Vue.component('MyComponent', {
+ template: ''
+ })
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'MyComponent.js',
+ code: `
+ Vue.component("MComponent", {
+ template: ''
+ })
+ `,
+ options: [{ extensions: ['js'] }],
+ parserOptions,
+ errors: [
+ {
+ message:
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ Vue.component("MyComponent", {
+ template: ''
+ })
+ `
+ }
+ ]
}
]
},
@@ -740,7 +992,17 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ Vue.component(\`MyComponent\`, {
+ template: ''
+ })
+ `
+ }
+ ]
}
]
},
@@ -756,7 +1018,17 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MComponent` should match file name `MyComponent`.'
+ 'Component name `MComponent` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ app.component(\`MyComponent\`, {
+ template: ''
+ })
+ `
+ }
+ ]
}
]
},
@@ -775,7 +1047,18 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `my-component` should match file name `MyComponent`.'
+ 'Component name `my-component` should match file name `MyComponent`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ export default {
+ name: 'MyComponent',
+ render() { return }
+ }
+ `
+ }
+ ]
}
]
},
@@ -792,7 +1075,18 @@ ruleTester.run('match-component-file-name', rule, {
errors: [
{
message:
- 'Component name `MyComponent` should match file name `my-component`.'
+ 'Component name `MyComponent` should match file name `my-component`.',
+ suggestions: [
+ {
+ desc: 'Rename component to match file name.',
+ output: `
+ export default {
+ name: 'my-component',
+ render() { return }
+ }
+ `
+ }
+ ]
}
]
}
diff --git a/tests/lib/rules/name-property-casing.js b/tests/lib/rules/name-property-casing.js
deleted file mode 100644
index a7a8d41cc..000000000
--- a/tests/lib/rules/name-property-casing.js
+++ /dev/null
@@ -1,213 +0,0 @@
-/**
- * @fileoverview Define a style for the name property casing for consistency purposes
- * @author Armano
- */
-'use strict'
-
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
-const rule = require('../../../lib/rules/name-property-casing')
-const RuleTester = require('eslint').RuleTester
-
-// ------------------------------------------------------------------------------
-// Tests
-// ------------------------------------------------------------------------------
-
-const parserOptions = {
- ecmaVersion: 2018,
- sourceType: 'module'
-}
-
-const ruleTester = new RuleTester()
-ruleTester.run('name-property-casing', rule, {
- valid: [
- {
- filename: 'test.vue',
- code: `
- export default {
- }
- `,
- parserOptions
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- ...name
- }
- `,
- parserOptions
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'FooBar'
- }
- `,
- parserOptions
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'FooBar'
- }
- `,
- options: ['PascalCase'],
- parserOptions
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'foo-bar'
- }
- `,
- options: ['kebab-case'],
- parserOptions
- }
- ],
-
- invalid: [
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'foo-bar'
- }
- `,
- output: `
- export default {
- name: 'FooBar'
- }
- `,
- parserOptions,
- errors: [
- {
- message: 'Property name "foo-bar" is not PascalCase.',
- type: 'Literal',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'foo bar'
- }
- `,
- output: null,
- parserOptions,
- errors: [
- {
- message: 'Property name "foo bar" is not PascalCase.',
- type: 'Literal',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'foo!bar'
- }
- `,
- output: null,
- parserOptions,
- errors: [
- {
- message: 'Property name "foo!bar" is not PascalCase.',
- type: 'Literal',
- line: 3
- }
- ]
- },
- {
- filename: 'test.js',
- code: `
- new Vue({
- name: 'foo!bar'
- })
- `,
- output: null,
- parserOptions: { ecmaVersion: 6 },
- errors: [
- {
- message: 'Property name "foo!bar" is not PascalCase.',
- type: 'Literal',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'foo_bar'
- }
- `,
- output: `
- export default {
- name: 'FooBar'
- }
- `,
- parserOptions,
- errors: [
- {
- message: 'Property name "foo_bar" is not PascalCase.',
- type: 'Literal',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'foo_bar'
- }
- `,
- output: `
- export default {
- name: 'FooBar'
- }
- `,
- options: ['PascalCase'],
- parserOptions,
- errors: [
- {
- message: 'Property name "foo_bar" is not PascalCase.',
- type: 'Literal',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
- export default {
- name: 'foo_bar'
- }
- `,
- output: `
- export default {
- name: 'foo-bar'
- }
- `,
- options: ['kebab-case'],
- parserOptions,
- errors: [
- {
- message: 'Property name "foo_bar" is not kebab-case.',
- type: 'Literal',
- line: 3
- }
- ]
- }
- ]
-})
diff --git a/tests/lib/rules/no-confusing-v-for-v-if.js b/tests/lib/rules/no-confusing-v-for-v-if.js
deleted file mode 100644
index 47959aeb4..000000000
--- a/tests/lib/rules/no-confusing-v-for-v-if.js
+++ /dev/null
@@ -1,59 +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 RuleTester = require('eslint').RuleTester
-const rule = require('../../../lib/rules/no-confusing-v-for-v-if')
-
-// ------------------------------------------------------------------------------
-// Tests
-// ------------------------------------------------------------------------------
-
-const tester = new RuleTester({
- parser: require.resolve('vue-eslint-parser'),
- parserOptions: { ecmaVersion: 2015 }
-})
-
-tester.run('no-confusing-v-for-v-if', rule, {
- valid: [
- {
- filename: 'test.vue',
- code: ''
- },
- {
- filename: 'test.vue',
- code: ''
- },
- {
- filename: 'test.vue',
- code: ''
- },
- {
- filename: 'test.vue',
- code: ''
- },
- {
- filename: 'test.vue',
- code: ''
- }
- ],
- invalid: [
- {
- filename: 'test.vue',
- code: '',
- errors: ["This 'v-if' should be moved to the wrapper element."]
- },
- {
- filename: 'test.vue',
- code: '',
- errors: ["This 'v-if' should be moved to the wrapper element."]
- }
- ]
-})
diff --git a/tests/lib/rules/no-expose-after-await.js b/tests/lib/rules/no-expose-after-await.js
index 2f33c58e6..d335da64a 100644
--- a/tests/lib/rules/no-expose-after-await.js
+++ b/tests/lib/rules/no-expose-after-await.js
@@ -101,7 +101,19 @@ tester.run('no-expose-after-await', rule, {
+ `,
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
`,
parserOptions: { ecmaVersion: 2022 }
@@ -122,7 +134,7 @@ tester.run('no-expose-after-await', rule, {
`,
errors: [
{
- message: 'The `expose` after `await` expression are forbidden.',
+ message: '`expose` is forbidden after an `await` expression.',
line: 6,
column: 11
}
@@ -142,11 +154,28 @@ tester.run('no-expose-after-await', rule, {
`,
errors: [
{
- message: 'The `expose` after `await` expression are forbidden.',
+ message: '`expose` is forbidden after an `await` expression.',
line: 6,
column: 11
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ message: '`defineExpose` is forbidden after an `await` expression.',
+ line: 4,
+ column: 7
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/no-irregular-whitespace.js b/tests/lib/rules/no-irregular-whitespace.js
index 9c3a40cdc..52c6dd0e4 100644
--- a/tests/lib/rules/no-irregular-whitespace.js
+++ b/tests/lib/rules/no-irregular-whitespace.js
@@ -11,17 +11,37 @@ const tester = new RuleTester({
parserOptions: { ecmaVersion: 2018 }
})
-const IRREGULAR_WHITESPACES =
- '\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000'.split(
- ''
- )
-const IRREGULAR_LINE_TERMINATORS = '\u2028\u2029'.split('')
-const ALL_IRREGULAR_WHITESPACES = [].concat(
- IRREGULAR_WHITESPACES,
- IRREGULAR_LINE_TERMINATORS
-)
+const IRREGULAR_WHITESPACES = [
+ '\f',
+ '\v',
+ '\u0085',
+ '\uFEFF',
+ '\u00A0',
+ '\u1680',
+ '\u180E',
+ '\u2000',
+ '\u2001',
+ '\u2002',
+ '\u2003',
+ '\u2004',
+ '\u2005',
+ '\u2006',
+ '\u2007',
+ '\u2008',
+ '\u2009',
+ '\u200A',
+ '\u200B',
+ '\u202F',
+ '\u205F',
+ '\u3000'
+]
+const IRREGULAR_LINE_TERMINATORS = ['\u2028', '\u2029']
+const ALL_IRREGULAR_WHITESPACES = [
+ ...IRREGULAR_WHITESPACES,
+ ...IRREGULAR_LINE_TERMINATORS
+]
const ALL_IRREGULAR_WHITESPACE_CODES = ALL_IRREGULAR_WHITESPACES.map((s) =>
- `000${s.charCodeAt(0).toString(16)}`.slice(-4)
+ `000${s.codePointAt(0).toString(16)}`.slice(-4)
)
tester.run('no-irregular-whitespace', rule, {
diff --git a/tests/lib/rules/no-lifecycle-after-await.js b/tests/lib/rules/no-lifecycle-after-await.js
index bb7414682..e75995a5f 100644
--- a/tests/lib/rules/no-lifecycle-after-await.js
+++ b/tests/lib/rules/no-lifecycle-after-await.js
@@ -174,8 +174,7 @@ tester.run('no-lifecycle-after-await', rule, {
`,
errors: [
{
- message:
- 'The lifecycle hooks after `await` expression are forbidden.',
+ message: 'Lifecycle hooks are forbidden after an `await` expression.',
line: 8,
column: 11,
endLine: 8,
diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js
index cdb3ab7a2..4b39c0858 100644
--- a/tests/lib/rules/no-reserved-component-names.js
+++ b/tests/lib/rules/no-reserved-component-names.js
@@ -14,9 +14,7 @@ const RuleTester = require('eslint').RuleTester
const htmlElements = require('../../../lib/utils/html-elements.json')
const RESERVED_NAMES_IN_HTML = new Set([
...htmlElements,
- ...htmlElements.map(
- (word) => word[0].toUpperCase() + word.substring(1, word.length)
- )
+ ...htmlElements.map((word) => word[0].toUpperCase() + word.slice(1))
])
// ------------------------------------------------------------------------------
@@ -538,212 +536,188 @@ ruleTester.run('no-reserved-component-names', rule, {
code: `fn1(component.data)`,
parserOptions
},
- ...vue2BuiltInComponents.map((name) => {
- return {
- filename: `${name}.vue`,
- code: `
+ ...vue2BuiltInComponents.map((name) => ({
+ filename: `${name}.vue`,
+ code: `
export default {
name: '${name}'
}
`,
- parserOptions
- }
- }),
- ...vue3BuiltInComponents.map((name) => {
- return {
- filename: `${name}.vue`,
- code: `
+ parserOptions
+ })),
+ ...vue3BuiltInComponents.map((name) => ({
+ filename: `${name}.vue`,
+ code: `
export default {
name: '${name}'
}
`,
- parserOptions
- }
- }),
- ...vue3BuiltInComponents.map((name) => {
- return {
- filename: `${name}.vue`,
- code: `
+ parserOptions
+ })),
+ ...vue3BuiltInComponents.map((name) => ({
+ filename: `${name}.vue`,
+ code: `
export default {
name: '${name}'
}
`,
- parserOptions,
- options: [{ disallowVueBuiltInComponents: true }]
- }
- })
+ parserOptions,
+ options: [{ disallowVueBuiltInComponents: true }]
+ }))
],
invalid: [
- ...invalidElements.map((name) => {
- return {
- filename: `${name}.vue`,
- code: `
+ ...invalidElements.map((name) => ({
+ filename: `${name}.vue`,
+ code: `
export default {
name: '${name}'
}
`,
- parserOptions,
- errors: [
- {
- messageId: RESERVED_NAMES_IN_HTML.has(name)
- ? 'reservedInHtml'
- : 'reserved',
- data: { name },
- type: 'Literal',
- line: 3
- }
- ]
- }
- }),
- ...invalidElements.map((name) => {
- return {
- filename: 'test.vue',
- code: `Vue.component('${name}', component)`,
- parserOptions,
- errors: [
- {
- messageId: RESERVED_NAMES_IN_HTML.has(name)
- ? 'reservedInHtml'
- : 'reserved',
- data: { name },
- type: 'Literal',
- line: 1
- }
- ]
- }
- }),
- ...invalidElements.map((name) => {
- return {
- filename: 'test.vue',
- code: `app.component('${name}', component)`,
- parserOptions,
- errors: [
- {
- messageId: RESERVED_NAMES_IN_HTML.has(name)
- ? 'reservedInHtml'
- : 'reserved',
- data: { name },
- type: 'Literal',
- line: 1
- }
- ]
- }
- }),
- ...invalidElements.map((name) => {
- return {
- filename: 'test.vue',
- code: `Vue.component(\`${name}\`, {})`,
- parserOptions,
- errors: [
- {
- messageId: RESERVED_NAMES_IN_HTML.has(name)
- ? 'reservedInHtml'
- : 'reserved',
- data: { name },
- type: 'TemplateLiteral',
- line: 1
- }
- ]
- }
- }),
- ...invalidElements.map((name) => {
- return {
- filename: 'test.vue',
- code: `app.component(\`${name}\`, {})`,
- parserOptions,
- errors: [
- {
- messageId: RESERVED_NAMES_IN_HTML.has(name)
- ? 'reservedInHtml'
- : 'reserved',
- data: { name },
- type: 'TemplateLiteral',
- line: 1
- }
- ]
- }
- }),
- ...invalidElements.map((name) => {
- return {
- filename: 'test.vue',
- code: `export default {
+ parserOptions,
+ errors: [
+ {
+ messageId: RESERVED_NAMES_IN_HTML.has(name)
+ ? 'reservedInHtml'
+ : 'reserved',
+ data: { name },
+ type: 'Literal',
+ line: 3
+ }
+ ]
+ })),
+ ...invalidElements.map((name) => ({
+ filename: 'test.vue',
+ code: `Vue.component('${name}', component)`,
+ parserOptions,
+ errors: [
+ {
+ messageId: RESERVED_NAMES_IN_HTML.has(name)
+ ? 'reservedInHtml'
+ : 'reserved',
+ data: { name },
+ type: 'Literal',
+ line: 1
+ }
+ ]
+ })),
+ ...invalidElements.map((name) => ({
+ filename: 'test.vue',
+ code: `app.component('${name}', component)`,
+ parserOptions,
+ errors: [
+ {
+ messageId: RESERVED_NAMES_IN_HTML.has(name)
+ ? 'reservedInHtml'
+ : 'reserved',
+ data: { name },
+ type: 'Literal',
+ line: 1
+ }
+ ]
+ })),
+ ...invalidElements.map((name) => ({
+ filename: 'test.vue',
+ code: `Vue.component(\`${name}\`, {})`,
+ parserOptions,
+ errors: [
+ {
+ messageId: RESERVED_NAMES_IN_HTML.has(name)
+ ? 'reservedInHtml'
+ : 'reserved',
+ data: { name },
+ type: 'TemplateLiteral',
+ line: 1
+ }
+ ]
+ })),
+ ...invalidElements.map((name) => ({
+ filename: 'test.vue',
+ code: `app.component(\`${name}\`, {})`,
+ parserOptions,
+ errors: [
+ {
+ messageId: RESERVED_NAMES_IN_HTML.has(name)
+ ? 'reservedInHtml'
+ : 'reserved',
+ data: { name },
+ type: 'TemplateLiteral',
+ line: 1
+ }
+ ]
+ })),
+ ...invalidElements.map((name) => ({
+ filename: 'test.vue',
+ code: `export default {
components: {
'${name}': {},
}
}`,
- parserOptions,
- errors: [
- {
- messageId: RESERVED_NAMES_IN_HTML.has(name)
- ? 'reservedInHtml'
- : 'reserved',
- data: { name },
- type: 'Property',
- line: 3
- }
- ]
- }
- }),
- ...vue2BuiltInComponents.map((name) => {
- return {
- filename: `${name}.vue`,
- code: `
+ parserOptions,
+ errors: [
+ {
+ messageId: RESERVED_NAMES_IN_HTML.has(name)
+ ? 'reservedInHtml'
+ : 'reserved',
+ data: { name },
+ type: 'Property',
+ line: 3
+ }
+ ]
+ })),
+ ...vue2BuiltInComponents.map((name) => ({
+ filename: `${name}.vue`,
+ code: `
export default {
name: '${name}'
}
`,
- parserOptions,
- options: [{ disallowVueBuiltInComponents: true }],
- errors: [
- {
- messageId: 'reservedInVue',
- data: { name },
- type: 'Literal',
- line: 3
- }
- ]
- }
- }),
- ...vue2BuiltInComponents.map((name) => {
- return {
- filename: `${name}.vue`,
- code: `
+ parserOptions,
+ options: [{ disallowVueBuiltInComponents: true }],
+ errors: [
+ {
+ messageId: 'reservedInVue',
+ data: { name },
+ type: 'Literal',
+ line: 3
+ }
+ ]
+ })),
+ ...vue2BuiltInComponents.map((name) => ({
+ filename: `${name}.vue`,
+ code: `
export default {
name: '${name}'
}
`,
- parserOptions,
- options: [{ disallowVue3BuiltInComponents: true }],
- errors: [
- {
- messageId: 'reservedInVue',
- data: { name },
- type: 'Literal',
- line: 3
- }
- ]
- }
- }),
- ...vue3BuiltInComponents.map((name) => {
- return {
- filename: `${name}.vue`,
- code: `
+ parserOptions,
+ options: [{ disallowVue3BuiltInComponents: true }],
+ errors: [
+ {
+ messageId: 'reservedInVue',
+ data: { name },
+ type: 'Literal',
+ line: 3
+ }
+ ]
+ })),
+ ...vue3BuiltInComponents.map((name) => ({
+ filename: `${name}.vue`,
+ code: `
export default {
name: '${name}'
}
`,
- parserOptions,
- options: [{ disallowVue3BuiltInComponents: true }],
- errors: [
- {
- messageId: 'reservedInVue3',
- data: { name },
- type: 'Literal',
- line: 3
- }
- ]
- }
- })
+ parserOptions,
+ options: [{ disallowVue3BuiltInComponents: true }],
+ errors: [
+ {
+ messageId: 'reservedInVue3',
+ data: { name },
+ type: 'Literal',
+ line: 3
+ }
+ ]
+ }))
]
})
diff --git a/tests/lib/rules/no-restricted-call-after-await.js b/tests/lib/rules/no-restricted-call-after-await.js
index 0eef6713b..69eaf3a42 100644
--- a/tests/lib/rules/no-restricted-call-after-await.js
+++ b/tests/lib/rules/no-restricted-call-after-await.js
@@ -170,7 +170,7 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.',
+ '`import("vue-i18n").useI18n` is forbidden after an `await` expression.',
line: 7,
column: 25,
endLine: 7,
@@ -195,7 +195,7 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("foo").bar.baz` after `await` expression are forbidden.',
+ '`import("foo").bar.baz` is forbidden after an `await` expression.',
line: 7
}
]
@@ -222,12 +222,12 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.',
+ '`import("vue-i18n").useI18n` is forbidden after an `await` expression.',
line: 8
},
{
message:
- 'The `import("foo").bar.baz` after `await` expression are forbidden.',
+ '`import("foo").bar.baz` is forbidden after an `await` expression.',
line: 9
}
]
@@ -249,7 +249,7 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("foo").default` after `await` expression are forbidden.',
+ '`import("foo").default` is forbidden after an `await` expression.',
line: 7
}
]
@@ -271,7 +271,7 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("foo").default` after `await` expression are forbidden.',
+ '`import("foo").default` is forbidden after an `await` expression.',
line: 7
}
]
@@ -293,7 +293,7 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.',
+ '`import("vue-i18n").useI18n` is forbidden after an `await` expression.',
line: 7
}
]
@@ -324,17 +324,17 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("./foo").bar` after `await` expression are forbidden.',
+ '`import("./foo").bar` is forbidden after an `await` expression.',
line: 10
},
{
message:
- 'The `import("./baz").qux` after `await` expression are forbidden.',
+ '`import("./baz").qux` is forbidden after an `await` expression.',
line: 11
},
{
message:
- 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.',
+ '`import("vue-i18n").useI18n` is forbidden after an `await` expression.',
line: 12
}
]
@@ -365,17 +365,17 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("./foo").bar` after `await` expression are forbidden.',
+ '`import("./foo").bar` is forbidden after an `await` expression.',
line: 10
},
{
message:
- 'The `import("./baz").qux` after `await` expression are forbidden.',
+ '`import("./baz").qux` is forbidden after an `await` expression.',
line: 11
},
{
message:
- 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.',
+ '`import("vue-i18n").useI18n` is forbidden after an `await` expression.',
line: 12
}
]
@@ -397,7 +397,7 @@ tester.run('no-restricted-call-after-await', rule, {
errors: [
{
message:
- 'The `import("..").foo` after `await` expression are forbidden.',
+ '`import("..").foo` is forbidden after an `await` expression.',
line: 7
}
]
diff --git a/tests/lib/rules/no-unregistered-components.js b/tests/lib/rules/no-unregistered-components.js
deleted file mode 100644
index f120cde81..000000000
--- a/tests/lib/rules/no-unregistered-components.js
+++ /dev/null
@@ -1,777 +0,0 @@
-/**
- * @fileoverview Report used components that are not registered
- * @author Jesús Ángel González Novez
- */
-'use strict'
-
-// ------------------------------------------------------------------------------
-// Requirements
-// ------------------------------------------------------------------------------
-
-const RuleTester = require('eslint').RuleTester
-const rule = require('../../../lib/rules/no-unregistered-components')
-
-// ------------------------------------------------------------------------------
-// Tests
-// ------------------------------------------------------------------------------
-
-const tester = new RuleTester({
- parser: require.resolve('vue-eslint-parser'),
- parserOptions: {
- ecmaVersion: 2018,
- sourceType: 'module'
- }
-})
-
-tester.run('no-unregistered-components', rule, {
- valid: [
- {
- filename: 'test.vue',
- code: `
-
- Lorem ipsum
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- `,
- options: [
- {
- ignorePatterns: ['custom(\\-\\w+)+']
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
- `,
- options: [
- {
- ignorePatterns: ['custom(\\-\\w+)+']
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
- `,
- options: [
- {
- ignorePatterns: ['custom(\\-\\w+)+']
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
- `,
- options: [
- {
- ignorePatterns: ['Custom(\\w+)+']
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
- `,
- options: [
- {
- ignorePatterns: ['Custom(\\w+)+']
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
- `,
- options: [
- {
- ignorePatterns: ['Custom(\\w+)+']
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Text
-
-
-
- `,
- options: [
- {
- ignorePatterns: ['Custom(\\w+)+', 'Warm(\\w+)+', 'InfoBtn(\\w+)+']
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Text
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Text
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- Text
-
-
-
-
- Text
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
-
- Text
-
-
-
-
- Text
-
-
-
-
- foo
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- Text
-
-
-
-
- `
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `
- }
- ],
- invalid: [
- {
- filename: 'test.vue',
- code: `
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- `,
- options: [
- {
- ignorePatterns: ['custom(\\-\\w+)+']
- }
- ],
- errors: [
- {
- message:
- 'The "WarmButton" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
- `,
- options: [
- {
- ignorePatterns: ['custom(\\-\\w+)+']
- }
- ],
- errors: [
- {
- message:
- 'The "WarmButton" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
- Some text
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponent" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
- Text
-
-
-
- `,
- errors: [
- {
- message:
- 'The "CustomComponentWithNamedSlots" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `,
- errors: [
- {
- message: 'The "foo" component has been used but not registered.',
- line: 3
- }
- ]
- },
- {
- filename: 'test.vue',
- code: `
-
-
-
-
- `,
- errors: [
- {
- message: 'The "foo" component has been used but not registered.',
- line: 3
- }
- ]
- }
- ]
-})
diff --git a/tests/lib/rules/no-watch-after-await.js b/tests/lib/rules/no-watch-after-await.js
index e97c78c63..c54c36eb7 100644
--- a/tests/lib/rules/no-watch-after-await.js
+++ b/tests/lib/rules/no-watch-after-await.js
@@ -188,7 +188,7 @@ tester.run('no-watch-after-await', rule, {
`,
errors: [
{
- message: 'The `watch` after `await` expression are forbidden.',
+ message: '`watch` is forbidden after an `await` expression.',
line: 8,
column: 11,
endLine: 8,
diff --git a/tests/lib/rules/script-indent.js b/tests/lib/rules/script-indent.js
index 229ea3273..2efb8b889 100644
--- a/tests/lib/rules/script-indent.js
+++ b/tests/lib/rules/script-indent.js
@@ -76,10 +76,10 @@ function loadPatterns(additionalValid, additionalInvalid) {
const lines = output.split('\n').map((text, number) => ({
number,
text,
- indentSize: (/^[ \t]+/.exec(text) || [''])[0].length
+ indentSize: (/^[\t ]+/.exec(text) || [''])[0].length
}))
const code = lines
- .map((line) => line.text.replace(/^[ \t]+/, ''))
+ .map((line) => line.text.replace(/^[\t ]+/, ''))
.join('\n')
const errors = lines
.map((line) =>
@@ -100,8 +100,8 @@ function loadPatterns(additionalValid, additionalInvalid) {
.filter(Boolean)
return {
- valid: valid.concat(additionalValid),
- invalid: invalid.concat(additionalInvalid)
+ valid: [...valid, ...additionalValid],
+ invalid: [...invalid, ...additionalInvalid]
}
}
diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js
index 5d081f468..220331b3b 100644
--- a/tests/lib/rules/this-in-template.js
+++ b/tests/lib/rules/this-in-template.js
@@ -190,7 +190,7 @@ function createInvalidTests(prefix, options, message, type) {
)}bar">`,
errors: [{ message, type }],
options
- }
+ },
// We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier.
// {
@@ -198,8 +198,7 @@ function createInvalidTests(prefix, options, message, type) {
// errors: [{ message, type }],
// options
// }
- ].concat(
- options[0] === 'always'
+ ...(options[0] === 'always'
? []
: [
{
@@ -214,68 +213,63 @@ function createInvalidTests(prefix, options, message, type) {
errors: [{ message, type }],
options
}
- ]
- )
+ ])
+ ]
}
ruleTester.run('this-in-template', rule, {
- valid: ['', '', '']
- .concat(createValidTests('', []))
- .concat(createValidTests('', ['never']))
- .concat(createValidTests('this.', ['always']))
- .concat(createValidTests('this?.', ['always'])),
- invalid: []
- .concat(
- createInvalidTests(
- 'this.',
- [],
- "Unexpected usage of 'this'.",
- 'ThisExpression'
- ),
- createInvalidTests(
- 'this?.',
- [],
- "Unexpected usage of 'this'.",
- 'ThisExpression'
- )
- )
- .concat(
- createInvalidTests(
- 'this.',
- ['never'],
- "Unexpected usage of 'this'.",
- 'ThisExpression'
- ),
- createInvalidTests(
- 'this?.',
- ['never'],
- "Unexpected usage of 'this'.",
- 'ThisExpression'
- )
- )
- .concat(
- createInvalidTests('', ['always'], "Expected 'this'.", 'Identifier')
- )
- .concat([
- {
- code: ``,
- output: ``,
- errors: ["Unexpected usage of 'this'."],
- options: ['never']
- },
- {
- code: ``,
- output: ``,
- errors: ["Unexpected usage of 'this'."],
- options: ['never']
- }
- ])
+ valid: [
+ '',
+ '',
+ '',
+ ...createValidTests('', []),
+ ...createValidTests('', ['never']),
+ ...createValidTests('this.', ['always']),
+ ...createValidTests('this?.', ['always'])
+ ],
+ invalid: [
+ ...createInvalidTests(
+ 'this.',
+ [],
+ "Unexpected usage of 'this'.",
+ 'ThisExpression'
+ ),
+ ...createInvalidTests(
+ 'this?.',
+ [],
+ "Unexpected usage of 'this'.",
+ 'ThisExpression'
+ ),
+ ...createInvalidTests(
+ 'this.',
+ ['never'],
+ "Unexpected usage of 'this'.",
+ 'ThisExpression'
+ ),
+ ...createInvalidTests(
+ 'this?.',
+ ['never'],
+ "Unexpected usage of 'this'.",
+ 'ThisExpression'
+ ),
+ ...createInvalidTests('', ['always'], "Expected 'this'.", 'Identifier'),
+ {
+ code: ``,
+ output: ``,
+ errors: ["Unexpected usage of 'this'."],
+ options: ['never']
+ },
+ {
+ code: ``,
+ output: ``,
+ errors: ["Unexpected usage of 'this'."],
+ options: ['never']
+ }
+ ]
})
function suggestionPrefix(prefix, options) {
- if (options[0] === 'always' && !['this.', 'this?.'].includes(prefix)) {
- return 'this.'
- } else {
- return ''
- }
+ return options[0] === 'always' && !['this.', 'this?.'].includes(prefix)
+ ? 'this.'
+ : ''
}
diff --git a/tests/lib/rules/valid-attribute-name.js b/tests/lib/rules/valid-attribute-name.js
new file mode 100644
index 000000000..920231899
--- /dev/null
+++ b/tests/lib/rules/valid-attribute-name.js
@@ -0,0 +1,146 @@
+/**
+ * @author Doug Wade
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/valid-attribute-name')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('valid-attribute-name', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ` ... `
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: `Now you see me
`
+ },
+ {
+ filename: 'test.vue',
+ code: ` ... `
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ` ... `
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: ``,
+ errors: [
+ {
+ message: 'Attribute name 0abc is not valid.',
+ line: 1,
+ column: 14
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ errors: [
+ {
+ message: 'Attribute name -def is not valid.',
+ line: 1,
+ column: 14
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ errors: [
+ {
+ message: 'Attribute name !ghi is not valid.',
+ line: 1,
+ column: 14
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ errors: [
+ {
+ message: 'Attribute name 0abc is not valid.',
+ line: 1,
+ column: 14
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ errors: [
+ {
+ message: 'Attribute name 0abc is not valid.',
+ line: 1,
+ column: 14
+ }
+ ]
+ }
+ ]
+})
diff --git a/tests/lib/rules/valid-model-definition.js b/tests/lib/rules/valid-model-definition.js
new file mode 100644
index 000000000..d9d8263ab
--- /dev/null
+++ b/tests/lib/rules/valid-model-definition.js
@@ -0,0 +1,151 @@
+/**
+ * @fileoverview Prevents invalid keys in model option.
+ * @author Alex Sokolov
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/valid-model-definition')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2018,
+ sourceType: 'module'
+ }
+})
+ruleTester.run('valid-model-definition', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ prop: 'list'
+ }
+ }
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ event: 'update'
+ }
+ }
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ prop: 'list',
+ event: 'update'
+ }
+ }
+ `
+ }
+ ],
+
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ props: 'list'
+ }
+ }
+ `,
+ errors: ["Invalid key 'props' in model option."]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ events: 'update'
+ }
+ }
+ `,
+ errors: ["Invalid key 'events' in model option."]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ props: 'list',
+ event: 'update'
+ }
+ }
+ `,
+ errors: ["Invalid key 'props' in model option."]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ prop: 'list',
+ events: 'update'
+ }
+ }
+ `,
+ errors: ["Invalid key 'events' in model option."]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ props: 'list',
+ events: 'update'
+ }
+ }
+ `,
+ errors: [
+ "Invalid key 'props' in model option.",
+ "Invalid key 'events' in model option."
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ prop: 'checked',
+ props: 'list',
+ event: 'update'
+ }
+ }
+ `,
+ errors: ["Invalid key 'props' in model option."]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ model: {
+ name: 'checked',
+ props: 'list',
+ event: 'update'
+ }
+ }
+ `,
+ errors: [
+ "Invalid key 'name' in model option.",
+ "Invalid key 'props' in model option."
+ ]
+ }
+ ]
+})
diff --git a/tests/lib/script-setup-vars.js b/tests/lib/script-setup-vars.js
new file mode 100644
index 000000000..90921984f
--- /dev/null
+++ b/tests/lib/script-setup-vars.js
@@ -0,0 +1,384 @@
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const eslint = require('eslint')
+const rules = new eslint.Linter().getRules()
+const ruleNoUnusedVars = rules.get('no-unused-vars')
+const ruleNoUndef = rules.get('no-undef')
+
+const RuleTester = eslint.RuleTester
+const ruleTester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 6,
+ sourceType: 'module'
+ }
+})
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+describe('vue-eslint-parser should properly mark the variables used in the template', () => {
+ ruleTester.run('no-unused-vars', ruleNoUnusedVars, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ {{ msg }}
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+
+ // Resolve component name
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+
+ `
+ },
+
+ // TopLevel await
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ {{post}}
+
+ `,
+ parserOptions: {
+ ecmaVersion: 2022,
+ sourceType: 'module'
+ }
+ },
+
+ // ref
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+
+ //style vars
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `
+ },
+ // ns
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ label
+
+
+ `
+ }
+ ],
+
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: "'Bar' is defined but never used.",
+ line: 5
+ },
+ {
+ message: "'baz' is assigned a value but never used.",
+ line: 18
+ }
+ ]
+ },
+
+ // Resolve component name
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: "'camelCase' is defined but never used.",
+ line: 3
+ }
+ ]
+ },
+
+ // Scope tests
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ {{ msg }}
+
+ `,
+ errors: [
+ {
+ message: "'msg' is assigned a value but never used.",
+ line: 4
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ {{ i }}
+
+ `,
+ errors: [
+ {
+ message: "'i' is assigned a value but never used.",
+ line: 3
+ }
+ ]
+ },
+
+ // Not `
+
+
+ {{ msg }}
+
+ `,
+ errors: [
+ {
+ message: "'msg' is assigned a value but never used.",
+ line: 3
+ }
+ ]
+ },
+
+ //style vars
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ errors: ["'color' is assigned a value but never used."]
+ }
+ ]
+ })
+
+ ruleTester.run('no-undef', ruleNoUndef, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: "'defineUnknown' is not defined.",
+ line: 3
+ },
+ {
+ message: "'defineUnknown' is not defined.",
+ line: 7
+ }
+ ]
+ }
+ ]
+ })
+})
diff --git a/tests/lib/script-setup.js b/tests/lib/script-setup.js
deleted file mode 100644
index ad7409dcb..000000000
--- a/tests/lib/script-setup.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * @author Yosuke Ota
- * See LICENSE file in root directory for full license.
- */
-'use strict'
-
-const Linter = require('eslint').Linter
-const parser = require('vue-eslint-parser')
-const assert = require('assert')
-const experimentalScriptSetupVars = require('../../lib/rules/experimental-script-setup-vars')
-
-const baseConfig = {
- parser: 'vue-eslint-parser',
- parserOptions: {
- ecmaVersion: 2020,
- sourceType: 'module'
- }
-}
-
-describe('script-setup test cases', () => {
- const linter = new Linter()
- linter.defineParser('vue-eslint-parser', parser)
- linter.defineRule(
- 'vue/experimental-script-setup-vars',
- experimentalScriptSetupVars
- )
-
- describe('temporary supports.', () => {
- const config = Object.assign({}, baseConfig, {
- globals: { console: false },
- rules: {
- 'vue/experimental-script-setup-vars': 'error',
- 'no-undef': 'error'
- }
- })
-
- it('should not be marked.', () => {
- const code = `
- `
- const messages = linter.verify(code, config, 'test.vue')
- assert.deepStrictEqual(messages, [])
- })
- })
-})
diff --git a/tests/lib/utils/index.js b/tests/lib/utils/index.js
index 89c81942d..9b02ba46b 100644
--- a/tests/lib/utils/index.js
+++ b/tests/lib/utils/index.js
@@ -4,12 +4,11 @@ const espree = require('espree')
const utils = require('../../../lib/utils/index')
const assert = require('assert')
-describe('getComputedProperties', () => {
- const parse = function (code) {
- return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0]
- .init
- }
+function parse(code) {
+ return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0].init
+}
+describe('getComputedProperties', () => {
it('should return empty array when there is no computed property', () => {
const node = parse(`const test = {
name: 'test',
@@ -111,11 +110,6 @@ describe('getComputedProperties', () => {
})
describe('getStaticPropertyName', () => {
- const parse = function (code) {
- return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0]
- .init
- }
-
it('should parse property expression with identifier', () => {
const node = parse(`const test = { computed: { } }`)
@@ -137,11 +131,6 @@ describe('getStaticPropertyName', () => {
})
describe('getStringLiteralValue', () => {
- const parse = function (code) {
- return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0]
- .init
- }
-
it('should parse literal', () => {
const node = parse(`const test = { ['computed'] () {} }`)
@@ -157,11 +146,6 @@ describe('getStringLiteralValue', () => {
})
describe('getMemberChaining', () => {
- const parse = function (code) {
- return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0]
- .init
- }
-
const jsonIgnoreKeys = ['expression', 'object']
it('should parse MemberExpression', () => {
@@ -276,11 +260,6 @@ describe('getMemberChaining', () => {
})
describe('getRegisteredComponents', () => {
- const parse = function (code) {
- return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0]
- .init
- }
-
it('should return empty array when there are no components registered', () => {
const node = parse(`const test = {
name: 'test',
@@ -336,15 +315,13 @@ describe('getRegisteredComponents', () => {
})
})
-describe('getComponentProps', () => {
- const parse = function (code) {
- const data = espree.parse(code, { ecmaVersion: 2020 }).body[0]
- .declarations[0].init
- return utils.getComponentProps(data)
- }
+function parseProps(code) {
+ return utils.getComponentPropsFromOptions(parse(code))
+}
+describe('getComponentProps', () => {
it('should return empty array when there is no component props', () => {
- const props = parse(`const test = {
+ const props = parseProps(`const test = {
name: 'test',
data() {
return {}
@@ -355,7 +332,7 @@ describe('getComponentProps', () => {
})
it('should return empty array when component props is empty array', () => {
- const props = parse(`const test = {
+ const props = parseProps(`const test = {
name: 'test',
props: []
}`)
@@ -364,7 +341,7 @@ describe('getComponentProps', () => {
})
it('should return empty array when component props is empty object', () => {
- const props = parse(`const test = {
+ const props = parseProps(`const test = {
name: 'test',
props: {}
}`)
@@ -373,7 +350,7 @@ describe('getComponentProps', () => {
})
it('should return computed props', () => {
- const props = parse(`const test = {
+ const props = parseProps(`const test = {
name: 'test',
...test,
data() {
@@ -412,7 +389,7 @@ describe('getComponentProps', () => {
})
it('should return computed from array props', () => {
- const props = parse(`const test = {
+ const props = parseProps(`const test = {
name: 'test',
data() {
return {}
diff --git a/tests/lib/utils/vue-component.js b/tests/lib/utils/vue-component.js
index 91da3d28c..c2fb7aaf5 100644
--- a/tests/lib/utils/vue-component.js
+++ b/tests/lib/utils/vue-component.js
@@ -255,7 +255,7 @@ function invalidTests(ext) {
// ${ext}
`,
parserOptions,
- errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(4)])
+ errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(4)]
},
{
filename: `test.${ext}`,
@@ -293,7 +293,7 @@ function invalidTests(ext) {
// ${ext}
`,
parserOptions,
- errors: (ext === 'js' ? [] : [makeError(3)]).concat([makeError(6)])
+ errors: [...(ext === 'js' ? [] : [makeError(3)]), makeError(6)]
},
{
filename: `test.${ext}`,
@@ -310,7 +310,7 @@ function invalidTests(ext) {
// ${ext}
`,
parserOptions,
- errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(8)])
+ errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(8)]
},
{
filename: `test.${ext}`,
@@ -332,11 +332,11 @@ ruleTester.run('vue-component', rule, {
filename: 'test.js',
code: `export default { }`,
parserOptions
- }
- ]
- .concat(validTests('js'))
- .concat(validTests('jsx'))
- .concat(validTests('vue')),
+ },
+ ...validTests('js'),
+ ...validTests('jsx'),
+ ...validTests('vue')
+ ],
invalid: [
{
filename: 'test.vue',
@@ -349,9 +349,9 @@ ruleTester.run('vue-component', rule, {
code: `export default { }`,
parserOptions,
errors: [makeError(1)]
- }
+ },
+ ...invalidTests('js'),
+ ...invalidTests('jsx'),
+ ...invalidTests('vue')
]
- .concat(invalidTests('js'))
- .concat(invalidTests('jsx'))
- .concat(invalidTests('vue'))
})
diff --git a/tools/lib/categories.js b/tools/lib/categories.js
index 7223fd87b..1d8550fb3 100644
--- a/tools/lib/categories.js
+++ b/tools/lib/categories.js
@@ -77,4 +77,4 @@ module.exports = categoryIds
(rule) => !rule.meta.deprecated
)
}))
- .filter((category) => category.rules.length >= 1)
+ .filter((category) => category.rules.length > 0)
diff --git a/tools/lib/utils.js b/tools/lib/utils.js
new file mode 100644
index 000000000..eb3f53fd7
--- /dev/null
+++ b/tools/lib/utils.js
@@ -0,0 +1,42 @@
+module.exports = { getPresetIds, formatItems }
+
+const presetCategories = {
+ base: null,
+ essential: 'base',
+ 'vue3-essential': 'base',
+ 'strongly-recommended': 'essential',
+ 'vue3-strongly-recommended': 'vue3-essential',
+ recommended: 'strongly-recommended',
+ 'vue3-recommended': 'vue3-strongly-recommended'
+ // 'use-with-caution': 'recommended',
+ // 'vue3-use-with-caution': 'vue3-recommended'
+}
+
+function formatItems(items, suffix) {
+ if (items.length === 1) {
+ return `${items[0]}${suffix ? ` ${suffix[0]}` : ''}`
+ }
+ if (items.length === 2) {
+ return `${items.join(' and ')}${suffix ? ` ${suffix[1]}` : ''}`
+ }
+ return `all of ${items.slice(0, -1).join(', ')} and ${[...items].pop()}${
+ suffix ? ` ${suffix[1]}` : ''
+ }`
+}
+
+function getPresetIds(categoryIds) {
+ const subsetCategoryIds = []
+ for (const categoryId of categoryIds) {
+ for (const [subsetCategoryId, supersetCategoryId] of Object.entries(
+ presetCategories
+ )) {
+ if (supersetCategoryId === categoryId) {
+ subsetCategoryIds.push(subsetCategoryId)
+ }
+ }
+ }
+ if (subsetCategoryIds.length === 0) {
+ return categoryIds
+ }
+ return [...new Set([...categoryIds, ...getPresetIds(subsetCategoryIds)])]
+}
diff --git a/tools/update-docs-rules-index.js b/tools/update-docs-rules-index.js
index 9b177d7ac..3db26c36b 100644
--- a/tools/update-docs-rules-index.js
+++ b/tools/update-docs-rules-index.js
@@ -7,9 +7,19 @@
const fs = require('fs')
const path = require('path')
const rules = require('./lib/rules')
-const categories = require('./lib/categories')
+const { getPresetIds, formatItems } = require('./lib/utils')
+const removedRules = require('../lib/removed-rules')
+
+const VUE3_EMOJI = ':three:'
+const VUE2_EMOJI = ':two:'
// -----------------------------------------------------------------------------
+const categorizedRules = rules.filter(
+ (rule) =>
+ rule.meta.docs.categories &&
+ !rule.meta.docs.extensionRule &&
+ !rule.meta.deprecated
+)
const uncategorizedRules = rules.filter(
(rule) =>
!rule.meta.docs.categories &&
@@ -24,16 +34,23 @@ const uncategorizedExtensionRule = rules.filter(
)
const deprecatedRules = rules.filter((rule) => rule.meta.deprecated)
-function toRuleRow(rule) {
+const TYPE_MARK = {
+ problem: ':warning:',
+ suggestion: ':hammer:',
+ layout: ':lipstick:'
+}
+
+function toRuleRow(rule, kindMarks = []) {
const mark = [
rule.meta.fixable ? ':wrench:' : '',
rule.meta.hasSuggestions ? ':bulb:' : '',
rule.meta.deprecated ? ':warning:' : ''
].join('')
+ const kindMark = [...kindMarks, TYPE_MARK[rule.meta.type]].join('')
const link = `[${rule.ruleId}](./${rule.name}.md)`
const description = rule.meta.docs.description || '(no description)'
- return `| ${link} | ${description} | ${mark} |`
+ return `| ${link} | ${description} | ${mark} | ${kindMark} |`
}
function toDeprecatedRuleRow(rule) {
@@ -46,29 +63,117 @@ function toDeprecatedRuleRow(rule) {
return `| ${link} | ${replacedBy || '(no replacement)'} |`
}
+function toRemovedRuleRow({
+ ruleName,
+ replacedBy,
+ deprecatedInVersion,
+ removedInVersion
+}) {
+ const link = `[vue/${ruleName}](./${ruleName}.md)`
+ const replacement =
+ replacedBy.map((name) => `[vue/${name}](./${name}.md)`).join(', ') ||
+ '(no replacement)'
+ const deprecatedVersionLink = `[${deprecatedInVersion}](https://github.com/vuejs/eslint-plugin-vue/releases/tag/${deprecatedInVersion})`
+ const removedVersionLink = `[${removedInVersion}](https://github.com/vuejs/eslint-plugin-vue/releases/tag/${removedInVersion})`
+
+ return `| ${link} | ${replacement} | ${deprecatedVersionLink} | ${removedVersionLink} |`
+}
+
+const categoryGroups = [
+ {
+ title: 'Base Rules (Enabling Correct ESLint Parsing)',
+ description:
+ 'Rules in this category are enabled for all presets provided by eslint-plugin-vue.',
+ categoryIdForVue3: 'base',
+ categoryIdForVue2: 'base',
+ useMark: false
+ },
+ {
+ title: 'Priority A: Essential (Error Prevention)',
+ categoryIdForVue3: 'vue3-essential',
+ categoryIdForVue2: 'essential',
+ useMark: true
+ },
+ {
+ title: 'Priority B: Strongly Recommended (Improving Readability)',
+ categoryIdForVue3: 'vue3-strongly-recommended',
+ categoryIdForVue2: 'strongly-recommended',
+ useMark: true
+ },
+ {
+ title: 'Priority C: Recommended (Potentially Dangerous Patterns)',
+ categoryIdForVue3: 'vue3-recommended',
+ categoryIdForVue2: 'recommended',
+ useMark: true
+ }
+]
+
// -----------------------------------------------------------------------------
-let rulesTableContent = categories
- .map(
- (category) => `
-## ${category.title.vuepress}
+let rulesTableContent = categoryGroups
+ .map((group) => {
+ const rules = categorizedRules.filter(
+ (rule) =>
+ rule.meta.docs.categories.includes(group.categoryIdForVue3) ||
+ rule.meta.docs.categories.includes(group.categoryIdForVue2)
+ )
+ let content = `
+## ${group.title}
+`
-Enforce all the rules in this category, as well as all higher priority rules, with:
+ if (group.description) {
+ content += `
+${group.description}
+`
+ }
+ if (group.useMark) {
+ const presetsForVue3 = getPresetIds([group.categoryIdForVue3]).map(
+ (categoryId) => `\`"plugin:vue/${categoryId}"\``
+ )
+ const presetsForVue2 = getPresetIds([group.categoryIdForVue2]).map(
+ (categoryId) => `\`"plugin:vue/${categoryId}"\``
+ )
+ content += `
+- ${VUE3_EMOJI} Indicates that the rule is for Vue 3 and is included in ${formatItems(
+ presetsForVue3,
+ ['preset', 'presets']
+ )}.
+- ${VUE2_EMOJI} Indicates that the rule is for Vue 2 and is included in ${formatItems(
+ presetsForVue2,
+ ['preset', 'presets']
+ )}.
+`
+ }
-\`\`\`json
-{
- "extends": "plugin:vue/${category.categoryId}"
-}
-\`\`\`
+ content += `
+
-| Rule ID | Description | |
-|:--------|:------------|:---|
-${category.rules.map(toRuleRow).join('\n')}
+| Rule ID | Description | | |
+|:--------|:------------|:--:|:--:|
+${rules
+ .map((rule) => {
+ const mark = group.useMark
+ ? [
+ rule.meta.docs.categories.includes(group.categoryIdForVue3)
+ ? [VUE3_EMOJI]
+ : [],
+ rule.meta.docs.categories.includes(group.categoryIdForVue2)
+ ? [VUE2_EMOJI]
+ : []
+ ].flat()
+ : []
+ return toRuleRow(rule, mark)
+ })
+ .join('\n')}
+
+
`
- )
+
+ return content
+ })
.join('')
// -----------------------------------------------------------------------------
-if (uncategorizedRules.length || uncategorizedExtensionRule.length) {
+if (uncategorizedRules.length > 0 || uncategorizedExtensionRule.length > 0) {
rulesTableContent += `
## Uncategorized
@@ -88,27 +193,35 @@ For example:
\`\`\`
`
}
-if (uncategorizedRules.length) {
+if (uncategorizedRules.length > 0) {
rulesTableContent += `
-| Rule ID | Description | |
-|:--------|:------------|:---|
-${uncategorizedRules.map(toRuleRow).join('\n')}
+
+
+| Rule ID | Description | | |
+|:--------|:------------|:--:|:--:|
+${uncategorizedRules.map((rule) => toRuleRow(rule)).join('\n')}
+
+
`
}
-if (uncategorizedExtensionRule.length) {
+if (uncategorizedExtensionRule.length > 0) {
rulesTableContent += `
### Extension Rules
The following rules extend the rules provided by ESLint itself and apply them to the expressions in the \`\`.
-| Rule ID | Description | |
-|:--------|:------------|:---|
-${uncategorizedExtensionRule.map(toRuleRow).join('\n')}
+
+
+| Rule ID | Description | | |
+|:--------|:------------|:--:|:--:|
+${uncategorizedExtensionRule.map((rule) => toRuleRow(rule)).join('\n')}
+
+
`
}
// -----------------------------------------------------------------------------
-if (deprecatedRules.length >= 1) {
+if (deprecatedRules.length > 0) {
rulesTableContent += `
## Deprecated
@@ -121,12 +234,24 @@ ${deprecatedRules.map(toDeprecatedRuleRow).join('\n')}
`
}
+// -----------------------------------------------------------------------------
+rulesTableContent += `
+## Removed
+
+- :no_entry_sign: These rules have been removed in a previous major release, after they have been deprecated for a while.
+
+| Rule ID | Replaced by | Deprecated in version | Removed in version |
+|:--------|:------------|:-----------------------|:-------------------|
+${removedRules.map(toRemovedRuleRow).join('\n')}
+`
+
// -----------------------------------------------------------------------------
const readmeFilePath = path.resolve(__dirname, '../docs/rules/README.md')
fs.writeFileSync(
readmeFilePath,
`---
sidebarDepth: 0
+pageClass: rule-list
---
# Available rules
@@ -139,5 +264,12 @@ sidebarDepth: 0
: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).
:::
-${rulesTableContent}`
+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.
+
+${rulesTableContent.trim()}
+`
)
diff --git a/tools/update-docs.js b/tools/update-docs.js
index 12deca0ea..75f6dab6d 100644
--- a/tools/update-docs.js
+++ b/tools/update-docs.js
@@ -20,45 +20,12 @@ For example:
const fs = require('fs')
const path = require('path')
-const last = require('lodash/last')
const rules = require('./lib/rules')
+const removedRules = require('../lib/removed-rules')
+const { getPresetIds, formatItems } = require('./lib/utils')
const ROOT = path.resolve(__dirname, '../docs/rules')
-const presetCategories = {
- base: null,
- essential: 'base',
- 'vue3-essential': 'base',
- 'strongly-recommended': 'essential',
- 'vue3-strongly-recommended': 'vue3-essential',
- recommended: 'strongly-recommended',
- 'vue3-recommended': 'vue3-strongly-recommended'
- // 'use-with-caution': 'recommended',
- // 'vue3-use-with-caution': 'vue3-recommended'
-}
-
-function formatItems(items) {
- if (items.length <= 2) {
- return items.join(' and ')
- }
- return `all of ${items.slice(0, -1).join(', ')} and ${last(items)}`
-}
-
-function getPresetIds(categoryIds) {
- const subsetCategoryIds = []
- for (const categoryId of categoryIds) {
- for (const subsetCategoryId in presetCategories) {
- if (presetCategories[subsetCategoryId] === categoryId) {
- subsetCategoryIds.push(subsetCategoryId)
- }
- }
- }
- if (subsetCategoryIds.length === 0) {
- return categoryIds
- }
- return [...new Set([...categoryIds, ...getPresetIds(subsetCategoryIds)])]
-}
-
function pickSince(content) {
const fileIntro = /^---\n(.*\n)+---\n*/g.exec(content)
if (fileIntro) {
@@ -107,21 +74,37 @@ class DocFile {
const fileIntroPattern = /^---\n(.*\n)+---\n*/g
- if (fileIntroPattern.test(this.content)) {
- this.content = this.content.replace(fileIntroPattern, computed)
- } else {
- this.content = `${computed}${this.content.trim()}\n`
- }
+ this.content = fileIntroPattern.test(this.content)
+ ? this.content.replace(fileIntroPattern, computed)
+ : `${computed}${this.content.trim()}\n`
return this
}
updateHeader() {
const { ruleId, meta } = this.rule
- const title = `# ${ruleId}\n\n> ${meta.docs.description}`
+ const description = meta.docs
+ ? meta.docs.description
+ : this.content.match(/^description: (.*)$/m)[1]
+ const title = `# ${ruleId}\n\n> ${description}`
const notes = []
- if (meta.deprecated) {
+ if (meta.removedInVersion) {
+ if (meta.replacedBy.length > 0) {
+ const replacedRules = meta.replacedBy.map(
+ (name) => `[vue/${name}](${name}.md) rule`
+ )
+ notes.push(
+ `- :no_entry_sign: This rule was **removed** in eslint-plugin-vue ${
+ meta.removedInVersion
+ } and replaced by ${formatItems(replacedRules)}.`
+ )
+ } else {
+ notes.push(
+ `- :no_entry_sign: This rule was **removed** in eslint-plugin-vue ${meta.removedInVersion}.`
+ )
+ }
+ } else if (meta.deprecated) {
if (meta.replacedBy) {
const replacedRules = meta.replacedBy.map(
(name) => `[vue/${name}](${name}.md) rule`
@@ -159,17 +142,15 @@ class DocFile {
}
// Add an empty line after notes.
- if (notes.length >= 1) {
+ if (notes.length > 0) {
notes.push('', '')
}
- const headerPattern = /#.+\n\n*[^\n]*\n+(?:- .+\n)*\n*/
+ const headerPattern = /#.+\n+[^\n]*\n+(?:- .+\n)*\n*/
const header = `${title}\n\n${notes.join('\n')}`
- if (headerPattern.test(this.content)) {
- this.content = this.content.replace(headerPattern, header)
- } else {
- this.content = `${header}${this.content.trim()}\n`
- }
+ this.content = headerPattern.test(this.content)
+ ? this.content.replace(headerPattern, header)
+ : `${header}${this.content.trim()}\n`
return this
}
@@ -178,7 +159,7 @@ class DocFile {
const { meta } = this.rule
this.content = this.content.replace(
- /)\n+```/gm,
+ /()\n+```/gm,
'$1\n\n```'
)
this.content = this.content.replace(
@@ -219,11 +200,9 @@ ${
`
: ''
}`
- if (footerPattern.test(this.content)) {
- this.content = this.content.replace(footerPattern, footer)
- } else {
- this.content = `${this.content.trim()}\n\n${footer}`
- }
+ this.content = footerPattern.test(this.content)
+ ? this.content.replace(footerPattern, footer)
+ : `${this.content.trim()}\n\n${footer}`
return this
}
@@ -263,6 +242,25 @@ ${
return this
}
+
+ checkGoodBadSymbols() {
+ const lines = this.content.split('\n')
+ for (const [lineNumber, line] of lines.entries()) {
+ const lineNumberFilePath = `${this.filePath}:${lineNumber + 1}`
+
+ if (line.includes('GOOD') && !line.includes('✓ GOOD')) {
+ throw new Error(
+ `Expected '✓ GOOD' instead of '${line}' in '${lineNumberFilePath}'`
+ )
+ }
+
+ if (line.includes('BAD') && !line.includes('✗ BAD')) {
+ throw new Error(
+ `Expected '✗ BAD' instead of '${line}' in '${lineNumberFilePath}'`
+ )
+ }
+ }
+ }
}
for (const rule of rules) {
@@ -274,4 +272,17 @@ for (const rule of rules) {
.adjustCodeBlocks()
.write()
.checkHeadings()
+ .checkGoodBadSymbols()
+}
+
+for (const { ruleName, replacedBy, removedInVersion } of removedRules) {
+ const rule = {
+ name: ruleName,
+ ruleId: `vue/${ruleName}`,
+ meta: {
+ replacedBy,
+ removedInVersion
+ }
+ }
+ DocFile.read(rule).updateHeader().write()
}
diff --git a/tools/update-lib-configs.js b/tools/update-lib-configs.js
index 26376b78a..f75aff4fa 100644
--- a/tools/update-lib-configs.js
+++ b/tools/update-lib-configs.js
@@ -14,7 +14,7 @@ const path = require('path')
const eslint = require('eslint')
const categories = require('./lib/categories')
-const errorCategories = ['base', 'essential', 'vue3-essential']
+const errorCategories = new Set(['base', 'essential', 'vue3-essential'])
const extendsCategories = {
base: null,
@@ -29,20 +29,21 @@ const extendsCategories = {
}
function formatRules(rules, categoryId) {
- const obj = rules.reduce((setting, rule) => {
- let options = errorCategories.includes(categoryId) ? 'error' : 'warn'
- const defaultOptions =
- rule.meta && rule.meta.docs && rule.meta.docs.defaultOptions
- if (defaultOptions) {
- const v = categoryId.startsWith('vue3') ? 3 : 2
- const defaultOption = defaultOptions[`vue${v}`]
- if (defaultOption) {
- options = [options, ...defaultOption]
+ const obj = Object.fromEntries(
+ rules.map((rule) => {
+ let options = errorCategories.has(categoryId) ? 'error' : 'warn'
+ const defaultOptions =
+ rule.meta && rule.meta.docs && rule.meta.docs.defaultOptions
+ if (defaultOptions) {
+ const v = categoryId.startsWith('vue3') ? 3 : 2
+ const defaultOption = defaultOptions[`vue${v}`]
+ if (defaultOption) {
+ options = [options, ...defaultOption]
+ }
}
- }
- setting[rule.ruleId] = options
- return setting
- }, {})
+ return [rule.ruleId, options]
+ })
+ )
return JSON.stringify(obj, null, 2)
}
@@ -85,12 +86,12 @@ module.exports = {
// Update files.
const ROOT = path.resolve(__dirname, '../lib/configs/')
-categories.forEach((category) => {
+for (const category of categories) {
const filePath = path.join(ROOT, `${category.categoryId}.js`)
const content = formatCategory(category)
fs.writeFileSync(filePath, content)
-})
+}
// Format files.
async function format() {
diff --git a/tools/update-lib-index.js b/tools/update-lib-index.js
index b9a5dcbd9..f57ae1e58 100644
--- a/tools/update-lib-index.js
+++ b/tools/update-lib-index.js
@@ -39,6 +39,8 @@ module.exports = {
'.vue': require('./processor')
},
environments: {
+ // TODO Remove in the next major version
+ /** @deprecated */
'setup-compiler-macros': {
globals: {
defineProps: 'readonly',
diff --git a/tools/update-no-layout-rules-config.js b/tools/update-no-layout-rules-config.js
index b0361b155..ee4690e41 100644
--- a/tools/update-no-layout-rules-config.js
+++ b/tools/update-no-layout-rules-config.js
@@ -17,10 +17,7 @@ const rules = require('./lib/rules')
const rulesToDisable = rules.filter(({ meta }) => meta.type === 'layout')
function formatRules(rules) {
- const obj = rules.reduce((setting, rule) => {
- setting[rule.ruleId] = 'off'
- return setting
- }, {})
+ const obj = Object.fromEntries(rules.map((rule) => [rule.ruleId, 'off']))
return JSON.stringify(obj, null, 2)
}
diff --git a/tools/update-vue3-export-names.js b/tools/update-vue3-export-names.js
index fcd31b3e3..8a3de00ea 100644
--- a/tools/update-vue3-export-names.js
+++ b/tools/update-vue3-export-names.js
@@ -33,39 +33,51 @@ async function* extractExportNames(m) {
range: true
})
for (const node of rootNode.body) {
- if (node.type === 'ExportAllDeclaration') {
- if (node.exported) {
- yield node.exported.name
- } else {
- for await (const name of extractExportNames(node.source.value)) {
- yield name
+ switch (node.type) {
+ case 'ExportAllDeclaration': {
+ if (node.exported) {
+ yield node.exported.name
+ } else {
+ for await (const name of extractExportNames(node.source.value)) {
+ yield name
+ }
}
+ break
}
- } else if (node.type === 'ExportNamedDeclaration') {
- if (node.declaration) {
- if (
- node.declaration.type === 'ClassDeclaration' ||
- node.declaration.type === 'ClassExpression' ||
- node.declaration.type === 'FunctionDeclaration' ||
- node.declaration.type === 'TSDeclareFunction' ||
- node.declaration.type === 'TSEnumDeclaration' ||
- node.declaration.type === 'TSInterfaceDeclaration' ||
- node.declaration.type === 'TSTypeAliasDeclaration'
- ) {
- yield node.declaration.id.name
- } else if (node.declaration.type === 'VariableDeclaration') {
- for (const decl of node.declaration.declarations) {
- yield* extractNamesFromPattern(decl.id)
+ case 'ExportNamedDeclaration': {
+ if (node.declaration) {
+ switch (node.declaration.type) {
+ case 'ClassDeclaration':
+ case 'ClassExpression':
+ case 'FunctionDeclaration':
+ case 'TSDeclareFunction':
+ case 'TSEnumDeclaration':
+ case 'TSInterfaceDeclaration':
+ case 'TSTypeAliasDeclaration': {
+ yield node.declaration.id.name
+ break
+ }
+ case 'VariableDeclaration': {
+ for (const decl of node.declaration.declarations) {
+ yield* extractNamesFromPattern(decl.id)
+ }
+ break
+ }
+ case 'TSModuleDeclaration': {
+ //?
+ break
+ }
}
- } else if (node.declaration.type === 'TSModuleDeclaration') {
- //?
}
+ for (const spec of node.specifiers) {
+ yield spec.exported.name
+ }
+ break
}
- for (const spec of node.specifiers) {
- yield spec.exported.name
+ case 'ExportDefaultDeclaration': {
+ yield 'default'
+ break
}
- } else if (node.type === 'ExportDefaultDeclaration') {
- yield 'default'
}
}
}
@@ -83,26 +95,39 @@ async function* extractExportNames(m) {
* @param {Identifier|ArrayPattern|ObjectPattern|AssignmentPattern|MemberExpression|RestElement} node
*/
function* extractNamesFromPattern(node) {
- if (node.type === 'Identifier') {
- yield node.name
- } else if (node.type === 'ArrayPattern') {
- for (const element of node.elements) {
- yield* extractNamesFromPattern(element)
+ switch (node.type) {
+ case 'Identifier': {
+ yield node.name
+ break
+ }
+ case 'ArrayPattern': {
+ for (const element of node.elements) {
+ yield* extractNamesFromPattern(element)
+ }
+ break
}
- } else if (node.type === 'ObjectPattern') {
- for (const prop of node.properties) {
- if (prop.type === 'Property') {
- yield prop.key.name
- } else if (prop.type === 'RestElement') {
- yield* extractNamesFromPattern(prop)
+ case 'ObjectPattern': {
+ for (const prop of node.properties) {
+ if (prop.type === 'Property') {
+ yield prop.key.name
+ } else if (prop.type === 'RestElement') {
+ yield* extractNamesFromPattern(prop)
+ }
}
+ break
+ }
+ case 'AssignmentPattern': {
+ yield* extractNamesFromPattern(node.left)
+ break
+ }
+ case 'RestElement': {
+ yield* extractNamesFromPattern(node.argument)
+ break
+ }
+ case 'MemberExpression': {
+ // ?
+ break
}
- } else if (node.type === 'AssignmentPattern') {
- yield* extractNamesFromPattern(node.left)
- } else if (node.type === 'RestElement') {
- yield* extractNamesFromPattern(node.argument)
- } else if (node.type === 'MemberExpression') {
- // ?
}
}
async function resolveTypeContents(m) {
diff --git a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
index bafff6d2b..16a734d2e 100644
--- a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
@@ -347,6 +347,7 @@ export interface PrivateIdentifier extends HasParentNode {
export interface Literal extends HasParentNode {
type: 'Literal'
value: string | boolean | null | number | RegExp | BigInt
+ raw: string
regex?: {
pattern: string
flags: string