diff --git a/src/rules/selector-nest-combinators/__tests__/index.js b/src/rules/selector-nest-combinators/__tests__/index.js index 46d25180..b92bd03b 100644 --- a/src/rules/selector-nest-combinators/__tests__/index.js +++ b/src/rules/selector-nest-combinators/__tests__/index.js @@ -1,4 +1,4 @@ -import rule, { ruleName, messages } from ".."; +import rule, { messages, ruleName } from ".."; testRule(rule, { ruleName, @@ -86,6 +86,16 @@ testRule(rule, { :not([class]:last-child) {} `, description: "when selectors are chained within a not selector" + }, + { + code: ` + .class-name { + #{if(&, "&", "")} { + + } + } + `, + description: "should support interpolation" } ], @@ -144,6 +154,15 @@ testRule(rule, { messages: messages.expected(":last-child", "pseudo"), line: 2, column: 25 + }, + { + code: ` + .class-name #{if(&, "&", "")} {} + `, + description: "when interpolation is used", + messages: messages.expectedInterpolation, + line: 2, + column: 18 } ] }); @@ -215,6 +234,12 @@ testRule(rule, { #foo:not([class]:last-child) {} `, description: "when using a not selector" + }, + { + code: ` + .class-name #{if(&, "&", "")} {} + `, + description: "should support interpolation" } ], @@ -288,6 +313,19 @@ testRule(rule, { messages: messages.rejected, line: 3, column: 9 + }, + { + code: ` + .class-name { + #{if(&, "&", "")} { + + } + } + `, + description: "when interpolation is used", + messages: messages.rejected, + line: 3, + column: 8 } ] }); diff --git a/src/rules/selector-nest-combinators/index.js b/src/rules/selector-nest-combinators/index.js index 65470817..c3ac73d1 100644 --- a/src/rules/selector-nest-combinators/index.js +++ b/src/rules/selector-nest-combinators/index.js @@ -4,9 +4,10 @@ import { namespace, parseSelector } from "../../utils"; export const ruleName = namespace("selector-nest-combinators"); export const messages = utils.ruleMessages(ruleName, { + expectedInterpolation: `Expected interpolation to be in a nested form`, expected: (combinator, type) => `Expected combinator "${combinator}" of type "${type}" to be in a nested form`, - rejected: () => `Unexpected nesting found in selector` + rejected: `Unexpected nesting found in selector` }); export default function(expectation) { @@ -20,21 +21,39 @@ export default function(expectation) { return; } + function precedesParentSelector(current) { + do { + current = current.next(); + + if (current.type === "nesting") { + return true; + } + } while (current.next()); + + return false; + } + + // attribute, class, combinator, comment, id, nesting, pseudo, root, selector, string, tag, or universal + const chainingTypes = [ + "attribute", + "class", + "id", + "pseudo", + "tag", + "universal" + ]; + + const interpolationRe = /#{.+?}$/; + root.walkRules(rule => { parseSelector(rule.selector, result, rule, fullSelector => { - // attribute, class, combinator, comment, id, nesting, pseudo, root, selector, string, tag, or universal - const chainingTypes = [ - "attribute", - "class", - "id", - "pseudo", - "tag", - "universal" - ]; - let message; fullSelector.walk(node => { + if (node.value === "}") { + return; + } + if (expectation === "always") { if (node.type === "selector") { return; @@ -81,7 +100,17 @@ export default function(expectation) { return; } - message = messages.expected(node.value, node.type); + const hasInterpolation = interpolationRe.test(rule.selector); + + if (node.type !== "combinator" && hasInterpolation) { + return; + } + + if (hasInterpolation) { + message = messages.expectedInterpolation; + } else { + message = messages.expected(node.value, node.type); + } } if (expectation === "never") { @@ -99,18 +128,6 @@ export default function(expectation) { message, index: node.sourceIndex }); - - function precedesParentSelector(current) { - do { - current = current.next(); - - if (current.type === "nesting") { - return true; - } - } while (current.next()); - - return false; - } }); }); });