@adguard/agtree
Version:
Tool set for working with adblock filter lists
83 lines (80 loc) • 3.3 kB
JavaScript
/*
* AGTree v4.1.1 (build date: Thu, 23 Apr 2026 09:15:37 GMT)
* (c) 2026 Adguard Software Ltd.
* Released under the MIT license
* https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
*/
import { AbpSnippetInjectionBodyCommon } from '../../../common/abp-snippet-injection-body-common.js';
import { AdblockSyntaxError } from '../../../errors/adblock-syntax-error.js';
import { SEMICOLON, SPACE } from '../../../utils/constants.js';
import { StringUtils } from '../../../utils/string.js';
import { BaseParser } from '../../base-parser.js';
import { ParameterListParser } from '../../misc/parameter-list-parser.js';
import { defaultParserOptions } from '../../options.js';
/**
* @file UBlock scriptlet injection body parser.
*/
/**
* `AbpSnippetInjectionBodyParser` is responsible for parsing the body of an Adblock Plus-style snippet rule.
*
* Please note that the parser will parse any scriptlet rule if it is syntactically correct.
* For example, it will parse this:.
* ```adblock
* example.com#$#snippet0 arg0
* ```
*
* But it didn't check if the scriptlet `snippet0` actually supported by any adblocker..
*
* @see {@link https://help.eyeo.com/adblockplus/snippet-filters-tutorial}
*/
class AbpSnippetInjectionBodyParser extends BaseParser {
/**
* Parses the body of an Adblock Plus-style snippet rule.
*
* @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 Node of the parsed scriptlet call body.
*
* @throws If the body is syntactically incorrect.
*
* @example
* ```
* #$#snippet0 arg0
* ```
*/
static parse(raw, options = defaultParserOptions, baseOffset = 0) {
const result = {
type: 'ScriptletInjectionRuleBody',
children: [],
};
if (options.isLocIncluded) {
result.start = baseOffset;
result.end = baseOffset + raw.length;
}
let offset = 0;
// Skip leading spaces
offset = StringUtils.skipWS(raw, offset);
while (offset < raw.length) {
offset = StringUtils.skipWS(raw, offset);
const scriptletCallStart = offset;
// Find the next semicolon or the end of the string
let semicolonIndex = StringUtils.findUnescapedNonStringNonRegexChar(raw, SEMICOLON, offset);
if (semicolonIndex === -1) {
semicolonIndex = raw.length;
}
const scriptletCallEnd = Math.max(StringUtils.skipWSBack(raw, semicolonIndex - 1) + 1, scriptletCallStart);
const params = ParameterListParser.parse(raw.slice(scriptletCallStart, scriptletCallEnd), options, baseOffset + scriptletCallStart, SPACE);
// Parse the scriptlet call
result.children.push(params);
// Skip the semicolon
offset = semicolonIndex + 1;
}
if (result.children.length === 0) {
throw new AdblockSyntaxError(AbpSnippetInjectionBodyCommon.ERROR_MESSAGES.EMPTY_SCRIPTLET_CALL, baseOffset, baseOffset + raw.length);
}
return result;
}
}
export { AbpSnippetInjectionBodyParser };