• Stars
    star
    177
  • Rank 208,292 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 4 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Walk an AST without being led astray

astray CI codecov

A tiny (1.01 kB) and fast utility to walk an AST without being led astray.

Install

$ npm install --save astray

Usage

import { parse } from 'meriyah';
import * as astray from 'astray';

const AST = parse(`
  const sum = (a, b) => a + b;

  function square(a, b) {
    return a * b;
  }

  function sqrt(num) {
    let value = Math.sqrt(num);
    console.log('square root is:', value);
    return value;
  }
`)

let ref, STATE = new Map;

// Walk AST and find `let value` reference
astray.walk(AST, {
  Identifier(node, state) {
    if (node.name === 'value') {
      ref = node;
    } else if (node.name === 'Math') {
      state.set('Math', true);
    }
  },
  FunctionDeclaration: {
    enter(node, state) {
      state.set('Math', false);
    },
    exit(node, state) {
      console.log(`"${node.id.name}" used Math?`, state.get('Math'));
    }
  }
}, STATE);

//=> "square" used Math? false
//=> "sqrt" used Math? true

// What does `let value` see?
const bindings = astray.lookup(ref);
for (let key in bindings) {
  console.log(`"${key}" ~> `, bindings[key]);
}

//=> "value" ~>  { type: 'VariableDeclarator', ... }
//=> "sqrt" ~>  { type: 'FunctionDeclaration', ... }
//=> "num" ~>  { type: 'Identifier', ... }
//=> "sum" ~>  { type: 'VariableDeclarator', ... }
//=> "square" ~>  { type: 'FunctionDeclaration', ... }

API

astray.walk<T, S, M>(node: T, visitor: Visitor<S, M>, state?: S, parent?: any)

Type: Function
Returns: Path<T> or T or undefined

Begin traversing an AST starting with node and using the visitor definition. You may optionally provide a state data object that each of the visitor methods can access and/or manipulate.

You may also define a parent, if known, for the starting node; however, this will likely be unknown most of the time.

If node is falsey, then astray.walk returns nothing.
If node is not an object, then the node itself is returned.
Otherwise, any other object/array value will be traversed and returned with an added Path context.

node

Type: any

The walker's starting node. Its children will be traversed recursively against the visitor definition.

visitor

Type: Visitor

The defined behavior for traversal. See Visitors for more.

state

Type: any
Required: false

Any state data to be shared or manipulated during traversal. When defined, all Visitors will have access to this value.

parent

Type: any
Required: false

The node's parent, if known.

Note: You will likely never need to define this!
In fact, astray.walk is recursive and sets/tracks this value as part of each node's Path Context.

astray.lookup<M, T>(node: T, target?: string)

Type: Function
Returns: Record<string, any>

Find all bindings that are accessible to this node by scaling its ancestry.

While doing so, each parent context container (eg, BlockStatement, FunctionDeclaration, or Program) is assigned its own cache of available bindings. See Path Context for more.

A dictionary of scopes are returned for the node. This will be an object whose keys are the identifier names and whose values are references to the nodes that the identifier points to.

Note: The return object will always include the node itself.

node

Type: any

The starting point — the node that's interested in learning what's available to it.

target

Type: string
Required: false

An optional target value that, if found, will immediately exit the ancestral lookup.
This should be the name of an identifier that your node is interested in, or the name of a parent container that you don't wish to exit.

astray.SKIP

Type: Boolean

Any Visitor may return this value to skip traversal of the current node's children.

Important: Trying to SKIP from an exit() block will have no effect.

astray.REMOVE

Type: Boolean

Any Visitor may return this value to remove this node from the tree.

Important: When the visitor's exit() block returns REMOVE, the node's children have already been walked.
Otherwise, returning REMOVE from enter() or the named/base block will skip children traversal.

Visitors

A "visitor" is a definition of behaviors/actions that should be invoked when a matching node's type is found.

The visitor keys can be of any (string) value – it's whatever types you expect to see!
By default, astray assumes you're dealing with the ESTree format (which is why the examples and TypeScript definitions reference ESTree types) but you are certainly not limited to this specification.

For example, if you want to target any VariableDeclaration nodes, you may do so like this:

const STATE = {};

// via method
astray.walk(tree, {
  VariableDeclaration(node, state) {
    // I entered `VariableDeclaration` node
    assert.is(state === STATE, true);
  }
});

// via enter/exit hooks
astray.walk(tree, {
  VariableDeclaration: {
    enter(node, state) {
      // I entered `VariableDeclaration` node
      assert.is(state === STATE, true);
    },
    exit(node, state) {
      // I exited `VariableDeclaration` node
      assert.is(state === STATE, true);
    }
  }
});

As you can see, the object-variant's enter() block is synonymous with the method-variant. (For simplicity, both formats will be referred to as the "enter" block.) However, an exit may only exist within the object-variant, forcing an existing method-variant to be converted into an enter key. When using the object-variant, the enter and exit keys are both optional – but at least one should exist, of course.

Regardless of the visitor's format, every method has access to the current node value as its first parameter. This is direct access to the tree's child, so any modification will mutate the value directly. Additionally, if you provided astray.walk() with a state value, that state is also passed to each visitor. This, too, allows you to directly mutate/modify your state object.

Anything that happens within the "enter" block happens before the node's children are traversed. In other words, you may alter the fate of this node's children. For example, returning the SKIP or REMOVE signals prevent your walker from ever seeing the children.

Anything that happens within the "exit" block happens after the node's children have been traversed. For example, because state is shared, you can use this opportunity to collect any state values/flags that the children may have provided. Again, since child traversal has already happened, returning the SKIP signal has no effect. Additionally, returning the REMOVE signal still remove the node and its children, but still allows you to know what was there.

Path Context

Any objects seen during traversal (astray.walk), even those that had no matching Visitors, receive a new path key. This is known as the "path context" – and will always have a parent key.

In cases where a node does not have a parent (eg, a Program), then node.path.parent will exist with undefined value.

When scaling a node's ancestry (astray.lookup), additional keys are added to its parents' contexts:

  • scoped — a dictionary of bindings owned by this node's context;
  • bindings — a dictionary of all bindings accessible by this node, including its own;
  • scanned — a boolean indicating that the bindings dictionary is complete; aka, has seen all parents

Important: Only parent contexts contain scope information.
These include BlockStatement, FunctionDeclaration, and Program node types.

Scopes

When using astray.lookup(), path contexts may obtain scope/binding information.
These are records of what each parent container provides (node.path.scoped) as well as what is accessible (node.path.bindings) to this scope level. Additionally, if a node/parent's entire ancestry has been recorded, then node.path.scanned will be true.

The records of bindings (including astray.lookup's return value) are objects keyed by the identifier names. The keys' values are references to the node that included/defined that identifier. For example, this means that VariableDeclarators will be returned instead of the VariableDeclaration that contained them. You may still access the VariableDeclaration via the VariableDeclarators path context (node.path.parent).

Here's a simple example:

import { parse } from 'meriyah';
import * as astray from 'astray';

const source = `
  const API = 'https://...';

  function send(url, isGET) {
    console.log('method:', isGET ? 'GET' : 'POST');
    console.log('URL:', API + url);
  }

  function Hello(props) {
    var foobar = props.url || '/hello';
    send(foobar, true)
  }
`;

let foobar;
const AST = parse(source);

// walk & find `var foobar`
astray.walk(AST, {
  Identifier(node) {
    if (node.name === 'foobar') {
      foobar = node; // save reference
    }
  }
});

// get everything `foobar` can see
const bindings = astray.lookup(foobar);

for (let key in bindings) {
  console.log(key, bindings[key].type);
}

//=> foobar VariableDeclarator
//=> Hello FunctionDeclaration
//=> props Identifier
//=> API VariableDeclarator
//=> send FunctionDeclaration

Benchmarks

Running on Node.js v10.13.1

Load Time

How long does it take to require the dependency?

@babel/traverse:  174.038ms
estree-walker:      0.711ms
acorn-walk:         1.329ms
ast-types:         31.591ms
astray:             0.544ms

Walking

All candidates traverse the pre-parsed AST (ESTree format, unless noted otherwise) of d3.min.js.
Each candidate must count the Identifier nodes seen as a validation step.

Validation:
  ✔ @babel/traverse ≠   (41,669 identifiers)
  ✔ estree-walker       (41,669 identifiers)
  ✘ acorn-walk †        (23,340 identifiers)
  ✔ ast-types           (41,669 identifiers)
  ✔ astray              (41,669 identifiers)

Benchmark:
  @babel/traverse ≠  x  12.25 ops/sec ± 5.46% (35 runs sampled)
  estree-walker      x 120.87 ops/sec ± 0.86% (79 runs sampled)
  acorn-walk †       x  81.49 ops/sec ± 0.76% (70 runs sampled)
  ast-types          x   4.77 ops/sec ±12.35% (16 runs sampled)
  astray             x 144.27 ops/sec ± 0.89% (81 runs sampled)

Notice:

Run $ cat bench/fixtures/estree.json | grep "Identifier" | wc -l to verify the 41,669 figure.

Babel does not follow the ESTree format. Instead @babel/traverse requires that @babel/parser be used in order for validation to pass.

Acorn does follow the ESTree format, but acorn-walk still fails to count all identifiers. All exported methods (simple, full, recursive) returned the same value. Results are taken using an acorn AST, although it fails using while traversing the ESTree fixture (estree.json).

License

MIT © Luke Edwards

More Repositories

1

clsx

A tiny (239B) utility for constructing `className` strings conditionally.
JavaScript
7,354
star
2

polka

A micro web server so fast, it'll make you dance! 👯
JavaScript
5,266
star
3

pwa

(WIP) Universal PWA Builder
JavaScript
3,123
star
4

uvu

uvu is an extremely fast and lightweight test runner for Node.js and the browser
JavaScript
2,938
star
5

taskr

A fast, concurrency-focused task automation tool.
JavaScript
2,528
star
6

sockette

The cutest little WebSocket wrapper! 🧦
JavaScript
2,398
star
7

worktop

The next generation web framework for Cloudflare Workers
TypeScript
1,635
star
8

kleur

The fastest Node.js library for formatting terminal text with ANSI colors~!
JavaScript
1,593
star
9

klona

A tiny (240B to 501B) and fast utility to "deep clone" Objects, Arrays, Dates, RegExps, and more!
JavaScript
1,537
star
10

dequal

A tiny (304B to 489B) utility to check for deep equality
JavaScript
1,271
star
11

tsm

TypeScript Module Loader
TypeScript
1,164
star
12

tinydate

A tiny (349B) reusable date formatter. Extremely fast!
JavaScript
1,060
star
13

sirv

An optimized middleware & CLI application for serving static files~!
JavaScript
1,023
star
14

sade

Smooth (CLI) Operator 🎶
JavaScript
1,009
star
15

rosetta

A general purpose internationalization library in 292 bytes
JavaScript
766
star
16

navaid

A navigation aid (aka, router) for the browser in 850 bytes~!
JavaScript
755
star
17

dset

A tiny (194B) utility for safely writing deep Object values~!
JavaScript
739
star
18

uid

A tiny (130B to 205B) and fast utility to generate random IDs of fixed length
JavaScript
634
star
19

httpie

A Node.js HTTP client as easy as pie! 🥧
JavaScript
575
star
20

ganalytics

A tiny (312B) client-side module for tracking with Google Analytics
JavaScript
575
star
21

trouter

🐟 A fast, small-but-mighty, familiar fish...errr, router*
JavaScript
563
star
22

regexparam

A tiny (394B) utility that converts route patterns into RegExp. Limited alternative to `path-to-regexp` 🙇‍♂️
JavaScript
555
star
23

dimport

Run ES Module syntax (`import`, `import()`, and `export`) in any browser – even IE!
JavaScript
547
star
24

mri

Quickly scan for CLI flags and arguments
JavaScript
533
star
25

tempura

A light, crispy, and delicious template engine 🍤
JavaScript
514
star
26

calendarize

A tiny (202B) utility to generate calendar views.
JavaScript
471
star
27

formee

A tiny (532B) library for handling <form> elements.
JavaScript
443
star
28

qss

A tiny (294b) browser utility for encoding & decoding a querystring.
JavaScript
408
star
29

preact-starter

Webpack3 boilerplate for building SPA / PWA / offline front-end apps with Preact
JavaScript
387
star
30

uuid

A tiny (~230B)and fast UUID (V4) generator for Node and the browser
JavaScript
386
star
31

vegemite

A Pub/Sub state manager you'll love... or hate
JavaScript
373
star
32

resolve.exports

A tiny (952b), correct, general-purpose, and configurable `"exports"` and `"imports"` resolver without file-system reliance
TypeScript
351
star
33

polkadot

The tiny HTTP server that gets out of your way! ・
JavaScript
322
star
34

matchit

Quickly parse & match URLs
JavaScript
321
star
35

fetch-event-stream

A tiny (736b) utility for Server Sent Event (SSE) streaming via `fetch` and Web Streams API
TypeScript
321
star
36

flru

A tiny (215B) and fast Least Recently Used (LRU) cache
JavaScript
310
star
37

mrmime

A tiny (2.8kB) and fast utility for getting a MIME type from an extension or filename
TypeScript
300
star
38

watchlist

Recursively watch a list of directories & run a command on any file system changes
JavaScript
261
star
39

arr

A collection of tiny, highly performant Array.prototype alternatives
JavaScript
255
star
40

ley

(WIP) Driver-agnostic database migrations
JavaScript
254
star
41

flattie

A tiny (203B) and fast utility to flatten an object with customizable glue
JavaScript
254
star
42

webpack-messages

Beautifully format Webpack messages throughout your bundle lifecycle(s)!
JavaScript
246
star
43

obj-str

A tiny (96B) library for serializing Object values to Strings.
JavaScript
225
star
44

templite

Lightweight templating in 150 bytes
JavaScript
221
star
45

ms

A tiny (414B) and fast utility to convert milliseconds to and from strings.
JavaScript
200
star
46

nestie

A tiny (215B) and fast utility to expand a flattened object
JavaScript
199
star
47

throttles

A tiny (139B to 204B) utility to regulate the execution rate of your functions
JavaScript
195
star
48

hexoid

A tiny (190B) and extremely fast utility to generate random IDs of fixed length
JavaScript
185
star
49

fromnow

A tiny (339B) utility for human-readable time differences between now and past or future dates.
JavaScript
178
star
50

tmp-cache

A least-recently-used cache in 35 lines of code~!
JavaScript
174
star
51

bundt

A simple bundler for your delicious modules
JavaScript
167
star
52

wrr

A tiny (148B) weighted round robin utility
JavaScript
164
star
53

freshie

(WIP) A fresh take on building universal applications with support for pluggable frontends and backends.
TypeScript
153
star
54

svelte-ssr-worker

A quick demo for rendering Svelte server-side (SSR), but within a Cloudflare Worker!
JavaScript
152
star
55

totalist

A tiny (195B to 224B) utility to recursively list all (total) files in a directory
JavaScript
148
star
56

typescript-module

Template repository for authoring npm modules via TypeScript
TypeScript
141
star
57

sublet

Reactive leases for data subscriptions
JavaScript
139
star
58

escalade

A tiny (183B to 210B) and fast utility to ascend parent directories
JavaScript
138
star
59

webpack-route-manifest

Generate an asset manifest file, keyed by route patterns!
JavaScript
127
star
60

url-shim

A 1.5kB browser polyfill for the Node.js `URL` and `URLSearchParams` classes.
JavaScript
123
star
61

semiver

A tiny (153B) utility to compare semver strings.
JavaScript
119
star
62

cfw

(WIP) A build and deploy utility for Cloudflare Workers.
TypeScript
113
star
63

svelte-demo

Multi-page demo built Svelte 3.x and Rollup with code-splitting
Svelte
113
star
64

webpack-format-messages

Beautiful formatting for Webpack messages; ported from Create React App!
JavaScript
111
star
65

gittar

🎸 Download and/or Extract git repositories (GitHub, GitLab, BitBucket). Cross-platform and Offline-first!
JavaScript
111
star
66

saturated

A tiny (203B) utility to enqueue items for batch processing and/or satisfying rate limits.
JavaScript
110
star
67

webpack-critical

Extracts & inlines Critical CSS with Wepack
JavaScript
109
star
68

pages-fullstack

Demo SvelteKit application running on Cloudflare Pages
Svelte
102
star
69

sort-isostring

A tiny (110B) and fast utility to sort ISO 8601 Date strings
JavaScript
98
star
70

colors-app

🎨 A PWA for copying values from popular color palettes. Supports HEX, RGB, and HSL formats.
JavaScript
95
star
71

salteen

A snappy and lightweight (259B) utility to encrypt and decrypt values with salt.
JavaScript
95
star
72

is-offline

A tiny (174B) library to detect `offline` status & respond to changes in the browser.
JavaScript
91
star
73

seolint

(WIP) A robust and configurable SEO linter
TypeScript
86
star
74

rafps

A tiny (178B) helper for playing, pausing, and setting `requestAnimationFrame` frame rates
JavaScript
82
star
75

preact-cli-ssr

A quick demo for adding SSR to a Preact CLI app
JavaScript
79
star
76

webpack-modules

Handle `.mjs` files correctly within webpack
JavaScript
71
star
77

csprng

A tiny (~90B) isomorphic wrapper for `crypto.randomBytes` in Node.js and browsers.
JavaScript
66
star
78

classico

A tiny (255B) shim when Element.classList cannot be used~!
JavaScript
62
star
79

premove

A tiny (201B to 247B) utility to remove items recursively
JavaScript
62
star
80

mk-dirs

A tiny (381B to 419B) utility to make a directory and its parents, recursively
JavaScript
54
star
81

primeval

A tiny (128B) utility to check if a value is a prime number
JavaScript
51
star
82

loadr

Quickly attach multiple ESM Loaders and/or Require Hooks together but without the repetitive `--experimental-loader` and/or `--require` Node flags
JavaScript
49
star
83

preact-progress

Simple and lightweight (~590 bytes gzip) progress bar component for Preact
JavaScript
49
star
84

route-manifest

A tiny (412B) runtime to retrieve the correct entry from a Route Manifest file.
JavaScript
46
star
85

svelte-preprocess-esbuild

A Svelte Preprocessor to compile TypeScript via esbuild!
TypeScript
44
star
86

preact-scroll-header

A (800b gzip) header that will show/hide while scrolling for Preact
JavaScript
41
star
87

inferno-starter

Webpack2 boilerplate for building SPA / PWA / offline front-end apps with Inferno.js
JavaScript
41
star
88

rollup-route-manifest

A Rollup plugin to generate an asset manifest, keyed by route patterns ("route manifest")
JavaScript
40
star
89

onloaded

A tiny (350B) library to detect when images have loaded.
JavaScript
38
star
90

webpack-plugin-replace

Replace content while bundling.
JavaScript
36
star
91

route-sort

A tiny (200B) utility to sort route patterns by specificity
JavaScript
35
star
92

scorta

A tiny (330B to 357B) and fast utility to find a package's hidden supply / cache directory.
JavaScript
33
star
93

local-hostname

A tiny (171B) utility to check if a hostname is local
JavaScript
32
star
94

taskr-outdated

A generator & coroutine-based task runner. Fasten your seatbelt. 🚀
JavaScript
32
star
95

rewrite-imports

Rewrite `import` statements as `require()`s; via RegExp
JavaScript
31
star
96

preact-offline

A (300b gzip) component to render alerts/messages when offline.
JavaScript
29
star
97

fly-kit-preact

A starter kit for building offline / SPA / PWA apps with Preact
JavaScript
28
star
98

fannypack

The tool belt for front-end developers
JavaScript
28
star
99

is-ready

A tiny (309B) library to detect when `window` globals are defined and ready to use~!
JavaScript
28
star
100

ava-http

Async HTTP request wrapper 🚀
JavaScript
25
star