diff --git a/docs/rules/no-unused-selector.md b/docs/rules/no-unused-selector.md
index d732e239..5df01d0f 100644
--- a/docs/rules/no-unused-selector.md
+++ b/docs/rules/no-unused-selector.md
@@ -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`
@@ -111,13 +113,49 @@ This is a limitation of this rule. Without this limitation, the root element can
+### `"captureClassesFromDoc": [ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i" ]`
+
+Example of [KSS] format:
+
+
+
+```vue
+
+
+
+
+```
+
+
+
## :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
diff --git a/docs/rules/require-selector-used-inside.md b/docs/rules/require-selector-used-inside.md
index 877cf6b8..fc8dab68 100644
--- a/docs/rules/require-selector-used-inside.md
+++ b/docs/rules/require-selector-used-inside.md
@@ -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`
+
+
+
+```vue
+
+
+
+
+```
+
+
+
+### `"captureClassesFromDoc": [ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i" ]`
+
+Example of [KSS] format:
+
+
+
+```vue
+
+
+
+
+```
+
+
## :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
diff --git a/lib/options.ts b/lib/options.ts
index 93e2fbab..20d27254 100644
--- a/lib/options.ts
+++ b/lib/options.ts
@@ -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")) ?? [],
+ }
+ }
}
diff --git a/lib/rules/no-parsing-error.ts b/lib/rules/no-parsing-error.ts
index d55af1ad..1b79d029 100644
--- a/lib/rules/no-parsing-error.ts
+++ b/lib/rules/no-parsing-error.ts
@@ -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: {
@@ -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,
@@ -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 || []) {
diff --git a/lib/rules/no-unused-keyframes.ts b/lib/rules/no-unused-keyframes.ts
index 0d8dba42..f893121e 100644
--- a/lib/rules/no-unused-keyframes.ts
+++ b/lib/rules/no-unused-keyframes.ts
@@ -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: {
@@ -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 {}
}
@@ -59,7 +60,7 @@ module.exports = {
* Extract nodes
*/
function extract(
- style: StyleContext,
+ style: ValidStyleContext,
): {
keyframes: { node: VCSSAtRule; params: Template }[]
animationNames: VCSSDeclarationProperty[]
@@ -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) {
diff --git a/lib/rules/no-unused-selector.ts b/lib/rules/no-unused-selector.ts
index 15384f1d..19440fac 100644
--- a/lib/rules/no-unused-selector.ts
+++ b/lib/rules/no-unused-selector.ts
@@ -1,11 +1,4 @@
-import {
- getStyleContexts,
- getCommentDirectivesReporter,
- StyleContext,
-} from "../styles"
-
import { getResolvedSelectors, ResolvedSelector } from "../styles/selectors"
-
import { VCSSSelectorNode, VCSSSelectorCombinator } from "../styles/ast"
import {
isTypeSelector,
@@ -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)
}
@@ -84,6 +83,16 @@ module.exports = {
ignoreBEMModifier: {
type: "boolean",
},
+ captureClassesFromDoc: {
+ type: "array",
+ items: [
+ {
+ type: "string",
+ },
+ ],
+ minItems: 0,
+ uniqueItems: true,
+ },
},
additionalProperties: false,
},
@@ -91,9 +100,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 {}
}
@@ -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) {
diff --git a/lib/rules/require-scoped.ts b/lib/rules/require-scoped.ts
index b39e6ced..6f0d9867 100644
--- a/lib/rules/require-scoped.ts
+++ b/lib/rules/require-scoped.ts
@@ -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: {
@@ -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 {}
}
diff --git a/lib/rules/require-selector-used-inside.ts b/lib/rules/require-selector-used-inside.ts
index b6422043..f17b34c3 100644
--- a/lib/rules/require-selector-used-inside.ts
+++ b/lib/rules/require-selector-used-inside.ts
@@ -1,9 +1,3 @@
-import {
- getStyleContexts,
- getCommentDirectivesReporter,
- StyleContext,
-} from "../styles"
-
import { getResolvedSelectors, ResolvedSelector } from "../styles/selectors"
import {
isTypeSelector,
@@ -13,17 +7,23 @@ import {
isSelectorCombinator,
isDeepCombinator,
} from "../styles/utils/selectors"
-
import { createQueryContext, QueryContext } from "../styles/selectors/query"
import { VCSSSelectorNode } from "../styles/ast"
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)
}
@@ -62,6 +62,16 @@ module.exports = {
ignoreBEMModifier: {
type: "boolean",
},
+ captureClassesFromDoc: {
+ type: "array",
+ items: [
+ {
+ type: "string",
+ },
+ ],
+ minItems: 0,
+ uniqueItems: true,
+ },
},
additionalProperties: false,
},
@@ -69,9 +79,9 @@ module.exports = {
type: "suggestion",
},
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 {}
}
@@ -137,7 +147,7 @@ module.exports = {
"Program:exit"() {
const queryContext = createQueryContext(
context,
- context.options[0] || {},
+ ParsedQueryOptions.parse(context.options[0]),
)
for (const style of styles) {
diff --git a/lib/styles/context/index.ts b/lib/styles/context/index.ts
index 26095786..9457ee96 100644
--- a/lib/styles/context/index.ts
+++ b/lib/styles/context/index.ts
@@ -1,4 +1,9 @@
-import { createStyleContexts, StyleContext } from "./style"
+import {
+ createStyleContexts,
+ StyleContext,
+ ValidStyleContext,
+ InvalidStyleContext,
+} from "./style"
import {
CommentDirectivesReporter,
createCommentDirectivesReporter,
@@ -76,7 +81,13 @@ export function getVueComponentContext(
}
return (cache.vueComponent = createVueComponentContext(context))
}
-export { StyleContext, CommentDirectivesReporter, VueComponentContext }
+export {
+ StyleContext,
+ ValidStyleContext,
+ InvalidStyleContext,
+ CommentDirectivesReporter,
+ VueComponentContext,
+}
/**
* Gets the comment directive context from given rule context.
diff --git a/lib/styles/context/style/index.ts b/lib/styles/context/style/index.ts
index 6f4477a9..80c5b385 100644
--- a/lib/styles/context/style/index.ts
+++ b/lib/styles/context/style/index.ts
@@ -110,10 +110,43 @@ interface Visitor {
leaveNode(node: VCSSNode): void
}
+interface BaseStyleContext {
+ readonly styleElement: AST.VElement
+ readonly sourceCode: SourceCode
+ readonly scoped: boolean
+ readonly lang: string
+ traverseNodes(visitor: Visitor): void
+}
+
+export interface ValidStyleContext extends BaseStyleContext {
+ readonly invalid: null
+ readonly cssNode: VCSSStyleSheet
+}
+export interface InvalidStyleContext extends BaseStyleContext {
+ readonly invalid: {
+ message: string
+ needReport: boolean
+ loc: LineAndColumnData
+ }
+ readonly cssNode: null
+}
+
+export type StyleContext = InvalidStyleContext | ValidStyleContext
+export namespace StyleContext {
+ /**
+ * Checks whether the given context is valid
+ */
+ export function isValid(
+ context: StyleContext,
+ ): context is ValidStyleContext {
+ return !context.invalid
+ }
+}
+
/**
* Style context
*/
-export class StyleContext {
+export class StyleContextImpl {
public readonly styleElement: AST.VElement
public readonly sourceCode: SourceCode
public readonly invalid: {
@@ -209,7 +242,9 @@ function traverseNodes(node: VCSSNode, visitor: Visitor): void {
export function createStyleContexts(context: RuleContext): StyleContext[] {
const styles = getStyleElements(context)
- return styles.map(style => new StyleContext(style, context))
+ return styles.map(
+ style => new StyleContextImpl(style, context) as StyleContext,
+ )
}
/**
diff --git a/lib/styles/index.ts b/lib/styles/index.ts
deleted file mode 100644
index eff808c2..00000000
--- a/lib/styles/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import {
- getStyleContexts,
- getCommentDirectivesReporter,
- StyleContext,
- CommentDirectivesReporter,
-} from "./context"
-
-export {
- StyleContext,
- CommentDirectivesReporter,
- /**
- * Gets the style contexts
- * @param {RuleContext} context ESLint rule context
- * @returns {StyleContext[]} the style contexts
- */
- getStyleContexts,
- /**
- * Gets the comment directive reporter
- * @param {RuleContext} context ESLint rule context
- * @returns {CommentDirectivesReporter} the comment directives
- */
- getCommentDirectivesReporter,
-}
diff --git a/lib/styles/selectors/index.ts b/lib/styles/selectors/index.ts
index 66e2e1f9..41fcc72d 100644
--- a/lib/styles/selectors/index.ts
+++ b/lib/styles/selectors/index.ts
@@ -1,5 +1,4 @@
-import { StyleContext } from "../context"
-
+import { ValidStyleContext } from "../context"
import {
CSSSelectorResolver,
ResolvedSelector,
@@ -20,10 +19,9 @@ const RESOLVERS = {
* @param {StyleContext} style The style context
* @returns {ResolvedSelectors[]} the selector that resolved the nesting.
*/
-export function getResolvedSelectors(style: StyleContext): ResolvedSelector[] {
- if (!style.cssNode) {
- return []
- }
+export function getResolvedSelectors(
+ style: ValidStyleContext,
+): ResolvedSelector[] {
const lang = style.lang
const Resolver = isSupportedStyleLang(lang)
? RESOLVERS[lang]
diff --git a/lib/styles/selectors/query/index.ts b/lib/styles/selectors/query/index.ts
index 40a1601b..f32be3e1 100644
--- a/lib/styles/selectors/query/index.ts
+++ b/lib/styles/selectors/query/index.ts
@@ -22,13 +22,18 @@ import {
} from "./elements"
import { VCSSSelectorNode } from "../../ast"
import { AST, RuleContext, ASTNode } from "../../../types"
-import { QueryOptions } from "../../../options"
+import { ParsedQueryOptions } from "../../../options"
import {
getAttributeValueNodes,
getReferenceExpressions,
ReferenceExpressions,
} from "./attribute-tracker"
-import { getVueComponentContext } from "../../context"
+import {
+ getVueComponentContext,
+ getStyleContexts,
+ StyleContext,
+ ValidStyleContext,
+} from "../../context"
import { getStringFromNode } from "../../utils/nodes"
import { Template } from "../../template"
@@ -48,6 +53,7 @@ const TRANSITION_GROUP_CLASS_BASES = [...TRANSITION_CLASS_BASES, "move"]
export class QueryContext {
public elements: AST.VElement[] = []
protected readonly document: VueDocumentQueryContext
+
protected constructor(document?: VueDocumentQueryContext) {
this.document = document || (this as any)
}
@@ -107,8 +113,9 @@ export class QueryContext {
*/
class VueDocumentQueryContext extends QueryContext {
public context: RuleContext
- public options: QueryOptions
- public constructor(context: RuleContext, options: QueryOptions) {
+ public options: ParsedQueryOptions
+ public docsModifiers: string[]
+ public constructor(context: RuleContext, options: ParsedQueryOptions) {
super()
const sourceCode = context.getSourceCode()
const { ast } = sourceCode
@@ -117,9 +124,48 @@ class VueDocumentQueryContext extends QueryContext {
: []
this.context = context
this.options = options
+
+ if (options.captureClassesFromDoc.length > 0) {
+ this.docsModifiers = getStyleContexts(context)
+ .filter(StyleContext.isValid)
+ .filter(style => style.scoped)
+ .map(style =>
+ extractClassesFromDoc(style, options.captureClassesFromDoc),
+ )
+ .reduce((r, a) => r.concat(a), [])
+ } else {
+ this.docsModifiers = []
+ }
}
}
+/**
+ * Extract class names documented in the comment.
+ */
+function extractClassesFromDoc(
+ style: ValidStyleContext,
+ captureClassesFromDoc: RegExp[],
+): string[] {
+ const results = new Set()
+ for (const comment of style.cssNode.comments) {
+ for (const regexp of captureClassesFromDoc) {
+ // Get all captures
+ regexp.lastIndex = 0
+ let re
+ while ((re = regexp.exec(comment.text))) {
+ if (re.length > 1) {
+ for (const s of re.slice(1)) {
+ results.add(s)
+ }
+ } else {
+ results.add(re[0])
+ }
+ }
+ }
+ }
+ return [...results]
+}
+
/**
* QueryContext as elements.
*/
@@ -141,7 +187,7 @@ class ElementsQueryContext extends QueryContext {
*/
export function createQueryContext(
context: RuleContext,
- options: QueryOptions = {},
+ options: ParsedQueryOptions,
): QueryContext {
return new VueDocumentQueryContext(context, options)
}
@@ -436,6 +482,8 @@ function* genElementsByClassName(
document: VueDocumentQueryContext,
): IterableIterator {
let removeModifierClassName = null
+
+ // ignoreBEMModifier option
if (document.options.ignoreBEMModifier) {
if (className.hasString("--")) {
const list = className.divide("--")
@@ -446,6 +494,21 @@ function* genElementsByClassName(
}
}
+ // captureClassesFromDoc option
+ for (const docMod of document.docsModifiers) {
+ if (docMod.startsWith(":")) {
+ continue
+ }
+ const modClassName: string = docMod.startsWith(".")
+ ? docMod.slice(1)
+ : docMod
+ if (className.matchString(modClassName)) {
+ // If the class name is documented, it is considered to match all elements.
+ yield* elements
+ return
+ }
+ }
+
for (const element of elements) {
if (matchClassName(element, className, document)) {
yield element
diff --git a/lib/utils/regexp.ts b/lib/utils/regexp.ts
new file mode 100644
index 00000000..50ae1515
--- /dev/null
+++ b/lib/utils/regexp.ts
@@ -0,0 +1,24 @@
+const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u
+
+/**
+ * Convert a string to the `RegExp`.
+ * Normal strings (e.g. `"foo"`) is converted to `/foo/` of `RegExp`.
+ * Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`.
+ *
+ * @param {string} string The string to convert.
+ * @returns {RegExp} Returns the `RegExp`.
+ */
+export function toRegExp(string: string, flags?: string): RegExp {
+ const parts = RE_REGEXP_STR.exec(string)
+ if (parts) {
+ let flagArgs: string
+ if (flags) {
+ flagArgs = [...new Set(parts[2] + flags)].join("")
+ } else {
+ flagArgs = parts[2]
+ }
+
+ return new RegExp(parts[1], flagArgs)
+ }
+ return new RegExp(string, flags)
+}
diff --git a/tests/lib/rules/no-unused-selector.ts b/tests/lib/rules/no-unused-selector.ts
index 9c78971d..8bc40df7 100644
--- a/tests/lib/rules/no-unused-selector.ts
+++ b/tests/lib/rules/no-unused-selector.ts
@@ -267,6 +267,40 @@ tester.run("no-unused-selector", rule, {
`,
options: [{ ignoreBEMModifier: true }],
},
+ // captureClassesFromDoc
+ {
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ captureClassesFromDoc: [
+ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i",
+ ],
+ },
+ ],
+ },
// ignore nodes
`
@@ -561,6 +595,51 @@ tester.run("no-unused-selector", rule, {
},
],
},
+ // captureClassesFromDoc
+ {
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ captureClassesFromDoc: [
+ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i",
+ ],
+ },
+ ],
+ errors: [
+ {
+ message: "The selector `a.button.star.unknown` is unused.",
+ line: 18,
+ column: 13,
+ endLine: 23,
+ endColumn: 26,
+ },
+ ],
+ },
// deep
{
diff --git a/tests/lib/rules/require-selector-used-inside.ts b/tests/lib/rules/require-selector-used-inside.ts
index 5f41d6d8..eeca51e0 100644
--- a/tests/lib/rules/require-selector-used-inside.ts
+++ b/tests/lib/rules/require-selector-used-inside.ts
@@ -228,6 +228,40 @@ tester.run("require-selector-used-inside", rule, {
`,
options: [{ ignoreBEMModifier: true }],
},
+ // captureClassesFromDoc
+ {
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ captureClassesFromDoc: [
+ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i",
+ ],
+ },
+ ],
+ },
// ignore nodes
`
@@ -482,6 +516,52 @@ tester.run("require-selector-used-inside", rule, {
},
],
},
+ // captureClassesFromDoc
+ {
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ captureClassesFromDoc: [
+ "/(\\.[a-z-]+)(?::[a-z-]+)?\\s+-\\s*[^\\r\\n]+/i",
+ ],
+ },
+ ],
+ errors: [
+ {
+ message:
+ "The selector `a.button.star.unknown` is unused in the template.",
+ line: 18,
+ column: 13,
+ endLine: 23,
+ endColumn: 26,
+ },
+ ],
+ },
// deep
{
code: `
diff --git a/tests/lib/styles/selectors/index.ts b/tests/lib/styles/selectors/index.ts
index d4adb9e6..6e75f3d1 100644
--- a/tests/lib/styles/selectors/index.ts
+++ b/tests/lib/styles/selectors/index.ts
@@ -5,6 +5,7 @@ import path from "path"
import { getStyleFixtureResults, writeFixture } from "../test-utils"
import { getResolvedSelectors } from "../../../../lib/styles/selectors"
+import { ValidStyleContext } from "../../../../lib/styles/context"
/**
* Remove `parent` proeprties from the given AST.
@@ -26,7 +27,9 @@ describe("CSS Selectors Test.", () => {
const resultPath = path.join(dir, "selectors.json")
const actual = JSON.stringify(
- getResolvedSelectors(style).map(r => r.selector),
+ getResolvedSelectors(style as ValidStyleContext).map(
+ r => r.selector,
+ ),
replacer,
4,
)
@@ -44,7 +47,7 @@ describe("CSS Selectors Test.", () => {
const resultPath = path.join(dir, "selectors-text.json")
const actual = JSON.stringify(
- getResolvedSelectors(style).map(r =>
+ getResolvedSelectors(style as ValidStyleContext).map(r =>
r.selector.map(s => s.selector),
),
replacer,
diff --git a/tests/lib/styles/selectors/query.ts b/tests/lib/styles/selectors/query.ts
index 92967b92..f200ed24 100644
--- a/tests/lib/styles/selectors/query.ts
+++ b/tests/lib/styles/selectors/query.ts
@@ -17,7 +17,8 @@ import {
isVDirectiveKeyV6,
isVDirective,
} from "../../../../lib/styles/utils/nodes"
-import { StyleContext } from "../../../../lib/styles"
+import { StyleContext, ValidStyleContext } from "../../../../lib/styles/context"
+import { ParsedQueryOptions } from "../../../../lib/options"
const ROOT = path.join(__dirname, "../fixtures/selectors/query")
@@ -35,11 +36,11 @@ function replacer(key: string, value: any): any {
}
function queries(style: StyleContext, context: RuleContext) {
- const selectors = getResolvedSelectors(style)
+ const selectors = getResolvedSelectors(style as ValidStyleContext)
return selectors
.map(r => r.selector)
.reduce((result, selector) => {
- let q = createQueryContext(context)
+ let q = createQueryContext(context, ParsedQueryOptions.parse({}))
for (const node of selector) {
q = q.queryStep(node)
}
@@ -53,11 +54,14 @@ function queries(style: StyleContext, context: RuleContext) {
}
function reverseQueries(style: StyleContext, context: RuleContext) {
- const selectors = getResolvedSelectors(style)
+ const selectors = getResolvedSelectors(style as ValidStyleContext)
return selectors
.map(r => r.selector)
.reduce((result, selector) => {
- const elementQueris = createQueryContext(context).split()
+ const elementQueris = createQueryContext(
+ context,
+ ParsedQueryOptions.parse({}),
+ ).split()
const elementsTexts = elementQueris
.filter(elementQuery => {
let q = elementQuery
diff --git a/tests/lib/styles/test-utils.ts b/tests/lib/styles/test-utils.ts
index c24a9173..1e634126 100644
--- a/tests/lib/styles/test-utils.ts
+++ b/tests/lib/styles/test-utils.ts
@@ -3,8 +3,8 @@ import eslint from "eslint"
import fs from "fs"
import path from "path"
-import { getStyleContexts, StyleContext } from "../../../lib/styles"
import { RuleContext } from "../../../lib/types"
+import { StyleContext, getStyleContexts } from "../../../lib/styles/context"
const ROOT = path.join(__dirname, "./fixtures/index")
diff --git a/tools/render-rules.ts b/tools/render-rules.ts
index 2dbc54db..29cbc28f 100644
--- a/tools/render-rules.ts
+++ b/tools/render-rules.ts
@@ -19,7 +19,7 @@ export default function renderRulesTableContent(
rule.meta.deprecated ? ":warning:" : ""
}`
const link = `[${rule.meta.docs.ruleId}](${buildRulePath(
- rule.meta.docs.ruleName,
+ rule.meta.docs.ruleName || "",
)})`
const description = rule.meta.docs.description || "(no description)"
@@ -29,7 +29,7 @@ export default function renderRulesTableContent(
//eslint-disable-next-line require-jsdoc
function toDeprecatedRuleRow(rule: Rule) {
const link = `[${rule.meta.docs.ruleId}](${buildRulePath(
- rule.meta.docs.ruleName,
+ rule.meta.docs.ruleName || "",
)})`
const replacedRules = rule.meta.docs.replacedBy || []
const replacedBy = replacedRules
diff --git a/tools/update-rules.ts b/tools/update-rules.ts
index 4fa1ba8f..8229b2be 100644
--- a/tools/update-rules.ts
+++ b/tools/update-rules.ts
@@ -39,7 +39,7 @@ export function collectRules(category?: string): { [key: string]: string } {
(!category || rule.meta.docs.category === category) &&
!rule.meta.deprecated
) {
- obj[rule.meta.docs.ruleId] = rule.meta.docs.default || "error"
+ obj[rule.meta.docs.ruleId || ""] = rule.meta.docs.default || "error"
}
return obj
}, {} as { [key: string]: string })