Gathering detailed insights and metrics for @mihkeleidast/astray
Gathering detailed insights and metrics for @mihkeleidast/astray
npm install @mihkeleidast/astray
Typescript
Module System
Min. Node Version
Node Version
NPM Version
72.1
Supply Chain
98.9
Quality
75
Maintenance
100
Vulnerability
100
License
JavaScript (100%)
Total Downloads
223
Last Day
1
Last Week
2
Last Month
4
Last Year
64
188 Stars
64 Commits
8 Forks
5 Watching
2 Branches
2 Contributors
Minified
Minified + Gzipped
Latest Version
1.2.0
Package Id
@mihkeleidast/astray@1.2.0
Unpacked Size
31.14 kB
Size
9.07 kB
File Count
7
NPM Version
8.11.0
Node Version
16.16.0
Cumulative downloads
Total Downloads
Last day
0%
1
Compared to previous day
Last week
0%
2
Compared to previous week
Last month
-50%
4
Compared to previous month
Last year
-17.9%
64
Compared to previous year
2
7
2
A tiny (1.01 kB) and fast utility to walk an AST without being led astray.
$ npm install --save astray
1import { parse } from 'meriyah'; 2import * as astray from 'astray'; 3 4const AST = parse(` 5 const sum = (a, b) => a + b; 6 7 function square(a, b) { 8 return a * b; 9 } 10 11 function sqrt(num) { 12 let value = Math.sqrt(num); 13 console.log('square root is:', value); 14 return value; 15 } 16`) 17 18let ref, STATE = new Map; 19 20// Walk AST and find `let value` reference 21astray.walk(AST, { 22 Identifier(node, state) { 23 if (node.name === 'value') { 24 ref = node; 25 } else if (node.name === 'Math') { 26 state.set('Math', true); 27 } 28 }, 29 FunctionDeclaration: { 30 enter(node, state) { 31 state.set('Math', false); 32 }, 33 exit(node, state) { 34 console.log(`"${node.id.name}" used Math?`, state.get('Math')); 35 } 36 } 37}, STATE); 38 39//=> "square" used Math? false 40//=> "sqrt" used Math? true 41 42// What does `let value` see? 43const bindings = astray.lookup(ref); 44for (let key in bindings) { 45 console.log(`"${key}" ~> `, bindings[key]); 46} 47 48//=> "value" ~> { type: 'VariableDeclarator', ... } 49//=> "sqrt" ~> { type: 'FunctionDeclaration', ... } 50//=> "num" ~> { type: 'Identifier', ... } 51//=> "sum" ~> { type: 'VariableDeclarator', ... } 52//=> "square" ~> { type: 'FunctionDeclaration', ... }
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.
Type: any
The walker's starting node
. Its children will be traversed recursively against the visitor
definition.
Type: Visitor
The defined behavior for traversal. See Visitors for more.
Type: any
Required: false
Any state data to be shared or manipulated during traversal. When defined, all Visitors will have access to this value.
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.
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.
Type: any
The starting point — the node that's interested in learning what's available to it.
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.
Type: Boolean
Any Visitor may return this value to skip traversal of the current node's children.
Important: Trying to
SKIP
from anexit()
block will have no effect.
Type: Boolean
Any Visitor may return this value to remove this node from the tree.
Important: When the visitor's
exit()
block returnsREMOVE
, the node's children have already been walked.
Otherwise, returningREMOVE
fromenter()
or the named/base block will skip children traversal.
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:
1const STATE = {}; 2 3// via method 4astray.walk(tree, { 5 VariableDeclaration(node, state) { 6 // I entered `VariableDeclaration` node 7 assert.is(state === STATE, true); 8 } 9}); 10 11// via enter/exit hooks 12astray.walk(tree, { 13 VariableDeclaration: { 14 enter(node, state) { 15 // I entered `VariableDeclaration` node 16 assert.is(state === STATE, true); 17 }, 18 exit(node, state) { 19 // I exited `VariableDeclaration` node 20 assert.is(state === STATE, true); 21 } 22 } 23});
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.
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:
boolean
indicating that the bindings
dictionary is complete; aka, has seen all parentsImportant: Only parent contexts contain scope information.
These includeBlockStatement
,FunctionDeclaration
, andProgram
node types.
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 VariableDeclarator
s will be returned instead of the VariableDeclaration
that contained them. You may still access the VariableDeclaration
via the VariableDeclarator
s path context (node.path.parent
).
Here's a simple example:
1import { parse } from 'meriyah'; 2import * as astray from 'astray'; 3 4const source = ` 5 const API = 'https://...'; 6 7 function send(url, isGET) { 8 console.log('method:', isGET ? 'GET' : 'POST'); 9 console.log('URL:', API + url); 10 } 11 12 function Hello(props) { 13 var foobar = props.url || '/hello'; 14 send(foobar, true) 15 } 16`; 17 18let foobar; 19const AST = parse(source); 20 21// walk & find `var foobar` 22astray.walk(AST, { 23 Identifier(node) { 24 if (node.name === 'foobar') { 25 foobar = node; // save reference 26 } 27 } 28}); 29 30// get everything `foobar` can see 31const bindings = astray.lookup(foobar); 32 33for (let key in bindings) { 34 console.log(key, bindings[key].type); 35} 36 37//=> foobar VariableDeclarator 38//=> Hello FunctionDeclaration 39//=> props Identifier 40//=> API VariableDeclarator 41//=> send FunctionDeclaration
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 the41,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, butacorn-walk
still fails to count all identifiers. All exported methods (simple, full, recursive) returned the same value. Results are taken using anacorn
AST, although it fails using while traversing the ESTree fixture (estree.json
).
MIT © Luke Edwards
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
0 existing vulnerabilities detected
Reason
license file detected
Details
Reason
Found 1/30 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2025-01-13
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More