acornwalk
A pure Python semantic walker for ECMAScript ESTree ASTs, targeting node shapes used by ECMAScript 2026 and the explicit JSX extension. Python 3.11+; no runtime dependencies. Native parser objects and deserialized mappings are accepted without copying nodes or mutating the AST.
python -m pip install "acornwalk>=2,<3"
The 2.x release preserves the import and camelCase APIs and requires Python
3.11 or newer. It has no runtime dependencies. For native parser ASTs, install
esprima-fork 5.x separately (imported as esprima) and request range=True
when using position searches. See the
migration notes
for the changes to searches, callback state and result objects.
import acornwalk as walk
ast = {"type": "Program", "range": [0, 2], "body": [
{"type": "ExpressionStatement", "range": [0, 2], "expression":
{"type": "Identifier", "name": "x", "range": [0, 1]}}
]}
names = []
walk.simple(ast, {"Identifier": lambda node, state: state.append(node["name"])}, state=names)
assert names == ["x"]
assert walk.findNodeAt(ast, 0, 1, "Identifier").node is ast["body"][0]["expression"]
| API | Callback or result |
|---|---|
simple(node, visitors, baseVisitor=None, state=None, override=None) |
Postorder visitor(node, state) |
ancestor(node, visitors, baseVisitor=None, state=None, override=None) |
Postorder visitor(node, state, ancestors) |
recursive(node, state, funcs, baseVisitor=None, override=None) |
visitor(node, state, recurse) controls child recursion |
full(node, callback, baseVisitor=None, state=None, override=None) |
callback(node, state, category) |
fullAncestor(node, callback, baseVisitor=None, state=None, override=None) |
callback(node, state, ancestors, category) |
make(funcs, baseVisitor=None) |
Independent handler overlay |
findNodeAt(node, start, end, test=None, baseVisitor=None, state=None) |
Innermost exact range match; None bounds are wildcards |
findNodeAround(node, pos, test=None, baseVisitor=None, state=None) |
Innermost containing match, including end boundary |
findNodeAfter(node, pos, test=None, baseVisitor=None, state=None) |
First outermost match starting at/after pos |
findNodeBefore(node, pos, test=None, baseVisitor=None, state=None) |
Greatest end at/before pos; ties favor outer nodes |
Searches return Found(node, state) or None. Tests are node type strings,
None for any type, or (category, node) -> bool callables. Callback exceptions
propagate. Ancestor stacks contain the current node and are reused; copy the
list when retaining it. State None supplies ancestors as state for ancestor
walkers. Explicit False, 0, "", {} and [] remain the supplied state.
seen = []
walk.fullAncestor(ast, lambda node, state, stack, category:
seen.append((walk.getField(node, "type"), len(stack))))
walk.recursive(ast, names, {"Identifier": lambda node, state, recurse:
state.append(walk.getField(node, "name"))})
base = walk.make({"MyExtension": lambda node, state, recurse:
recurse(walk.getField(node, "argument"), state, "Expression")})
walk.simple({"type": "MyExtension", "argument": ast["body"][0]["expression"]}, {}, base)
Visitor keys may be concrete types or categories such as Expression,
Statement, Pattern and VariablePattern. Bindings use VariablePattern;
ordinary references use Identifier. Noncomputed keys, labels, import binding
metadata, export specifier metadata and meta-property names are skipped as
evaluated references. Import attributes visit their value, not their key.
This is semantic traversal, not an exhaustive serialization visitor.
Custom bases may be mappings, Base objects or overlays. Instance and subclass
overrides are honored. Base handlers have the unbound signature
(node, state, recurse); subclass handlers should use that signature or
@staticmethod. make does not mutate the supplied base. Unknown types raise
ValueError naming the type and the extension mechanism.
Positions are nonnegative integer UTF-16 code-unit offsets. Searches use
start/end or two-element range; start/end take precedence. Astral characters
occupy two units. Offsets are read as supplied, not converted from Python scalar
indices. With esprima-fork, request range=True. Missing/invalid positions
raise ValueError. nodeRange(node) exposes this accessor. getField(node, name, default=None) reads mapping/attribute fields and native isAsync/async_
and allowAwait/await_ aliases. An explicit canonical mapping field takes precedence.
Missing native fields honor the supplied default even for esprima's permissive
attribute lookup; declared null fields retain None.
Modern traversal covers object rest/spread, async functions/generators,
for-await metadata, chains, private names, public/private fields, static blocks,
module attributes and dynamic import options. Operators and literal metadata
use ordinary expression/leaf shapes. Legacy FieldDefinition is accepted as a
PropertyDefinition alias. Explicit JSX handlers cover elements, fragments,
names, attributes, expression containers, spread attributes/children and empty
expressions. JSX names keep their JSX-specific node types.
TypeScript, Flow, decorators and other proposal nodes require custom visitors. The walker does not validate grammar or editions. Mechanically walking an unknown declaration kind with existing node shapes does not advertise parser support for proposals. Circular graphs and excessive recursion depth are outside the AST contract.
See contributor instructions
and CI results.
Core parity is pinned
to acorn-walk in acorn 8.15.0.
Python differences include None state handling, mappings/ranges, validated
offsets, native aliases and the documented JSX extension.
Performance measurements compare the original, modern starting and current versions, including the old destructuring omission and remaining native validation cost.
Release automation and version bumps are documented in RELEASING.md.
Metadata
Release files for acornwalk 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| acornwalk-2.0.0.tar.gz | 49.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| acornwalk-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.1 kB
Release files / acornwalk-2.0.0.tar.gz
| Download URL | acornwalk-2.0.0.tar.gz |
|---|---|
| Size | 49.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0379d4b18f4aaa306831b12a72f5f66f2839d65d1ab8b21f5daad17d3dc82759
|
|
BLAKE2b-256 checksum How to use checksums |
7a5dee10ff07034d2445daacb08b325119af68b084f160fe3c9b548a5b1e151f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / acornwalk-2.0.0-py3-none-any.whl
| Download URL | acornwalk-2.0.0-py3-none-any.whl |
|---|---|
| Size | 13.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
32da5ca532bf844a28da887a8b6f92888b860c21c8f4703f405c4c63d1d0bc8d
|
|
BLAKE2b-256 checksum How to use checksums |
b12b612bca05508d94d3909872a92daf43637cdce99b27f49adf172161e27357
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for acornwalk 2.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.