From c232e26e797dc5e750a949a6eff55148fc206007 Mon Sep 17 00:00:00 2001 From: Mussin Benarbia <77260898+mussinbenarbia@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:07:26 +0900 Subject: [PATCH] Add new `vue/enforce-style-attribute` rule (#2110) Co-authored-by: Flo Edelmann Co-authored-by: Mussin Benarbia --- docs/rules/enforce-style-attribute.md | 86 ++++++++++++ docs/rules/index.md | 1 + lib/index.js | 1 + lib/rules/enforce-style-attribute.js | 153 +++++++++++++++++++++ tests/lib/rules/enforce-style-attribute.js | 148 ++++++++++++++++++++ 5 files changed, 389 insertions(+) create mode 100644 docs/rules/enforce-style-attribute.md create mode 100644 lib/rules/enforce-style-attribute.js create mode 100644 tests/lib/rules/enforce-style-attribute.js diff --git a/docs/rules/enforce-style-attribute.md b/docs/rules/enforce-style-attribute.md new file mode 100644 index 000000000..6dc042353 --- /dev/null +++ b/docs/rules/enforce-style-attribute.md @@ -0,0 +1,86 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/enforce-style-attribute +description: enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags +--- + +# vue/enforce-style-attribute + +> enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule allows you to explicitly allow the use of the `scoped` and `module` attributes on your top level style tags. + +### `"scoped"` + + + +```vue + + + + + + + + + +``` + + + +### `"module"` + + + +```vue + + + + + + + + +``` + + + +### `"plain"` + + + +```vue + + + + + + + + +``` + + + +## :wrench: Options + +```json +{ + "vue/enforce-style-attribute": [ + "error", + { "allow": ["scoped", "module", "plain"] } + ] +} +``` + +- `"allow"` (`["scoped" | "module" | "plain"]`) Array of attributes to allow on a top level style tag. The option `plain` is used to allow style tags that have neither the `scoped` nor `module` attributes. Default: `["scoped"]` + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/enforce-style-attribute.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/enforce-style-attribute.js) diff --git a/docs/rules/index.md b/docs/rules/index.md index afc8deb98..4ff1cebbb 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -215,6 +215,7 @@ For example: | [vue/define-emits-declaration](./define-emits-declaration.md) | enforce declaration style of `defineEmits` | | :hammer: | | [vue/define-macros-order](./define-macros-order.md) | enforce order of `defineEmits` and `defineProps` compiler macros | :wrench: | :lipstick: | | [vue/define-props-declaration](./define-props-declaration.md) | enforce declaration style of `defineProps` | | :hammer: | +| [vue/enforce-style-attribute](./enforce-style-attribute.md) | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags | | :hammer: | | [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | | :hammer: | | [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: | :lipstick: | | [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: | :lipstick: | diff --git a/lib/index.js b/lib/index.js index 3497a7b4f..3eb5208ce 100644 --- a/lib/index.js +++ b/lib/index.js @@ -35,6 +35,7 @@ module.exports = { 'define-props-declaration': require('./rules/define-props-declaration'), 'dot-location': require('./rules/dot-location'), 'dot-notation': require('./rules/dot-notation'), + 'enforce-style-attribute': require('./rules/enforce-style-attribute'), eqeqeq: require('./rules/eqeqeq'), 'first-attribute-linebreak': require('./rules/first-attribute-linebreak'), 'func-call-spacing': require('./rules/func-call-spacing'), diff --git a/lib/rules/enforce-style-attribute.js b/lib/rules/enforce-style-attribute.js new file mode 100644 index 000000000..b4b39a73f --- /dev/null +++ b/lib/rules/enforce-style-attribute.js @@ -0,0 +1,153 @@ +/** + * @author Mussin Benarbia + * See LICENSE file in root directory for full license. + */ +'use strict' + +const { isVElement } = require('../utils') + +/** + * check whether a tag has the `scoped` attribute + * @param {VElement} componentBlock + */ +function isScoped(componentBlock) { + return componentBlock.startTag.attributes.some( + (attribute) => !attribute.directive && attribute.key.name === 'scoped' + ) +} + +/** + * check whether a tag has the `module` attribute + * @param {VElement} componentBlock + */ +function isModule(componentBlock) { + return componentBlock.startTag.attributes.some( + (attribute) => !attribute.directive && attribute.key.name === 'module' + ) +} + +/** + * check if a tag doesn't have either the `scoped` nor `module` attribute + * @param {VElement} componentBlock + */ +function isPlain(componentBlock) { + return !isScoped(componentBlock) && !isModule(componentBlock) +} + +function getUserDefinedAllowedAttrs(context) { + if (context.options[0] && context.options[0].allow) { + return context.options[0].allow + } + return [] +} + +const defaultAllowedAttrs = ['scoped'] + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: + 'enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/enforce-style-attribute.html' + }, + fixable: null, + schema: [ + { + type: 'object', + properties: { + allow: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + enum: ['plain', 'scoped', 'module'] + } + } + }, + additionalProperties: false + } + ], + messages: { + notAllowedScoped: + 'The scoped attribute is not allowed. Allowed: {{ allowedAttrsString }}.', + notAllowedModule: + 'The module attribute is not allowed. Allowed: {{ allowedAttrsString }}.', + notAllowedPlain: + 'Plain ' + }, + // With scoped option + { + filename: 'test.vue', + code: '', + options: [{ allow: ['scoped'] }] + }, + // With module option + { + filename: 'test.vue', + code: '', + options: [{ allow: ['module'] }] + }, + // With plain option + { + filename: 'test.vue', + code: '', + options: [{ allow: ['plain'] }] + }, + // With all options + { + filename: 'test.vue', + code: '', + options: [{ allow: ['scoped', 'module', 'plain'] }] + }, + { + filename: 'test.vue', + code: '', + options: [{ allow: ['scoped', 'module', 'plain'] }] + }, + { + filename: 'test.vue', + code: '', + options: [{ allow: ['scoped', 'module', 'plain'] }] + } + ], + invalid: [ + // With default (scoped) + { + code: ``, + errors: [ + { + message: 'Plain `, + errors: [ + { + message: 'The module attribute is not allowed. Allowed: scoped.' + } + ] + }, + // With scoped option + { + code: ``, + options: [{ allow: ['scoped'] }], + errors: [ + { + message: 'Plain `, + options: [{ allow: ['scoped'] }], + errors: [ + { + message: 'The module attribute is not allowed. Allowed: scoped.' + } + ] + }, + // With module option + { + code: ``, + options: [{ allow: ['module'] }], + errors: [ + { + message: 'Plain `, + options: [{ allow: ['module'] }], + errors: [ + { + message: 'The scoped attribute is not allowed. Allowed: module.' + } + ] + }, + // With different combinations of two options + { + code: ``, + options: [{ allow: ['module', 'scoped'] }], + errors: [ + { + message: + 'Plain `, + options: [{ allow: ['scoped', 'plain'] }], + errors: [ + { + message: + 'The module attribute is not allowed. Allowed: plain, scoped.' + } + ] + }, + { + code: ``, + options: [{ allow: ['module', 'plain'] }], + errors: [ + { + message: + 'The scoped attribute is not allowed. Allowed: module, plain.' + } + ] + } + ] +})