From 7827c7ca4f5590cc98ecc50d4c128cdc5e10ad6c Mon Sep 17 00:00:00 2001 From: Lauri Date: Sat, 23 May 2020 23:44:16 +0300 Subject: [PATCH 1/4] various improvements --- lib/is-node-under-scope.js | 4 +- lib/is-under-scope.js | 12 +- lib/resolve-decl.js | 163 +++++++++++++++++- lib/resolve-value.js | 12 +- package.json | 6 +- test/fixtures/compound1.css | 9 + test/fixtures/compound1.expected.css | 8 + test/fixtures/compound10.css | 9 + test/fixtures/compound10.expected.css | 4 + test/fixtures/compound11.css | 18 ++ test/fixtures/compound11.expected.css | 12 ++ test/fixtures/compound12.css | 18 ++ test/fixtures/compound12.expected.css | 12 ++ test/fixtures/compound13.css | 9 + test/fixtures/compound13.expected.css | 8 + test/fixtures/compound14.css | 31 ++++ test/fixtures/compound14.expected.css | 28 +++ test/fixtures/compound15.css | 12 ++ test/fixtures/compound15.expected.css | 4 + test/fixtures/compound16.css | 22 +++ test/fixtures/compound16.expected.css | 15 ++ test/fixtures/compound17.css | 41 +++++ test/fixtures/compound17.expected.css | 24 +++ test/fixtures/compound18.css | 13 ++ test/fixtures/compound18.expected.css | 8 + test/fixtures/compound19.css | 21 +++ test/fixtures/compound19.expected.css | 16 ++ test/fixtures/compound2.css | 9 + test/fixtures/compound2.expected.css | 8 + test/fixtures/compound3.css | 9 + test/fixtures/compound3.expected.css | 8 + test/fixtures/compound4.css | 9 + test/fixtures/compound4.expected.css | 8 + test/fixtures/compound5.css | 9 + test/fixtures/compound5.expected.css | 4 + test/fixtures/compound6.css | 9 + test/fixtures/compound6.expected.css | 8 + test/fixtures/compound7.css | 9 + test/fixtures/compound7.expected.css | 4 + test/fixtures/compound8.css | 9 + test/fixtures/compound8.expected.css | 8 + test/fixtures/compound9.css | 13 ++ test/fixtures/compound9.expected.css | 12 ++ test/fixtures/pseudo-selector-multi.css | 17 ++ .../pseudo-selector-multi.expected.css | 37 ++++ test/fixtures/pseudo-selector-multi2.css | 13 ++ .../pseudo-selector-multi2.expected.css | 29 ++++ test/fixtures/pseudo-selector-nested.css | 10 ++ .../pseudo-selector-nested.expected.css | 9 + test/test.js | 17 +- 50 files changed, 787 insertions(+), 20 deletions(-) create mode 100644 test/fixtures/compound1.css create mode 100644 test/fixtures/compound1.expected.css create mode 100644 test/fixtures/compound10.css create mode 100644 test/fixtures/compound10.expected.css create mode 100644 test/fixtures/compound11.css create mode 100644 test/fixtures/compound11.expected.css create mode 100644 test/fixtures/compound12.css create mode 100644 test/fixtures/compound12.expected.css create mode 100644 test/fixtures/compound13.css create mode 100644 test/fixtures/compound13.expected.css create mode 100644 test/fixtures/compound14.css create mode 100644 test/fixtures/compound14.expected.css create mode 100644 test/fixtures/compound15.css create mode 100644 test/fixtures/compound15.expected.css create mode 100644 test/fixtures/compound16.css create mode 100644 test/fixtures/compound16.expected.css create mode 100644 test/fixtures/compound17.css create mode 100644 test/fixtures/compound17.expected.css create mode 100644 test/fixtures/compound18.css create mode 100644 test/fixtures/compound18.expected.css create mode 100644 test/fixtures/compound19.css create mode 100644 test/fixtures/compound19.expected.css create mode 100644 test/fixtures/compound2.css create mode 100644 test/fixtures/compound2.expected.css create mode 100644 test/fixtures/compound3.css create mode 100644 test/fixtures/compound3.expected.css create mode 100644 test/fixtures/compound4.css create mode 100644 test/fixtures/compound4.expected.css create mode 100644 test/fixtures/compound5.css create mode 100644 test/fixtures/compound5.expected.css create mode 100644 test/fixtures/compound6.css create mode 100644 test/fixtures/compound6.expected.css create mode 100644 test/fixtures/compound7.css create mode 100644 test/fixtures/compound7.expected.css create mode 100644 test/fixtures/compound8.css create mode 100644 test/fixtures/compound8.expected.css create mode 100644 test/fixtures/compound9.css create mode 100644 test/fixtures/compound9.expected.css create mode 100644 test/fixtures/pseudo-selector-multi.css create mode 100644 test/fixtures/pseudo-selector-multi.expected.css create mode 100644 test/fixtures/pseudo-selector-multi2.css create mode 100644 test/fixtures/pseudo-selector-multi2.expected.css create mode 100644 test/fixtures/pseudo-selector-nested.css create mode 100644 test/fixtures/pseudo-selector-nested.expected.css diff --git a/lib/is-node-under-scope.js b/lib/is-node-under-scope.js index 02bab38..11ef281 100644 --- a/lib/is-node-under-scope.js +++ b/lib/is-node-under-scope.js @@ -1,11 +1,11 @@ var isUnderScope = require('./is-under-scope'); var generateScopeList = require('./generate-scope-list'); -var isNodeUnderScope = function(node, scopeNode, /*optional*/ignorePseudo) { +var isNodeUnderScope = function(node, scopeNode, /*optional*/ignorePseudo, /*optional*/keepPseudo) { var nodeScopeList = generateScopeList(node, true); var scopeNodeScopeList = generateScopeList(scopeNode, true); - return isUnderScope(nodeScopeList, scopeNodeScopeList, ignorePseudo); + return isUnderScope(nodeScopeList, scopeNodeScopeList, ignorePseudo, keepPseudo); }; module.exports = isNodeUnderScope; diff --git a/lib/is-under-scope.js b/lib/is-under-scope.js index 7ce0baf..badd228 100644 --- a/lib/is-under-scope.js +++ b/lib/is-under-scope.js @@ -133,11 +133,13 @@ var stripPseudoSelectorsFromScopeList = function(scopeList) { // Another way to think about it: Can the target scope cascade properties to the node? // // For scope-lists see: `generateScopeList` -var isUnderScope = function(nodeScopeList, scopeNodeScopeList, /*optional*/ignorePseudo) { - // Because we only care about the scopeNodeScope matching to the nodeScope - // Remove the pseudo selectors from the nodeScope so it can match a broader version - // ex. `.foo:hover` can resolve variables from `.foo` - nodeScopeList = stripPseudoSelectorsFromScopeList(nodeScopeList); +var isUnderScope = function(nodeScopeList, scopeNodeScopeList, /*optional*/ignorePseudo, /*optional*/keepPseudo) { + if(!keepPseudo) { + // Because we only care about the scopeNodeScope matching to the nodeScope + // Remove the pseudo selectors from the nodeScope so it can match a broader version + // ex. `.foo:hover` can resolve variables from `.foo` + nodeScopeList = stripPseudoSelectorsFromScopeList(nodeScopeList); + } if(ignorePseudo) { scopeNodeScopeList = stripPseudoSelectorsFromScopeList(scopeNodeScopeList); diff --git a/lib/resolve-decl.js b/lib/resolve-decl.js index e469849..5f98316 100644 --- a/lib/resolve-decl.js +++ b/lib/resolve-decl.js @@ -1,3 +1,6 @@ + +var log = require('debug')('postcss-css-variables:resolve-decl'); + var resolveValue = require('./resolve-value'); var generateScopeList = require('./generate-scope-list'); var gatherVariableDependencies = require('./gather-variable-dependencies'); @@ -9,15 +12,85 @@ var shallowCloneNode = require('./shallow-clone-node'); var findNodeAncestorWithSelector = require('./find-node-ancestor-with-selector'); var cloneSpliceParentOntoNodeWhen = require('./clone-splice-parent-onto-node-when'); +var parser = require('postcss-selector-parser'); + +function equals(a, b) { + return a && b && a.type !== undefined && a.value !== undefined && a.type === b.type && a.value === b.value; +} + +function compatible(a, b) { + var af = a.filter(c => !b.find(d => equals(d, c))); + var bf = b.filter(c => !a.find(d => equals(d, c))); + var ac = a.find(c => c.type == 'combinator'); + var bc = b.find(c => c.type == 'combinator'); + + if(bf.length == 0 && ((!ac && !bc) || equals(ac, bc))) + { + return true; + } + + return false; +} + +function compatible2(a, b) { + var af = a.filter(c => !b.find(d => equals(d, c))); + var bf = b.filter(c => !a.find(d => equals(d, c))); + + if(bf.length == 0 && af.length != 0) + { + return true; + } + + return false; +} + +function isSpace(a) { + return a && a.type === 'combinator' && a.value.trim() === ''; +} + +function findIndex(s, fn, start) +{ + if(!start) + { + start = 0; + } + else + { + s = s.slice(start); + } + + var pos = s.findIndex(fn); + + return pos === -1 ? -1 : s.findIndex(fn) + start; +} + +function parseSelector(selector) { + var ret; + + parser(selectors => { + ret = selectors.first.split(selector => selector.type === 'combinator').map(a => a.filter(b => !isSpace(b))); + }).processSync(selector); + return ret; +} function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { + log('>>>>>>>>> DECL ' + decl.parent.selector + ' / ' + decl.prop + ' = ' + decl.value); + + var declSelector = decl.parent.selector ? parseSelector(decl.parent) : null; + + log(declSelector); + // Now find any at-rule declarations that pertains to each rule // Loop through the variables used variablesUsedList.forEach(function(variableUsedName) { + log('>>>>>> VAR '+variableUsedName); + // Find anything in the map that corresponds to that variable - gatherVariableDependencies(variablesUsedList, map).deps.forEach(function(mapItem) { + gatherVariableDependencies(variablesUsedList, map).deps.forEach(function(mapItem, i) { + + log('>>> DEP '+i+' / ' + mapItem.parent.selector + ' / ' + mapItem.prop + ' = ' + mapItem.calculatedInPlaceValue); var mimicDecl; if(mapItem.isUnderAtRule) { @@ -41,29 +114,103 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { //console.log(generateScopeList(mapItem.parent, true)); //console.log('amd isNodeUnderScope', isNodeUnderScope(mimicDecl.parent, mapItem.parent), mapItem.decl.value); } - // TODO: use regex from `isUnderScope` - else if(isUnderScope.RE_PSEUDO_SELECTOR.test(mapItem.parent.selector)) { +// // TODO: use regex from `isUnderScope` +// else if(isUnderScope.RE_PSEUDO_SELECTOR.test(mapItem.parent.selector)) { +// // Create a detached clone +// var ruleClone = shallowCloneNode(decl.parent); +// ruleClone.parent = decl.parent.parent; +// +// // Add the declaration to it +// mimicDecl = decl.clone(); +// ruleClone.append(mimicDecl); +// +// var lastPseudoSelectorMatches = mapItem.parent.selector.match(new RegExp(isUnderScope.RE_PSEUDO_SELECTOR.source + '$')); +// var lastPseudoSelector = lastPseudoSelectorMatches ? lastPseudoSelectorMatches[2] : ''; +// +// ruleClone.selector += lastPseudoSelector; +// } + else if(mapItem.parent.selector !== decl.parent.selector && declSelector) { + var s = parseSelector(mapItem.parent); + + log(s); + + var append = null; + var idx = 0; + var process = false; + for(var i = 0; i < declSelector.length; i++) { + var a = declSelector[i]; + var b = findIndex(s, c => compatible(c, a), idx); + process |= s.findIndex(c => compatible2(c, a)) != -1; + + if(b < idx) { + // already compatible + if(i === 0) { + log('i === 0', b, idx); + return; + } + // invalid unless last item + else if(i != declSelector.length-1) { + log('i != declSelector.length-1', b, idx); + return; + } + + log('append', a); + append = a; + } + else { + idx = b; + } + } + + // #foo li:hover a @ compound16.css + if(!append && idx != s.length-1) { + log('idx != s.length-1', idx, s.length-1); + return; + } + + // already compatible + if(!process) { + log('!process'); + return; + } + // Create a detached clone var ruleClone = shallowCloneNode(decl.parent); ruleClone.parent = decl.parent.parent; // Add the declaration to it mimicDecl = decl.clone(); - ruleClone.append(mimicDecl); - var lastPseudoSelectorMatches = mapItem.parent.selector.match(new RegExp(isUnderScope.RE_PSEUDO_SELECTOR.source + '$')); - var lastPseudoSelector = lastPseudoSelectorMatches ? lastPseudoSelectorMatches[2] : ''; + // FIXME? short-circuit to fix pseudo selectors + mimicDecl.value = mapItem.calculatedInPlaceValue; + + ruleClone.append(mimicDecl); + ruleClone.selector = mapItem.parent.selector; - ruleClone.selector += lastPseudoSelector; + // append last element of selector where needed + if(append) { + ruleClone.selector += ' '; + append.forEach(a => ruleClone.selector += String(a)); + } } // If it is under the proper scope, // we need to check because we are iterating over all map entries - if(mimicDecl && isNodeUnderScope(mimicDecl, mapItem.parent, true)) { + var valid = mimicDecl && isNodeUnderScope(mimicDecl, mapItem.parent, true); + + if(valid) { cb(mimicDecl, mapItem); } + + log('SELECTOR '+(mimicDecl ? mimicDecl.parent.selector : null)); + log('VALID? '+valid); + log('<<< DEP '+i+' / ' + mapItem.parent.selector + ' / ' + mapItem.prop + ' = ' + mapItem.calculatedInPlaceValue); }); + + log('<<<<<< VAR '+variableUsedName); }); + + log('<<<<<<<<< DECL ' + decl.parent.selector + ' / ' + decl.prop + ' = ' + decl.value); } diff --git a/lib/resolve-value.js b/lib/resolve-value.js index 60b6789..200c897 100644 --- a/lib/resolve-value.js +++ b/lib/resolve-value.js @@ -1,3 +1,6 @@ + +var log = require('debug')('postcss-css-variables:resolve-value'); + var balanced = require('balanced-match'); var generateScopeList = require('./generate-scope-list'); @@ -64,6 +67,8 @@ function balancedVar(value) { var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal debugging*/_debugIsInternal) { var debugIndent = _debugIsInternal ? '\t' : ''; +// log(decl, map, ignorePseudoScope); + var matchingVarDecl = undefined; var resultantValue = toString(decl.value); var warnings = []; @@ -117,7 +122,9 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root'; var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent); - var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope); + var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope, !!matchingVarDeclMapItem); + +// log("###", variableName, underScope, underScsopeIgnorePseudo, matchingVarDeclMapItem && matchingVarDeclMapItem.decl.value, /*matchingVarDeclMapItem,*/ decl.parent, varDeclMapItem.parent); //console.log(debugIndent, 'isNodeUnderScope', underScope, underScsopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value); @@ -141,6 +148,7 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal return fallbackValue; })(); + // Otherwise if the dependency health is good(no circular or self references), dive deeper and resolve if(matchingVarDeclMapItem !== undefined && !gatherVariableDependencies(variablesUsedInValue, map).hasCircularOrSelfReference) { // Splice the declaration parent onto the matching entry @@ -165,6 +173,8 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal resultantValue = (matchingVarDecl.pre || '') + replaceValue + (matchingVarDecl.post || '') } + log("RESULT:", !isResultantValueUndefined ? resultantValue : undefined); + return { // The resolved value value: !isResultantValueUndefined ? resultantValue : undefined, diff --git a/package.json b/package.json index 8fbb1d5..5e5177e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "balanced-match": "^1.0.0", "escape-string-regexp": "^1.0.3", "extend": "^3.0.1", - "postcss": "^6.0.8" + "postcss": "^6.0.8", + "postcss-selector-parser": "^6.0.2" }, "devDependencies": { "bluebird": "^3.5.0", @@ -28,7 +29,8 @@ "eslint-plugin-react": "^7.1.0", "mocha": "^5.2.0", "postcss-discard-comments": "^4.0.0", - "postcss-normalize-whitespace": "^4.0.0" + "postcss-normalize-whitespace": "^4.0.0", + "debug": "*" }, "scripts": { "test": "mocha", diff --git a/test/fixtures/compound1.css b/test/fixtures/compound1.css new file mode 100644 index 0000000..25d1416 --- /dev/null +++ b/test/fixtures/compound1.css @@ -0,0 +1,9 @@ + +.foo { + --color: #ffffff; + color: var(--color); +} + +.foo.bar { + --color: #000000; +} diff --git a/test/fixtures/compound1.expected.css b/test/fixtures/compound1.expected.css new file mode 100644 index 0000000..de259e3 --- /dev/null +++ b/test/fixtures/compound1.expected.css @@ -0,0 +1,8 @@ + +.foo { + color: #ffffff; +} + +.foo.bar { + color: #000000; +} diff --git a/test/fixtures/compound10.css b/test/fixtures/compound10.css new file mode 100644 index 0000000..509643e --- /dev/null +++ b/test/fixtures/compound10.css @@ -0,0 +1,9 @@ + +#foo.bar li { + --color: #ffffff; + color: var(--color); +} + +#foo li { + --color: #000000; +} diff --git a/test/fixtures/compound10.expected.css b/test/fixtures/compound10.expected.css new file mode 100644 index 0000000..5f3d4d5 --- /dev/null +++ b/test/fixtures/compound10.expected.css @@ -0,0 +1,4 @@ + +#foo.bar li { + color: #ffffff; +} diff --git a/test/fixtures/compound11.css b/test/fixtures/compound11.css new file mode 100644 index 0000000..f46f5c0 --- /dev/null +++ b/test/fixtures/compound11.css @@ -0,0 +1,18 @@ + +#foo a li { + --color: #ffffff; + color: var(--color); +} + +#foo.bar a + li { + --color: #000000; +} + +#bar a + li { + --color2: #ffffff; + color: var(--color2); +} + +#bar.bar a + li { + --color2: #000000; +} diff --git a/test/fixtures/compound11.expected.css b/test/fixtures/compound11.expected.css new file mode 100644 index 0000000..890dee4 --- /dev/null +++ b/test/fixtures/compound11.expected.css @@ -0,0 +1,12 @@ + +#foo a li { + color: #ffffff; +} + +#bar a + li { + color: #ffffff; +} + +#bar.bar a + li { + color: #000000; +} diff --git a/test/fixtures/compound12.css b/test/fixtures/compound12.css new file mode 100644 index 0000000..d9737af --- /dev/null +++ b/test/fixtures/compound12.css @@ -0,0 +1,18 @@ + +#foo a b + c d li { + --color: #ffffff; + color: var(--color); +} + +#foo.bar a c b + c d e { + --color: #000000; +} + +#bar a b + c d li { + --color2: #ffffff; + color: var(--color2); +} + +#bar.bar a c b c + d e { + --color: #000000; +} diff --git a/test/fixtures/compound12.expected.css b/test/fixtures/compound12.expected.css new file mode 100644 index 0000000..d3c686f --- /dev/null +++ b/test/fixtures/compound12.expected.css @@ -0,0 +1,12 @@ + +#foo a b + c d li { + color: #ffffff; +} + +#foo.bar a c b + c d e li { + color: #000000; +} + +#bar a b + c d li { + color: #ffffff; +} diff --git a/test/fixtures/compound13.css b/test/fixtures/compound13.css new file mode 100644 index 0000000..fc916e7 --- /dev/null +++ b/test/fixtures/compound13.css @@ -0,0 +1,9 @@ + +#foo a b c a c d li { + --color: #ffffff; + color: var(--color); +} + +#foo.bar a b c a c d f g li { + --color: #fff000; +} diff --git a/test/fixtures/compound13.expected.css b/test/fixtures/compound13.expected.css new file mode 100644 index 0000000..436b71d --- /dev/null +++ b/test/fixtures/compound13.expected.css @@ -0,0 +1,8 @@ + +#foo a b c a c d li { + color: #ffffff; +} + +#foo.bar a b c a c d f g li { + color: #fff000; +} diff --git a/test/fixtures/compound14.css b/test/fixtures/compound14.css new file mode 100644 index 0000000..a966c76 --- /dev/null +++ b/test/fixtures/compound14.css @@ -0,0 +1,31 @@ + +:root { + --color: #ffffff; + --color2: #ffffff; + --color3: #ffffff; +} + +#foo li { + color: var(--color); +} + +#foo.bar ul:hover { + --color: #000000; + color: var(--color); +} + +#bar li { + color: var(--color2); +} + +#bar.bar li:hover { + --color2: #000000; +} + +#baz li { + color: var(--color3); +} + +#baz.foo.bar:not(.hello) li { + --color3: #000000; +} diff --git a/test/fixtures/compound14.expected.css b/test/fixtures/compound14.expected.css new file mode 100644 index 0000000..9ee87a5 --- /dev/null +++ b/test/fixtures/compound14.expected.css @@ -0,0 +1,28 @@ + +#foo li { + color: #ffffff; +} + +#foo.bar ul:hover li { + color: #000000; +} + +#foo.bar ul:hover { + color: #000000; +} + +#bar li { + color: #ffffff; +} + +#bar.bar li:hover { + color: #000000; +} + +#baz li { + color: #ffffff; +} + +#baz.foo.bar:not(.hello) li { + color: #000000; +} diff --git a/test/fixtures/compound15.css b/test/fixtures/compound15.css new file mode 100644 index 0000000..06c3ddc --- /dev/null +++ b/test/fixtures/compound15.css @@ -0,0 +1,12 @@ + +:root { + --color2: #ffffff; +} + +#bar { + --color2: #000000; +} + +#bar:hover li { + color: var(--color2); +} diff --git a/test/fixtures/compound15.expected.css b/test/fixtures/compound15.expected.css new file mode 100644 index 0000000..6e5e969 --- /dev/null +++ b/test/fixtures/compound15.expected.css @@ -0,0 +1,4 @@ + +#bar:hover li { + color: #ffffff; +} diff --git a/test/fixtures/compound16.css b/test/fixtures/compound16.css new file mode 100644 index 0000000..6ae8d84 --- /dev/null +++ b/test/fixtures/compound16.css @@ -0,0 +1,22 @@ + +:root { + --color: #fff000; +} + +#foo li { + color: var(--color); +} + +#foo:hover +{ + --color: #000000; +} + +#foo div ul li:hover { + --color: #aaaaaa; +} + +/* no selector created */ +#foo li:hover a { + --color: #bbbbbb; +} diff --git a/test/fixtures/compound16.expected.css b/test/fixtures/compound16.expected.css new file mode 100644 index 0000000..31f2273 --- /dev/null +++ b/test/fixtures/compound16.expected.css @@ -0,0 +1,15 @@ + +#foo li +{ + color: #fff000; +} + +#foo div ul li:hover +{ + color: #aaaaaa; +} + +#foo:hover li +{ + color: #000000; +} diff --git a/test/fixtures/compound17.css b/test/fixtures/compound17.css new file mode 100644 index 0000000..ab67a4d --- /dev/null +++ b/test/fixtures/compound17.css @@ -0,0 +1,41 @@ + +:root +{ + --color: #ffffff; +} + +#foo1 ul > li { + color: var(--color); +} + +#foo2 ul ~ li { + color: var(--color); +} + +#foo3 ul + li { + color: var(--color); +} + +#foo1.bar ul > li { + --color: #111111; +} + +#foo2.bar ul ~ li { + --color: #222222; +} + +#foo3.bar ul + li { + --color: #333333; +} + +#foo1 ul ~ li { + --color: #444444; +} + +#foo1 ul + li { + --color: #555555; +} + +#foo2 ul + li { + --color: #666666; +} diff --git a/test/fixtures/compound17.expected.css b/test/fixtures/compound17.expected.css new file mode 100644 index 0000000..427d9ae --- /dev/null +++ b/test/fixtures/compound17.expected.css @@ -0,0 +1,24 @@ + +#foo1 ul > li { + color: #ffffff; +} + +#foo1.bar ul > li { + color: #111111; +} + +#foo2 ul ~ li { + color: #ffffff; +} + +#foo2.bar ul ~ li { + color: #222222; +} + +#foo3 ul + li { + color: #ffffff; +} + +#foo3.bar ul + li { + color: #333333; +} diff --git a/test/fixtures/compound18.css b/test/fixtures/compound18.css new file mode 100644 index 0000000..4701709 --- /dev/null +++ b/test/fixtures/compound18.css @@ -0,0 +1,13 @@ + +:root +{ + --color: #ffffff; +} + +#foo > * li { + color: var(--color); +} + +#foo.bar > * li { + --color: #111111; +} diff --git a/test/fixtures/compound18.expected.css b/test/fixtures/compound18.expected.css new file mode 100644 index 0000000..005caa4 --- /dev/null +++ b/test/fixtures/compound18.expected.css @@ -0,0 +1,8 @@ + +#foo > * li { + color: #ffffff; +} + +#foo.bar > * li { + color: #111111; +} diff --git a/test/fixtures/compound19.css b/test/fixtures/compound19.css new file mode 100644 index 0000000..b1ebbba --- /dev/null +++ b/test/fixtures/compound19.css @@ -0,0 +1,21 @@ + +:root +{ + --color: #ffffff; +} + +#foo li { + color: var(--color); +} + +#foo[bar] li { + --color: #111111; +} + +#foo li[bar] { + --color: #222222; +} + +#foo:not(.bar) li[bar] { + --color: #333333; +} diff --git a/test/fixtures/compound19.expected.css b/test/fixtures/compound19.expected.css new file mode 100644 index 0000000..52559d0 --- /dev/null +++ b/test/fixtures/compound19.expected.css @@ -0,0 +1,16 @@ + +#foo li { + color: #ffffff; +} + +#foo:not(.bar) li[bar] { + color: #333333; +} + +#foo li[bar] { + color: #222222; +} + +#foo[bar] li { + color: #111111; +} diff --git a/test/fixtures/compound2.css b/test/fixtures/compound2.css new file mode 100644 index 0000000..52bf99f --- /dev/null +++ b/test/fixtures/compound2.css @@ -0,0 +1,9 @@ + +#foo { + --color: #ffffff; + color: var(--color); +} + +#foo.bar { + --color: #000000; +} diff --git a/test/fixtures/compound2.expected.css b/test/fixtures/compound2.expected.css new file mode 100644 index 0000000..35fb7c9 --- /dev/null +++ b/test/fixtures/compound2.expected.css @@ -0,0 +1,8 @@ + +#foo { + color: #ffffff; +} + +#foo.bar { + color: #000000; +} diff --git a/test/fixtures/compound3.css b/test/fixtures/compound3.css new file mode 100644 index 0000000..90d1df2 --- /dev/null +++ b/test/fixtures/compound3.css @@ -0,0 +1,9 @@ + +#foo li { + --color: #ffffff; + color: var(--color); +} + +#foo.bar { + --color: #000000; +} diff --git a/test/fixtures/compound3.expected.css b/test/fixtures/compound3.expected.css new file mode 100644 index 0000000..c84050e --- /dev/null +++ b/test/fixtures/compound3.expected.css @@ -0,0 +1,8 @@ + +#foo li { + color: #ffffff; +} + +#foo.bar li { + color: #000000; +} diff --git a/test/fixtures/compound4.css b/test/fixtures/compound4.css new file mode 100644 index 0000000..9566b4b --- /dev/null +++ b/test/fixtures/compound4.css @@ -0,0 +1,9 @@ + +#foo li { + --color: #ffffff; + color: var(--color); +} + +#foo.bar ul { + --color: #000000; +} diff --git a/test/fixtures/compound4.expected.css b/test/fixtures/compound4.expected.css new file mode 100644 index 0000000..6b1581e --- /dev/null +++ b/test/fixtures/compound4.expected.css @@ -0,0 +1,8 @@ + +#foo li { + color: #ffffff; +} + +#foo.bar ul li { + color: #000000; +} diff --git a/test/fixtures/compound5.css b/test/fixtures/compound5.css new file mode 100644 index 0000000..251dba4 --- /dev/null +++ b/test/fixtures/compound5.css @@ -0,0 +1,9 @@ + +#foo ul li { + --color: #ffffff; + color: var(--color); +} + +#foo.bar li { + --color: #000000; +} diff --git a/test/fixtures/compound5.expected.css b/test/fixtures/compound5.expected.css new file mode 100644 index 0000000..0291318 --- /dev/null +++ b/test/fixtures/compound5.expected.css @@ -0,0 +1,4 @@ + +#foo ul li { + color: #ffffff; +} diff --git a/test/fixtures/compound6.css b/test/fixtures/compound6.css new file mode 100644 index 0000000..fe4dd1c --- /dev/null +++ b/test/fixtures/compound6.css @@ -0,0 +1,9 @@ + +#foo li { + --color: #ffffff; + color: var(--color); +} + +#bar #foo.bar { + --color: #000000; +} diff --git a/test/fixtures/compound6.expected.css b/test/fixtures/compound6.expected.css new file mode 100644 index 0000000..29054db --- /dev/null +++ b/test/fixtures/compound6.expected.css @@ -0,0 +1,8 @@ + +#foo li { + color: #ffffff; +} + +#bar #foo.bar li { + color: #000000; +} diff --git a/test/fixtures/compound7.css b/test/fixtures/compound7.css new file mode 100644 index 0000000..fa49baa --- /dev/null +++ b/test/fixtures/compound7.css @@ -0,0 +1,9 @@ + +#foo a b c d li { + --color: #ffffff; + color: var(--color); +} + +#foo.bar a c { + --color: #000000; +} diff --git a/test/fixtures/compound7.expected.css b/test/fixtures/compound7.expected.css new file mode 100644 index 0000000..2487f78 --- /dev/null +++ b/test/fixtures/compound7.expected.css @@ -0,0 +1,4 @@ + +#foo a b c d li { + color: #ffffff; +} diff --git a/test/fixtures/compound8.css b/test/fixtures/compound8.css new file mode 100644 index 0000000..efd47fe --- /dev/null +++ b/test/fixtures/compound8.css @@ -0,0 +1,9 @@ + +#foo a c li { + --color: #ffffff; + color: var(--color); +} + +#bar #foo.bar a b c d { + --color: #000000; +} diff --git a/test/fixtures/compound8.expected.css b/test/fixtures/compound8.expected.css new file mode 100644 index 0000000..26412f1 --- /dev/null +++ b/test/fixtures/compound8.expected.css @@ -0,0 +1,8 @@ + +#foo a c li { + color: #ffffff; +} + +#bar #foo.bar a b c d li { + color: #000000; +} diff --git a/test/fixtures/compound9.css b/test/fixtures/compound9.css new file mode 100644 index 0000000..cbe5924 --- /dev/null +++ b/test/fixtures/compound9.css @@ -0,0 +1,13 @@ + +#foo a c li { + --color: #ffffff; + color: var(--color); +} + +#baz #foo.bar a a c b c d li { + --color: #fff000; +} + +#bar #foo.bar a a c b c d { + --color: #000000; +} diff --git a/test/fixtures/compound9.expected.css b/test/fixtures/compound9.expected.css new file mode 100644 index 0000000..ea7b247 --- /dev/null +++ b/test/fixtures/compound9.expected.css @@ -0,0 +1,12 @@ + +#foo a c li { + color: #ffffff; +} + +#bar #foo.bar a a c b c d li { + color: #000000; +} + +#baz #foo.bar a a c b c d li { + color: #fff000; +} diff --git a/test/fixtures/pseudo-selector-multi.css b/test/fixtures/pseudo-selector-multi.css new file mode 100644 index 0000000..2a6349b --- /dev/null +++ b/test/fixtures/pseudo-selector-multi.css @@ -0,0 +1,17 @@ + +.bar { + --color: pink; + color: var(--color); +} + +.bar:active { + --color: blue; +} + +.bar:hover { + --color: red; +} + +.bar:focus { + --color: green; +} diff --git a/test/fixtures/pseudo-selector-multi.expected.css b/test/fixtures/pseudo-selector-multi.expected.css new file mode 100644 index 0000000..2e2e684 --- /dev/null +++ b/test/fixtures/pseudo-selector-multi.expected.css @@ -0,0 +1,37 @@ + +/* + * wrong order + * + +.bar { + color: pink; +} + +.bar:active { + color: blue; +} + +.bar:hover { + color: red; +} + +.bar:focus { + color: green; +} +*/ + +.bar { + color: pink; +} + +.bar:focus { + color: green; +} + +.bar:hover { + color: red; +} + +.bar:active { + color: blue; +} diff --git a/test/fixtures/pseudo-selector-multi2.css b/test/fixtures/pseudo-selector-multi2.css new file mode 100644 index 0000000..1c585fd --- /dev/null +++ b/test/fixtures/pseudo-selector-multi2.css @@ -0,0 +1,13 @@ + +.bar a { + --color: pink; + color: var(--color); +} + +.bar:active { + --color: green; +} + +.bar:active:hover { + --color: blue; +} diff --git a/test/fixtures/pseudo-selector-multi2.expected.css b/test/fixtures/pseudo-selector-multi2.expected.css new file mode 100644 index 0000000..60de813 --- /dev/null +++ b/test/fixtures/pseudo-selector-multi2.expected.css @@ -0,0 +1,29 @@ + +/* + * wrong order + * + +.bar a { + color: pink; +} + +.bar:active a { + color: green; +} + +.bar:active:hover a { + color: blue; +} +*/ + +.bar a { + color: pink; +} + +.bar:active:hover a { + color: blue; +} + +.bar:active a { + color: green; +} diff --git a/test/fixtures/pseudo-selector-nested.css b/test/fixtures/pseudo-selector-nested.css new file mode 100644 index 0000000..a4de59b --- /dev/null +++ b/test/fixtures/pseudo-selector-nested.css @@ -0,0 +1,10 @@ + +#test .foo { + --foo-color: '#ffffff'; + color: var(--foo-color); +} + +#test:hover { + --foo-color: '#ffff00'; +} + diff --git a/test/fixtures/pseudo-selector-nested.expected.css b/test/fixtures/pseudo-selector-nested.expected.css new file mode 100644 index 0000000..e60ae2e --- /dev/null +++ b/test/fixtures/pseudo-selector-nested.expected.css @@ -0,0 +1,9 @@ + +#test .foo { + color: '#ffffff'; +} + +#test:hover .foo { + color: '#ffff00'; +} + diff --git a/test/test.js b/test/test.js index 45a2f33..19ed28d 100644 --- a/test/test.js +++ b/test/test.js @@ -79,6 +79,7 @@ var test = function(message, fixtureName, options) { }); }; + describe("postcss-css-variables", function() { // Just make sure it doesn't mangle anything test( @@ -128,14 +129,17 @@ describe("postcss-css-variables", function() { "should work with direct descendant selector where variables are scoped in a direct descendant selector", "direct-descendant-selector-direct-descendant-scope" ); - test("should work with pseudo selectors", "pseudo-selector"); - //test('should work with multiple pseudo selectors', 'pseudo-multi'); + test( "should work with variables declared in pseudo selectors", "pseudo-selector-declare-variable" ); + test("should work with top level pseudo selectors", "pseudo-selector-nested"); + test("should work with multiple pseudo selectors", "pseudo-selector-multi"); + test("should work with multiple pseudo selectors", "pseudo-selector-multi2"); + test( "should work with variables defined in comma separated selector", "comma-separated-variable-declaration" @@ -371,6 +375,15 @@ describe("postcss-css-variables", function() { ).to.eventually.be.rejected; }); + describe("compound selectors", function() { + for(var i = 1; i <= 19; i++) { + test( + "should support compound selectors #"+i, + "compound"+i + ); + } + }); + describe("rule clean up", function() { test( "should clean up rules if we removed variable declarations to make it empty", From 55691c9b43ba5b232fe5dddb218844607e815d96 Mon Sep 17 00:00:00 2001 From: Lauri Date: Sun, 24 May 2020 16:12:16 +0300 Subject: [PATCH 2/4] add partial star selector support --- lib/resolve-decl.js | 76 ++++++++++++++++++--------- test/fixtures/compound20.css | 16 ++++++ test/fixtures/compound20.expected.css | 8 +++ test/test.js | 3 +- 4 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 test/fixtures/compound20.css create mode 100644 test/fixtures/compound20.expected.css diff --git a/lib/resolve-decl.js b/lib/resolve-decl.js index 5f98316..27af118 100644 --- a/lib/resolve-decl.js +++ b/lib/resolve-decl.js @@ -74,6 +74,12 @@ function parseSelector(selector) { return ret; } +function removeWildcard(selector) { + return parser(selectors => { + selectors.first.nodes[selectors.first.nodes.length-1].remove(); + }).processSync(selector, { updateSelector: false }); +} + function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { log('>>>>>>>>> DECL ' + decl.parent.selector + ' / ' + decl.prop + ' = ' + decl.value); @@ -92,6 +98,7 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { log('>>> DEP '+i+' / ' + mapItem.parent.selector + ' / ' + mapItem.prop + ' = ' + mapItem.calculatedInPlaceValue); + var state = 0; var mimicDecl; if(mapItem.isUnderAtRule) { @@ -143,15 +150,15 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { process |= s.findIndex(c => compatible2(c, a)) != -1; if(b < idx) { - // already compatible + // no match: already compatible or wildcard if(i === 0) { - log('i === 0', b, idx); - return; + state = 1; + break; } // invalid unless last item else if(i != declSelector.length-1) { - log('i != declSelector.length-1', b, idx); - return; + state = 2; + break; } log('append', a); @@ -163,40 +170,59 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { } // #foo li:hover a @ compound16.css - if(!append && idx != s.length-1) { - log('idx != s.length-1', idx, s.length-1); - return; + if(state === 0 && !append && idx != s.length-1) { + state = 3; } // already compatible - if(!process) { - log('!process'); - return; + if(state === 0 && !process) { + state = 4; } - // Create a detached clone - var ruleClone = shallowCloneNode(decl.parent); - ruleClone.parent = decl.parent.parent; + // compound20.css + if(state === 1 && s.length != 1 && s[s.length-1].length === 1 && s[s.length-1][0].type === 'universal') { + state = 5; + } + + log("STATE", state); + + if(state === 0 || state === 5) { + // Create a detached clone + var ruleClone = shallowCloneNode(decl.parent); + ruleClone.parent = decl.parent.parent; - // Add the declaration to it - mimicDecl = decl.clone(); + // Add the declaration to it + mimicDecl = decl.clone(); - // FIXME? short-circuit to fix pseudo selectors - mimicDecl.value = mapItem.calculatedInPlaceValue; + // FIXME? short-circuit to fix pseudo selectors + mimicDecl.value = mapItem.calculatedInPlaceValue; - ruleClone.append(mimicDecl); - ruleClone.selector = mapItem.parent.selector; + ruleClone.append(mimicDecl); - // append last element of selector where needed - if(append) { - ruleClone.selector += ' '; - append.forEach(a => ruleClone.selector += String(a)); + if(state === 0) + { + ruleClone.selector = mapItem.parent.selector; + + // append last element of selector where needed + if(append) { + ruleClone.selector += ' '; + append.forEach(a => ruleClone.selector += String(a)); + } + } + else if(state === 5) + { + ruleClone.selector = removeWildcard(mapItem.parent.selector).trim() + ' ' + decl.parent.selector; + } + else + { + throw new Error("Invalid state: "+state); + } } } // If it is under the proper scope, // we need to check because we are iterating over all map entries - var valid = mimicDecl && isNodeUnderScope(mimicDecl, mapItem.parent, true); + var valid = state === 5 || (mimicDecl && isNodeUnderScope(mimicDecl, mapItem.parent, true)); if(valid) { cb(mimicDecl, mapItem); diff --git a/test/fixtures/compound20.css b/test/fixtures/compound20.css new file mode 100644 index 0000000..2b1fde0 --- /dev/null +++ b/test/fixtures/compound20.css @@ -0,0 +1,16 @@ + +:root { + --color: #000000; +} + +body.bar * { + --color: #ffffff; +} + +body.bar *:hover { + --color: #111111; +} + +#foo { + color: var(--color); +} diff --git a/test/fixtures/compound20.expected.css b/test/fixtures/compound20.expected.css new file mode 100644 index 0000000..d314fae --- /dev/null +++ b/test/fixtures/compound20.expected.css @@ -0,0 +1,8 @@ + +#foo { + color: #000000; +} + +body.bar #foo { + color: #ffffff; +} diff --git a/test/test.js b/test/test.js index 19ed28d..4e5e70b 100644 --- a/test/test.js +++ b/test/test.js @@ -79,7 +79,6 @@ var test = function(message, fixtureName, options) { }); }; - describe("postcss-css-variables", function() { // Just make sure it doesn't mangle anything test( @@ -376,7 +375,7 @@ describe("postcss-css-variables", function() { }); describe("compound selectors", function() { - for(var i = 1; i <= 19; i++) { + for(var i = 1; i <= 20; i++) { test( "should support compound selectors #"+i, "compound"+i From 976e60550d8970ab153658be9408f1c5dbaca122 Mon Sep 17 00:00:00 2001 From: Lauri Date: Sun, 24 May 2020 22:29:31 +0300 Subject: [PATCH 3/4] more fixes --- lib/is-under-scope.js | 27 ++++++++ lib/resolve-decl.js | 43 ++++-------- lib/resolve-value.js | 67 ++++++++++++++----- test/fixtures/compound17.css | 8 +++ test/fixtures/compound18.css | 2 + test/fixtures/compound21.css | 16 +++++ test/fixtures/compound21.expected.css | 12 ++++ .../pseudo-selector-declare-variable.css | 5 +- ...udo-selector-declare-variable.expected.css | 4 ++ test/test.js | 3 +- 10 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 test/fixtures/compound21.css create mode 100644 test/fixtures/compound21.expected.css diff --git a/lib/is-under-scope.js b/lib/is-under-scope.js index badd228..4113946 100644 --- a/lib/is-under-scope.js +++ b/lib/is-under-scope.js @@ -1,3 +1,5 @@ +var log = require('debug')('postcss-css-variables:is-under-scope'); + var escapeStringRegexp = require('escape-string-regexp'); var isPieceAlwaysAncestorSelector = require('./is-piece-always-ancestor-selector'); @@ -14,6 +16,8 @@ function getScopeMatchResults(nodeScopeList, scopeNodeScopeList) { var currentPieceOffset; var scopePieceIndex; + log('######### getScopeMatchResults', nodeScopeList, scopeNodeScopeList); + // Check each comma separated piece of the complex selector var doesMatchScope = scopeNodeScopeList.some(function(scopeNodeScopePieces) { return nodeScopeList.some(function(nodeScopePieces) { @@ -21,12 +25,16 @@ function getScopeMatchResults(nodeScopeList, scopeNodeScopeList) { //console.log('sp', scopeNodeScopePieces); //console.log('np', nodeScopePieces); + log(nodeScopePieces, scopeNodeScopePieces); + currentPieceOffset = null; var wasEveryPieceFound = true; for(scopePieceIndex = 0; scopePieceIndex < scopeNodeScopePieces.length; scopePieceIndex++) { var scopePiece = scopeNodeScopePieces[scopePieceIndex]; var pieceOffset = currentPieceOffset || 0; + log('###### LOOP1', scopePiece, pieceOffset); + var foundIndex = -1; // Look through the remaining pieces(start from the offset) var piecesWeCanMatch = nodeScopePieces.slice(pieceOffset); @@ -34,6 +42,8 @@ function getScopeMatchResults(nodeScopeList, scopeNodeScopeList) { var nodeScopePiece = piecesWeCanMatch[nodeScopePieceIndex]; var overallIndex = pieceOffset + nodeScopePieceIndex; + log('### LOOP2', nodeScopePiece, overallIndex); + // Find the scope piece at the end of the node selector // Last-occurence if( @@ -42,6 +52,7 @@ function getScopeMatchResults(nodeScopeList, scopeNodeScopeList) { // scopePiece `.bar` matches node `.foo + .bar` new RegExp(escapeStringRegexp(scopePiece) + '$').test(nodeScopePiece) ) { + log('break 1', scopePiece, nodeScopePiece); foundIndex = overallIndex; break; } @@ -54,15 +65,19 @@ function getScopeMatchResults(nodeScopeList, scopeNodeScopeList) { if(isPieceAlwaysAncestorSelector(scopePiece) || isPieceAlwaysAncestorSelector(nodeScopePiece)) { foundIndex = overallIndex; + log('break 2', scopePiece, isPieceAlwaysAncestorSelector(scopePiece), nodeScopePiece, isPieceAlwaysAncestorSelector(nodeScopePiece)); break; } // Handle any direct descendant operators in each piece var directDescendantPieces = generateDirectDescendantPiecesFromSelector(nodeScopePiece); + // Only try to work out direct descendants if there was the `>` combinator, meaning multiple pieces if(directDescendantPieces.length > 1) { + log('directDescendantPieces', directDescendantPieces); + var ddNodeScopeList = [].concat([directDescendantPieces]); // Massage into a direct descendant separated list var ddScopeList = [].concat([ @@ -83,6 +98,13 @@ function getScopeMatchResults(nodeScopeList, scopeNodeScopeList) { scopePieceIndex += result.scopePieceIndex - 1; } + log('break 3', result.doesMatchScope); + +// if(!result.doesMatchScope) +// { +// wasEveryPieceFound = false; +// } + break; } } @@ -95,14 +117,19 @@ function getScopeMatchResults(nodeScopeList, scopeNodeScopeList) { // Mimicing a `[].every` with a for-loop wasEveryPieceFound = wasEveryPieceFound && isFurther; if(!wasEveryPieceFound) { + log('break 4'); break; } } + log('###### FOUND', wasEveryPieceFound, scopePiece, nodeScopePiece); + return wasEveryPieceFound; }); }); + log('######### RETURN', doesMatchScope, nodeScopeList, scopeNodeScopeList); + return { doesMatchScope: doesMatchScope, nodeScopePieceIndex: currentPieceOffset - 1, diff --git a/lib/resolve-decl.js b/lib/resolve-decl.js index 27af118..a11e9df 100644 --- a/lib/resolve-decl.js +++ b/lib/resolve-decl.js @@ -50,12 +50,10 @@ function isSpace(a) { function findIndex(s, fn, start) { - if(!start) - { + if(!start) { start = 0; } - else - { + else { s = s.slice(start); } @@ -121,21 +119,6 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { //console.log(generateScopeList(mapItem.parent, true)); //console.log('amd isNodeUnderScope', isNodeUnderScope(mimicDecl.parent, mapItem.parent), mapItem.decl.value); } -// // TODO: use regex from `isUnderScope` -// else if(isUnderScope.RE_PSEUDO_SELECTOR.test(mapItem.parent.selector)) { -// // Create a detached clone -// var ruleClone = shallowCloneNode(decl.parent); -// ruleClone.parent = decl.parent.parent; -// -// // Add the declaration to it -// mimicDecl = decl.clone(); -// ruleClone.append(mimicDecl); -// -// var lastPseudoSelectorMatches = mapItem.parent.selector.match(new RegExp(isUnderScope.RE_PSEUDO_SELECTOR.source + '$')); -// var lastPseudoSelector = lastPseudoSelectorMatches ? lastPseudoSelectorMatches[2] : ''; -// -// ruleClone.selector += lastPseudoSelector; -// } else if(mapItem.parent.selector !== decl.parent.selector && declSelector) { var s = parseSelector(mapItem.parent); @@ -143,11 +126,11 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { var append = null; var idx = 0; - var process = false; + var process = s.length > 1; for(var i = 0; i < declSelector.length; i++) { var a = declSelector[i]; var b = findIndex(s, c => compatible(c, a), idx); - process |= s.findIndex(c => compatible2(c, a)) != -1; + process |= s.findIndex(c => compatible2(c, a), idx) != -1; if(b < idx) { // no match: already compatible or wildcard @@ -165,6 +148,12 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { append = a; } else { + // check if the element following the combinator is compatible + if(s[b].find(a => a.type === 'combinator') && !equals(declSelector[i+1][0], s[b+1][0])) { + state = 6; + break; + } + idx = b; } } @@ -194,13 +183,9 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { // Add the declaration to it mimicDecl = decl.clone(); - // FIXME? short-circuit to fix pseudo selectors - mimicDecl.value = mapItem.calculatedInPlaceValue; - ruleClone.append(mimicDecl); - if(state === 0) - { + if(state === 0) { ruleClone.selector = mapItem.parent.selector; // append last element of selector where needed @@ -209,12 +194,10 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { append.forEach(a => ruleClone.selector += String(a)); } } - else if(state === 5) - { + else if(state === 5) { ruleClone.selector = removeWildcard(mapItem.parent.selector).trim() + ' ' + decl.parent.selector; } - else - { + else { throw new Error("Invalid state: "+state); } } diff --git a/lib/resolve-value.js b/lib/resolve-value.js index 200c897..1284dd9 100644 --- a/lib/resolve-value.js +++ b/lib/resolve-value.js @@ -116,26 +116,61 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal var variableName = variableFallbackSplitPieces[0].trim(); var fallback = variableFallbackSplitPieces.length > 1 ? variableFallbackSplitPieces.slice(1).join(',').trim() : undefined; - (map[variableName] || []).forEach(function(varDeclMapItem) { - // Make sure the variable declaration came from the right spot - // And if the current matching variable is already important, a new one to replace it has to be important - var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root'; + var process = (ignorePseudo, ignorePseudo2) => + { + (map[variableName] || []).forEach(function(varDeclMapItem) { + // Make sure the variable declaration came from the right spot + // And if the current matching variable is already important, a new one to replace it has to be important + var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root'; + + var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent, false, !ignorePseudo2); +log('---------------'); + var underScopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope, !ignorePseudo2); + + log("###", variableName, ignorePseudo, ignorePseudo2, '=', underScope, underScopeIgnorePseudo, varDeclMapItem.decl.value, decl.parent.selector, '<>', varDeclMapItem.parent.selector); + + //console.log(debugIndent, 'isNodeUnderScope', underScope, underScopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value); + + if( + (ignorePseudo ? underScopeIgnorePseudo : underScope) && + // And if the currently matched declaration is `!important`, it will take another `!important` to override it + (!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant) + ) { + matchingVarDeclMapItem = varDeclMapItem; + } + }); + }; - var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent); - var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope, !!matchingVarDeclMapItem); + !matchingVarDeclMapItem && process(false, false); + !matchingVarDeclMapItem && process(false, true); + !matchingVarDeclMapItem && process(true, false); + !matchingVarDeclMapItem && process(true, true); -// log("###", variableName, underScope, underScsopeIgnorePseudo, matchingVarDeclMapItem && matchingVarDeclMapItem.decl.value, /*matchingVarDeclMapItem,*/ decl.parent, varDeclMapItem.parent); + if(!matchingVarDeclMapItem) + { + (map[variableName] || []).forEach(function(varDeclMapItem) { + // Make sure the variable declaration came from the right spot + // And if the current matching variable is already important, a new one to replace it has to be important + var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root'; - //console.log(debugIndent, 'isNodeUnderScope', underScope, underScsopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value); + var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent, false, true); + var underScopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope, false); - if( - underScsopeIgnorePseudo && - // And if the currently matched declaration is `!important`, it will take another `!important` to override it - (!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant) - ) { - matchingVarDeclMapItem = varDeclMapItem; - } - }); + log("###", variableName, underScope, underScopeIgnorePseudo, varDeclMapItem.decl.value, decl.parent.selector, varDeclMapItem.parent.selector); + + //console.log(debugIndent, 'isNodeUnderScope', underScope, underScopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value); + + if( + underScope && + // And if the currently matched declaration is `!important`, it will take another `!important` to override it + (!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant) + ) { + matchingVarDeclMapItem = varDeclMapItem; + } + }); + } + +// log('%%%%%%%%%%%%%%%%%%%%%%%%%%', matchingVarDeclMapItem); // Default to the calculatedInPlaceValue which might be a previous fallback, then try this declarations fallback var replaceValue = (matchingVarDeclMapItem || {}).calculatedInPlaceValue || (function() { diff --git a/test/fixtures/compound17.css b/test/fixtures/compound17.css index ab67a4d..a761645 100644 --- a/test/fixtures/compound17.css +++ b/test/fixtures/compound17.css @@ -20,6 +20,14 @@ --color: #111111; } +#foo1.bar ul div > li { + --color: #777777; +} + +#foo1.bar ul > div li { + --color: #888888; +} + #foo2.bar ul ~ li { --color: #222222; } diff --git a/test/fixtures/compound18.css b/test/fixtures/compound18.css index 4701709..1587eb0 100644 --- a/test/fixtures/compound18.css +++ b/test/fixtures/compound18.css @@ -4,6 +4,8 @@ --color: #ffffff; } +/* getScopeMatchResults fails */ + #foo > * li { color: var(--color); } diff --git a/test/fixtures/compound21.css b/test/fixtures/compound21.css new file mode 100644 index 0000000..4502b48 --- /dev/null +++ b/test/fixtures/compound21.css @@ -0,0 +1,16 @@ + +:root { + --padding: 25px; +} + +#foo { + max-width: calc(5px + var(--padding) + var(--padding)); +} + +body.bar * { + --padding: 10px; +} + +#bar #foo { + --padding: 5px; +} diff --git a/test/fixtures/compound21.expected.css b/test/fixtures/compound21.expected.css new file mode 100644 index 0000000..031cbd6 --- /dev/null +++ b/test/fixtures/compound21.expected.css @@ -0,0 +1,12 @@ + +#foo { + max-width: calc(5px + 25px + 25px); +} + +#bar #foo { + max-width: calc(5px + 5px + 5px); +} + +body.bar #foo { + max-width: calc(5px + 10px + 10px); +} diff --git a/test/fixtures/pseudo-selector-declare-variable.css b/test/fixtures/pseudo-selector-declare-variable.css index e9fbf9d..4f16a0a 100644 --- a/test/fixtures/pseudo-selector-declare-variable.css +++ b/test/fixtures/pseudo-selector-declare-variable.css @@ -7,11 +7,10 @@ --foo-color: #00ff00; } - -/* This should add nothing to `.foo`, wrong scope */ .bar:hover + .foo { --foo-color: #f0f000; } + /* This should add nothing to `.foo`, wrong scope */ .foo:hover + .bar { --foo-color: #0000ff; @@ -19,4 +18,4 @@ /* This should add nothing to `.foo`, wrong scope */ .foo:hover + .bar:focus { --foo-color: #000f0f; -} \ No newline at end of file +} diff --git a/test/fixtures/pseudo-selector-declare-variable.expected.css b/test/fixtures/pseudo-selector-declare-variable.expected.css index e74e941..b778789 100644 --- a/test/fixtures/pseudo-selector-declare-variable.expected.css +++ b/test/fixtures/pseudo-selector-declare-variable.expected.css @@ -2,6 +2,10 @@ color: #ff0000; } +.bar:hover + .foo { + color: #f0f000; +} + .foo:hover { color: #00ff00; } diff --git a/test/test.js b/test/test.js index 4e5e70b..a7d047f 100644 --- a/test/test.js +++ b/test/test.js @@ -79,6 +79,7 @@ var test = function(message, fixtureName, options) { }); }; + describe("postcss-css-variables", function() { // Just make sure it doesn't mangle anything test( @@ -375,7 +376,7 @@ describe("postcss-css-variables", function() { }); describe("compound selectors", function() { - for(var i = 1; i <= 20; i++) { + for(var i = 1; i <= 21; i++) { test( "should support compound selectors #"+i, "compound"+i From d0cd0342bed22764a9edabecaecedcddf84f0457 Mon Sep 17 00:00:00 2001 From: Lauri Date: Mon, 25 May 2020 02:01:13 +0300 Subject: [PATCH 4/4] fix cloning nodes with undefined values --- lib/shallow-clone-node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/shallow-clone-node.js b/lib/shallow-clone-node.js index 9f8c7c7..ab1f09e 100644 --- a/lib/shallow-clone-node.js +++ b/lib/shallow-clone-node.js @@ -24,7 +24,7 @@ var shallowCloneNode = function(obj, parent) { } else { cloned[i] = value.map(function(j) { - shallowCloneNode(j, cloned); + j && shallowCloneNode(j, cloned); }); } }