@adguard/agtree
Version:
Tool set for working with adblock filter lists
90 lines (87 loc) • 3.89 kB
JavaScript
/*
* AGTree v3.2.2 (build date: Tue, 08 Jul 2025 13:39:47 GMT)
* (c) 2025 Adguard Software Ltd.
* Released under the MIT license
* https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
*/
import { NEGATION_MARKER, MODIFIER_ASSIGN_OPERATOR } from '../../utils/constants.js';
import { StringUtils } from '../../utils/string.js';
import { AdblockSyntaxError } from '../../errors/adblock-syntax-error.js';
import { defaultParserOptions } from '../options.js';
import { BaseParser } from '../base-parser.js';
import { ValueParser } from './value-parser.js';
/**
* `ModifierParser` is responsible for parsing modifiers.
*
* @example
* `match-case`, `~third-party`, `domain=example.com|~example.org`
*/
class ModifierParser extends BaseParser {
/**
* Parses a modifier.
*
* @param raw Raw input to parse.
* @param options Global parser options.
* @param baseOffset Starting offset of the input. Node locations are calculated relative to this offset.
*
* @returns Parsed modifier
* @throws An error if modifier name or value is empty.
*/
static parse(raw, options = defaultParserOptions, baseOffset = 0) {
let offset = 0;
// Skip leading whitespace
offset = StringUtils.skipWS(raw, offset);
// Save the offset of the first character of the modifier (whole modifier)
const modifierStart = offset;
// Check if the modifier is an exception
let exception = false;
if (raw[offset] === NEGATION_MARKER) {
offset += NEGATION_MARKER.length;
exception = true;
}
// Skip whitespace after the exception marker (if any)
offset = StringUtils.skipWS(raw, offset);
// Save the offset of the first character of the modifier name
const modifierNameStart = offset;
// Find assignment operator
const assignmentIndex = StringUtils.findNextUnescapedCharacter(raw, MODIFIER_ASSIGN_OPERATOR);
// Find the end of the modifier
const modifierEnd = Math.max(StringUtils.skipWSBack(raw) + 1, modifierNameStart);
// Modifier name can't be empty
if (modifierNameStart === modifierEnd) {
throw new AdblockSyntaxError('Modifier name cannot be empty', baseOffset, baseOffset + raw.length);
}
let modifier;
let value;
// If there is no assignment operator, the whole modifier is the name
// without a value
if (assignmentIndex === -1) {
modifier = ValueParser.parse(raw.slice(modifierNameStart, modifierEnd), options, baseOffset + modifierNameStart);
}
else {
// If there is an assignment operator, first we need to find the
// end of the modifier name, then we can parse the value
const modifierNameEnd = StringUtils.skipWSBack(raw, assignmentIndex - 1) + 1;
modifier = ValueParser.parse(raw.slice(modifierNameStart, modifierNameEnd), options, baseOffset + modifierNameStart);
// Value can't be empty
if (assignmentIndex + 1 === modifierEnd) {
throw new AdblockSyntaxError('Modifier value cannot be empty', baseOffset, baseOffset + raw.length);
}
// Skip whitespace after the assignment operator
const valueStart = StringUtils.skipWS(raw, assignmentIndex + MODIFIER_ASSIGN_OPERATOR.length);
value = ValueParser.parse(raw.slice(valueStart, modifierEnd), options, baseOffset + valueStart);
}
const result = {
type: 'Modifier',
name: modifier,
value,
exception,
};
if (options.isLocIncluded) {
result.start = baseOffset + modifierStart;
result.end = baseOffset + modifierEnd;
}
return result;
}
}
export { ModifierParser };