postcss-selector-parser
Version:
Selector parser with built in methods for working with selector strings.
182 lines • 7.2 kB
JavaScript
;
/*!
* Adapted from cssesc v3.0.0 <https://mths.be/cssesc>
*
* Copyright Mathias Bynens <https://mathiasbynens.be/>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = cssesc;
// The only behavioural difference from cssesc 3.0.0 is how redundant spaces
// after `\HEX` escapes are removed: upstream uses a regular expression that
// backtracks quadratically on long runs of backslashes, which a crafted
// selector could use to pin a CPU core. removeExcessiveSpaces() produces the
// same output in linear time. Upstream has not been released since 2019, and
// the pending fix there (mathiasbynens/cssesc#36) changes the output. An
// unreachable branch escaping \t, \n, \f, \r and \x0B was also dropped: those
// characters are below 0x20 and always take the hex escape path first.
var DEFAULTS = {
escapeEverything: false,
isIdentifier: false,
quotes: "single",
wrap: false,
};
var hasOwnProperty = Object.prototype.hasOwnProperty;
function merge(options, defaults) {
if (!options) {
return defaults;
}
var result = {};
for (var key in defaults) {
result[key] = hasOwnProperty.call(options, key) ? options[key] : defaults[key];
}
return result;
}
var regexAnySingleEscape = /[ -,./:-@[-^`{-~]/;
var regexSingleEscape = /[ -,./:-@[\]^`{-~]/;
var BACKSLASH = 0x5c;
var SPACE = 0x20;
function isUpperHex(code) {
return (code >= 0x30 && code <= 0x39) || (code >= 0x41 && code <= 0x46);
}
function isHexOrSpace(code) {
return isUpperHex(code) || (code >= 0x61 && code <= 0x66) || code === SPACE;
}
/**
* Remove spaces after `\HEX` escapes that are not followed by a hex digit or a
* space, since they are redundant. This is only possible if the escape is not
* preceded by an odd number of backslashes, which would make its own backslash
* an escaped one.
*
* Same result as cssesc 3.0.0's
* `output.replace(/(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g, ...)`:
* a match can only start at the beginning of a run of backslashes and only
* its last backslash can open the escape, so each run is measured once
* instead of being re-scanned from every position inside it.
*/
function removeExcessiveSpaces(output) {
var length = output.length;
var result = "";
var copied = 0;
var i = 0;
while (i < length) {
if (output.charCodeAt(i) !== BACKSLASH) {
i++;
continue;
}
var runEnd = i + 1;
while (runEnd < length && output.charCodeAt(runEnd) === BACKSLASH) {
runEnd++;
}
// Look at up to 7 hex digits: more than 6 can never be followed directly
// by the space.
var hexEnd = runEnd;
while (hexEnd < length && hexEnd - runEnd < 7 && isUpperHex(output.charCodeAt(hexEnd))) {
hexEnd++;
}
var hexLength = hexEnd - runEnd;
if (hexLength >= 1 &&
hexLength <= 6 &&
output.charCodeAt(hexEnd) === SPACE &&
!isHexOrSpace(output.charCodeAt(hexEnd + 1))) {
// Backslashes preceding the one that opens the escape.
if ((runEnd - i - 1) % 2 === 0) {
result += output.slice(copied, hexEnd);
copied = hexEnd + 1;
}
i = hexEnd + 1;
}
else {
i = runEnd;
}
}
return copied === 0 ? output : result + output.slice(copied);
}
// https://mathiasbynens.be/notes/css-escapes#css
function cssesc(string, options) {
options = merge(options, DEFAULTS);
if (options.quotes !== "single" && options.quotes !== "double") {
options.quotes = "single";
}
var quote = options.quotes === "double" ? '"' : "'";
var isIdentifier = options.isIdentifier;
var firstChar = string.charAt(0);
var output = "";
var counter = 0;
var length = string.length;
while (counter < length) {
var character = string.charAt(counter++);
var codePoint = character.charCodeAt(0);
var value = void 0;
// If it's not a printable ASCII character...
if (codePoint < 0x20 || codePoint > 0x7e) {
if (codePoint >= 0xd800 && codePoint <= 0xdbff && counter < length) {
// It's a high surrogate, and there is a next character.
var extra = string.charCodeAt(counter++);
if ((extra & 0xfc00) === 0xdc00) {
// next character is low surrogate
codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
}
else {
// It's an unmatched surrogate; only append this code unit, in case
// the next code unit is the high surrogate of a surrogate pair.
counter--;
}
}
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
}
else {
if (options.escapeEverything) {
if (regexAnySingleEscape.test(character)) {
value = "\\" + character;
}
else {
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
}
}
else if (character === "\\" ||
(!isIdentifier &&
((character === '"' && quote === character) ||
(character === "'" && quote === character))) ||
(isIdentifier && regexSingleEscape.test(character))) {
value = "\\" + character;
}
else {
value = character;
}
}
output += value;
}
if (isIdentifier) {
if (/^-[-\d]/.test(output)) {
output = "\\-" + output.slice(1);
}
else if (/\d/.test(firstChar)) {
output = "\\3" + firstChar + " " + output.slice(1);
}
}
output = removeExcessiveSpaces(output);
if (!isIdentifier && options.wrap) {
return quote + output + quote;
}
return output;
}
//# sourceMappingURL=cssesc.js.map