diff --git a/src/rules/selector-no-union-class-name/__tests__/index.js b/src/rules/selector-no-union-class-name/__tests__/index.js index 5a644f3f..97e81039 100644 --- a/src/rules/selector-no-union-class-name/__tests__/index.js +++ b/src/rules/selector-no-union-class-name/__tests__/index.js @@ -95,6 +95,20 @@ testRule(rule, { } `, description: "ignores an ampersand chained with an attribute" + }, + { + code: ` + @mixin demo() { + @content; + } + .a-selector { + @include demo() { + button:hover & { + background:purple + } + } + }`, + description: "should not throw an error when using nesting (issue #345)" } ], diff --git a/src/rules/selector-no-union-class-name/index.js b/src/rules/selector-no-union-class-name/index.js index c59981b1..4555c71b 100644 --- a/src/rules/selector-no-union-class-name/index.js +++ b/src/rules/selector-no-union-class-name/index.js @@ -35,9 +35,15 @@ export default function(actual) { root.walkRules(/&/, rule => { const parentNodes = []; - parseSelector(rule.parent.selector, result, rule, fullSelector => { - fullSelector.walk(node => parentNodes.push(node)); - }); + const selector = getSelectorFromRule(rule.parent); + + if (selector) { + parseSelector(selector, result, rule, fullSelector => { + fullSelector.walk(node => parentNodes.push(node)); + }); + } + + if (parentNodes.length === 0) return; const lastParentNode = parentNodes[parentNodes.length - 1]; @@ -63,3 +69,21 @@ export default function(actual) { }); }; } + +/** + * Searches for the closest rule which + * has a selector and returns the selector + * @returns {string|undefined} + */ +function getSelectorFromRule(rule) { + // All non at-rules have their own selector + if (rule.selector !== undefined) { + return rule.selector; + } + + // At-rules like @mixin don't have a selector themself + // but their parents might have one + if (rule.parent) { + return getSelectorFromRule(rule.parent); + } +}