@adguard/agtree
Version:
Tool set for working with adblock filter lists
103 lines (100 loc) • 3.92 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 { RuleParser } from './rule-parser.js';
import { CR, LF } from '../utils/constants.js';
import { StringUtils } from '../utils/string.js';
import { defaultParserOptions } from './options.js';
import { BaseParser } from './base-parser.js';
/**
* `FilterListParser` is responsible for parsing a whole adblock filter list (list of rules).
* It is a wrapper around `RuleParser` which parses each line separately.
*/
class FilterListParser extends BaseParser {
/**
* Parses a whole adblock filter list (list of rules).
*
* @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 AST of the source code (list of rules)
* @example
* ```js
* import { readFileSync } from 'fs';
* import { FilterListParser } from '@adguard/agtree';
*
* // Read filter list content from file
* const content = readFileSync('your-adblock-filter-list.txt', 'utf-8');
*
* // Parse the filter list content, then do something with the AST
* const ast = FilterListParser.parse(content);
* ```
* @throws If one of the rules is syntactically invalid (if `tolerant` is `false`)
*/
static parse(raw, options = defaultParserOptions, baseOffset = 0) {
// Actual position in the source code
let offset = 0;
// Collect adblock rules here
const rules = [];
// Start offset of the current line (initially it's 0)
let lineStartOffset = offset;
while (offset < raw.length) {
// Check if we found a new line
if (StringUtils.isEOL(raw[offset])) {
// Rule text
const text = raw.slice(lineStartOffset, offset);
// Parse the rule
const rule = RuleParser.parse(text, options, lineStartOffset);
// Get newline type (possible values: 'crlf', 'lf', 'cr' or undefined if no newline found)
let nl;
if (raw[offset] === CR) {
if (raw[offset + 1] === LF) {
nl = 'crlf';
}
else {
nl = 'cr';
}
}
else if (raw[offset] === LF) {
nl = 'lf';
}
// Add newline type to the rule (rule parser already added raws.text)
if (!rule.raws) {
rule.raws = {
text,
nl,
};
}
else {
rule.raws.nl = nl;
}
// Add the rule to the list
rules.push(rule);
// Update offset: add 2 if we found CRLF, otherwise add 1
offset += nl === 'crlf' ? 2 : 1;
// Update line start offset
lineStartOffset = offset;
}
else {
// No new line found, just increase offset
offset += 1;
}
}
// Parse the last rule (it doesn't end with a new line)
rules.push(RuleParser.parse(raw.slice(lineStartOffset, offset), options, baseOffset + lineStartOffset));
// Return the list of rules (FilterList node)
const result = {
type: 'FilterList',
children: rules,
};
if (options.isLocIncluded) {
result.start = baseOffset;
result.end = baseOffset + raw.length;
}
return result;
}
}
export { FilterListParser };