ast-types
Advanced tools
Comparing version 0.3.38 to 0.4.0
@@ -21,2 +21,3 @@ var assert = require("assert"); | ||
Object.defineProperty(this, "node", { | ||
configurable: true, // Enable deletion. | ||
value: this._computeNode() | ||
@@ -32,2 +33,3 @@ }); | ||
Object.defineProperty(this, "parent", { | ||
configurable: true, // Enable deletion. | ||
value: this._computeParent() | ||
@@ -43,2 +45,3 @@ }); | ||
Object.defineProperty(this, "scope", { | ||
configurable: true, // Enable deletion. | ||
value: this._computeScope() | ||
@@ -52,2 +55,9 @@ }); | ||
NPp.replace = function() { | ||
delete this.node; | ||
delete this.parent; | ||
delete this.scope; | ||
return Path.prototype.replace.apply(this, arguments); | ||
}; | ||
// The value of the first ancestor Path whose value is a Node. | ||
@@ -54,0 +64,0 @@ NPp._computeNode = function() { |
108
lib/path.js
@@ -33,3 +33,3 @@ var assert = require("assert"); | ||
// child Path object, for both performance and consistency reasons. | ||
this.__childCache = {}; | ||
this.__childCache = Object.create(null); | ||
} | ||
@@ -118,43 +118,28 @@ | ||
Pp.replace = function(replacement) { | ||
var count = arguments.length; | ||
assert.ok( | ||
this.parentPath instanceof Path, | ||
"Instead of replacing the root of the tree, create a new tree." | ||
); | ||
var name = this.name; | ||
var results = []; | ||
var parentValue = this.parentPath.value; | ||
var parentCache = this.parentPath.__childCache; | ||
var results = []; | ||
var count = arguments.length; | ||
if (toString.call(parentValue) === arrayToString) { | ||
var i; | ||
var newIndex; | ||
var originalLength = parentValue.length; | ||
var moved = Object.create(null); | ||
if (this.value !== parentCache[name].value) { | ||
if (parentValue[this.name] !== this.value) { | ||
// Something caused our index (name) to become out of date. | ||
for (i = 0; i < parentValue.length; ++i) { | ||
if (parentValue[i] === this.value) { | ||
this.name = name = i; | ||
break; | ||
} | ||
var i = parentValue.indexOf(this.value); | ||
if (i >= 0) { | ||
parentCache[this.name = i] = this; | ||
} | ||
assert.ok( | ||
this.value === parentCache[name].value, | ||
"Cannot replace already replaced node: " + this.value.type | ||
); | ||
} | ||
delete parentCache.length; | ||
delete parentCache[name]; | ||
assert.strictEqual(parentValue[this.name], this.value); | ||
assert.strictEqual(this.parentPath.get(this.name), this); | ||
var moved = {}; | ||
for (i = name + 1; i < parentValue.length; ++i) { | ||
var child = parentCache[i]; | ||
if (child) { | ||
newIndex = i - 1 + count; | ||
moved[newIndex] = child; | ||
Object.defineProperty(child, "name", { value: newIndex }); | ||
for (var i = this.name + 1; i < originalLength; ++i) { | ||
if (hasOwn.call(parentCache, i)) { | ||
var childPath = parentCache[i]; | ||
var newIndex = i - 1 + count; | ||
moved[newIndex] = childPath; | ||
childPath.name = newIndex; | ||
delete parentCache[i]; | ||
@@ -164,27 +149,58 @@ } | ||
var args = slice.call(arguments); | ||
args.unshift(name, 1); | ||
parentValue.splice.apply(parentValue, args); | ||
delete parentCache.length; | ||
var spliceArgs = [this.name, 1]; | ||
for (i = 0; i < count; ++i) { | ||
spliceArgs.push(arguments[i]); | ||
} | ||
var splicedOut = parentValue.splice.apply(parentValue, spliceArgs); | ||
assert.strictEqual(splicedOut[0], this.value); | ||
assert.strictEqual( | ||
parentValue.length, | ||
originalLength - 1 + count | ||
); | ||
for (newIndex in moved) { | ||
if (hasOwn.call(moved, newIndex)) { | ||
parentCache[newIndex] = moved[newIndex]; | ||
} | ||
parentCache[newIndex] = moved[newIndex]; | ||
} | ||
for (i = name; i < name + count; ++i) { | ||
results.push(this.parentPath.get(i)); | ||
if (count === 0) { | ||
delete this.value; | ||
delete parentCache[this.name]; | ||
this.__childCache = {}; | ||
} else { | ||
assert.strictEqual(parentValue[this.name], replacement); | ||
if (this.value !== replacement) { | ||
this.value = replacement; | ||
this.__childCache = {}; | ||
} | ||
for (i = 0; i < count; ++i) { | ||
results.push(this.parentPath.get(this.name + i)); | ||
} | ||
assert.strictEqual(results[0], this); | ||
} | ||
} else if (count === 1) { | ||
delete parentCache[name]; | ||
parentValue[name] = replacement; | ||
results.push(this.parentPath.get(name)); | ||
if (this.value !== replacement) { | ||
this.__childCache = {}; | ||
} | ||
this.value = parentValue[this.name] = replacement; | ||
results.push(this); | ||
} else if (count === 0) { | ||
delete parentCache[name]; | ||
delete parentValue[name]; | ||
delete parentValue[this.name]; | ||
delete this.value; | ||
this.__childCache = {}; | ||
// Leave this path cached as parentCache[this.name], even though | ||
// it no longer has a value defined. | ||
} else { | ||
assert.ok(false, "Could not replace Path."); | ||
assert.ok(false, "Could not replace path"); | ||
} | ||
@@ -191,0 +207,0 @@ |
@@ -1,102 +0,27 @@ | ||
var assert = require("assert"); | ||
var types = require("./types"); | ||
var Node = types.namedTypes.Node; | ||
var isObject = types.builtInTypes.object; | ||
var isArray = types.builtInTypes.array; | ||
var NodePath = require("./node-path"); | ||
var funToStr = Function.prototype.toString; | ||
var thisPattern = /\bthis\b/; | ||
var visit = require("./path-visitor").visit; | ||
var warnedAboutDeprecation = false; | ||
// Good for traversals that need to modify the syntax tree or to access | ||
// path/scope information via `this` (a NodePath object). Somewhat slower | ||
// than traverseWithNoPathInfo because of the NodePath bookkeeping. | ||
function traverseWithFullPathInfo(node, callback) { | ||
if (!thisPattern.test(funToStr.call(callback))) { | ||
// If the callback function contains no references to `this`, then | ||
// it will have no way of using any of the NodePath information | ||
// that traverseWithFullPathInfo provides, so we can skip that | ||
// bookkeeping altogether. | ||
return traverseWithNoPathInfo( | ||
node instanceof NodePath ? node.value : node, | ||
callback | ||
if (!warnedAboutDeprecation) { | ||
warnedAboutDeprecation = true; | ||
console.warn( | ||
"\033[33m", // yellow | ||
'DEPRECATED(ast-types): Please use require("ast-types").visit ' + | ||
"instead of .traverse for syntax tree manipulation." + | ||
"\033[0m" // reset | ||
); | ||
} | ||
function traverse(path) { | ||
assert.ok(path instanceof NodePath); | ||
var value = path.value; | ||
if (isArray.check(value)) { | ||
path.each(traverse); | ||
return; | ||
} | ||
if (Node.check(value)) { | ||
if (callback.call(path, value, traverse) === false) { | ||
return; | ||
return visit(node, { | ||
visitNode: function(path) { | ||
if (callback.call(path, path.value) !== false) { | ||
this.traverse(path); | ||
} | ||
} else if (!isObject.check(value)) { | ||
return; | ||
} | ||
types.eachField(value, function(name, child) { | ||
var childPath = path.get(name); | ||
if (childPath.value !== child) { | ||
childPath.replace(child); | ||
} | ||
traverse(childPath); | ||
}); | ||
} | ||
if (node instanceof NodePath) { | ||
traverse(node); | ||
return node.value; | ||
} | ||
// Just in case we call this.replace at the root, there needs to be an | ||
// additional parent Path to update. | ||
var rootPath = new NodePath({ root: node }); | ||
traverse(rootPath.get("root")); | ||
return rootPath.value.root; | ||
} | ||
// Good for read-only traversals that do not require any NodePath | ||
// information. Faster than traverseWithFullPathInfo because less | ||
// information is exposed. A context parameter is supported because `this` | ||
// no longer has to be a NodePath object. | ||
function traverseWithNoPathInfo(node, callback, context) { | ||
Node.assert(node); | ||
context = context || null; | ||
function traverse(node) { | ||
if (isArray.check(node)) { | ||
node.forEach(traverse); | ||
return; | ||
return false; | ||
} | ||
if (Node.check(node)) { | ||
if (callback.call(context, node, traverse) === false) { | ||
return; | ||
} | ||
} else if (!isObject.check(node)) { | ||
return; | ||
} | ||
types.eachField(node, function(name, child) { | ||
traverse(child); | ||
}); | ||
} | ||
traverse(node); | ||
return node; | ||
}); | ||
} | ||
// Since we export traverseWithFullPathInfo as module.exports, we need to | ||
// attach traverseWithNoPathInfo to it as a property. In other words, you | ||
// should use require("ast-types").traverse.fast(ast, ...) to invoke the | ||
// quick-and-dirty traverseWithNoPathInfo function. | ||
traverseWithFullPathInfo.fast = traverseWithNoPathInfo; | ||
traverseWithFullPathInfo.fast = traverseWithFullPathInfo; | ||
module.exports = traverseWithFullPathInfo; |
@@ -29,2 +29,3 @@ var types = require("./lib/types"); | ||
exports.NodePath = require("./lib/node-path"); | ||
exports.computeSupertypeLookupTable = types.computeSupertypeLookupTable; | ||
exports.PathVisitor = require("./lib/path-visitor"); | ||
exports.visit = exports.PathVisitor.visit; |
@@ -21,3 +21,3 @@ { | ||
], | ||
"version": "0.3.38", | ||
"version": "0.4.0", | ||
"homepage": "http://github.com/benjamn/ast-types", | ||
@@ -33,8 +33,5 @@ "repository": { | ||
}, | ||
"dependencies": { | ||
"private": "~0.1.2" | ||
}, | ||
"devDependencies": { | ||
"esprima": "~1.1.1", | ||
"mocha": "~1.16.2" | ||
"esprima": "~1.2.2", | ||
"mocha": "~1.20.1" | ||
}, | ||
@@ -41,0 +38,0 @@ "engines": { |
356
test/run.js
@@ -12,2 +12,3 @@ var assert = require("assert"); | ||
var NodePath = require("../lib/node-path"); | ||
var PathVisitor = require("../lib/path-visitor"); | ||
@@ -53,3 +54,3 @@ describe("basic type checking", function() { | ||
it("should resolve the most precise supertypes", function() { | ||
var table = types.computeSupertypeLookupTable({ | ||
var table = require("../lib/types").computeSupertypeLookupTable({ | ||
Function: true, | ||
@@ -588,3 +589,3 @@ Declaration: true, | ||
if (n.VariableDeclaration.check(node)) { | ||
var scopeBody = this.scope.path.get("body").get("body"); | ||
var scopeBody = this.scope.path.get("body", "body"); | ||
@@ -617,4 +618,6 @@ // This is contrived such that we just happen to be replacing | ||
assert.strictEqual(scopeBody.get(0), this); | ||
// Then replace the node, not the one we just added. | ||
this.replace(b.returnStatement(b.identifier("$$"))); | ||
this.replace(b.returnStatement(b.identifier("$3"))); | ||
} | ||
@@ -626,21 +629,31 @@ }); | ||
statements.map(function(node) { return node.type; }), | ||
['VariableDeclaration', 'VariableDeclaration', 'ReturnStatement'] | ||
['ReturnStatement', 'VariableDeclaration', 'VariableDeclaration'] | ||
); | ||
assert.ok(n.VariableDeclaration.check(statements[0]), "not a variable declaration: " + statements[0].type); | ||
assert.equal(statements[0].declarations[0].id.name, "$2"); | ||
assert.ok(n.VariableDeclaration.check(statements[1]), "not a variable declaration: " + statements[1].type); | ||
n.ReturnStatement.assert(statements[0]); | ||
assert.equal(statements[0].argument.name, "$3"); | ||
n.VariableDeclaration.assert(statements[1]); | ||
assert.equal(statements[1].declarations[0].id.name, "$$"); | ||
assert.ok(n.ReturnStatement.check(statements[2]), "not a return statement: " + statements[2].type); | ||
assert.equal(statements[2].argument.name, "$$"); | ||
n.VariableDeclaration.assert(statements[2]); | ||
assert.equal(statements[2].declarations[0].id.name, "a"); | ||
}); | ||
it("should throw when trying to replace the same node twice", function() { | ||
it("should not throw when replacing the same node twice", function() { | ||
types.traverse(ast, function(node) { | ||
if (n.VariableDeclaration.check(node)) { | ||
this.replace(b.expressionStatement(b.literal(null))); | ||
n.ExpressionStatement.assert(this.value); | ||
n.Literal.assert(this.value.expression); | ||
assert.strictEqual(this.value.expression.value, null); | ||
var self = this; | ||
assert.throws(function() { | ||
self.replace(b.expressionStatement(b.literal('NOPE'))); | ||
}, /Cannot replace already replaced node: VariableDeclaration/); | ||
this.replace(b.expressionStatement(b.literal("OK"))); | ||
n.ExpressionStatement.assert(this.value); | ||
n.Literal.assert(this.value.expression); | ||
assert.strictEqual(this.value.expression.value, "OK"); | ||
if (this.parentPath.get(this.name) !== this) { | ||
assert.ok(false, "Should have reused the same path"); | ||
} | ||
} | ||
@@ -823,1 +836,316 @@ }); | ||
}); | ||
describe("types.visit", function() { | ||
var objProp; | ||
beforeEach(function() { | ||
objProp = b.memberExpression( | ||
b.identifier("object"), | ||
b.identifier("property"), | ||
false | ||
); | ||
}); | ||
it("should be identical to PathVisitor.visit", function() { | ||
assert.strictEqual(types.visit, PathVisitor.visit); | ||
}); | ||
it("should work with no visitors", function() { | ||
var foo = b.identifier("foo"); | ||
assert.strictEqual(types.visit(foo), foo); | ||
}); | ||
it("should allow simple tree modifications", function() { | ||
var bar = types.visit(b.identifier("foo"), { | ||
visitIdentifier: function(path) { | ||
assert.ok(path instanceof NodePath); | ||
path.value.name = "bar"; | ||
return false; | ||
} | ||
}); | ||
n.Identifier.assert(bar); | ||
assert.strictEqual(bar.name, "bar"); | ||
}); | ||
it("should complain about missing this.traverse", function() { | ||
try { | ||
types.visit(objProp, { | ||
visitIdentifier: function(path) { | ||
// buh? | ||
} | ||
}); | ||
assert.ok(false, "should have thrown an exception"); | ||
} catch (err) { | ||
assert.strictEqual( | ||
err.message, | ||
"Must either call this.traverse or return false in visitIdentifier" | ||
); | ||
} | ||
}); | ||
it("should support this.traverse", function() { | ||
var idNames = []; | ||
types.visit(objProp, { | ||
visitMemberExpression: function(path) { | ||
this.traverse(path, { | ||
visitIdentifier: function(path) { | ||
idNames.push("*" + path.value.name + "*"); | ||
return false; | ||
} | ||
}); | ||
path.get("object", "name").replace("asdfasdf"); | ||
path.get("property", "name").replace("zxcvzxcv"); | ||
this.visitor.visit(path.get("property")); | ||
}, | ||
visitIdentifier: function(path) { | ||
idNames.push(path.value.name); | ||
return false; | ||
} | ||
}); | ||
assert.deepEqual(idNames, ["*object*", "*property*", "zxcvzxcv"]); | ||
idNames.length = 0; | ||
types.visit(objProp, { | ||
visitMemberExpression: function(path) { | ||
path.get("object", "name").replace("asdfasdf"); | ||
path.get("property", "name").replace("zxcvzxcv"); | ||
this.traverse(path, { | ||
visitIdentifier: function(path) { | ||
idNames.push(path.value.name); | ||
return false; | ||
} | ||
}); | ||
} | ||
}); | ||
assert.deepEqual(idNames, ["asdfasdf", "zxcvzxcv"]); | ||
}); | ||
it("should support this.replace", function() { | ||
var seqExpr = b.sequenceExpression([ | ||
b.literal("asdf"), | ||
b.identifier("zxcv"), | ||
b.thisExpression() | ||
]); | ||
types.visit(seqExpr, { | ||
visitIdentifier: function(path) { | ||
assert.strictEqual(path.value.name, "zxcv"); | ||
path.replace( | ||
b.identifier("foo"), | ||
b.identifier("bar") | ||
); | ||
return false; | ||
} | ||
}); | ||
assert.strictEqual(seqExpr.expressions.length, 4); | ||
var foo = seqExpr.expressions[1]; | ||
n.Identifier.assert(foo); | ||
assert.strictEqual(foo.name, "foo"); | ||
var bar = seqExpr.expressions[2]; | ||
n.Identifier.assert(bar); | ||
assert.strictEqual(bar.name, "bar"); | ||
types.visit(seqExpr, { | ||
visitIdentifier: function(path) { | ||
if (path.value.name === "foo") { | ||
path.replace(path.value, path.value); | ||
} | ||
return false; | ||
} | ||
}); | ||
assert.strictEqual(seqExpr.expressions.length, 5); | ||
var foo = seqExpr.expressions[1]; | ||
n.Identifier.assert(foo); | ||
assert.strictEqual(foo.name, "foo"); | ||
var foo = seqExpr.expressions[2]; | ||
n.Identifier.assert(foo); | ||
assert.strictEqual(foo.name, "foo"); | ||
var bar = seqExpr.expressions[3]; | ||
n.Identifier.assert(bar); | ||
assert.strictEqual(bar.name, "bar"); | ||
types.visit(seqExpr, { | ||
visitLiteral: function(path) { | ||
path.replace(); | ||
return false; | ||
}, | ||
visitIdentifier: function(path) { | ||
if (path.value.name === "bar") { | ||
path.replace(); | ||
} | ||
return false; | ||
} | ||
}); | ||
assert.strictEqual(seqExpr.expressions.length, 3); | ||
var first = seqExpr.expressions[0]; | ||
n.Identifier.assert(first); | ||
assert.strictEqual(first.name, "foo"); | ||
var second = seqExpr.expressions[1]; | ||
assert.strictEqual(second, first); | ||
var third = seqExpr.expressions[2]; | ||
n.ThisExpression.assert(third); | ||
}); | ||
it("should reuse old VisitorContext objects", function() { | ||
var objectContext; | ||
var propertyContext; | ||
types.visit(objProp, { | ||
visitIdentifier: function(path) { | ||
assert.strictEqual(this.needToCallTraverse, true); | ||
this.traverse(path); | ||
assert.strictEqual(path.name, path.value.name); | ||
if (path.name === "object") { | ||
objectContext = this; | ||
} else if (path.name === "property") { | ||
propertyContext = this; | ||
} | ||
} | ||
}); | ||
assert.ok(objectContext); | ||
assert.ok(propertyContext); | ||
assert.strictEqual(objectContext, propertyContext); | ||
}); | ||
it("should dispatch to closest visitSupertype method", function() { | ||
var foo = b.identifier("foo"); | ||
var bar = b.identifier("bar"); | ||
var callExpr = b.callExpression( | ||
b.memberExpression( | ||
b.functionExpression( | ||
b.identifier("add"), | ||
[foo, bar], | ||
b.blockStatement([ | ||
b.returnStatement( | ||
b.binaryExpression("+", foo, bar) | ||
) | ||
]) | ||
), | ||
b.identifier("bind"), | ||
false | ||
), | ||
[b.thisExpression()] | ||
); | ||
var nodes = []; | ||
var expressions = []; | ||
var identifiers = []; | ||
var statements = []; | ||
var returnStatements = []; | ||
var functions = []; | ||
function makeVisitorMethod(array) { | ||
return function(path) { | ||
array.push(path.value); | ||
this.traverse(path); | ||
}; | ||
} | ||
types.visit(callExpr, { | ||
visitNode: makeVisitorMethod(nodes), | ||
visitExpression: makeVisitorMethod(expressions), | ||
visitIdentifier: makeVisitorMethod(identifiers), | ||
visitStatement: makeVisitorMethod(statements), | ||
visitReturnStatement: makeVisitorMethod(returnStatements), | ||
visitFunction: makeVisitorMethod(functions) | ||
}); | ||
function check(array) { | ||
var rest = Array.prototype.slice.call(arguments, 1); | ||
assert.strictEqual(array.length, rest.length); | ||
for (var i = 0; i < rest.length; ++i) { | ||
assert.strictEqual(array[i], rest[i]); | ||
} | ||
} | ||
check(nodes); | ||
check(expressions, | ||
callExpr, | ||
callExpr.callee, | ||
callExpr.callee.object.body.body[0].argument, | ||
callExpr.arguments[0]); | ||
check(identifiers, | ||
callExpr.callee.object.id, | ||
foo, | ||
bar, | ||
foo, | ||
bar, | ||
callExpr.callee.property); | ||
check(statements, | ||
callExpr.callee.object.body); | ||
check(returnStatements, | ||
callExpr.callee.object.body.body[0]); | ||
check(functions, | ||
callExpr.callee.object); | ||
}); | ||
it("should replace this.currentPath with returned value", function() { | ||
assert.strictEqual(objProp.computed, false); | ||
types.visit(objProp, { | ||
visitIdentifier: function(path) { | ||
if (path.value.name === "property") { | ||
path.parent.get("computed").replace(true); | ||
return b.callExpression( | ||
b.memberExpression( | ||
b.thisExpression(), | ||
b.identifier("toString"), | ||
false | ||
), | ||
[] | ||
); | ||
} | ||
this.traverse(path); | ||
}, | ||
visitThisExpression: function(path) { | ||
return b.identifier("self"); | ||
} | ||
}); | ||
assert.strictEqual(objProp.computed, true); | ||
n.CallExpression.assert(objProp.property); | ||
var callee = objProp.property.callee; | ||
n.MemberExpression.assert(callee); | ||
n.Identifier.assert(callee.object); | ||
assert.strictEqual(callee.object.name, "self"); | ||
n.Identifier.assert(callee.property); | ||
assert.strictEqual(callee.property.name, "toString"); | ||
assert.deepEqual(objProp.property.arguments, []); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
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
451946
0
23
12577
- Removedprivate@~0.1.2
- Removedprivate@0.1.8(transitive)