@medv/finder
Advanced tools
Comparing version 3.0.0 to 3.1.0
107
finder.js
@@ -0,4 +1,6 @@ | ||
// License: MIT | ||
// Author: Anton Medvedev <anton@medv.io> | ||
// Source: https://github.com/antonmedv/finder | ||
let config; | ||
let rootDocument; | ||
let uniqueCache; | ||
export function finder(input, options) { | ||
@@ -24,3 +26,2 @@ if (input.nodeType !== Node.ELEMENT_NODE) { | ||
rootDocument = findRootDocument(config.root, defaults); | ||
uniqueCache = new Map(); | ||
let path = bottomUpSearch(input, 'all', () => bottomUpSearch(input, 'two', () => bottomUpSearch(input, 'one', () => bottomUpSearch(input, 'none')))); | ||
@@ -138,6 +139,4 @@ if (path) { | ||
case 1: | ||
uniqueCache.set(css, true); | ||
return true; | ||
default: | ||
uniqueCache.set(css, false); | ||
return false; | ||
@@ -150,3 +149,3 @@ } | ||
return { | ||
name: '#' + cssesc(elementId, { isIdentifier: true }), | ||
name: '#' + CSS.escape(elementId), | ||
penalty: 0, | ||
@@ -160,7 +159,3 @@ }; | ||
return attrs.map((attr) => ({ | ||
name: '[' + | ||
cssesc(attr.name, { isIdentifier: true }) + | ||
'="' + | ||
cssesc(attr.value) + | ||
'"]', | ||
name: `[${CSS.escape(attr.name)}="${CSS.escape(attr.value)}"]`, | ||
penalty: 0.5, | ||
@@ -172,3 +167,3 @@ })); | ||
return names.map((name) => ({ | ||
name: '.' + cssesc(name, { isIdentifier: true }), | ||
name: '.' + CSS.escape(name), | ||
penalty: 1, | ||
@@ -273,91 +268,1 @@ })); | ||
} | ||
const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/; | ||
const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/; | ||
const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g; | ||
const defaultOptions = { | ||
escapeEverything: false, | ||
isIdentifier: false, | ||
quotes: 'single', | ||
wrap: false, | ||
}; | ||
function cssesc(string, opt = {}) { | ||
const options = { ...defaultOptions, ...opt }; | ||
if (options.quotes != 'single' && options.quotes != 'double') { | ||
options.quotes = 'single'; | ||
} | ||
const quote = options.quotes == 'double' ? '"' : '\''; | ||
const isIdentifier = options.isIdentifier; | ||
const firstChar = string.charAt(0); | ||
let output = ''; | ||
let counter = 0; | ||
const length = string.length; | ||
while (counter < length) { | ||
const character = string.charAt(counter++); | ||
let codePoint = character.charCodeAt(0); | ||
let 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. | ||
const 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 (/[\t\n\f\r\x0B]/.test(character)) { | ||
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); | ||
} | ||
} | ||
// Remove spaces after `\HEX` escapes that are not followed by a hex digit, | ||
// since they’re redundant. Note that this is only possible if the escape | ||
// sequence isn’t preceded by an odd number of backslashes. | ||
output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { | ||
if ($1 && $1.length % 2) { | ||
// It’s not safe to remove the space, so don’t. | ||
return $0; | ||
} | ||
// Strip the space. | ||
return ($1 || '') + $2; | ||
}); | ||
if (!isIdentifier && options.wrap) { | ||
return quote + output + quote; | ||
} | ||
return output; | ||
} |
100
finder.ts
@@ -191,3 +191,3 @@ // License: MIT | ||
return { | ||
name: '#' + cssesc(elementId, {isIdentifier: true}), | ||
name: '#' + CSS.escape(elementId), | ||
penalty: 0, | ||
@@ -205,8 +205,3 @@ } | ||
(attr): Knot => ({ | ||
name: | ||
'[' + | ||
cssesc(attr.name, {isIdentifier: true}) + | ||
'="' + | ||
cssesc(attr.value) + | ||
'"]', | ||
name: `[${CSS.escape(attr.name)}="${CSS.escape(attr.value)}"]`, | ||
penalty: 0.5, | ||
@@ -221,3 +216,3 @@ }) | ||
(name): Knot => ({ | ||
name: '.' + cssesc(name, {isIdentifier: true}), | ||
name: '.' + CSS.escape(name), | ||
penalty: 1, | ||
@@ -342,90 +337,1 @@ }) | ||
} | ||
const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/ | ||
const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/ | ||
const regexExcessiveSpaces = | ||
/(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g | ||
const defaultOptions = { | ||
escapeEverything: false, | ||
isIdentifier: false, | ||
quotes: 'single', | ||
wrap: false, | ||
} | ||
function cssesc(string: string, opt: Partial<typeof defaultOptions> = {}) { | ||
const options = {...defaultOptions, ...opt} | ||
if (options.quotes != 'single' && options.quotes != 'double') { | ||
options.quotes = 'single' | ||
} | ||
const quote = options.quotes == 'double' ? '"' : '\'' | ||
const isIdentifier = options.isIdentifier | ||
const firstChar = string.charAt(0) | ||
let output = '' | ||
let counter = 0 | ||
const length = string.length | ||
while (counter < length) { | ||
const character = string.charAt(counter++) | ||
let codePoint = character.charCodeAt(0) | ||
let value: string | undefined = 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. | ||
const 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 (/[\t\n\f\r\x0B]/.test(character)) { | ||
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) | ||
} | ||
} | ||
// Remove spaces after `\HEX` escapes that are not followed by a hex digit, | ||
// since they’re redundant. Note that this is only possible if the escape | ||
// sequence isn’t preceded by an odd number of backslashes. | ||
output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { | ||
if ($1 && $1.length % 2) { | ||
// It’s not safe to remove the space, so don’t. | ||
return $0 | ||
} | ||
// Strip the space. | ||
return ($1 || '') + $2 | ||
}) | ||
if (!isIdentifier && options.wrap) { | ||
return quote + output + quote | ||
} | ||
return output | ||
} |
{ | ||
"name": "@medv/finder", | ||
"version": "3.0.0", | ||
"version": "3.1.0", | ||
"description": "CSS Selector Generator", | ||
@@ -18,2 +18,3 @@ "type": "module", | ||
"devDependencies": { | ||
"css.escape": "^1.5.1", | ||
"jsdom": "^21.1.0", | ||
@@ -20,0 +21,0 @@ "release-it": "^15.7.0", |
@@ -5,5 +5,3 @@ ![finder](https://medv.io/assets/finder.png) | ||
[![Version](https://img.shields.io/npm/v/@medv/finder?color=grightgreen)](https://www.npmjs.com/package/@medv/finder) | ||
[![Test](https://github.com/antonmedv/finder/actions/workflows/test.yml/badge.svg)](https://github.com/antonmedv/finder/actions/workflows/test.yml) | ||
[![Size](https://img.shields.io/bundlephobia/minzip/@medv/finder?label=size)](https://bundlephobia.com/result?p=@medv/finder) | ||
@@ -64,3 +62,3 @@ **The CSS Selector Generator** | ||
For more robust selectors give this param value around 4-5 depending on depth of | ||
you DOM tree. If `finder` hits `root` this param is ignored. | ||
you DOM tree. If finder hits the `root`, this param is ignored. | ||
@@ -75,10 +73,9 @@ ### optimizedMinLength | ||
Max number of selectors to check before falling into `nth-child` usage. | ||
Checking for uniqueness of selector is very costs operation, if you have DOM | ||
Checking for uniqueness of selector is very costly operation, if you have DOM | ||
tree depth of 5, with 5 classes on each level, that gives you more than 3k | ||
selectors to check. Finder uses two-step approach,Ï so it's reaching this | ||
threshold in some cases twice. Default `1000` is good enough in most cases. | ||
selectors to check. Default `1000` is good enough in most cases. | ||
### maxNumberOfTries | ||
Max number of tries when we do the optimization. It is a trade-off between | ||
Max number of tries for the optimization. This is a trade-off between | ||
optimization and efficiency. Default `10_000` is good enough in most cases. | ||
@@ -85,0 +82,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19255
5
578
83