AST Walk
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>"}| Field | Type | Required | Description |
|---|---|---|---|
rules | string[] | One of rules/text | Array of raw rule strings. Max 5,000 items. Each rule ≤ 4,096 chars. |
text | string | One of rules/text | Full filter list as a single newline-separated string. Max 1 MiB. |
nodeTypes | string[] | No | Restrict results to specific AGTree node types (see Node Types). Max 30. |
maxDepth | integer | No | Maximum traversal depth (0–50, inclusive). Default: 50. Nodes deeper than this are silently skipped. |
includeContext | boolean | No | When true, each result node includes key and index context fields. Default: false. |
turnstileToken | string | No | Optional 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"}| Field | Type | Description |
|---|---|---|
success | boolean | Always true on 200. |
nodes | WalkResultNode[] | Flat list of every visited node in pre-order. |
nodes[].type | string | AGTree node type discriminant (e.g. NetworkRule). |
nodes[].depth | integer | Traversal depth — 0 is the FilterList root, rule children start at 1. |
nodes[].key | string|null | (Only when includeContext: true) Property name on parent (e.g. "children", "modifiers"). |
nodes[].index | integer|null | (Only when includeContext: true) Zero-based array index within the parent collection, or null for non-array properties. |
nodes[].node | object | Full AGTree AST node object. |
summary | Record<string, number> | Per-type node counts plus total. |
duration | string | Server-side processing time (e.g. "4ms"). |
Error Responses
| Status | Condition |
|---|---|
400 | Malformed JSON body. |
422 | Validation failure — missing rules/text, both provided (XOR violation), values out of range, or invalid nodeTypes enum value. |
500 | Internal server error (walker threw unexpectedly). |
Examples
Walk a list and collect all modifiers:
curl -X POST https://api.bloqr.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:
curl -X POST https://api.bloqr.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
| Flag | Type | Description |
|---|---|---|
--ast-walk | boolean | Activate 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> | number | Maximum traversal depth (default: 50). |
Examples
Walk all nodes from stdin:
echo '||example.org^$domain=foo.com' | deno run ... src/cli/CliApp.deno.ts --ast-walkFilter to specific node types:
echo '||example.org^$domain=foo.com' | deno run ... src/cli/CliApp.deno.ts \ --ast-walk \ --ast-walk-types NetworkRule \ --ast-walk-types ModifierRead from a file, cap traversal depth:
deno run ... src/cli/CliApp.deno.ts \ --ast-walk \ --input rules.txt \ --ast-walk-depth 3Pipe JSON output to jq:
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-orderwalkAGTree(filterList, (node, ctx) => { console.log(ctx.depth, node.type);});
// Halt traversal early by returning falselet 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 invokedconst excludedDomains: string[] = [];AGTreeParser.walkDeep(filterList, { Domain(d) { if (d.exception) excludedDomains.push(d.value); },});
// Catch-all handler for any unspecified node typesAGTreeParser.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:
| Category | Node Types |
|---|---|
| Root | FilterList |
| Network rules | NetworkRule, HostRule, ModifierList, Modifier, HostnameList |
| Cosmetic rules | ElementHidingRule, ElementHidingRuleBody, CssInjectionRule, CssInjectionRuleBody, ScriptletInjectionRule, ScriptletInjectionRuleBody, HtmlFilteringRule, JsInjectionRule, DomainList, Domain |
| Comment rules | CommentRule, MetadataCommentRule, HintCommentRule, ConfigCommentRule, ConfigNode, AgentCommentRule, PreProcessorCommentRule, EmptyRule |
| Structural / low-level | Value, ParameterList, Hint, Agent |
| Preprocessor expressions | Operator, Parenthesis, Variable |
| Other | App, 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:
| Interface | Responsibility | Key Methods |
|---|---|---|
IFilterRuleParser | Parsing | parse(), parseFilterList(), detectSyntax() |
IFilterRuleConverter | Syntax conversion | convertRuleText(), convertFilterListToAdg() |
IFilterRuleGenerator | Serialization | serialize(), serializeAll() |
IAGTreeWalker | Deep AST traversal | walkDeep() |
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
- AGTree Integration — Overview of the
@adguard/agtreeintegration - CLI Reference — Full CLI flag reference
- API Reference — All REST API endpoints
- Zod Validation — How request validation works
Bloqr AI™ — The privacy you didn't know you needed.
© 2026 Bloqr AI™, a trademark of Bloqr Systems™. Created by Bloqr Systems™, founded by Jayson Knight.
Internet Hygiene (n.) — the ongoing practices that keep your digital life clean, private, and safe.
Our product repos live in the BloqrAI org, part of the Bloqr Systems GitHub Enterprise. Product repos are internal-visibility — enterprise membership is required to view them.