Skip to content

Commit 502a4e5

Browse files
committed
Added option to not report errors if documentation of class names exist to no-unused-selector and require-selector-used-inside.
1 parent a6e768a commit 502a4e5

21 files changed

+508
-109
lines changed

docs/rules/no-unused-selector.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,14 @@ This is a limitation of this rule. Without this limitation, the root element can
8888
```json
8989
{
9090
"vue-scoped-css/no-unused-selector": ["error", {
91-
"ignoreBEMModifier": false
91+
"ignoreBEMModifier": false,
92+
"captureClassesFromDoc": []
9293
}]
9394
}
9495
```
9596

9697
- `ignoreBEMModifier` ... Set `true` if you want to ignore the `BEM` modifier. Default is false.
98+
- `captureClassesFromDoc` ... Specifies the regexp that extracts the class name from the documentation in the comments. Even if there is no matching element, no error is reported if the document of a class name exists in the comments.
9799

98100
### `"ignoreBEMModifier": true`
99101

@@ -111,13 +113,49 @@ This is a limitation of this rule. Without this limitation, the root element can
111113

112114
</eslint-code-block>
113115

116+
### `"captureClassesFromDoc": [ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i" ]`
117+
118+
Example of [KSS] format:
119+
120+
<eslint-code-block :rules="{'vue-scoped-css/no-unused-selector': ['error', {captureClassesFromDoc: ['/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i']}]}">
121+
122+
```vue
123+
<template>
124+
<div>
125+
<a class="button star"></a>
126+
</div>
127+
</template>
128+
<style scoped lang="scss">
129+
/* ✓ GOOD */
130+
131+
// A button suitable for giving a star to someone.
132+
//
133+
// :hover - Subtle hover highlight.
134+
// .star-given - A highlight indicating you've already given a star.
135+
// .star-given:hover - Subtle hover highlight on top of star-given styling.
136+
// .disabled - Dims the button to indicate it cannot be used.
137+
//
138+
// Styleguide 2.1.3.
139+
a.button.star {
140+
&.star-given {
141+
}
142+
&.disabled {
143+
}
144+
}
145+
</style>
146+
```
147+
148+
</eslint-code-block>
149+
114150
## :books: Further reading
115151

116152
- [vue-scoped-css/require-selector-used-inside]
117153
- [Vue Loader - Scoped CSS]
154+
- [KSS]
118155

119156
[Vue Loader - Scoped CSS]: https://vue-loader.vuejs.org/guide/scoped-css.html
120157
[vue-scoped-css/require-selector-used-inside]: ./require-selector-used-inside.md
158+
[KSS]: http://warpspire.com/kss/
121159

122160
## Implementation
123161

docs/rules/require-selector-used-inside.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,74 @@ div {}
5353
```json
5454
{
5555
"vue-scoped-css/require-selector-used-inside": ["error", {
56-
"ignoreBEMModifier": false
56+
"ignoreBEMModifier": false,
57+
"captureClassesFromDoc": []
5758
}]
5859
}
5960
```
6061

6162
- `ignoreBEMModifier` ... Set `true` if you want to ignore the `BEM` modifier. Default is false.
63+
- `captureClassesFromDoc` ... Specifies the regexp that extracts the class name from the documentation in the comments. Even if there is no matching element, no error is reported if the document of a class name exists in the comments.
64+
65+
### `"ignoreBEMModifier": true`
66+
67+
<eslint-code-block :rules="{'vue-scoped-css/require-selector-used-inside': ['error', {ignoreBEMModifier: true}]}">
68+
69+
```vue
70+
<template>
71+
<div class="cool-component"></div>
72+
</template>
73+
<style scoped>
74+
/* ✓ GOOD */
75+
.cool-component--active {}
76+
</style>
77+
```
78+
79+
</eslint-code-block>
80+
81+
### `"captureClassesFromDoc": [ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i" ]`
82+
83+
Example of [KSS] format:
84+
85+
<eslint-code-block :rules="{'vue-scoped-css/require-selector-used-inside': ['error', {captureClassesFromDoc: ['/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i']}]}">
86+
87+
```vue
88+
<template>
89+
<div>
90+
<a class="button star"></a>
91+
</div>
92+
</template>
93+
<style scoped lang="scss">
94+
/* ✓ GOOD */
95+
96+
// A button suitable for giving a star to someone.
97+
//
98+
// :hover - Subtle hover highlight.
99+
// .star-given - A highlight indicating you've already given a star.
100+
// .star-given:hover - Subtle hover highlight on top of star-given styling.
101+
// .disabled - Dims the button to indicate it cannot be used.
102+
//
103+
// Styleguide 2.1.3.
104+
a.button.star {
105+
&.star-given {
106+
}
107+
&.disabled {
108+
}
109+
}
110+
</style>
111+
```
112+
113+
</eslint-code-block>
62114

63115
## :books: Further reading
64116

65117
- [vue-scoped-css/no-unused-selector]
66118
- [Vue Loader - Scoped CSS]
119+
- [KSS]
67120

68121
[Vue Loader - Scoped CSS]: https://vue-loader.vuejs.org/guide/scoped-css.html
69122
[vue-scoped-css/no-unused-selector]: ./no-unused-selector.md
123+
[KSS]: http://warpspire.com/kss/
70124

71125
## Implementation
72126

lib/options.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
import { toRegExp } from "./utils/regexp"
2+
13
export interface QueryOptions {
24
ignoreBEMModifier?: boolean
5+
captureClassesFromDoc?: string[]
6+
}
7+
8+
export interface ParsedQueryOptions {
9+
ignoreBEMModifier: boolean
10+
captureClassesFromDoc: RegExp[]
11+
}
12+
13+
export namespace ParsedQueryOptions {
14+
/**
15+
* Parse options
16+
*/
17+
export function parse(
18+
options: QueryOptions | undefined,
19+
): ParsedQueryOptions {
20+
const { ignoreBEMModifier, captureClassesFromDoc } = options || {}
21+
22+
return {
23+
ignoreBEMModifier: ignoreBEMModifier ?? false,
24+
captureClassesFromDoc:
25+
captureClassesFromDoc?.map(s => toRegExp(s, "g")) ?? [],
26+
}
27+
}
328
}

lib/rules/no-parsing-error.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import { RuleContext } from "../types"
2+
import { VCSSParsingError } from "../styles/ast"
13
import {
24
getStyleContexts,
35
getCommentDirectivesReporter,
4-
StyleContext,
5-
} from "../styles"
6-
import { RuleContext, LineAndColumnData } from "../types"
7-
import { VCSSParsingError } from "../styles/ast"
6+
InvalidStyleContext,
7+
} from "../styles/context"
88

99
module.exports = {
1010
meta: {
@@ -48,15 +48,7 @@ module.exports = {
4848
* Reports the given style
4949
* @param {ASTNode} node node to report
5050
*/
51-
function reportInvalidStyle(
52-
style: StyleContext & {
53-
invalid: {
54-
message: string
55-
needReport: boolean
56-
loc: LineAndColumnData
57-
}
58-
},
59-
) {
51+
function reportInvalidStyle(style: InvalidStyleContext) {
6052
reporter.report({
6153
node: style.styleElement,
6254
loc: style.invalid.loc,
@@ -72,15 +64,7 @@ module.exports = {
7264
for (const style of styles) {
7365
if (style.invalid != null) {
7466
if (style.invalid.needReport) {
75-
reportInvalidStyle(
76-
style as StyleContext & {
77-
invalid: {
78-
message: string
79-
needReport: boolean
80-
loc: LineAndColumnData
81-
}
82-
},
83-
)
67+
reportInvalidStyle(style)
8468
}
8569
} else {
8670
for (const node of style.cssNode?.errors || []) {

lib/rules/no-unused-keyframes.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { VCSSAtRule, VCSSDeclarationProperty } from "../styles/ast"
2+
import { RuleContext } from "../types"
3+
import { Template } from "../styles/template"
14
import {
25
getStyleContexts,
36
getCommentDirectivesReporter,
7+
ValidStyleContext,
48
StyleContext,
5-
} from "../styles"
6-
import { VCSSAtRule, VCSSDeclarationProperty } from "../styles/ast"
7-
import { RuleContext } from "../types"
8-
import { Template } from "../styles/template"
9+
} from "../styles/context"
910

1011
module.exports = {
1112
meta: {
@@ -24,9 +25,9 @@ module.exports = {
2425
type: "suggestion", // "problem",
2526
},
2627
create(context: RuleContext) {
27-
const styles = getStyleContexts(context).filter(
28-
style => !style.invalid && style.scoped,
29-
)
28+
const styles = getStyleContexts(context)
29+
.filter(StyleContext.isValid)
30+
.filter(style => style.scoped)
3031
if (!styles.length) {
3132
return {}
3233
}
@@ -59,7 +60,7 @@ module.exports = {
5960
* Extract nodes
6061
*/
6162
function extract(
62-
style: StyleContext,
63+
style: ValidStyleContext,
6364
): {
6465
keyframes: { node: VCSSAtRule; params: Template }[]
6566
animationNames: VCSSDeclarationProperty[]
@@ -106,7 +107,7 @@ module.exports = {
106107
/**
107108
* Verify the style
108109
*/
109-
function verify(style: StyleContext) {
110+
function verify(style: ValidStyleContext) {
110111
const { keyframes, animationNames, animations } = extract(style)
111112

112113
for (const decl of animationNames) {

lib/rules/no-unused-selector.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import {
2-
getStyleContexts,
3-
getCommentDirectivesReporter,
4-
StyleContext,
5-
} from "../styles"
6-
71
import { getResolvedSelectors, ResolvedSelector } from "../styles/selectors"
8-
92
import { VCSSSelectorNode, VCSSSelectorCombinator } from "../styles/ast"
103
import {
114
isTypeSelector,
@@ -18,17 +11,23 @@ import {
1811
isGeneralSiblingCombinator,
1912
isDeepCombinator,
2013
} from "../styles/utils/selectors"
21-
2214
import { createQueryContext, QueryContext } from "../styles/selectors/query"
2315
import { isRootElement } from "../styles/selectors/query/elements"
2416
import { RuleContext } from "../types"
17+
import { ParsedQueryOptions } from "../options"
18+
import {
19+
ValidStyleContext,
20+
getStyleContexts,
21+
StyleContext,
22+
getCommentDirectivesReporter,
23+
} from "../styles/context"
2524

2625
/**
2726
* Gets scoped selectors.
2827
* @param {StyleContext} style The style context
2928
* @returns {VCSSSelectorNode[][]} selectors
3029
*/
31-
function getScopedSelectors(style: StyleContext): VCSSSelectorNode[][] {
30+
function getScopedSelectors(style: ValidStyleContext): VCSSSelectorNode[][] {
3231
const resolvedSelectors = getResolvedSelectors(style)
3332
return resolvedSelectors.map(getScopedSelector)
3433
}
@@ -84,16 +83,26 @@ module.exports = {
8483
ignoreBEMModifier: {
8584
type: "boolean",
8685
},
86+
captureClassesFromDoc: {
87+
type: "array",
88+
items: [
89+
{
90+
type: "string",
91+
},
92+
],
93+
minItems: 0,
94+
uniqueItems: true,
95+
},
8796
},
8897
additionalProperties: false,
8998
},
9099
],
91100
type: "suggestion", // "problem",
92101
},
93102
create(context: RuleContext) {
94-
const styles = getStyleContexts(context).filter(
95-
style => !style.invalid && style.scoped,
96-
)
103+
const styles = getStyleContexts(context)
104+
.filter(StyleContext.isValid)
105+
.filter(style => style.scoped)
97106
if (!styles.length) {
98107
return {}
99108
}
@@ -215,7 +224,7 @@ module.exports = {
215224
"Program:exit"() {
216225
const queryContext = createQueryContext(
217226
context,
218-
context.options[0] || {},
227+
ParsedQueryOptions.parse(context.options[0]),
219228
)
220229

221230
for (const style of styles) {

lib/rules/require-scoped.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import { getStyleContexts, getCommentDirectivesReporter } from "../styles"
21
import { RuleContext, AST, TokenStore } from "../types"
2+
import {
3+
getStyleContexts,
4+
StyleContext,
5+
getCommentDirectivesReporter,
6+
} from "../styles/context"
37

48
module.exports = {
59
meta: {
@@ -21,7 +25,7 @@ module.exports = {
2125
type: "suggestion",
2226
},
2327
create(context: RuleContext) {
24-
const styles = getStyleContexts(context).filter(style => !style.invalid)
28+
const styles = getStyleContexts(context).filter(StyleContext.isValid)
2529
if (!styles.length) {
2630
return {}
2731
}

0 commit comments

Comments
 (0)