Socket
Socket
Sign inDemoInstall

ast-types

Package Overview
Dependencies
Maintainers
1
Versions
172
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ast-types - npm Package Compare versions

Comparing version 0.4.5 to 0.4.7

17

lib/scope.js

@@ -5,3 +5,2 @@ var assert = require("assert");

var namedTypes = types.namedTypes;
var builders = types.builders;
var Node = namedTypes.Node;

@@ -66,3 +65,12 @@ var isArray = types.builtInTypes.array;

Sp.declareTemporary = function(prefix) {
assert.ok(/^[a-z$_]/i.test(prefix), prefix);
if (prefix) {
assert.ok(/^[a-z$_]/i.test(prefix), prefix);
} else {
prefix = "t$";
}
// Include this.depth in the name to make sure the name does not
// collide with any variables in nested/enclosing scopes.
prefix += this.depth.toString(36) + "$";
this.scan();

@@ -75,5 +83,4 @@

var id = builders.identifier(prefix + index);
this.bindings[prefix + index] = id;
return id;
var name = prefix + index;
return this.bindings[name] = types.builders.identifier(name);
};

@@ -80,0 +87,0 @@

@@ -21,3 +21,3 @@ {

],
"version": "0.4.5",
"version": "0.4.7",
"homepage": "http://github.com/benjamn/ast-types",

@@ -24,0 +24,0 @@ "repository": {

@@ -60,4 +60,48 @@ AST Types

Here's how you might iterate over the fields of an arbitrary AST node:
If you want complete control over the traversal, and all you need is a way
of enumerating the known fields of your AST nodes and getting their
values, you may be interested in the primitives `getFieldNames` and
`getFieldValue`:
```js
var types = require("ast-types");
var partialFunExpr = { type: "FunctionExpression" };
// Even though partialFunExpr doesn't actually contain all the fields that
// are expected for a FunctionExpression, types.getFieldNames knows:
console.log(types.getFieldNames(partialFunExpr));
// [ 'type', 'id', 'params', 'body', 'generator', 'expression',
// 'defaults', 'rest', 'async' ]
// For fields that have default values, types.getFieldValue will return
// the default if the field is not actually defined.
console.log(types.getFieldValue(partialFunExpr, "generator"));
// false
```
Two more low-level helper functions, `eachField` and `someField`, are
defined in terms of `getFieldNames` and `getFieldValue`:
```js
// Iterate over all defined fields of an object, including those missing
// or undefined, passing each field name and effective value (as returned
// by getFieldValue) to the callback. If the object has no corresponding
// Def, the callback will never be called.
exports.eachField = function(object, callback, context) {
getFieldNames(object).forEach(function(name) {
callback.call(this, name, getFieldValue(object, name));
}, context);
};
// Similar to eachField, except that iteration stops as soon as the
// callback returns a truthy value. Like Array.prototype.some, the final
// result is either true or false to indicates whether the callback
// returned true for any element or not.
exports.someField = function(object, callback, context) {
return getFieldNames(object).some(function(name) {
return callback.call(this, name, getFieldValue(object, name));
}, context);
};
```
So here's how you might make a copy of an AST node:
```js
var copy = {};

@@ -72,18 +116,31 @@ require("ast-types").eachField(node, function(name, value) {

If you want to perform a depth-first traversal of the entire AST,
that's also easy:
But that's not all! You can also easily visit entire syntax trees using
the powerful `types.visit` abstraction.
Here's a trivial example of how you might assert that `arguments.callee`
is never used in `ast`:
```js
var assert = require("assert");
var types = require("ast-types");
var Literal = types.namedTypes.Literal;
var isString = types.builtInTypes.string;
var stringCounts = {};
var n = types.namedTypes;
// Count the occurrences of all the string literals in this AST.
require("ast-types").traverse(ast, function(node) {
if (Literal.check(node) && isString.check(node.value)) {
if (stringCounts.hasOwnProperty(node.value)) {
stringCounts[node.value] += 1;
} else {
stringCounts[node.value] = 1;
types.visit(ast, {
// This method will be called for any node with .type "MemberExpression":
visitMemberExpression: function(path) {
// Visitor methods receive a single argument, a NodePath object
// wrapping the node of interest.
var node = path.node;
if (n.Identifier.check(node.object) &&
node.object.name === "arguments" &&
n.Identifier.check(node.property)) {
assert.notStrictEqual(node.property.name, "callee");
}
// It's your responsibility to call this.traverse with some
// NodePath object (usually the one passed into the visitor
// method) before the visitor method returns, or return false to
// indicate that the traversal need not continue any further down
// this subtree.
this.traverse(path);
}

@@ -93,60 +150,269 @@ });

Here's an slightly deeper example demonstrating how to ignore certain
subtrees and inspect the node's ancestors:
Here's a slightly more involved example of transforming `...rest`
parameters into browser-runnable ES5 JavaScript:
```js
var types = require("ast-types");
var namedTypes = types.namedTypes;
var isString = types.builtInTypes.string;
var thisProperties = {};
var b = types.builders;
// Populate thisProperties with every property name accessed via
// this.name or this["name"] in the current scope.
types.traverse(ast, function(node) {
// Don't descend into new function scopes.
if (namedTypes.FunctionExpression.check(node) ||
namedTypes.FunctionDeclaration.check(node)) {
// Return false to stop traversing this subtree without aborting
// the entire traversal.
return false;
}
// Reuse the same AST structure for Array.prototype.slice.call.
var sliceExpr = b.memberExpression(
b.memberExpression(
b.memberExpression(
b.identifier("Array"),
b.identifier("prototype"),
false
),
b.identifier("slice"),
false
),
b.identifier("call"),
false
);
// If node is a ThisExpression that happens to be the .object of a
// MemberExpression, then we're interested in the .property of the
// MemberExpression. We could have inverted this test to find
// MemberExpressions whose .object is a ThisExpression, but I wanted
// to demonstrate the use of this.parent.
if (namedTypes.ThisExpression.check(node) &&
namedTypes.MemberExpression.check(this.parent.node) &&
this.parent.node.object === node) {
types.visit(ast, {
// This method will be called for any node whose type is a subtype of
// Function (e.g., FunctionDeclaration, FunctionExpression, and
// ArrowFunctionExpression). Note that types.visit precomputes a
// lookup table from every known type to the appropriate visitor
// method to call for nodes of that type, so the dispatch takes
// constant time.
visitFunction: function(path) {
// Visitor methods receive a single argument, a NodePath object
// wrapping the node of interest.
var node = path.node;
var property = this.parent.node.property;
// It's your responsibility to call this.traverse with some
// NodePath object (usually the one passed into the visitor
// method) before the visitor method returns, or return false to
// indicate that the traversal need not continue any further down
// this subtree. An assertion will fail if you forget, which is
// awesome, because it means you will never again make the
// disastrous mistake of forgetting to traverse a subtree. Also
// cool: because you can call this method at any point in the
// visitor method, it's up to you whether your traversal is
// pre-order, post-order, or both!
this.traverse(path);
if (namedTypes.Identifier.check(property)) {
// The this.name case.
thisProperties[property.name] = true;
// This traversal is only concerned with Function nodes that have
// rest parameters.
if (!node.rest) {
return;
}
} else if (namedTypes.Literal.check(property) &&
isString.check(property.value)) {
// The this["name"] case.
thisProperties[property.value] = true;
}
// For the purposes of this example, we won't worry about functions
// with Expression bodies.
n.BlockStatement.assert(node.body);
// Use types.builders to build a variable declaration of the form
//
// var rest = Array.prototype.slice.call(arguments, n);
//
// where `rest` is the name of the rest parameter, and `n` is a
// numeric literal specifying the number of named parameters the
// function takes.
var restVarDecl = b.variableDeclaration("var", [
b.variableDeclarator(
node.rest,
b.callExpression(sliceExpr, [
b.identifier("arguments"),
b.literal(node.params.length)
])
)
]);
// Similar to doing node.body.body.unshift(restVarDecl), except
// that the other NodePath objects wrapping body statements will
// have their indexes updated to accommodate the new statement.
path.get("body", "body").unshift(restVarDecl);
// Nullify node.rest now that we have simulated the behavior of
// the rest parameter using ordinary JavaScript.
path.get("rest").replace(null);
// There's nothing wrong with doing node.rest = null, but I wanted
// to point out that the above statement has the same effect.
assert.strictEqual(node.rest, null);
}
});
```
Within the callback function, `this` is always an instance of a simple
`Path` type that has immutable `.node`, `.parent`, and `.scope`
properties. In general, `this.node` refers to the same node as the `node`
parameter, `this.parent.node` refers to the nearest `Node` ancestor,
`this.parent.parent.node` to the grandparent, and so on. These `Path`
objects are created during the traversal without modifying the AST nodes
themselves, so it's not a problem if the same node appears more than once
in the AST, because it will be visited with a distict `Path` each time it
appears.
Here's how you might use `types.visit` to implement a function that
determines if a given function node refers to `this`:
```js
function usesThis(funcNode) {
n.Function.assert(funcNode);
var result = false;
types.visit(funcNode, {
visitThisExpression: function(path) {
result = true;
// ThisExpression nodes don't have any children, so it
// wouldn't hurt to call this.traverse(path) here instead of
// returning false. Either way, we're done with this subtree.
return false;
},
visitFunction: function(path) {
// ThisExpression nodes in nested scopes don't count as `this`
// references for the original function node, so we can safely
// avoid traversing this subtree.
return false;
},
visitCallExpression: function(path) {
var node = path.node;
// If the function contains CallExpression nodes involving
// super, those expressions will implicitly depend on the
// value of `this`, even though they do not explicitly contain
// any ThisExpression nodes.
if (this.isSuperCallExpression(node)) {
result = true;
return false;
}
this.traverse(path);
},
// Yes, you can define arbitrary helper methods.
isSuperCallExpression: function(callExpr) {
n.CallExpression.assert(callExpr);
return this.isSuperIdentifier(callExpr.callee)
|| this.isSuperMemberExpression(callExpr.callee);
},
// And even helper helper methods!
isSuperIdentifier: function(node) {
return n.Identifier.check(node.callee)
&& node.callee.name === "super";
},
isSuperMemberExpression: function(node) {
return n.MemberExpression.check(node.callee)
&& n.Identifier.check(node.callee.object)
&& node.callee.object.name === "super";
}
});
return result;
}
```
NodePath
---
The `NodePath` object passed to visitor methods is a wrapper around an AST
node, and it serves to provide access to the chain of ancestor objects
(all the way back to the root of the AST) and scope information.
In general, `path.node` refers to the wrapped node, `path.parent.node`
refers to the nearest `Node` ancestor, `path.parent.parent.node` to the
grandparent, and so on.
Note that `path.node` may not be a direct property value of
`path.parent.node`; for instance, it might be the case that `path.node` is
an element of an array that is a direct child of the parent node:
```js
path.node === path.parent.node.elements[3]
```
in which case you should know that `path.parentPath` provides
finer-grained access to the complete path of objects (not just the `Node`
ones) from the root of the AST:
```js
// In reality, path.parent is the grandparent of path:
path.parentPath.parentPath === path.parent
// The path.parentPath object wraps the elements array (note that we use
// .value because the elements array is not a Node):
path.parentPath.value === path.parent.node.elements
// The path.node object is the fourth element in that array:
path.parentPath.value[3] === path.node
// Unlike path.node and path.value, which are synonyms because path.node
// is a Node object, path.parentPath.node is distinct from
// path.parentPath.value, because the elements array is not a
// Node. Instead, path.parentPath.node refers to the closest ancestor
// Node, which happens to be the same as path.parent.node:
path.parentPath.node === path.parent.node
// The path is named for its index in the elements array:
path.name === 3
// Likewise, path.parentPath is named for the property by which
// path.parent.node refers to it:
path.parentPath.name === "elements"
// Putting it all together, we can follow the chain of object references
// from path.parent.node all the way to path.node by accessing each
// property by name:
path.parent.node[path.parentPath.name][path.name] === path.node
```
These `NodePath` objects are created during the traversal without
modifying the AST nodes themselves, so it's not a problem if the same node
appears more than once in the AST (like `Array.prototype.slice.call` in
the example above), because it will be visited with a distict `NodePath`
each time it appears.
Child `NodePath` objects are created lazily, by calling the `.get` method
of a parent `NodePath` object:
```js
// If a NodePath object for the elements array has never been created
// before, it will be created here and cached in the future:
path.get("elements").get(3).value === path.value.elements[3]
// Alternatively, you can pass multiple property names to .get instead of
// chaining multiple .get calls:
path.get("elements", 0).value === path.value.elements[0]
```
`NodePath` objects support a number of useful methods:
```js
// Replace one node with another node:
var fifth = path.get("elements", 4);
fifth.replace(newNode);
// Now do some stuff that might rearrange the list, and this replacement
// remains safe:
fifth.replace(newerNode);
// Replace the third element in an array with two new nodes:
path.get("elements", 2).replace(
b.identifier("foo"),
b.thisExpression()
);
// Remove a node from a list of nodes:
path.get("elements", 3).replace();
// Add three new nodes to the beginning of a list of nodes:
path.get("elements").unshift(a, b, c);
// Remove and return the first node in a list of nodes:
path.get("elements").shift();
// Push two new nodes onto the end of a list of nodes:
path.get("elements").push(d, e);
// Remove and return the last node in a list of nodes:
path.get("elements").pop();
// Insert a new node before/after the seventh node in a list of nodes:
var seventh = path.get("elements", 6);
seventh.insertBefore(newNode);
seventh.insertAfter(newNode);
// Insert a new element at index 5 in a list of nodes:
path.get("elements").insertAt(5, newNode);
```
Scope
---
The object exposed as `this.scope` during AST traversals provides
The object exposed as `path.scope` during AST traversals provides
information about variable and function declarations in the scope that
contains `this.node`. See [scope.js](lib/scope.js) for its public
contains `path.node`. See [scope.js](lib/scope.js) for its public
interface, which currently includes `.isGlobal`, `.getGlobalScope()`,

@@ -153,0 +419,0 @@ `.depth`, `.declares(name)`, `.lookup(name)`, and `.getBindings()`.

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc