Skip to content

Commit 504db38

Browse files
feat: add new vue/enforce-style-attribute rule (vuejs#2109)
1 parent dd3df38 commit 504db38

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed

docs/rules/enforce-style-attribute.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/enforce-style-attribute
5+
description: enforce either the `scoped` or `module` attribute in SFC top level style tags
6+
---
7+
8+
# vue/enforce-style-attribute
9+
10+
> enfore either the `scoped` or `module` attribute in SFC top level style tags
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
13+
14+
## :wrench: Options
15+
16+
```json
17+
{
18+
"vue/attribute-hyphenation": ["error", "either" | "scoped" | "module"]
19+
}
20+
```
21+
22+
## :book: Rule Details
23+
24+
This rule warns you about top level style tags that are missing either the `scoped` or `module` attribute.
25+
26+
- `"either"` (default) ... Warn if a style tag doesn't have neither `scoped` nor `module` attributes.
27+
- `"scoped"` ... Warn if a style tag doesn't have the `scoped` attribute.
28+
- `"module"` ... Warn if a style tag doesn't have the `module` attribute.
29+
30+
### `"either"`
31+
32+
<eslint-code-block :rules="{'vue/enforce-style-attribute': ['error', 'either']}">
33+
34+
```vue
35+
<!-- ✓ GOOD -->
36+
<style scoped></style>
37+
<style lang="scss" src="../path/to/style.scss" scoped></style>
38+
39+
<!-- ✓ GOOD -->
40+
<style module></style>
41+
42+
<!-- ✗ BAD -->
43+
<style></style>
44+
```
45+
46+
</eslint-code-block>
47+
48+
### `"scoped"`
49+
50+
<eslint-code-block :rules="{'vue/enforce-style-attribute': ['error', 'scoped']}">
51+
52+
```vue
53+
<!-- ✓ GOOD -->
54+
<style scoped></style>
55+
<style lang="scss" src="../path/to/style.scss" scoped></style>
56+
57+
<!-- ✗ BAD -->
58+
<style></style>
59+
<style module></style>
60+
```
61+
62+
</eslint-code-block>
63+
64+
### `"module"`
65+
66+
<eslint-code-block :rules="{'vue/enforce-style-attribute': ['error', 'module']}">
67+
68+
```vue
69+
<!-- ✓ GOOD -->
70+
<style module></style>
71+
72+
<!-- ✗ BAD -->
73+
<style></style>
74+
<style scoped></style>
75+
<style lang="scss" src="../path/to/style.scss" scoped></style>
76+
```
77+
78+
</eslint-code-block>
79+
80+
## :mag: Implementation
81+
82+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/enforce-style-attribute.js)
83+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/enforce-style-attribute.js)

lib/rules/enforce-style-attribute.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* @author Mussin Benarbia
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const { isVElement } = require('../utils')
8+
9+
/**
10+
* check whether a tag has the `scoped` attribute
11+
* @param {VElement} componentBlock
12+
*/
13+
function isScoped(componentBlock) {
14+
return componentBlock.startTag.attributes.some(
15+
(attribute) => !attribute.directive && attribute.key.name === 'scoped'
16+
)
17+
}
18+
19+
/**
20+
* check whether a tag has the `module` attribute
21+
* @param {VElement} componentBlock
22+
*/
23+
function isModule(componentBlock) {
24+
return componentBlock.startTag.attributes.some(
25+
(attribute) => !attribute.directive && attribute.key.name === 'module'
26+
)
27+
}
28+
29+
module.exports = {
30+
meta: {
31+
type: 'suggestion',
32+
docs: {
33+
description:
34+
'enforce either the `scoped` or `module` attribute in SFC top level style tags',
35+
categories: undefined,
36+
url: 'https://eslint.vuejs.org/rules/enforce-style-attribute.html'
37+
},
38+
fixable: 'code',
39+
schema: [{ enum: ['scoped', 'module', 'either'] }],
40+
messages: {
41+
needsScoped: 'The <style> tag should have the scoped attribute.',
42+
needsModule: 'The <style> tag should have the module attribute.',
43+
needsEither:
44+
'The <style> tag should have either the scoped or module attribute.'
45+
}
46+
},
47+
48+
/** @param {RuleContext} context */
49+
create(context) {
50+
if (!context.parserServices.getDocumentFragment) {
51+
return {}
52+
}
53+
const documentFragment = context.parserServices.getDocumentFragment()
54+
if (!documentFragment) {
55+
return {}
56+
}
57+
58+
const topLevelElements = documentFragment.children.filter(isVElement)
59+
const topLevelStyleTags = topLevelElements.filter(
60+
(element) => element.rawName === 'style'
61+
)
62+
63+
if (topLevelStyleTags.length === 0) {
64+
return {}
65+
}
66+
67+
const mode = context.options[0] || 'either'
68+
const needsScoped = mode === 'scoped'
69+
const needsModule = mode === 'module'
70+
const needsEither = mode === 'either'
71+
72+
return {
73+
Program() {
74+
for (const styleTag of topLevelStyleTags) {
75+
if (needsScoped && !isScoped(styleTag)) {
76+
context.report({
77+
node: styleTag,
78+
messageId: 'needsScoped'
79+
// fix(fixer) {
80+
// const openingTagEnd = styleTag.startTag.range[1]
81+
// const insertionText = ' scoped'
82+
// return fixer.insertTextAfterRange(
83+
// [openingTagEnd, openingTagEnd],
84+
// insertionText
85+
// )
86+
// }
87+
})
88+
return
89+
}
90+
91+
if (needsModule && !isModule(styleTag)) {
92+
context.report({
93+
node: styleTag,
94+
messageId: 'needsModule'
95+
})
96+
return
97+
}
98+
99+
if (needsEither && !isScoped(styleTag) && !isModule(styleTag)) {
100+
context.report({
101+
node: styleTag,
102+
messageId: 'needsEither'
103+
})
104+
return
105+
}
106+
}
107+
}
108+
}
109+
}
110+
}
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @author Mussin Benarbia
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../lib/rules/enforce-style-attribute')
9+
10+
const tester = new RuleTester({
11+
parser: require.resolve('vue-eslint-parser'),
12+
parserOptions: {
13+
ecmaVersion: 2020,
14+
sourceType: 'module'
15+
}
16+
})
17+
18+
tester.run('enforce-style-attribute', rule, {
19+
valid: [
20+
// With default options
21+
{
22+
filename: 'test.vue',
23+
code: '<template></template><script></script><style scoped></style>'
24+
},
25+
{
26+
filename: 'test.vue',
27+
code: '<template></template><script></script><style module></style>'
28+
},
29+
{
30+
filename: 'test.vue',
31+
code: '<template></template><script></script><style lang=scss" "src="../path/to/style.scss" scoped></style>'
32+
},
33+
// With scoped option
34+
{
35+
filename: 'test.vue',
36+
code: '<template></template><script></script><style scoped></style>',
37+
options: ['scoped']
38+
},
39+
{
40+
filename: 'test.vue',
41+
code: '<template></template><script></script><style lang=scss" "src="../path/to/style.scss" scoped></style>',
42+
options: ['scoped']
43+
},
44+
// With module option
45+
{
46+
filename: 'test.vue',
47+
code: '<template></template><script></script><style module></style>',
48+
options: ['module']
49+
}
50+
],
51+
invalid: [
52+
// With default options
53+
{
54+
code: `<template></template><script></script><style></style>`,
55+
errors: [
56+
{
57+
message:
58+
'The <style> tag should have either the scoped or module attribute.'
59+
}
60+
]
61+
},
62+
// With scoped option
63+
{
64+
code: `<template></template><script></script><style></style>`,
65+
errors: [
66+
{
67+
message: 'The <style> tag should have the scoped attribute.'
68+
}
69+
],
70+
options: ['scoped']
71+
},
72+
{
73+
code: `<template></template><script></script><style module></style>`,
74+
errors: [
75+
{
76+
message: 'The <style> tag should have the scoped attribute.'
77+
}
78+
],
79+
options: ['scoped']
80+
},
81+
// With module option
82+
{
83+
code: `<template></template><script></script><style></style>`,
84+
errors: [
85+
{
86+
message: 'The <style> tag should have the module attribute.'
87+
}
88+
],
89+
options: ['module']
90+
},
91+
{
92+
code: `<template></template><script></script><style scoped></style>`,
93+
errors: [
94+
{
95+
message: 'The <style> tag should have the module attribute.'
96+
}
97+
],
98+
options: ['module']
99+
}
100+
]
101+
})

0 commit comments

Comments
 (0)