ast-types
Advanced tools
Comparing version 0.4.5 to 0.4.7
@@ -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": { |
380
README.md
@@ -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()`. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
106210
2477
476