Skip to content

AST Walk

The /ast/walk endpoint and its CLI counterpart perform a deep, structure-aware traversal of the AGTree abstract syntax tree (AST) built from a filter list. Unlike the shallow /ast/parse endpoint — which returns only top-level rule nodes — /ast/walk descends into every structurally meaningful sub-node (modifier lists, domain lists, rule bodies, scriptlet parameter lists, preprocessor expression trees, etc.) and returns a flat list of every node visited.

This feature was introduced in PR #1632 and satisfies the AGTree adapter layer requirements from issue #1131.


HTTP API

POST /api/ast/walk

Auth required: Free+
Middleware: bodySizeMiddleware, rateLimitMiddleware, turnstileMiddleware

Request Body

Exactly one of rules or text must be provided. Supplying both returns 422.

{
"rules": ["||example.org^$third-party", "@@||safe.example.com^"],
"text": "||example.org^\n! A comment",
"nodeTypes": ["NetworkRule", "Modifier"],
"maxDepth": 10,
"includeContext": true,
"turnstileToken": "<optional>"
}
FieldTypeRequiredDescription
rulesstring[]One of rules/textArray of raw rule strings. Max 5,000 items. Each rule ≤ 4,096 chars.
textstringOne of rules/textFull filter list as a single newline-separated string. Max 1 MiB.
nodeTypesstring[]NoRestrict results to specific AGTree node types (see Node Types). Max 30.
maxDepthintegerNoMaximum traversal depth (0–50, inclusive). Default: 50. Nodes deeper than this are silently skipped.
includeContextbooleanNoWhen true, each result node includes key and index context fields. Default: false.
turnstileTokenstringNoOptional Cloudflare Turnstile token.

Response — 200 OK

{
"success": true,
"nodes": [
{
"type": "FilterList",
"depth": 0,
"node": { "type": "FilterList", "children": [ ... ] }
},
{
"type": "NetworkRule",
"depth": 1,
"key": "children",
"index": 0,
"node": { "type": "NetworkRule", "pattern": { ... }, "modifiers": { ... } }
},
{
"type": "Modifier",
"depth": 3,
"key": "children",
"index": 0,
"node": { "type": "Modifier", "name": { ... }, "value": { ... } }
}
],
"summary": {
"FilterList": 1,
"NetworkRule": 2,
"Modifier": 3,
"total": 6
},
"duration": "4ms"
}
FieldTypeDescription
successbooleanAlways true on 200.
nodesWalkResultNode[]Flat list of every visited node in pre-order.
nodes[].typestringAGTree node type discriminant (e.g. NetworkRule).
nodes[].depthintegerTraversal depth — 0 is the FilterList root, rule children start at 1.
nodes[].keystring|null(Only when includeContext: true) Property name on parent (e.g. "children", "modifiers").
nodes[].indexinteger|null(Only when includeContext: true) Zero-based array index within the parent collection, or null for non-array properties.
nodes[].nodeobjectFull AGTree AST node object.
summaryRecord<string, number>Per-type node counts plus total.
durationstringServer-side processing time (e.g. "4ms").

Error Responses

StatusCondition
400Malformed JSON body.
422Validation failure — missing rules/text, both provided (XOR violation), values out of range, or invalid nodeTypes enum value.
500Internal server error (walker threw unexpectedly).

Examples

Walk a list and collect all modifiers:

Terminal window
curl -X POST https://bloqr-backend.jk-com.workers.dev/api/ast/walk \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"rules": [
"||example.org^$third-party,domain=example.net",
"@@||safe.example.com^$important"
],
"nodeTypes": ["Modifier"]
}'

Walk with full context, capped at depth 3:

Terminal window
curl -X POST https://bloqr-backend.jk-com.workers.dev/api/ast/walk \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"text": "||example.org^\nexample.com##.banner",
"maxDepth": 3,
"includeContext": true
}'

CLI

Three flags are added to the CLI in AST Walk mode. When --ast-walk is present, the compiler skips the normal compilation pipeline and instead parses the input, walks the AST, and outputs a JSON result to stdout.

Note: All log and debug output is redirected to stderr in this mode so that the JSON written to stdout is clean and pipeable.

Flags

FlagTypeDescription
--ast-walkbooleanActivate AST walk mode. Reads from --input <file> or stdin. Outputs { nodes, summary } JSON to stdout.
--ast-walk-types <type>string (repeatable)Restrict results to specific node types. May be repeated. See Node Types.
--ast-walk-depth <n>numberMaximum traversal depth (default: 50).

Examples

Walk all nodes from stdin:

Terminal window
echo '||example.org^$domain=foo.com' | deno run ... src/cli/CliApp.deno.ts --ast-walk

Filter to specific node types:

Terminal window
echo '||example.org^$domain=foo.com' | deno run ... src/cli/CliApp.deno.ts \
--ast-walk \
--ast-walk-types NetworkRule \
--ast-walk-types Modifier

Read from a file, cap traversal depth:

Terminal window
deno run ... src/cli/CliApp.deno.ts \
--ast-walk \
--input rules.txt \
--ast-walk-depth 3

Pipe JSON output to jq:

Terminal window
cat my-filter-list.txt | deno run ... src/cli/CliApp.deno.ts --ast-walk | jq '.summary'

Output Shape

{
"nodes": [
{ "type": "FilterList", "depth": 0, "key": null, "index": null, "node": { ... } },
{ "type": "NetworkRule", "depth": 1, "key": "children", "index": 0, "node": { ... } }
],
"summary": {
"FilterList": 1,
"NetworkRule": 1,
"total": 2
}
}

TypeScript / Library API

The walker is exposed via two entry-points in src/utils/:

walkAGTree(root, visitor)

The free function. Accepts a single Node or an array of nodes.

import { walkAGTree } from '../utils/AGTreeParser.ts';
// Generic callback — visits every node in pre-order
walkAGTree(filterList, (node, ctx) => {
console.log(ctx.depth, node.type);
});
// Halt traversal early by returning false
let firstNetworkRule: Node | undefined;
walkAGTree(filterList, (node) => {
if (node.type === 'NetworkRule') {
firstNetworkRule = node;
return false; // stop
}
});

AGTreeParser.walkDeep(root, visitor)

A convenience static wrapper around walkAGTree for discoverability. Identical in behaviour.

import { AGTreeParser } from '../utils/AGTreeParser.ts';
// Typed visitor map — only specified handlers are invoked
const excludedDomains: string[] = [];
AGTreeParser.walkDeep(filterList, {
Domain(d) {
if (d.exception) excludedDomains.push(d.value);
},
});
// Catch-all handler for any unspecified node types
AGTreeParser.walkDeep(filterList, {
Modifier(m) { /* ... */ },
'*'(node) { /* all other types */ },
});

WalkContext

Every visitor callback receives a WalkContext as its second argument:

export interface WalkContext {
/** The parent node, or null if this is the root. */
parent: Node | null;
/** The property name on the parent holding this node (e.g. 'children', 'body', 'modifiers'). */
key: string | null;
/** Zero-based index within the parent array property; null for non-array properties. */
index: number | null;
/** Zero-based traversal depth (root node = 0). */
depth: number;
}

AGTreeTypedVisitor

Pass a typed visitor map to invoke handlers only for nodes of specific types:

AGTreeParser.walkDeep(filterList, {
NetworkRule(node, ctx) { /* ... */ },
HostRule(node, ctx) { /* ... */ },
Modifier(node, ctx) { /* ... */ },
Domain(node, ctx) { /* ... */ },
ElementHidingRule(node, ctx) { /* ... */ },
ScriptletInjectionRule(node, ctx) { /* ... */ },
CommentRule(node, ctx) { /* ... */ },
PreProcessorCommentRule(node, ctx) { /* ... */ },
// ... etc.
'*'(node, ctx) { /* catch-all */ },
});

Return false from any handler to immediately halt the entire traversal.


Node Types

The following AGTree node type strings are recognised by the nodeTypes filter and the AGTreeTypedVisitor map:

CategoryNode Types
RootFilterList
Network rulesNetworkRule, HostRule, ModifierList, Modifier, HostnameList
Cosmetic rulesElementHidingRule, ElementHidingRuleBody, CssInjectionRule, CssInjectionRuleBody, ScriptletInjectionRule, ScriptletInjectionRuleBody, HtmlFilteringRule, JsInjectionRule, DomainList, Domain
Comment rulesCommentRule, MetadataCommentRule, HintCommentRule, ConfigCommentRule, ConfigNode, AgentCommentRule, PreProcessorCommentRule, EmptyRule
Structural / low-levelValue, ParameterList, Hint, Agent
Preprocessor expressionsOperator, Parenthesis, Variable
OtherApp, Method, StealthOption, InvalidRule

Adapter Interfaces (issue #1131)

The walker is part of a broader adapter layer that insulates the codebase from direct @adguard/agtree dependency. Four formal TypeScript interfaces are defined in src/utils/IAGTreeAdapter.ts:

InterfaceResponsibilityKey Methods
IFilterRuleParserParsingparse(), parseFilterList(), detectSyntax()
IFilterRuleConverterSyntax conversionconvertRuleText(), convertFilterListToAdg()
IFilterRuleGeneratorSerializationserialize(), serializeAll()
IAGTreeWalkerDeep AST traversalwalkDeep()

AGTreeParser is the sole concrete implementation of all four interfaces. All internal code must use AGTreeParser (or the free functions exported from src/utils/index.ts) — never import from @adguard/agtree directly.

See docs/api/AGTREE_INTEGRATION.md for the full AGTree integration overview.


Further Reading