diff --git a/.changeset/brown-eels-care.md b/.changeset/brown-eels-care.md new file mode 100644 index 000000000..01bb97da6 --- /dev/null +++ b/.changeset/brown-eels-care.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-vue": minor +--- + +Added [new `-error` config variants](https://eslint.vuejs.org/user-guide/#bundle-configurations-eslint-config-js), with all rules' severity set to `error`: `strongly-recommended-error`, `recommended-error`, `vue2-strongly-recommended-error`, `vue2-recommended-error` (along with their flat config equivalents) diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md index 84c87c580..d0216c481 100644 --- a/docs/user-guide/index.md +++ b/docs/user-guide/index.md @@ -64,15 +64,21 @@ You can use the following configs by adding them to `eslint.config.js`. - `*.configs["flat/base"]` ... Settings and rules to enable correct ESLint parsing. - Configurations for using Vue.js 3.x: - `*.configs["flat/essential"]` ... `base`, plus rules to prevent errors or unintended behavior. - - `*.configs["flat/strongly-recommended"]` ... Above, plus rules to considerably improve code readability and/or dev experience. - - `*.configs["flat/recommended"]` ... Above, plus rules to enforce subjective community defaults to ensure consistency. + - `*.configs["flat/strongly-recommended"]` ... `essential`, plus rules to considerably improve code readability and/or dev experience. + - `*.configs["flat/strongly-recommended-error"]` ... Same as `strongly-recommended`, except with all rules configured to error instead of warn. + - `*.configs["flat/recommended"]` ... `strongly-recommended`, plus rules to enforce subjective community defaults to ensure consistency. + - `*.configs["flat/recommended-error"]` ... Same as `recommended`, except with all rules configured to error instead of warn. - Configurations for using Vue.js 2.x: - `*.configs["flat/vue2-essential"]` ... `base`, plus rules to prevent errors or unintended behavior. - - `*.configs["flat/vue2-strongly-recommended"]` ... Above, plus rules to considerably improve code readability and/or dev experience. - - `*.configs["flat/vue2-recommended"]` ... Above, plus rules to enforce subjective community defaults to ensure consistency + - `*.configs["flat/vue2-strongly-recommended"]` ... `vue2-essential`, plus rules to considerably improve code readability and/or dev experience. + - `*.configs["flat/vue2-strongly-recommended-error"]` ... Same as `vue2-strongly-recommended`, except with all rules configured to error instead of warn. + - `*.configs["flat/vue2-recommended"]` ... `vue2-strongly-recommended`, plus rules to enforce subjective community defaults to ensure consistency. + - `*.configs["flat/vue2-recommended-error"]` ... Same as `vue2-recommended`, except with all rules configured to error instead of warn. :::warning Reporting rules By default, all rules from **base** and **essential** categories report ESLint errors. Other rules - because they're not covering potential bugs in the application - report warnings. What does it mean? By default - nothing, but if you want - you can set up a threshold and break the build after a certain amount of warnings, instead of any. More information [here](https://eslint.org/docs/user-guide/command-line-interface#handling-warnings). + +Alternatively, there are versions of the configs with all rules set to error suffixed with `-error` that you can use. ::: #### Specifying Globals (`eslint.config.js`) @@ -186,15 +192,21 @@ You can use the following configs by adding them to `extends`. - `"plugin:vue/base"` ... Settings and rules to enable correct ESLint parsing. - Configurations for using Vue.js 3.x: - `"plugin:vue/essential"` ... `base`, plus rules to prevent errors or unintended behavior. - - `"plugin:vue/strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience. - - `"plugin:vue/recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency. + - `"plugin:vue/strongly-recommended"` ... `essential`, plus rules to considerably improve code readability and/or dev experience. + - `"plugin:vue/strongly-recommended-error"` ... Same as `strongly-recommended`, except with all rules configured to error instead of warn. + - `"plugin:vue/recommended"` ... `strongly-recommended`, plus rules to enforce subjective community defaults to ensure consistency. + - `"plugin:vue/recommended-error"` ... Same as `recommended`, except with all rules configured to error instead of warn. - Configurations for using Vue.js 2.x: - `"plugin:vue/vue2-essential"` ... `base`, plus rules to prevent errors or unintended behavior. - - `"plugin:vue/vue2-strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience. - - `"plugin:vue/vue2-recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency. + - `"plugin:vue/vue2-strongly-recommended"` ... `vue2-essential`, plus rules to considerably improve code readability and/or dev experience. + - `"plugin:vue/vue2-strongly-recommended-error"` ... Same as `vue2-strongly-recommended`, except with all rules configured to error instead of warn. + - `"plugin:vue/vue2-recommended"` ... `vue2-strongly-recommended`, plus rules to enforce subjective community defaults to ensure consistency. + - `"plugin:vue/vue2-recommended-error"` ... Same as `vue2-recommended`, except with all rules configured to error instead of warn. :::warning Reporting rules By default, all rules from **base** and **essential** categories report ESLint errors. Other rules - because they're not covering potential bugs in the application - report warnings. What does it mean? By default - nothing, but if you want - you can set up a threshold and break the build after a certain amount of warnings, instead of any. More information [here](https://eslint.org/docs/user-guide/command-line-interface#handling-warnings). + +Alternatively, there are versions of the configs with all rules set to error suffixed with `-error` that you can use. ::: :::warning Status of Vue.js 3.x supports diff --git a/lib/configs/flat/vue2-recommended-error.js b/lib/configs/flat/vue2-recommended-error.js new file mode 100644 index 000000000..efcc64d22 --- /dev/null +++ b/lib/configs/flat/vue2-recommended-error.js @@ -0,0 +1,24 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +'use strict' +const config = require('./vue2-strongly-recommended-error.js') + +module.exports = [ + ...config, + { + name: 'vue/vue2-recommended-error/rules', + rules: { + 'vue/attributes-order': 'error', + 'vue/block-order': 'error', + 'vue/no-lone-template': 'error', + 'vue/no-multiple-slot-args': 'error', + 'vue/no-required-prop-with-default': 'error', + 'vue/no-v-html': 'error', + 'vue/order-in-components': 'error', + 'vue/this-in-template': 'error' + } + } +] diff --git a/lib/configs/flat/vue2-strongly-recommended-error.js b/lib/configs/flat/vue2-strongly-recommended-error.js new file mode 100644 index 000000000..6c26c53ec --- /dev/null +++ b/lib/configs/flat/vue2-strongly-recommended-error.js @@ -0,0 +1,39 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +'use strict' +const config = require('./vue2-essential.js') + +module.exports = [ + ...config, + { + name: 'vue/vue2-strongly-recommended-error/rules', + rules: { + 'vue/attribute-hyphenation': 'error', + 'vue/component-definition-name-casing': 'error', + 'vue/first-attribute-linebreak': 'error', + 'vue/html-closing-bracket-newline': 'error', + 'vue/html-closing-bracket-spacing': 'error', + 'vue/html-end-tags': 'error', + 'vue/html-indent': 'error', + 'vue/html-quotes': 'error', + 'vue/html-self-closing': 'error', + 'vue/max-attributes-per-line': 'error', + 'vue/multiline-html-element-content-newline': 'error', + 'vue/mustache-interpolation-spacing': 'error', + 'vue/no-multi-spaces': 'error', + 'vue/no-spaces-around-equal-signs-in-attribute': 'error', + 'vue/no-template-shadow': 'error', + 'vue/one-component-per-file': 'error', + 'vue/prop-name-casing': 'error', + 'vue/require-default-prop': 'error', + 'vue/require-prop-types': 'error', + 'vue/singleline-html-element-content-newline': 'error', + 'vue/v-bind-style': 'error', + 'vue/v-on-style': 'error', + 'vue/v-slot-style': 'error' + } + } +] diff --git a/lib/configs/flat/vue3-recommended-error.js b/lib/configs/flat/vue3-recommended-error.js new file mode 100644 index 000000000..9db908382 --- /dev/null +++ b/lib/configs/flat/vue3-recommended-error.js @@ -0,0 +1,24 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +'use strict' +const config = require('./vue3-strongly-recommended-error.js') + +module.exports = [ + ...config, + { + name: 'vue/recommended-error/rules', + rules: { + 'vue/attributes-order': 'error', + 'vue/block-order': 'error', + 'vue/no-lone-template': 'error', + 'vue/no-multiple-slot-args': 'error', + 'vue/no-required-prop-with-default': 'error', + 'vue/no-v-html': 'error', + 'vue/order-in-components': 'error', + 'vue/this-in-template': 'error' + } + } +] diff --git a/lib/configs/flat/vue3-strongly-recommended-error.js b/lib/configs/flat/vue3-strongly-recommended-error.js new file mode 100644 index 000000000..55fda62a7 --- /dev/null +++ b/lib/configs/flat/vue3-strongly-recommended-error.js @@ -0,0 +1,47 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +'use strict' +const config = require('./vue3-essential.js') + +module.exports = [ + ...config, + { + name: 'vue/strongly-recommended-error/rules', + rules: { + 'vue/attribute-hyphenation': 'error', + 'vue/component-definition-name-casing': 'error', + 'vue/first-attribute-linebreak': 'error', + 'vue/html-closing-bracket-newline': 'error', + 'vue/html-closing-bracket-spacing': 'error', + 'vue/html-end-tags': 'error', + 'vue/html-indent': 'error', + 'vue/html-quotes': 'error', + 'vue/html-self-closing': 'error', + 'vue/max-attributes-per-line': 'error', + 'vue/multiline-html-element-content-newline': 'error', + 'vue/mustache-interpolation-spacing': 'error', + 'vue/no-multi-spaces': 'error', + 'vue/no-spaces-around-equal-signs-in-attribute': 'error', + 'vue/no-template-shadow': 'error', + 'vue/one-component-per-file': 'error', + 'vue/prop-name-casing': 'error', + 'vue/require-default-prop': 'error', + 'vue/require-explicit-emits': 'error', + 'vue/require-prop-types': 'error', + 'vue/singleline-html-element-content-newline': 'error', + 'vue/v-bind-style': 'error', + 'vue/v-on-event-hyphenation': [ + 'error', + 'always', + { + autofix: true + } + ], + 'vue/v-on-style': 'error', + 'vue/v-slot-style': 'error' + } + } +] diff --git a/lib/configs/index.js b/lib/configs/index.js index fbc7a84f5..cd7dfbb32 100644 --- a/lib/configs/index.js +++ b/lib/configs/index.js @@ -4,22 +4,30 @@ const configs = { 'vue2-essential': require('./vue2-essential'), 'vue2-strongly-recommended': require('./vue2-strongly-recommended'), + 'vue2-strongly-recommended-error': require('./vue2-strongly-recommended-error'), 'vue2-recommended': require('./vue2-recommended'), + 'vue2-recommended-error': require('./vue2-recommended-error'), essential: require('./vue3-essential'), 'strongly-recommended': require('./vue3-strongly-recommended'), + 'strongly-recommended-error': require('./vue3-strongly-recommended-error'), recommended: require('./vue3-recommended'), + 'recommended-error': require('./vue3-recommended-error'), // flat configs 'flat/base': require('./flat/base.js'), 'flat/vue2-essential': require('./flat/vue2-essential.js'), 'flat/vue2-strongly-recommended': require('./flat/vue2-strongly-recommended.js'), + 'flat/vue2-strongly-recommended-error': require('./flat/vue2-strongly-recommended-error.js'), 'flat/vue2-recommended': require('./flat/vue2-recommended.js'), + 'flat/vue2-recommended-error': require('./flat/vue2-recommended-error.js'), 'flat/essential': require('./flat/vue3-essential.js'), 'flat/strongly-recommended': require('./flat/vue3-strongly-recommended.js'), + 'flat/strongly-recommended-error': require('./flat/vue3-strongly-recommended-error.js'), 'flat/recommended': require('./flat/vue3-recommended.js'), + 'flat/recommended-error': require('./flat/vue3-recommended-error.js'), // config-format-agnostic configs 'no-layout-rules': require('./no-layout-rules') diff --git a/lib/configs/vue2-recommended-error.js b/lib/configs/vue2-recommended-error.js new file mode 100644 index 000000000..eb7c624fd --- /dev/null +++ b/lib/configs/vue2-recommended-error.js @@ -0,0 +1,18 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +module.exports = { + extends: require.resolve('./vue2-strongly-recommended-error'), + rules: { + 'vue/attributes-order': 'error', + 'vue/block-order': 'error', + 'vue/no-lone-template': 'error', + 'vue/no-multiple-slot-args': 'error', + 'vue/no-required-prop-with-default': 'error', + 'vue/no-v-html': 'error', + 'vue/order-in-components': 'error', + 'vue/this-in-template': 'error' + } +} diff --git a/lib/configs/vue2-strongly-recommended-error.js b/lib/configs/vue2-strongly-recommended-error.js new file mode 100644 index 000000000..482914c8c --- /dev/null +++ b/lib/configs/vue2-strongly-recommended-error.js @@ -0,0 +1,33 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +module.exports = { + extends: require.resolve('./vue2-essential'), + rules: { + 'vue/attribute-hyphenation': 'error', + 'vue/component-definition-name-casing': 'error', + 'vue/first-attribute-linebreak': 'error', + 'vue/html-closing-bracket-newline': 'error', + 'vue/html-closing-bracket-spacing': 'error', + 'vue/html-end-tags': 'error', + 'vue/html-indent': 'error', + 'vue/html-quotes': 'error', + 'vue/html-self-closing': 'error', + 'vue/max-attributes-per-line': 'error', + 'vue/multiline-html-element-content-newline': 'error', + 'vue/mustache-interpolation-spacing': 'error', + 'vue/no-multi-spaces': 'error', + 'vue/no-spaces-around-equal-signs-in-attribute': 'error', + 'vue/no-template-shadow': 'error', + 'vue/one-component-per-file': 'error', + 'vue/prop-name-casing': 'error', + 'vue/require-default-prop': 'error', + 'vue/require-prop-types': 'error', + 'vue/singleline-html-element-content-newline': 'error', + 'vue/v-bind-style': 'error', + 'vue/v-on-style': 'error', + 'vue/v-slot-style': 'error' + } +} diff --git a/lib/configs/vue3-recommended-error.js b/lib/configs/vue3-recommended-error.js new file mode 100644 index 000000000..23a3c21a2 --- /dev/null +++ b/lib/configs/vue3-recommended-error.js @@ -0,0 +1,18 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +module.exports = { + extends: require.resolve('./vue3-strongly-recommended-error'), + rules: { + 'vue/attributes-order': 'error', + 'vue/block-order': 'error', + 'vue/no-lone-template': 'error', + 'vue/no-multiple-slot-args': 'error', + 'vue/no-required-prop-with-default': 'error', + 'vue/no-v-html': 'error', + 'vue/order-in-components': 'error', + 'vue/this-in-template': 'error' + } +} diff --git a/lib/configs/vue3-strongly-recommended-error.js b/lib/configs/vue3-strongly-recommended-error.js new file mode 100644 index 000000000..b48e0deec --- /dev/null +++ b/lib/configs/vue3-strongly-recommended-error.js @@ -0,0 +1,41 @@ +/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +module.exports = { + extends: require.resolve('./vue3-essential'), + rules: { + 'vue/attribute-hyphenation': 'error', + 'vue/component-definition-name-casing': 'error', + 'vue/first-attribute-linebreak': 'error', + 'vue/html-closing-bracket-newline': 'error', + 'vue/html-closing-bracket-spacing': 'error', + 'vue/html-end-tags': 'error', + 'vue/html-indent': 'error', + 'vue/html-quotes': 'error', + 'vue/html-self-closing': 'error', + 'vue/max-attributes-per-line': 'error', + 'vue/multiline-html-element-content-newline': 'error', + 'vue/mustache-interpolation-spacing': 'error', + 'vue/no-multi-spaces': 'error', + 'vue/no-spaces-around-equal-signs-in-attribute': 'error', + 'vue/no-template-shadow': 'error', + 'vue/one-component-per-file': 'error', + 'vue/prop-name-casing': 'error', + 'vue/require-default-prop': 'error', + 'vue/require-explicit-emits': 'error', + 'vue/require-prop-types': 'error', + 'vue/singleline-html-element-content-newline': 'error', + 'vue/v-bind-style': 'error', + 'vue/v-on-event-hyphenation': [ + 'error', + 'always', + { + autofix: true + } + ], + 'vue/v-on-style': 'error', + 'vue/v-slot-style': 'error' + } +} diff --git a/tools/update-lib-configs.js b/tools/update-lib-configs.js index 0c2845e45..e49208eec 100644 --- a/tools/update-lib-configs.js +++ b/tools/update-lib-configs.js @@ -28,10 +28,11 @@ const extendsCategories = { 'vue3-use-with-caution': 'vue3-recommended' } -function formatRules(rules, categoryId) { +function formatRules(rules, categoryId, alwaysError) { const obj = Object.fromEntries( rules.map((rule) => { - let options = errorCategories.has(categoryId) ? 'error' : 'warn' + let options = + alwaysError || errorCategories.has(categoryId) ? 'error' : 'warn' const defaultOptions = rule.meta && rule.meta.docs && rule.meta.docs.defaultOptions if (defaultOptions) { @@ -47,8 +48,8 @@ function formatRules(rules, categoryId) { return JSON.stringify(obj, null, 2) } -function formatCategory(category) { - const extendsCategoryId = extendsCategories[category.categoryId] +function formatCategory(category, alwaysError = false) { + let extendsCategoryId = extendsCategories[category.categoryId] if (extendsCategoryId == null) { return `/* * IMPORTANT! @@ -63,7 +64,7 @@ module.exports = { plugins: [ 'vue' ], - rules: ${formatRules(category.rules, category.categoryId)}, + rules: ${formatRules(category.rules, category.categoryId, alwaysError)}, overrides: [ { files: '*.vue', @@ -73,6 +74,10 @@ module.exports = { } ` } + if (alwaysError && !errorCategories.has(extendsCategoryId)) { + extendsCategoryId += '-error' + } + return `/* * IMPORTANT! * This file has been automatically generated, @@ -80,7 +85,7 @@ module.exports = { */ module.exports = { extends: require.resolve('./${extendsCategoryId}'), - rules: ${formatRules(category.rules, category.categoryId)} + rules: ${formatRules(category.rules, category.categoryId, alwaysError)} } ` } @@ -92,6 +97,13 @@ for (const category of categories) { const content = formatCategory(category) fs.writeFileSync(filePath, content) + + if (!errorCategories.has(category.categoryId)) { + fs.writeFileSync( + path.join(ROOT, `${category.categoryId}-error.js`), + formatCategory(category, true) + ) + } } // Format files. diff --git a/tools/update-lib-flat-configs.js b/tools/update-lib-flat-configs.js index bc103cad1..548911146 100644 --- a/tools/update-lib-flat-configs.js +++ b/tools/update-lib-flat-configs.js @@ -28,10 +28,11 @@ const extendsCategories = { 'vue3-use-with-caution': 'vue3-recommended' } -function formatRules(rules, categoryId) { +function formatRules(rules, categoryId, alwaysError) { const obj = Object.fromEntries( rules.map((rule) => { - let options = errorCategories.has(categoryId) ? 'error' : 'warn' + let options = + alwaysError || errorCategories.has(categoryId) ? 'error' : 'warn' const defaultOptions = rule.meta && rule.meta.docs && rule.meta.docs.defaultOptions if (defaultOptions) { @@ -47,8 +48,9 @@ function formatRules(rules, categoryId) { return JSON.stringify(obj, null, 2) } -function formatCategory(category) { - const extendsCategoryId = extendsCategories[category.categoryId] +function formatCategory(category, alwaysError = false) { + let extendsCategoryId = extendsCategories[category.categoryId] + if (category.categoryId === 'base') { return `/* * IMPORTANT! @@ -79,12 +81,17 @@ module.exports = [ parser: require('vue-eslint-parser'), sourceType: 'module', }, - rules: ${formatRules(category.rules, category.categoryId)}, + rules: ${formatRules(category.rules, category.categoryId, alwaysError)}, processor: 'vue/vue' } ] ` } + + if (alwaysError && !errorCategories.has(extendsCategoryId)) { + extendsCategoryId += '-error' + } + return `/* * IMPORTANT! * This file has been automatically generated, @@ -96,8 +103,8 @@ const config = require('./${extendsCategoryId}.js') module.exports = [ ...config, { - name: 'vue/${category.categoryId.replace(/^vue3-/u, '')}/rules', - rules: ${formatRules(category.rules, category.categoryId)}, + name: 'vue/${category.categoryId.replace(/^vue3-/u, '')}${alwaysError ? '-error' : ''}/rules', + rules: ${formatRules(category.rules, category.categoryId, alwaysError)}, } ] ` @@ -110,6 +117,13 @@ for (const category of categories) { const content = formatCategory(category) fs.writeFileSync(filePath, content) + + if (!errorCategories.has(category.categoryId)) { + fs.writeFileSync( + path.join(ROOT, `${category.categoryId}-error.js`), + formatCategory(category, true) + ) + } } // Format files.