Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion docs/rules/no-unused-selector.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ This is a limitation of this rule. Without this limitation, the root element can
```json
{
"vue-scoped-css/no-unused-selector": ["error", {
"ignoreBEMModifier": false
"ignoreBEMModifier": false,
"captureClassesFromDoc": []
}]
}
```

- `ignoreBEMModifier` ... Set `true` if you want to ignore the `BEM` modifier. Default is false.
- `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.

### `"ignoreBEMModifier": true`

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

</eslint-code-block>

### `"captureClassesFromDoc": [ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i" ]`

Example of [KSS] format:

<eslint-code-block :rules="{'vue-scoped-css/no-unused-selector': ['error', {captureClassesFromDoc: ['/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i']}]}">

```vue
<template>
<div>
<a class="button star"></a>
</div>
</template>
<style scoped lang="scss">
/* ✓ GOOD */

// A button suitable for giving a star to someone.
//
// :hover - Subtle hover highlight.
// .star-given - A highlight indicating you've already given a star.
// .star-given:hover - Subtle hover highlight on top of star-given styling.
// .disabled - Dims the button to indicate it cannot be used.
//
// Styleguide 2.1.3.
a.button.star {
&.star-given {
}
&.disabled {
}
}
</style>
```

</eslint-code-block>

## :books: Further reading

- [vue-scoped-css/require-selector-used-inside]
- [Vue Loader - Scoped CSS]
- [KSS]

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

## Implementation

Expand Down
56 changes: 55 additions & 1 deletion docs/rules/require-selector-used-inside.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,74 @@ div {}
```json
{
"vue-scoped-css/require-selector-used-inside": ["error", {
"ignoreBEMModifier": false
"ignoreBEMModifier": false,
"captureClassesFromDoc": []
}]
}
```

- `ignoreBEMModifier` ... Set `true` if you want to ignore the `BEM` modifier. Default is false.
- `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.

### `"ignoreBEMModifier": true`

<eslint-code-block :rules="{'vue-scoped-css/require-selector-used-inside': ['error', {ignoreBEMModifier: true}]}">

```vue
<template>
<div class="cool-component"></div>
</template>
<style scoped>
/* ✓ GOOD */
.cool-component--active {}
</style>
```

</eslint-code-block>

### `"captureClassesFromDoc": [ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i" ]`

Example of [KSS] format:

<eslint-code-block :rules="{'vue-scoped-css/require-selector-used-inside': ['error', {captureClassesFromDoc: ['/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i']}]}">

```vue
<template>
<div>
<a class="button star"></a>
</div>
</template>
<style scoped lang="scss">
/* ✓ GOOD */

// A button suitable for giving a star to someone.
//
// :hover - Subtle hover highlight.
// .star-given - A highlight indicating you've already given a star.
// .star-given:hover - Subtle hover highlight on top of star-given styling.
// .disabled - Dims the button to indicate it cannot be used.
//
// Styleguide 2.1.3.
a.button.star {
&.star-given {
}
&.disabled {
}
}
</style>
```

</eslint-code-block>

## :books: Further reading

- [vue-scoped-css/no-unused-selector]
- [Vue Loader - Scoped CSS]
- [KSS]

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

## Implementation

Expand Down
25 changes: 25 additions & 0 deletions lib/options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
import { toRegExp } from "./utils/regexp"

export interface QueryOptions {
ignoreBEMModifier?: boolean
captureClassesFromDoc?: string[]
}

export interface ParsedQueryOptions {
ignoreBEMModifier: boolean
captureClassesFromDoc: RegExp[]
}

export namespace ParsedQueryOptions {
/**
* Parse options
*/
export function parse(
options: QueryOptions | undefined,
): ParsedQueryOptions {
const { ignoreBEMModifier, captureClassesFromDoc } = options || {}

return {
ignoreBEMModifier: ignoreBEMModifier ?? false,
captureClassesFromDoc:
captureClassesFromDoc?.map(s => toRegExp(s, "g")) ?? [],
}
}
}
28 changes: 6 additions & 22 deletions lib/rules/no-parsing-error.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { RuleContext } from "../types"
import { VCSSParsingError } from "../styles/ast"
import {
getStyleContexts,
getCommentDirectivesReporter,
StyleContext,
} from "../styles"
import { RuleContext, LineAndColumnData } from "../types"
import { VCSSParsingError } from "../styles/ast"
InvalidStyleContext,
} from "../styles/context"

module.exports = {
meta: {
Expand Down Expand Up @@ -48,15 +48,7 @@ module.exports = {
* Reports the given style
* @param {ASTNode} node node to report
*/
function reportInvalidStyle(
style: StyleContext & {
invalid: {
message: string
needReport: boolean
loc: LineAndColumnData
}
},
) {
function reportInvalidStyle(style: InvalidStyleContext) {
reporter.report({
node: style.styleElement,
loc: style.invalid.loc,
Expand All @@ -72,15 +64,7 @@ module.exports = {
for (const style of styles) {
if (style.invalid != null) {
if (style.invalid.needReport) {
reportInvalidStyle(
style as StyleContext & {
invalid: {
message: string
needReport: boolean
loc: LineAndColumnData
}
},
)
reportInvalidStyle(style)
}
} else {
for (const node of style.cssNode?.errors || []) {
Expand Down
19 changes: 10 additions & 9 deletions lib/rules/no-unused-keyframes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { VCSSAtRule, VCSSDeclarationProperty } from "../styles/ast"
import { RuleContext } from "../types"
import { Template } from "../styles/template"
import {
getStyleContexts,
getCommentDirectivesReporter,
ValidStyleContext,
StyleContext,
} from "../styles"
import { VCSSAtRule, VCSSDeclarationProperty } from "../styles/ast"
import { RuleContext } from "../types"
import { Template } from "../styles/template"
} from "../styles/context"

module.exports = {
meta: {
Expand All @@ -24,9 +25,9 @@ module.exports = {
type: "suggestion", // "problem",
},
create(context: RuleContext) {
const styles = getStyleContexts(context).filter(
style => !style.invalid && style.scoped,
)
const styles = getStyleContexts(context)
.filter(StyleContext.isValid)
.filter(style => style.scoped)
if (!styles.length) {
return {}
}
Expand Down Expand Up @@ -59,7 +60,7 @@ module.exports = {
* Extract nodes
*/
function extract(
style: StyleContext,
style: ValidStyleContext,
): {
keyframes: { node: VCSSAtRule; params: Template }[]
animationNames: VCSSDeclarationProperty[]
Expand Down Expand Up @@ -106,7 +107,7 @@ module.exports = {
/**
* Verify the style
*/
function verify(style: StyleContext) {
function verify(style: ValidStyleContext) {
const { keyframes, animationNames, animations } = extract(style)

for (const decl of animationNames) {
Expand Down
35 changes: 22 additions & 13 deletions lib/rules/no-unused-selector.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
getStyleContexts,
getCommentDirectivesReporter,
StyleContext,
} from "../styles"

import { getResolvedSelectors, ResolvedSelector } from "../styles/selectors"

import { VCSSSelectorNode, VCSSSelectorCombinator } from "../styles/ast"
import {
isTypeSelector,
Expand All @@ -18,17 +11,23 @@ import {
isGeneralSiblingCombinator,
isDeepCombinator,
} from "../styles/utils/selectors"

import { createQueryContext, QueryContext } from "../styles/selectors/query"
import { isRootElement } from "../styles/selectors/query/elements"
import { RuleContext } from "../types"
import { ParsedQueryOptions } from "../options"
import {
ValidStyleContext,
getStyleContexts,
StyleContext,
getCommentDirectivesReporter,
} from "../styles/context"

/**
* Gets scoped selectors.
* @param {StyleContext} style The style context
* @returns {VCSSSelectorNode[][]} selectors
*/
function getScopedSelectors(style: StyleContext): VCSSSelectorNode[][] {
function getScopedSelectors(style: ValidStyleContext): VCSSSelectorNode[][] {
const resolvedSelectors = getResolvedSelectors(style)
return resolvedSelectors.map(getScopedSelector)
}
Expand Down Expand Up @@ -84,16 +83,26 @@ module.exports = {
ignoreBEMModifier: {
type: "boolean",
},
captureClassesFromDoc: {
type: "array",
items: [
{
type: "string",
},
],
minItems: 0,
uniqueItems: true,
},
},
additionalProperties: false,
},
],
type: "suggestion", // "problem",
},
create(context: RuleContext) {
const styles = getStyleContexts(context).filter(
style => !style.invalid && style.scoped,
)
const styles = getStyleContexts(context)
.filter(StyleContext.isValid)
.filter(style => style.scoped)
if (!styles.length) {
return {}
}
Expand Down Expand Up @@ -215,7 +224,7 @@ module.exports = {
"Program:exit"() {
const queryContext = createQueryContext(
context,
context.options[0] || {},
ParsedQueryOptions.parse(context.options[0]),
)

for (const style of styles) {
Expand Down
8 changes: 6 additions & 2 deletions lib/rules/require-scoped.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { getStyleContexts, getCommentDirectivesReporter } from "../styles"
import { RuleContext, AST, TokenStore } from "../types"
import {
getStyleContexts,
StyleContext,
getCommentDirectivesReporter,
} from "../styles/context"

module.exports = {
meta: {
Expand All @@ -21,7 +25,7 @@ module.exports = {
type: "suggestion",
},
create(context: RuleContext) {
const styles = getStyleContexts(context).filter(style => !style.invalid)
const styles = getStyleContexts(context).filter(StyleContext.isValid)
if (!styles.length) {
return {}
}
Expand Down
Loading