| Index: lib/common.js | 
| =================================================================== | 
| --- a/lib/common.js | 
| +++ b/lib/common.js | 
| @@ -96,8 +96,102 @@ | 
| } | 
| } | 
| selectors.push(selector.substring(start)); | 
| return selectors; | 
| } | 
| exports.splitSelector = splitSelector; | 
| + | 
| +function findTargetSelectorIndex(selector) | 
| +{ | 
| + let index = 0; | 
| + let whitespace = 0; | 
| + let scope = []; | 
| + | 
| + // Start from the end of the string and go character by character, where each | 
| + // character is a Unicode code point. | 
| + for (let character of [...selector].reverse()) | 
| + { | 
| + let currentScope = scope[scope.length - 1]; | 
| + | 
| + if (character == "'" || character == "\"") | 
| + { | 
| + // If we're already within the same type of quote, close the scope; | 
| + // otherwise open a new scope. | 
| + if (currentScope == character) | 
| + scope.pop(); | 
| + else | 
| + scope.push(character); | 
| + } | 
| + else if (character == "]" || character == ")") | 
| + { | 
| + // For closing brackets and parentheses, open a new scope only if we're | 
| + // not within a quote. Within quotes these characters should have no | 
| + // meaning. | 
| + if (currentScope != "'" && currentScope != "\"") | 
| + scope.push(character); | 
| + } | 
| + else if (character == "[") | 
| + { | 
| + // If we're already within a bracket, close the scope. | 
| + if (currentScope == "]") | 
| + scope.pop(); | 
| + } | 
| + else if (character == "(") | 
| + { | 
| + // If we're already within a parenthesis, close the scope. | 
| + if (currentScope == ")") | 
| + scope.pop(); | 
| + } | 
| + else if (!currentScope) | 
| + { | 
| + // At the top level (not within any scope), count the whitespace if we've | 
| + // encountered it. Otherwise if we've hit one of the combinators, | 
| + // terminate here; otherwise if we've hit a non-colon character, | 
| + // terminate here. | 
| + if (/\s/u.test(character)) | 
| + { | 
| + whitespace++; | 
| + } | 
| + else if ((character == ">" || character == "+" || character == "~") || | 
| + (whitespace > 0 && character != ":")) | 
| + { | 
| + break; | 
| + } | 
| + } | 
| + | 
| + // Zero out the whitespace count if we've entered a scope. | 
| + if (scope.length > 0) | 
| + whitespace = 0; | 
| + | 
| + // Increment the index by the size of the character. Note that for Unicode | 
| + // composite characters (like emoji) this will be more than one. | 
| + index += character.length; | 
| + } | 
| + | 
| + return selector.length - index + whitespace; | 
| +} | 
| + | 
| +function qualifySelector(selector, qualifier) | 
| 
 
hub
2018/05/14 19:35:12
It would be nice to have a bit of documentation to
 
Manish Jethani
2018/05/15 12:03:58
Done.
 
 | 
| +{ | 
| + let qualifiedSelector = ""; | 
| + | 
| + for (let sub of splitSelector(selector)) | 
| + { | 
| + sub = sub.trim(); | 
| + | 
| + qualifiedSelector += ", "; | 
| + | 
| + let index = findTargetSelectorIndex(sub); | 
| + let [, type = "", rest] = /^([a-z][a-z-]*)?(.*)/i.exec(sub.substr(index)); | 
| + | 
| + // Note that the first group in the regular expression is optional. If it | 
| + // doesn't match (e.g. "#foo::nth-child(1)"), type will be an empty string. | 
| + qualifiedSelector += sub.substr(0, index) + type + qualifier + rest; | 
| + } | 
| + | 
| + // Remove the initial comma and space. | 
| + return qualifiedSelector.substr(2); | 
| +} | 
| + | 
| +exports.qualifySelector = qualifySelector; |