Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

backOpen Source→ast-monkey-traverse-with-lookahead

ast-monkey-traverse-with-lookahead4.2.5

Utility library to traverse AST, reports upcoming values

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • MIGRATION TO…
  • PURPOSE
  • API — TRAVERSE()
  • API — VERSION
  • API — TYPES
  • A MINIMAL EXA…
  • THE CALLBACK…
  • LOOKING INTO…
  • STOPPING
  • WHY THIS PROG…
  • Changelog

This package is retired. The published package, documentation, examples, and changelog remain available.

Migration to ast-monkey

This standalone package is retired. ast-monkey@10.0.0 includes the observer as traverseWithLookahead, with the lightweight ast-monkey/lookahead entry point. The documentation below describes the archived ast-monkey-traverse-with-lookahead@4.2.5 release. Its published files, examples, and changelog remain available.

Install the maintained package:

npm install ast-monkey@^10.0.0

Preserve existing call sites with an import alias:

import { traverseWithLookahead as traverse } from "ast-monkey/lookahead";

Keep the callback and lookahead argument unchanged. Callback return values are ignored, and callbacks can still mutate original subtrees. Sparse holes, detached parent clones, upcoming tuples, and buffered stopping retain their existing behavior. Validation errors use ast-monkey/traverseWithLookahead(): [THROW_ID_XX] instead of the old package prefix. Exceptions thrown by callbacks remain unchanged.

Rename type imports to LookaheadCallback, LookaheadInnerObj, LookaheadNextToken, and LookaheadObj, respectively, or alias them to the old names locally. See the consolidated migration guide for entry-point choices and the transformation API.

The published standalone browser artifact remains ast-monkey-traverse-with-lookahead.umd.js, with the global astMonkeyTraverseWithLookahead.

Purpose

Observe depth-first tree visits and preview upcoming visits. The observer ignores callback return values and returns undefined. Use the transformer when callback returns must replace or delete values in a cloned tree.

API — traverse()

The main function traverse() is imported like this:

It’s a function which takes three input arguments:

where the callback has the following API:

This function traverses AST tree given to it in the first input argument. You use it via a callback, similar way to Array.forEach().

The function returns undefined (because it’s operated via callbacks).

API — version

You can import version:

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
Callback
Type: Callback
CallbackThe signature of the callback — it receives the key, the value, an InnerObj and a stop object.
InnerObj
Type: InnerObj
InnerObjThe third callback argument. Unlike the plain traverser, it also carries next, the lookahead tokens.
NextToken
Type: NextToken
NextTokenOne lookahead entry under innerObj.next — a [key, value, innerObj] tuple.
Obj
Type: Obj
ObjA plain object with string keys and values of any type.
import type { Callback, InnerObj, NextToken, Obj } from "ast-monkey-traverse-with-lookahead";

A minimal example

import { traverse } from "ast-monkey-traverse-with-lookahead";
var ast = [{ a: "a", b: "b" }];
traverse(ast, (key, val, innerObj, stop) => {
  console.log(`key = ${JSON.stringify(key, null, 4)}`);
  console.log(`val = ${JSON.stringify(val, null, 4)}`);
  console.log(`innerObj = ${JSON.stringify(innerObj, null, 4)}`);
});

Unlike the transformer, this observer ignores callback return values, so you do not need to return anything. Callback values can reference original subtrees: mutating them can change your input.

The callback interface

When you call traverse() like this:

traverse(input, function (key, val, innerObj, stop) {
  ...
})

you get four variables:

  • key
  • val
  • innerObj
  • stop — set stop.now = true; to stop collecting visits; already buffered callbacks still run

If traverse() is currently traversing a plain object, going each key/value pair, key will be the object’s current key and val will be the value. If traverse() is currently traversing an array, going through all elements, a key will be the current element and val will be undefined. An object property can also have an undefined value, so use innerObj.parentType to distinguish objects from arrays.

innerObj object’s keyTypeDescription
depth
Type: Integer number
depthInteger numberZero is the root, topmost level. Every level deeper increments depth by 1.
path
Type: String
pathStringThe path to the current value. The path uses object-path notation.
topmostKey
Type: String
topmostKeyStringWhen you are very deep, this is the topmost parent’s key.
parent
Type: Type of the parent of the current element being traversed
parentType of the parent of the current element being traversedA whole parent (array or a plain object) which contains the current element. Its purpose is to allow you to query the siblings of the current element.
parentType
Type: String
parentTypeStringEither array if parent is array or object if parent is a plain object (not the “object” type, which includes functions, arrays etc.).
next
Type: Array
nextArrayZero or more arrays, each representing a set of callback call arguments that will be reported next.

Looking into the future

The whole point of this program is being able to “see” the future. Otherwise, you could be using vanilla ast-monkey-traverse.

You can request how many sets from the future you want to have reported using the third argument, for example:

const gathered = [];
traverse(
  input,
  (key1, val1, innerObj) => {
    gathered.push([key1, val1, innerObj]);
  },
  2, // <---------------- ! lookahead
);

The innerObj.next array contains up to that many upcoming depth-first visits. These visits can include descendants and need not be siblings. Each entry is a [key, value, innerObj] tuple. The root itself does not receive a callback; sparse array holes do.

For example, consider this AST:

const ast = [
  {
    a: "b",
  },
  {
    c: "d",
  },
  {
    e: "f",
  },
];

If you didn’t request lookahead, if it’s default zero, and you traversed it simply pushing all inputs into an array:

const gathered = [];
traverse(
  input,
  (key1, val1, innerObj) => {
    gathered.push([key1, val1, innerObj]);
  },
  0, // <--- hardcoded lookahead, zero sets requested from the future, but it can be omitted
);

You’d get gathered populated with:

[
  // ===================
  [
    {
      a: "b",
    },
    null,
    {
      depth: 0,
      path: "0",
      parent: [
        {
          a: "b",
        },
        {
          c: "d",
        },
        {
          e: "f",
        },
      ],
      parentType: "array",
      next: [],
    },
  ],
  // ===================
  [
    "a",
    "b",
    {
      depth: 1,
      path: "0.a",
      parent: {
        a: "b",
      },
      parentType: "object",
      next: [],
    },
  ],
  // ===================
  ...

Notice above, next: [] is empty. No future sets are reported.

But, if you request the next set to be reported:

const gathered = [];
traverse(
  input,
  (key1, val1, innerObj) => {
    gathered.push([key1, val1, innerObj]);
  },
  1, // <---------------- ! lookahead
);

You’d get gathered populated with:

[
  // ===================
  [
    {
      a: "b",
    },
    null,
    {
      depth: 0,
      path: "0",
      parent: [
        {
          a: "b",
        },
        {
          c: "d",
        },
        {
          e: "f",
        },
      ],
      parentType: "array",
      next: [
        [
          "a",
          "b",
          {
            depth: 1,
            path: "0.a",
            parent: {
              a: "b",
            },
            parentType: "object",
          },
        ],
      ],
    },
  ],
  // ===================
  [
    "a",
    "b",
    {
      depth: 1,
      path: "0.a",
      parent: {
        a: "b",
      },
      parentType: "object",
      next: [
        [
          {
            c: "d",
          },
          null,
          {
            depth: 0,
            path: "1",
            parent: [
              {
                a: "b",
              },
              {
                c: "d",
              },
              {
                e: "f",
              },
            ],
            parentType: "array",
          },
        ],
      ],
    },
  ],
  // ===================
  ...

Notice how innerObj.next is reporting one set from the future, as you requested.

Stopping

Here’s how to stop the traversal. Let’s gather all the traversed paths first. By the way, paths are marked in object-path notation (arrays use dots too, a.1.b instead of a[1].b).

import { traverse } from "ast-monkey-traverse-with-lookahead";
const input = { a: "1", b: { c: "2" } };
const gathered = [];
traverse(input, (key1, val1, innerObj) => {
  const current = val1 !== undefined ? val1 : key1;
  gathered.push(innerObj.path);
  return current;
});
console.log(gathered);
// => ["a", "b", "b.c"]

All paths were gathered: ["a", "b", "b.c"].

Now let’s make the monkey to stop at the path “b”:

import { traverse } from "ast-monkey-traverse-with-lookahead";
const input = { a: "1", b: { c: "2" } };
const gathered = [];
traverse(input, (key1, val1, innerObj, stop) => {
  const current = val1 !== undefined ? val1 : key1;
  gathered.push(innerObj.path);
  if (innerObj.path === "b") {
    stop.now = true; // <---------------- !!!!!!!!!!
  }
  return current;
});
console.log(gathered);
// => ["a", "b"]

Notice how there were no more gathered paths after “b”, only ["a", "b"].

Stopping is not affected by lookahead, it does not matter how many sets from the future you request, a stopping will happen in the right place.

Why this program is read-only

This program is aimed at AST traversal, for example, to be used in codsen-parser, to enable the parser to patch up AST errors. When parser sees something wrong, it needs to see the next AST node to make a decision: is it something missing or is it something rogue what should be skipped?

Normally, people don’t mutate the AST — if you do need to delete something from it, note the path and perform the operation outside of the traversal.

On the other hand, the deletion feature impacts performance. That’s why we made this program read-only.

Permalink to changelogChangelog

Open Changelog
↑ back to top
back 

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab