diff --git a/lib/generate-descendant-pieces-from-selector.js b/lib/generate-descendant-pieces-from-selector.js index 42ddcdf..1524578 100644 --- a/lib/generate-descendant-pieces-from-selector.js +++ b/lib/generate-descendant-pieces-from-selector.js @@ -4,6 +4,21 @@ // We could almost use `/\b\s(?![><+~][\s]+?)/` to split the selector but this doesn't work with attribute selectors var RE_SELECTOR_DESCENDANT_SPLIT = (/(.*?(?:(?:\([^\)]+\)|\[[^\]]+\]|(?![><+~\s]).)+)(?:(?:(?:\s(?!>>))|(?:\t(?!>>))|(?:\s?>>\s?))(?!\s+))(?![><+~][\s]+?))/); +// Separate selector to classes and ids +var RE_SELECTOR_SEPARATOR = /(([\.#]?)[^\.#]+)/g; + +// Helper function to get all combination of a text array +var getCombinations = function(strings) { + var result = []; + var f = function(prefix, strings) { + for (var i = 0; i < strings.length; i++) { + result.push(prefix + strings[i]); + f(prefix + strings[i], strings.slice(i + 1)); + } + } + f('', strings); + return result; +} var generateDescendantPiecesFromSelector = function(selector) { return selector.split(RE_SELECTOR_DESCENDANT_SPLIT) @@ -17,7 +32,19 @@ var generateDescendantPiecesFromSelector = function(selector) { // Trim whitespace which would be a normal descendant selector // and trim off the CSS4 descendant `>>` into a normal descendant selector return piece.trim().replace(/\s*?>>\s*?/g, ''); - }); + }) + .reduce(function(result, piece) { + if (piece.indexOf(' ') !== -1) { + result.push(piece); + return result; + } + // a.b#c => [a, .b, #c] + var pieces = piece.match(RE_SELECTOR_SEPARATOR); + // [a, .b, #c] => [a, a.b, a.b#c, a#c, .b, .b#c, #c] + var combinations = getCombinations(pieces); + result = result.concat(combinations); + return result; + }, []); }; module.exports = generateDescendantPiecesFromSelector; diff --git a/test/fixtures/chained-selector.css b/test/fixtures/chained-selector.css new file mode 100644 index 0000000..19b9748 --- /dev/null +++ b/test/fixtures/chained-selector.css @@ -0,0 +1,30 @@ +button { + --width-small: 50px; +} + +button.custom { + --width-small: 100px; + --width-medium: 200px; + --width-large: 300px; + --font-size: 12px; +} + +button.rounded.small { + width: var(--width-small); +} + +button.custom.large { + width: var(--width-large); +} + +button.rounded.custom.medium { + width: var(--width-medium); +} + +button#id.rounded.custom.medium { + width: var(--width-medium); +} + +button.small.pill.custom { + width: var(--width-small); +} diff --git a/test/fixtures/chained-selector.expected.css b/test/fixtures/chained-selector.expected.css new file mode 100644 index 0000000..7a53acf --- /dev/null +++ b/test/fixtures/chained-selector.expected.css @@ -0,0 +1,19 @@ +button.rounded.small { + width: 50px; +} + +button.custom.large { + width: 300px; +} + +button.rounded.custom.medium { + width: 200px; +} + +button#id.rounded.custom.medium { + width: 200px; +} + +button.small.pill.custom { + width: 100px; +} diff --git a/test/test.js b/test/test.js index b0867b0..3d83d9b 100644 --- a/test/test.js +++ b/test/test.js @@ -129,6 +129,7 @@ describe('postcss-css-variables', function() { test('should work with `!important` variable declarations', 'important-variable-declaration'); + test('should work with chained selectors', 'chained-selector'); describe('with at-rules', function() {