@adguard/agtree
Version:
Tool set for working with adblock filter lists
215 lines (212 loc) • 7.99 kB
JavaScript
/*
* AGTree v3.4.3 (build date: Thu, 11 Dec 2025 13:43:19 GMT)
* (c) 2025 Adguard Software Ltd.
* Released under the MIT license
* https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
*/
import { ESCAPE_CHARACTER, EMPTY, SINGLE_QUOTE, DOUBLE_QUOTE, BACKTICK_QUOTE, COMMA, SPACE } from './constants.js';
/**
* @file Utility functions for working with quotes
*/
/**
* Set of all possible quote characters supported by the library
*/
const QUOTE_SET = new Set([
SINGLE_QUOTE,
DOUBLE_QUOTE,
BACKTICK_QUOTE,
]);
/**
* Possible quote types for scriptlet parameters
*/
const QuoteType = {
/**
* No quotes at all
*/
None: 'none',
/**
* Single quotes (`'`)
*/
Single: 'single',
/**
* Double quotes (`"`)
*/
Double: 'double',
/**
* Backtick quotes (`` ` ``)
*/
Backtick: 'backtick',
};
/**
* Utility functions for working with quotes
*/
class QuoteUtils {
/**
* Escape all unescaped occurrences of the character
*
* @param string String to escape
* @param char Character to escape
* @returns Escaped string
*/
static escapeUnescapedOccurrences(string, char) {
let result = EMPTY;
for (let i = 0; i < string.length; i += 1) {
if (string[i] === char && (i === 0 || string[i - 1] !== ESCAPE_CHARACTER)) {
result += ESCAPE_CHARACTER;
}
result += string[i];
}
return result;
}
/**
* Unescape all single escaped occurrences of the character
*
* @param string String to unescape
* @param char Character to unescape
* @returns Unescaped string
*/
static unescapeSingleEscapedOccurrences(string, char) {
let result = EMPTY;
for (let i = 0; i < string.length; i += 1) {
if (string[i] === char
&& string[i - 1] === ESCAPE_CHARACTER
&& (i === 1 || string[i - 2] !== ESCAPE_CHARACTER)) {
result = result.slice(0, -1);
}
result += string[i];
}
return result;
}
/**
* Get quote type of the string
*
* @param string String to check
* @returns Quote type of the string
*/
static getStringQuoteType(string) {
// Don't check 1-character strings to avoid false positives
if (string.length > 1) {
if (string.startsWith(SINGLE_QUOTE) && string.endsWith(SINGLE_QUOTE)) {
return QuoteType.Single;
}
if (string.startsWith(DOUBLE_QUOTE) && string.endsWith(DOUBLE_QUOTE)) {
return QuoteType.Double;
}
if (string.startsWith(BACKTICK_QUOTE) && string.endsWith(BACKTICK_QUOTE)) {
return QuoteType.Backtick;
}
}
return QuoteType.None;
}
/**
* Set quote type of the string
*
* @param string String to set quote type of
* @param quoteType Quote type to set
* @returns String with the specified quote type
*/
static setStringQuoteType(string, quoteType) {
const actualQuoteType = QuoteUtils.getStringQuoteType(string);
switch (quoteType) {
case QuoteType.None:
if (actualQuoteType === QuoteType.Single) {
return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), SINGLE_QUOTE);
}
if (actualQuoteType === QuoteType.Double) {
return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE);
}
if (actualQuoteType === QuoteType.Backtick) {
return QuoteUtils.escapeUnescapedOccurrences(string.slice(1, -1), BACKTICK_QUOTE);
}
return string;
case QuoteType.Single:
if (actualQuoteType === QuoteType.None) {
return SINGLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, SINGLE_QUOTE) + SINGLE_QUOTE;
}
if (actualQuoteType === QuoteType.Double) {
return SINGLE_QUOTE
+ QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), DOUBLE_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
}
if (actualQuoteType === QuoteType.Backtick) {
return SINGLE_QUOTE
+ QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), BACKTICK_QUOTE), SINGLE_QUOTE) + SINGLE_QUOTE;
}
return string;
case QuoteType.Double:
if (actualQuoteType === QuoteType.None) {
return DOUBLE_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, DOUBLE_QUOTE) + DOUBLE_QUOTE;
}
if (actualQuoteType !== QuoteType.Double) {
// eslint-disable-next-line max-len
return DOUBLE_QUOTE
+ QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), DOUBLE_QUOTE) + DOUBLE_QUOTE;
}
return string;
case QuoteType.Backtick:
if (actualQuoteType === QuoteType.None) {
// eslint-disable-next-line max-len
return BACKTICK_QUOTE + QuoteUtils.escapeUnescapedOccurrences(string, BACKTICK_QUOTE) + BACKTICK_QUOTE;
}
if (actualQuoteType !== QuoteType.Backtick) {
// eslint-disable-next-line max-len
return BACKTICK_QUOTE
+ QuoteUtils.escapeUnescapedOccurrences(QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), SINGLE_QUOTE), BACKTICK_QUOTE) + BACKTICK_QUOTE;
}
return string;
default:
return string;
}
}
/**
* Removes bounding quotes from a string, if any
*
* @param string Input string
* @returns String without quotes
*/
static removeQuotes(string) {
if (
// We should check for string length to avoid false positives
string.length > 1
&& (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE || string[0] === BACKTICK_QUOTE)
&& string[0] === string[string.length - 1]) {
return string.slice(1, -1);
}
return string;
}
/**
* Removes bounding quotes from a string, if any, and unescapes the escaped quotes,
* like transforming `'abc\'def'` to `abc'def`.
*
* @param string Input string
* @returns String without quotes
*/
static removeQuotesAndUnescape(string) {
if (
// We should check for string length to avoid false positives
string.length > 1
&& (string[0] === SINGLE_QUOTE || string[0] === DOUBLE_QUOTE || string[0] === BACKTICK_QUOTE)
&& string[0] === string[string.length - 1]) {
return QuoteUtils.unescapeSingleEscapedOccurrences(string.slice(1, -1), string[0]);
}
return string;
}
/**
* Wraps given `strings` with `quote` (defaults to single quote `'`)
* and joins them with `separator` (defaults to comma+space `, `).
*
* @param strings Strings to quote and join.
* @param quoteType Quote to use.
* @param separator Separator to use.
*
* @returns String with joined items.
*
* @example
* ['abc', 'def']: strings[] -> "'abc', 'def'": string
*/
static quoteAndJoinStrings(strings, quoteType = QuoteType.Single, separator = `${COMMA}${SPACE}`) {
return strings
.map((s) => QuoteUtils.setStringQuoteType(s, quoteType))
.join(separator);
}
}
export { QUOTE_SET, QuoteType, QuoteUtils };