pax_global_header 0000666 0000000 0000000 00000000064 12451033276 0014515 g ustar 00root root 0000000 0000000 52 comment=6ea220d1999979c970c6a0a11faf4c9db4c1fddf
ast-util-0.6.0/ 0000775 0000000 0000000 00000000000 12451033276 0013262 5 ustar 00root root 0000000 0000000 ast-util-0.6.0/.travis.yml 0000664 0000000 0000000 00000000061 12451033276 0015370 0 ustar 00root root 0000000 0000000 language: node_js
node_js:
- "0.11"
- "0.10"
ast-util-0.6.0/Makefile 0000664 0000000 0000000 00000002550 12451033276 0014724 0 ustar 00root root 0000000 0000000 SHELL=bash
all: lib/helpers/get.js lib/helpers/getIterator.js lib/helpers/getArrayIterator.js lib/helpers/getIteratorRange.js
lib/helpers/get.js: helpers/get.js Makefile
@mkdir -p lib/helpers
@echo "var b = require('ast-types').builders;" > $@
@echo 'module.exports = function(scope) {' >> $@
@echo -n ' return ' >> $@
./bin/make-builder < $< >> $@
@echo '};' >> $@
lib/helpers/getIterator.js: helpers/getIterator.js Makefile
@mkdir -p lib/helpers
@echo "var b = require('ast-types').builders;" > $@
@echo 'module.exports = function(scope) {' >> $@
@echo " var getArrayIterator = require('..').getArrayIterator;" >> $@
@echo >> $@
@echo -n ' return ' >> $@
./bin/make-builder 'getArrayIterator=getArrayIterator(scope)' < $< >> $@
@echo '};' >> $@
lib/helpers/getArrayIterator.js: helpers/getArrayIterator.js Makefile
@mkdir -p lib/helpers
@echo "var b = require('ast-types').builders;" > $@
@echo 'module.exports = function(scope) {' >> $@
@echo -n ' return ' >> $@
./bin/make-builder < $< >> $@
@echo '};' >> $@
lib/helpers/getIteratorRange.js: helpers/getIteratorRange.js Makefile
@mkdir -p lib/helpers
@echo "var b = require('ast-types').builders;" > $@
@echo 'module.exports = function(scope) {' >> $@
@echo -n ' return ' >> $@
./bin/make-builder < $< >> $@
@echo '};' >> $@
clean:
rm -rf lib/helpers
test: all
npm test
.PHONY: clean test
ast-util-0.6.0/README.md 0000664 0000000 0000000 00000016575 12451033276 0014557 0 ustar 00root root 0000000 0000000 # ast-util
Utilities for AST transformers.
## Install
```
$ npm install [--save] ast-util
```
## API
# callArraySlice(scope, node[, begin, end])
Returns a call to `Array.prototype.slice` with `node` as the context and
`begin` and `end` as the arguments to `slice`.
# callFunctionBind(scope, fn, context[, args])
Returns a call to `Function.prototype.bind` using either `call` or `apply`
depending on what the value of `args` is. If `args` is an expression then
`apply` is used. If `args` is an array of expressions, then `call`.
# callGet(scope, object, property, receiver)
The [[Get]] internal method on objects would look something like
[helpers/get.js](helpers/get.js).
# callGetOwnPropertyDescriptor(scope, object, property)
Returns a call to `Object.getOwnPropertyDescriptor` with the given `object` and
`property`.
# callGetPrototypeOf(scope, object)
Returns a call to `Object.getPrototypeOf` with the given `object`.
# callHasOwnProperty(scope, node, property)
Returns a call to `hasOwnProperty` with `node` as the context and `property` as
the property to check.
# callSharedMethod(scope, callee, args)
Returns a call to the given `callee` with `args` as the arguments. If `callee`
is a string then it is treated as a globally-accessible function such as
`Object.defineProperty` which will be stored in a unique temporary variable.
Subsequent calls to this function will re-use the same temporary variable.
# callSharedMethodWithContext(scope, callee, context, args)
Returns a call to the given `callee` with `context` as the method context and
`args` as the arguments. If `callee` is a string then it is treated as a
globally-accessible function such as `Array.prototype.slice` which will be
stored in a unique temporary variable. Subsequent calls to this function will
re-use the same temporary variable.
# getGlobals(ast)
Gets a list of identifiers referencing global variables anywhere within the
given `ast`. Assuming the ast is for this code:
```js
var a;
function b(){ return c; }
b(d);
```
Then `getGlobals` will return two identifiers, `c` and `d`.
# identifierForString(string)
Generate a safe JavaScript identifier for the given string.
# injectShared(scope, name, expression)
Injects a shared variable with a unique identifier. Only the first call with
the same `scope` and `name` will result in a variable declaration being
created. The `expression` passed in can either be an AST node or a function to
generate one. This function is generally used to inject repeatedly-used values
and prevent repeated execution.
# injectVariable(scope, identifier[, init])
Injects a variable with the given `identifier` into the given `scope` as a
`var` declaration with an optional initial value.
# isReference(path)
Determines whether the given `path` is a value reference. For example, `a` and
`b` are references, but `c` is not:
```js
a(b.c);
```
Only identifiers count as references.
# isUsed(scope, name)
Determines whether the given `name` should be considered "used" in the given
`scope`. For a name to be used, it should either:
1. Be declared in this scope or a parent scope.
2. Be referenced in this scope, a parent scope, or any child scopes.
For example, `a`, `b`, and `d` are used in the global scope of this example
while `c` is not:
```js
var a;
function b() {}
try {
a = b(d);
} catch (c) {
}
```
# sharedFor(scope, name)
Injects a shared variable by getting the named value from a dotted path. For
example, this will return an identifier that can be used in place of the named
expression:
```js
sharedFor(scope, 'Object.defineProperty')
```
Subsequent calls to `sharedFor` in the same scope will return the same
identifier.
# uniqueIdentifier(scope[, name])
Generates an identifier guaranteed not to collide with any others in the given
`scope`. This function will also never generate the same identifier twice for
any `scope` whose global scope already got that identifier.
Called in a scope with no global references and no variables, the first time
this function is called it will return an identifier named `$__0`.
When called with a name that name will be used with a prefix, "$\_\_", if
possible. If that name is already used then it will append incrementing numbers
until it finds a name that isn't used.
## Usage
These methods are useful to source transforms, such as transpilers or macros.
Such transforms often have to insert variables into scopes and replace
expressions. Using `injectVariable` and `injectShared` are specifically for
that purpose. In conjunction with `ast-types`, here's how you'd write a simple
version of a `swap` macro:
```js
// var tmp;
var tmp = util.injectVariable(
this.scope,
util.uniqueIdentifier(this.scope)
);
this.replace(
b.sequenceExpression([
// tmp = left
b.assignmentExpression(
'=',
tmp,
left
),
// left = right
b.assignmentExpression(
'=',
left,
right
),
// right = tmp
b.assignmentExpression(
'=',
right,
tmp
)
])
);
```
See [examples/swap-macro.js](examples/swap-macro.js) for a more complete
example.
## Contributing
[](https://travis-ci.org/eventualbuddha/ast-util)
### Setup
First, install the development dependencies:
```
$ npm install
```
Then, try running the tests:
```
$ make test
```
If you're adding or editing code that injects helpers into a scope, you'll need
to edit and run the Makefile to have it generate the files in lib/helpers from
the files in helpers.
### Pull Requests
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
## Acknowledgements
Huge thanks to [Ben Newman][benjamn] for [ast-types][ast-types], on which much
of this library depends.
[benjamn]: https://github.com/benjamn
[ast-types]: https://github.com/benjamn/ast-types
ast-util-0.6.0/bin/ 0000775 0000000 0000000 00000000000 12451033276 0014032 5 ustar 00root root 0000000 0000000 ast-util-0.6.0/bin/make-builder 0000775 0000000 0000000 00000020255 12451033276 0016325 0 ustar 00root root 0000000 0000000 #!/usr/bin/env node
/* jshint node:true, unused:true, undef:true */
var recast = require('recast');
var types = recast.types;
var b = types.builders;
var n = types.namedTypes;
var assert = require('assert');
var vm = require('vm');
var BUILD_PARAMS_CACHE = {
'ThisExpression': [],
'BreakStatement': []
};
/**
* Get the named parameters to pass the named builder.
*
* @param {!string} type
* @return {[string]}
*/
function buildParamsForType(type) {
var entry = BUILD_PARAMS_CACHE[type];
if (entry) {
return entry;
}
try {
b[builderNameForType(type)]();
assert.ok(false, 'should have failed to build ' + type + ' with no params');
} catch (ex) {
var message = ex.message;
var typeIndex = message.indexOf(type);
var openParenIndex = message.indexOf('(', typeIndex);
var closeParenIndex = message.indexOf(')', openParenIndex);
assert.ok(
closeParenIndex >= 0,
'unexpected exception format trying to parse build params: ' + message
);
var paramsList = message.slice(openParenIndex, closeParenIndex);
var result = [];
paramsList.replace(/"([^"]+)"/g, function(_, name) {
result.push(name);
});
BUILD_PARAMS_CACHE[type] = result;
return result;
}
}
/**
* Get the name of the builder given a node type. For example, "ThisExpression"
* becomes "thisExpression".
*
* @param {!string} type
* @return {!string}
*/
function builderNameForType(type) {
return type[0].toLowerCase() + type.slice(1);
}
/**
* Read all the contents of STDIN and call back with it.
*
* @param {function(!string)} callback
*/
function readStdin(callback) {
var stdin = '';
process.stdin.setEncoding('utf8');
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
stdin += chunk;
}
});
process.stdin.on('end', function() {
callback(stdin);
});
}
/**
* Makes the AST for a JavaScript program that will build the given node using
* the builder functions from the ast-types package.
*
* @param {ast-types.Node} node
* @param {{string: string}} replacements
* @return {ast-types.Node}
*/
function makeBuilder(node, replacements) {
if (n.File.check(node)) {
return b.expressionStatement(makeBuilder(node.program, replacements));
} else if (n.Program.check(node)) {
return b.callExpression(
b.memberExpression(
b.identifier('b'),
b.identifier('program'),
false
),
[b.arrayExpression(node.body.map(function(statement) {
return makeBuilder(statement, replacements);
}))]
);
} else if (Array.isArray(node)) {
return b.arrayExpression(
node.map(function(item) { return makeBuilder(item, replacements); })
);
} else if (node && node.constructor === RegExp) {
return b.literal(node);
}
assert.ok(
n.Node.check(node),
'unexpected node type: ' + JSON.stringify(node)
);
if (n.Identifier.check(node)) {
var replacement = replacements[node.name];
if (replacement) {
var newReplacements = Object.create(replacements);
newReplacements[node.name] = undefined;
return recast.parse(replacement).program.body[0].expression;
}
}
return b.callExpression(
b.memberExpression(
b.identifier('b'),
b.identifier(node.type[0].toLowerCase() + node.type.slice(1)),
false
),
buildParamsForType(node.type).reduce(function(result, key) {
if (key !== 'type' && key !== 'loc') {
var value;
if (node[key] !== null && typeof node[key] === 'object') {
value = makeBuilder(node[key], replacements);
} else if (node[key] !== undefined) {
value = b.literal(node[key]);
}
if (value) {
result.push(value);
}
}
return result;
}, [])
);
}
/**
* Determines whether, when printed, the node should be on multiple lines.
*
* @param {ast-types.Node}
* @param {boolean}
*/
function isMultiline(node) {
switch (node.type) {
case 'ExpressionStatement':
return isMultiline(node.expression);
case 'CallExpression':
return node.arguments.length > 1 || node.arguments.some(isMultiline);
case 'MemberExpression':
return isMultiline(node.object) || isMultiline(node.property);
case 'Identifier':
return false;
case 'ArrayExpression':
return node.elements.length > 1 || (node.elements.length === 1 && isMultiline(node.elements[0]));
case 'Literal':
return (node.raw || JSON.stringify(node.value)).indexOf('\n') >= 0;
default:
throw new Error('unexpected node type: ' + node.type);
}
}
/**
* @const
*/
var INDENT = ' ';
/**
* Prints the given list of AST nodes as JavaScript as part of a list of array
* elements or function arguments.
*
* @param {[ast-types.Node]} list
* @param {string=} indent
*/
function printListLines(list, indent) {
if (!indent) { indent = ''; }
var output = '';
list.forEach(function(item, i) {
output += indent + print(item, indent);
if (i !== list.length - 1) {
output += ',';
}
output += '\n';
});
return output;
}
/**
* Prints the given AST node as JavaScript, formatted so as to favor shorter
* lines. For example, this:
*
* b.callExpression(b.identifier('a'), [b.literal(1), b.literal(2)]);
*
* Would be printed as:
*
* b.callExpression(
* b.identifier('a'),
* [
* b.literal(1),
* b.literal(2)
* ]
* )
*
* @param {ast-types.Node} node
* @param {string=} indent
* @return {string}
*/
function print(node, indent) {
if (!indent) { indent = ''; }
switch (node.type) {
case 'ExpressionStatement':
return print(node.expression, indent) + ';';
case 'CallExpression':
if (isMultiline(node)) {
if (node.arguments.length === 1 && node.arguments[0].type === 'ArrayExpression') {
return print(node.callee, indent) + '([\n' +
printListLines(node.arguments[0].elements, indent + INDENT) +
indent + '])';
} else {
return print(node.callee, indent) + '(\n' +
printListLines(node.arguments, indent + INDENT) +
indent + ')';
}
}
return print(node.callee, indent) + '(' +
node.arguments.map(function(arg) { return print(arg, indent); }).join(', ') +
')';
case 'MemberExpression':
if (node.computed) {
return print(node.object, indent) + '[' + print(node.property, indent) + ']';
} else {
return print(node.object, indent) + '.' + print(node.property, indent);
}
break;
case 'Identifier':
return node.name;
case 'ArrayExpression':
if (isMultiline(node)) {
return '[\n' +
printListLines(node.elements, indent + INDENT) +
indent + ']';
} else {
return '[' +
node.elements.map(function(element) { return print(element, indent); }).join(', ') +
']';
}
break;
case 'Literal':
if (typeof node.value === 'string') {
return "'" + node.value.replace(/'/g, "\\'") + "'";
} else {
return node.raw || JSON.stringify(node.value);
}
break;
default:
throw new Error('unexpected node type: ' + node.type);
}
}
var replacements = process.argv.slice(2).reduce(function(map, arg) {
var parts = arg.split('=');
if (parts.length === 2) {
map[parts[0]] = parts[1];
}
return map;
}, {});
var TEST = process.argv.indexOf('--test') >= 0;
readStdin(function(stdin) {
var inputSource = stdin;
var inputAST = recast.parse(inputSource);
var body = inputAST.program.body;
var ast = inputAST.program;
if (body.length === 1) {
var statement = body[0];
// Favor processing just an expression if possible.
ast = n.ExpressionStatement.check(statement) ?
statement.expression : statement;
}
var code = print(makeBuilder(ast, replacements));
if (TEST) {
// verify the result
var context = { b: b };
vm.runInNewContext('result = ' + code, context);
var normalizedInputSource = recast.prettyPrint(inputAST).code;
var normalizedBuiltSource = recast.prettyPrint(context.result).code;
assert.equal(
normalizedBuiltSource,
normalizedInputSource
);
}
process.stdout.write(code);
});
ast-util-0.6.0/examples/ 0000775 0000000 0000000 00000000000 12451033276 0015100 5 ustar 00root root 0000000 0000000 ast-util-0.6.0/examples/swap-macro.js 0000775 0000000 0000000 00000005111 12451033276 0017510 0 ustar 00root root 0000000 0000000 #!/usr/bin/env node
var recast = require('recast');
var util = require('../lib');
var types = util.types;
var n = types.namedTypes;
var b = types.builders;
var assert = require('assert');
/**
* Treats calls to `swap` as a macro to swap the identifiers passed to swap.
*
* swap(left, right);
*
* Becomes
*
* var tmp;
* tmp = left;
* left = right;
* right = tmp;
*
* @param {ast-types.Node} ast
* @return {ast-types.Node} Returns `ast`.
*/
function transform(ast) {
var replaced = [];
types.traverse(ast, function(node) {
if (n.ExpressionStatement.check(node)) {
if (isSwapCall(this.get('expression'))) {
// swap(left, right)
var expression = node.expression;
assert.equal(expression.arguments.length, 2, 'expected 2 arguments to `swap`, got ' + expression.arguments.length);
var left = expression.arguments[0];
var right = expression.arguments[1];
assert.ok(
n.Identifier.check(left) || n.MemberExpression.check(left),
'expected first argument of `swap` to be an Identifier or MemberExpression, found ' + left.type
);
assert.ok(
n.Identifier.check(right) || n.MemberExpression.check(right),
'expected second argument of `swap` to be an Identifier or MemberExpression, found ' + right.type
);
var tmp = util.uniqueIdentifier(this.scope);
replaced.push(expression);
this.replace(
// var tmp = left;
b.variableDeclaration(
'var',
[b.variableDeclarator(tmp, left)]
),
// left = right
b.expressionStatement(b.assignmentExpression(
'=',
left,
right
)),
// right = tmp
b.expressionStatement(b.assignmentExpression(
'=',
right,
tmp
))
);
}
} else if (isSwapCall(this) && replaced.indexOf(node) < 0) {
throw new Error('unexpected `swap` macro used as an expression instead of a statement');
}
});
return ast;
}
function isSwapCall(path) {
return n.CallExpression.check(path.value) && util.isReference(path.get('callee'), 'swap');
}
function readStdin(callback) {
var stdin = '';
process.stdin.setEncoding('utf8');
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
stdin += chunk;
}
});
process.stdin.on('end', function() {
callback(stdin);
});
}
readStdin(function(stdin) {
process.stdout.write(recast.print(transform(recast.parse(stdin))).code);
});
ast-util-0.6.0/helpers/ 0000775 0000000 0000000 00000000000 12451033276 0014724 5 ustar 00root root 0000000 0000000 ast-util-0.6.0/helpers/get.js 0000664 0000000 0000000 00000000773 12451033276 0016050 0 ustar 00root root 0000000 0000000 (function get(object, property, receiver) {
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === void 0) {
var parent = Object.getPrototypeOf(object);
if (parent === null) {
return void 0;
} else {
return get(parent, property, receiver);
}
} else if ('value' in desc && 'writable' in desc) {
return desc.value;
} else {
var getter = desc.get;
if (getter === void 0) {
return void 0;
}
return getter.call(receiver);
}
});
ast-util-0.6.0/helpers/getArrayIterator.js 0000664 0000000 0000000 00000000453 12451033276 0020554 0 ustar 00root root 0000000 0000000 (function(array) {
var index = 0;
return {
next: function() {
if (index < array.length) {
return {
done: false,
value: array[index++]
};
} else {
return {
done: true,
value: void 0
};
}
}
};
});
ast-util-0.6.0/helpers/getIterator.js 0000664 0000000 0000000 00000000521 12451033276 0017551 0 ustar 00root root 0000000 0000000 (function(iterable) {
var sym = typeof Symbol === "function" && Symbol.iterator || "@@iterator";
if (typeof iterable[sym] === "function") {
return iterable[sym]();
} else if (typeof iterable === "object" || typeof iterable === "function") {
return getArrayIterator(iterable);
} else {
throw new TypeError();
}
});
ast-util-0.6.0/helpers/getIteratorRange.js 0000664 0000000 0000000 00000000651 12451033276 0020532 0 ustar 00root root 0000000 0000000 (function(iterator, index, begin, len) {
if (index > begin) {
throw new RangeError();
}
if (typeof len === "undefined") {
len = Infinity;
}
var range = [], end = begin + len;
while (index < end) {
var next = iterator.next();
if (next.done) {
break;
}
if (index >= begin) {
range.push(next.value);
}
index++;
}
return {
range: range,
index: index
};
});
ast-util-0.6.0/lib/ 0000775 0000000 0000000 00000000000 12451033276 0014030 5 ustar 00root root 0000000 0000000 ast-util-0.6.0/lib/helpers/ 0000775 0000000 0000000 00000000000 12451033276 0015472 5 ustar 00root root 0000000 0000000 ast-util-0.6.0/lib/helpers/get.js 0000664 0000000 0000000 00000007346 12451033276 0016621 0 ustar 00root root 0000000 0000000 var b = require('ast-types').builders;
module.exports = function(scope) {
return b.functionExpression(
b.identifier('get'),
[
b.identifier('object'),
b.identifier('property'),
b.identifier('receiver')
],
b.blockStatement([
b.variableDeclaration(
'var',
[
b.variableDeclarator(
b.identifier('desc'),
b.callExpression(
b.memberExpression(
b.identifier('Object'),
b.identifier('getOwnPropertyDescriptor'),
false
),
[
b.identifier('object'),
b.identifier('property')
]
)
)
]
),
b.ifStatement(
b.binaryExpression(
'===',
b.identifier('desc'),
b.unaryExpression(
'void',
b.literal(0),
true
)
),
b.blockStatement([
b.variableDeclaration(
'var',
[
b.variableDeclarator(
b.identifier('parent'),
b.callExpression(
b.memberExpression(
b.identifier('Object'),
b.identifier('getPrototypeOf'),
false
),
[b.identifier('object')]
)
)
]
),
b.ifStatement(
b.binaryExpression(
'===',
b.identifier('parent'),
b.literal(null)
),
b.blockStatement([
b.returnStatement(
b.unaryExpression(
'void',
b.literal(0),
true
)
)
]),
b.blockStatement([
b.returnStatement(
b.callExpression(
b.identifier('get'),
[
b.identifier('parent'),
b.identifier('property'),
b.identifier('receiver')
]
)
)
])
)
]),
b.ifStatement(
b.logicalExpression(
'&&',
b.binaryExpression(
'in',
b.literal('value'),
b.identifier('desc')
),
b.binaryExpression(
'in',
b.literal('writable'),
b.identifier('desc')
)
),
b.blockStatement([
b.returnStatement(
b.memberExpression(
b.identifier('desc'),
b.identifier('value'),
false
)
)
]),
b.blockStatement([
b.variableDeclaration(
'var',
[
b.variableDeclarator(
b.identifier('getter'),
b.memberExpression(
b.identifier('desc'),
b.identifier('get'),
false
)
)
]
),
b.ifStatement(
b.binaryExpression(
'===',
b.identifier('getter'),
b.unaryExpression(
'void',
b.literal(0),
true
)
),
b.blockStatement([
b.returnStatement(
b.unaryExpression(
'void',
b.literal(0),
true
)
)
]),
null
),
b.returnStatement(
b.callExpression(
b.memberExpression(
b.identifier('getter'),
b.identifier('call'),
false
),
[b.identifier('receiver')]
)
)
])
)
)
]),
false,
false
)};
ast-util-0.6.0/lib/helpers/getArrayIterator.js 0000664 0000000 0000000 00000004667 12451033276 0021335 0 ustar 00root root 0000000 0000000 var b = require('ast-types').builders;
module.exports = function(scope) {
return b.functionExpression(
null,
[b.identifier('array')],
b.blockStatement([
b.variableDeclaration(
'var',
[
b.variableDeclarator(
b.identifier('index'),
b.literal(0)
)
]
),
b.returnStatement(
b.objectExpression([
b.property(
'init',
b.identifier('next'),
b.functionExpression(
null,
[],
b.blockStatement([
b.ifStatement(
b.binaryExpression(
'<',
b.identifier('index'),
b.memberExpression(
b.identifier('array'),
b.identifier('length'),
false
)
),
b.blockStatement([
b.returnStatement(
b.objectExpression([
b.property(
'init',
b.identifier('done'),
b.literal(false)
),
b.property(
'init',
b.identifier('value'),
b.memberExpression(
b.identifier('array'),
b.updateExpression(
'++',
b.identifier('index'),
false
),
true
)
)
])
)
]),
b.blockStatement([
b.returnStatement(
b.objectExpression([
b.property(
'init',
b.identifier('done'),
b.literal(true)
),
b.property(
'init',
b.identifier('value'),
b.unaryExpression(
'void',
b.literal(0),
true
)
)
])
)
])
)
]),
false,
false
)
)
])
)
]),
false,
false
)};
ast-util-0.6.0/lib/helpers/getIterator.js 0000664 0000000 0000000 00000004625 12451033276 0020330 0 ustar 00root root 0000000 0000000 var b = require('ast-types').builders;
module.exports = function(scope) {
var getArrayIterator = require('..').getArrayIterator;
return b.functionExpression(
null,
[b.identifier('iterable')],
b.blockStatement([
b.variableDeclaration(
'var',
[
b.variableDeclarator(
b.identifier('sym'),
b.logicalExpression(
'||',
b.logicalExpression(
'&&',
b.binaryExpression(
'===',
b.unaryExpression(
'typeof',
b.identifier('Symbol'),
true
),
b.literal('function')
),
b.memberExpression(
b.identifier('Symbol'),
b.identifier('iterator'),
false
)
),
b.literal('@@iterator')
)
)
]
),
b.ifStatement(
b.binaryExpression(
'===',
b.unaryExpression(
'typeof',
b.memberExpression(
b.identifier('iterable'),
b.identifier('sym'),
true
),
true
),
b.literal('function')
),
b.blockStatement([
b.returnStatement(
b.callExpression(
b.memberExpression(
b.identifier('iterable'),
b.identifier('sym'),
true
),
[]
)
)
]),
b.ifStatement(
b.logicalExpression(
'||',
b.binaryExpression(
'===',
b.unaryExpression(
'typeof',
b.identifier('iterable'),
true
),
b.literal('object')
),
b.binaryExpression(
'===',
b.unaryExpression(
'typeof',
b.identifier('iterable'),
true
),
b.literal('function')
)
),
b.blockStatement([
b.returnStatement(
b.callExpression(
getArrayIterator(scope),
[b.identifier('iterable')]
)
)
]),
b.blockStatement([
b.throwStatement(
b.newExpression(
b.identifier('TypeError'),
[]
)
)
])
)
)
]),
false,
false
)};
ast-util-0.6.0/lib/helpers/getIteratorRange.js 0000664 0000000 0000000 00000006357 12451033276 0021311 0 ustar 00root root 0000000 0000000 var b = require('ast-types').builders;
module.exports = function(scope) {
return b.functionExpression(
null,
[
b.identifier('iterator'),
b.identifier('index'),
b.identifier('begin'),
b.identifier('len')
],
b.blockStatement([
b.ifStatement(
b.binaryExpression(
'>',
b.identifier('index'),
b.identifier('begin')
),
b.blockStatement([
b.throwStatement(
b.newExpression(
b.identifier('RangeError'),
[]
)
)
]),
null
),
b.ifStatement(
b.binaryExpression(
'===',
b.unaryExpression(
'typeof',
b.identifier('len'),
true
),
b.literal('undefined')
),
b.blockStatement([
b.expressionStatement(
b.assignmentExpression(
'=',
b.identifier('len'),
b.identifier('Infinity')
)
)
]),
null
),
b.variableDeclaration(
'var',
[
b.variableDeclarator(
b.identifier('range'),
b.arrayExpression([])
),
b.variableDeclarator(
b.identifier('end'),
b.binaryExpression(
'+',
b.identifier('begin'),
b.identifier('len')
)
)
]
),
b.whileStatement(
b.binaryExpression(
'<',
b.identifier('index'),
b.identifier('end')
),
b.blockStatement([
b.variableDeclaration(
'var',
[
b.variableDeclarator(
b.identifier('next'),
b.callExpression(
b.memberExpression(
b.identifier('iterator'),
b.identifier('next'),
false
),
[]
)
)
]
),
b.ifStatement(
b.memberExpression(
b.identifier('next'),
b.identifier('done'),
false
),
b.blockStatement([b.breakStatement()]),
null
),
b.ifStatement(
b.binaryExpression(
'>=',
b.identifier('index'),
b.identifier('begin')
),
b.blockStatement([
b.expressionStatement(
b.callExpression(
b.memberExpression(
b.identifier('range'),
b.identifier('push'),
false
),
[
b.memberExpression(
b.identifier('next'),
b.identifier('value'),
false
)
]
)
)
]),
null
),
b.expressionStatement(
b.updateExpression(
'++',
b.identifier('index'),
false
)
)
])
),
b.returnStatement(
b.objectExpression([
b.property(
'init',
b.identifier('range'),
b.identifier('range')
),
b.property(
'init',
b.identifier('index'),
b.identifier('index')
)
])
)
]),
false,
false
)};
ast-util-0.6.0/lib/index.js 0000664 0000000 0000000 00000037153 12451033276 0015506 0 ustar 00root root 0000000 0000000 /* jshint node:true, undef:true, unused:true */
var types = require('ast-types');
var b = types.builders;
var n = types.namedTypes;
var NodePath = types.NodePath;
var getSecret = require('private').makeAccessor();
var hasOwnProp = Object.prototype.hasOwnProperty;
var assert = require('assert');
/**
* Re-export ast-types for ease of our users.
*/
exports.types = types;
/**
* Export the Replacement helper for anything that needs to delay replacement.
*/
exports.Replacement = require('./replacement');
/**
* Returns a call to `Array.prototype.slice` with `node` as the context and
* `begin` and `end` as the arguments to `slice`.
*
* @param {Scope} scope
* @param {Expression} node
* @param {Expression|number=} begin
* @param {Expression|number=} end
* @return {CallExpression}
*/
function callArraySlice(scope, node, begin, end) {
if (typeof begin === 'number') {
begin = b.literal(begin);
}
if (typeof end === 'number') {
end = b.literal(end);
}
var args = [];
if (begin) { args.push(begin); }
if (end) { args.push(end); }
return callSharedMethodWithContext(
scope,
'Array.prototype.slice',
node,
args
);
}
exports.callArraySlice = callArraySlice;
/**
* Returns a call to `Function.prototype.bind` using either `call` or `apply`
* depending on what the value of `args` is. If `args` is an expression then
* `apply` is used. If `args` is an array of expressions, then `call`.
*
* @param {Scope} scope
* @param {Expression} fn
* @param {Expression} context
* @param {Expression|Array.} args
* @return {CallExpression}
*/
function callFunctionBind(scope, fn, context, args) {
var bind = sharedFor(scope, 'Function.prototype.bind');
if (n.Expression.check(args)) {
return b.callExpression(
b.memberExpression(bind, b.identifier('apply'), false),
[fn, b.callExpression(
b.memberExpression(
b.arrayExpression([context]),
b.identifier('concat'),
false
),
[args]
)]
);
} else {
return b.callExpression(
b.memberExpression(bind, b.identifier('call'), false),
[fn, context].concat(args || [])
);
}
}
exports.callFunctionBind = callFunctionBind;
/**
* Gets an iterator for the value representing the given expression.
*
* @param {Scope} scope
* @param {Expression} expression
* @return {CallExpression}
*/
function callGetIterator(scope, expression) {
var getIterator = injectGetIteratorHelper(scope.getGlobalScope());
return b.callExpression(getIterator, [expression]);
}
exports.callGetIterator = callGetIterator;
/**
* Returns a reference to a shared function that implements the default
* `@@iterator` for arrays.
*
* @private
* @param {Scope} scope
* @return {CallExpression}
*/
function getArrayIterator(scope) {
return injectGetArrayIteratorHelper(scope.getGlobalScope());
}
exports.getArrayIterator = getArrayIterator;
/**
* return a range of value from an iterator
*
* @param {Scope} scope
* @param {Expression} iterator
* @param {Literal} index
* @param {Literal} begin
* @param {Literal} len
* @return {CallExpression}
*/
function callGetIteratorRange(scope, iterator, index, begin, len) {
var getIteratorRange = injectGetIteratorRangeHelper(scope.getGlobalScope());
return b.callExpression(getIteratorRange, [iterator, index, begin, len]);
}
exports.callGetIteratorRange = callGetIteratorRange;
/**
* The [[Get]] internal method on objects.
*
* @param {Scope} scope
* @param {Expression} object
* @param {Expression} property
* @param {Expression} receiver
* @return {CallExpression}
*/
function callGet(scope, object, property, receiver) {
var get = injectGetHelper(scope.getGlobalScope());
return b.callExpression(get, [object, property, receiver]);
}
exports.callGet = callGet;
/**
* Returns a call to `Object.getOwnPropertyDescriptor` with the given `object`
* and `property`.
*
* @param {Scope} scope
* @param {Expression} object
* @param {Expression|string} property
* @return {CallExpression}
*/
function callGetOwnPropertyDescriptor(scope, object, property) {
if (typeof property === 'string') {
property = b.literal(property);
}
return callSharedMethod(
scope,
'Object.getOwnPropertyDescriptor',
[object, property]
);
}
exports.callGetOwnPropertyDescriptor = callGetOwnPropertyDescriptor;
/**
* Returns a call to `Object.getPrototypeOf` with the given `object`.
*
* @param {Scope} scope
* @param {Expression} object
* @return {CallExpression}
*/
function callGetPrototypeOf(scope, object) {
return callSharedMethod(scope, 'Object.getPrototypeOf', [object]);
}
exports.callGetPrototypeOf = callGetPrototypeOf;
/**
* Returns a call to `hasOwnProperty` with `node` as the context and `property`
* as the property to check.
*
* @param {Scope} scope
* @param {Expression} node
* @param {Expression|string} property
* @return {CallExpression}
*/
function callHasOwnProperty(scope, node, property) {
if (typeof property === 'string') {
property = b.literal(property);
}
return callSharedMethodWithContext(
scope,
'Object.prototype.hasOwnProperty',
node,
[property]
);
}
exports.callHasOwnProperty = callHasOwnProperty;
/**
* Returns a call to the given `callee` with `args` as the arguments. If
* `callee` is a string then it is treated as a globally-accessible function
* such as `Object.defineProperty` which will be stored in a unique temporary
* variable. Subsequent calls to this function will re-use the same temporary
* variable.
*
* @param {Scope} scope
* @param {Expression|string} callee
* @param {Array.} args
* @return {CallExpression}
*/
function callSharedMethod(scope, callee, args) {
if (typeof callee === 'string') {
callee = sharedFor(scope, callee);
}
return b.callExpression(callee, args);
}
exports.callSharedMethod = callSharedMethod;
/**
* Returns a call to the given `callee` with `context` as the method context
* and `args` as the arguments. If `callee` is a string then it is treated as a
* globally-accessible function such as `Array.prototype.slice` which will be
* stored in a unique temporary variable. Subsequent calls to this function
* will re-use the same temporary variable.
*
* @param {Scope} scope
* @param {Expression|string} callee
* @param {Expression} context
* @param {Array.} args
* @return {CallExpression}
*/
function callSharedMethodWithContext(scope, callee, context, args) {
if (typeof callee === 'string') {
callee = sharedFor(scope, callee);
}
return b.callExpression(
b.memberExpression(callee, b.identifier('call'), false),
[context].concat(args)
);
}
exports.callSharedMethodWithContext = callSharedMethodWithContext;
/**
* Gets a list of identifiers referencing global variables anywhere within the
* given `ast`. Assuming the ast is for this code:
*
* var a;
* function b(){ return c; }
* b(d);
*
* Then `getGlobals` will return two identifiers, `c` and `d`.
*
* @param {Node} ast
* @return {Array.}
*/
function getGlobals(ast) {
var globals = [];
var seen = Object.create(null);
types.visit(ast, {
visitNode: function(path) {
this.traverse(path);
var node = path.value;
if (isReference(path) && !path.scope.lookup(node.name)) {
if (!(node.name in seen)) {
seen[node.name] = true;
globals.push(node);
}
}
}
});
return globals;
}
exports.getGlobals = getGlobals;
/**
* Generate a safe JavaScript identifier for the given string.
*
* @param {string} string
* @return {string}
* @private
*/
function identifierForString(string) {
// TODO: Verify this regex.
return string.replace(/[^\w\d\$_]/g, '$');
}
/**
* Injects the 'get' pre-built helper.
*
* @param {Scope} scope
* @return {Identifier}
*/
function injectGetHelper(scope) {
return injectShared(
scope,
'get',
function() {
return require('./helpers/get')(scope);
}
);
}
/**
* Injects the 'getArrayIterator' pre-built helper.
*
* @param {Scope} scope
* @return {Identifier}
*/
function injectGetArrayIteratorHelper(scope) {
return injectShared(
scope,
'getArrayIterator',
function() {
return require('./helpers/getArrayIterator')(scope);
}
);
}
/**
* Injects the 'getIterator' pre-built helper.
*
* @param {Scope} scope
* @return {Identifier}
*/
function injectGetIteratorHelper(scope) {
return injectShared(
scope,
'getIterator',
function() {
return require('./helpers/getIterator')(scope);
}
);
}
/**
* Injects the 'getIteratorRange' pre-built helper.
*
* @param {Scope} scope
* @return {Identifier}
*/
function injectGetIteratorRangeHelper(scope) {
return injectShared(
scope,
'getIteratorRange',
function() {
return require('./helpers/getIteratorRange')(scope);
}
);
}
/**
* Injects a shared variable with a unique identifier. Only the first call with
* the same `scope` and `name` will result in a variable declaration being
* created. The `expression` passed in can either be an AST node or a function
* to generate one. This function is generally used to inject repeatedly-used
* values and prevent repeated execution.
*
* @param {Scope} scope
* @param {string} name
* @param {Expression|function(): Expression} expression
* @return {Identifier}
*/
function injectShared(scope, name, expression) {
var scopeSecret = getSecret(scope);
if (!(name in scopeSecret)) {
scopeSecret[name] = injectVariable(
scope,
uniqueIdentifier(scope, name),
typeof expression === 'function' ?
expression() :
expression
);
}
return scopeSecret[name];
}
exports.injectShared = injectShared;
/**
* Injects a variable with the given `identifier` into the given `scope` as a
* `var` declaration with an optional initial value.
*
* @param {Scope} scope
* @param {Identifier} identifier
* @param {Expression=} init
* @return {Identifier} Returns the given `identifier`.
*/
function injectVariable(scope, identifier, init) {
var bodyPath = scope.path.get('body');
if (n.BlockStatement.check(bodyPath.value)) {
bodyPath = bodyPath.get('body');
}
var declarationIndex;
var bodyStatements = bodyPath.node.body;
for (declarationIndex = 0; declarationIndex < bodyStatements.length; declarationIndex++) {
var statement = bodyStatements[declarationIndex];
if (!isDirectivePrologue(statement)) {
break;
}
}
bodyPath.insertAt(
declarationIndex,
b.variableDeclaration(
'var',
[b.variableDeclarator(identifier, init || null)]
)
);
// Ensure this identifier counts as used in this scope.
var name = identifier.name;
var bindings = scope.getBindings();
if (!hasOwnProp.call(bindings, name)) {
bindings[name] = [];
}
bindings[name].push(new NodePath(identifier));
return identifier;
}
exports.injectVariable = injectVariable;
/**
* Determines whether the given statement is a directive prologue, e.g.
* "use strict".
*
* @param {Statement} statement
* @returns {boolean}
*/
function isDirectivePrologue(statement) {
if (n.ExpressionStatement.check(statement)) {
var expression = statement.expression;
if (n.Literal.check(expression)) {
return typeof expression.value === 'string';
}
}
return false;
}
/**
* Determines whether the given `path` is a value reference. For example, `a`
* and `b` are references, but `c` is not:
*
* a(b.c);
*
* Only identifiers count as references.
*
* @param {NodePath} path
* @param {string=} name
* @return {boolean}
*/
function isReference(path, name) {
var node = path.value;
assert.ok(n.Node.check(node));
if (n.Identifier.check(node)) {
if (name && node.name !== name) { return false; }
var parent = path.parent.value;
if (n.VariableDeclarator.check(parent)) {
return parent.init === node;
} else if (n.MemberExpression.check(parent)) {
return parent.object === node || (
parent.computed && parent.property === node
);
} else if (n.Function.check(parent)) {
return parent.id !== node && !parent.params.some(function(param) {
return param === node;
});
} else if (n.ClassDeclaration.check(parent) || n.ClassExpression.check(parent)) {
return parent.id !== node;
} else if (n.CatchClause.check(parent)) {
return parent.param !== node;
} else if (n.Property.check(parent)) {
return parent.key !== node;
} else if (n.MethodDefinition.check(parent)) {
return parent.key !== node;
} else if (n.ImportSpecifier.check(parent)) {
return false;
} else if (n.ImportDefaultSpecifier.check(parent)) {
return false;
} else if (n.ImportNamespaceSpecifier.check(parent)) {
return false;
} else if (n.LabeledStatement.check(parent)) {
return false;
} else {
return true;
}
}
return false;
}
exports.isReference = isReference;
/**
* Determines whether the given `name` should be considered "used" in the given
* `scope`. For a name to be used, it should either:
*
* 1. Be declared in this scope or a parent scope.
* 2. Be referenced in this scope, a parent scope, or any child scopes.
*
* For example, `a`, `b`, and `d` are used in the global scope of this example
* while `c` is not:
*
* var a;
* function b() {}
*
* try {
* a = b(d);
* } catch (c) {
* }
*
* @param {Scope} scope
* @param {string} name
* @return {boolean}
*/
function isUsed(scope, name) {
if (scope.lookup(name)) {
return true;
}
var globalScope = scope.getGlobalScope();
var globalScopeSecret = getSecret(globalScope);
if (!globalScopeSecret.globals) {
globalScopeSecret.globals = getGlobals(globalScope.node);
}
return globalScopeSecret.globals.some(function(global) {
return global.name === name;
});
}
exports.isUsed = isUsed;
/**
* Injects a shared variable by getting the named value from a dotted path. For
* example, this will return an identifier that can be used in place of the
* named expression:
*
* sharedFor(scope, 'Object.defineProperty')
*
* Subsequent calls to `sharedFor` in the same scope will return the same
* identifier.
*
* @param {Scope} scope
* @param {string} name
* @return {Identifier}
*/
function sharedFor(scope, name) {
return injectShared(
scope,
name,
function() {
var parts = name.split('.');
var result = b.identifier(parts[0]);
for (var i = 1, length = parts.length; i < length; i++) {
result = b.memberExpression(
result,
b.identifier(parts[i]),
false
);
}
return result;
}
);
}
exports.sharedFor = sharedFor;
/**
* Generates an identifier guaranteed not to collide with any others in the
* given `scope`. This function will also never generate the same identifier
* twice for any `scope` whose global scope already got that identifier.
*
* Called in a scope with no global references and no variables, the first time
* this function is called it will return an identifier named `$__0`.
*
* When called with a name that name will be used with a prefix, "$__", if
* possible. If that name is already used then it will append incrementing
* numbers until it finds a name that isn't used.
*
* @param {Scope} scope
* @param {string=} name
* @return {Identifier}
* @see isUsed
*/
function uniqueIdentifier(scope, name) {
var prefix = '$__' + identifierForString(name ? name : '');
var globalScopeSecret = getSecret(scope.getGlobalScope());
var n = globalScopeSecret.nextId || 0;
var identifier = name ? prefix : null;
while (!identifier || isUsed(scope, identifier)) {
identifier = prefix + n;
n++;
}
globalScopeSecret.nextId = n;
return b.identifier(identifier);
}
exports.uniqueIdentifier = uniqueIdentifier;
ast-util-0.6.0/lib/replacement.js 0000664 0000000 0000000 00000004471 12451033276 0016673 0 ustar 00root root 0000000 0000000 /* jshint node:true, undef:true, unused:true */
/**
* Represents a replacement of a node path with zero or more nodes.
*
* @constructor
* @param {NodePath} nodePath
* @param {Array.} nodes
*/
function Replacement(nodePath, nodes) {
this.queue = [];
if (nodePath && nodes) {
this.queue.push([nodePath, nodes]);
}
}
/**
* Performs the replacement.
*/
Replacement.prototype.replace = function() {
for (var i = 0, length = this.queue.length; i < length; i++) {
var item = this.queue[i];
item[0].replace.apply(item[0], item[1]);
}
};
/**
* Incorporates the replacements from the given Replacement into this one.
*
* @param {Replacement} anotherReplacement
*/
Replacement.prototype.and = function(anotherReplacement) {
this.queue.push.apply(this.queue, anotherReplacement.queue);
return this;
};
/**
* Constructs a Replacement that, when run, will remove the node from the AST.
*
* @param {NodePath} nodePath
* @returns {Replacement}
*/
Replacement.removes = function(nodePath) {
return new Replacement(nodePath, []);
};
/**
* Constructs a Replacement that, when run, will insert the given nodes after
* the one in nodePath.
*
* @param {NodePath} nodePath
* @param {Array.} nodes
* @returns {Replacement}
*/
Replacement.adds = function(nodePath, nodes) {
return new Replacement(nodePath, [nodePath.node].concat(nodes));
};
/**
* Constructs a Replacement that, when run, swaps the node in nodePath with the
* given node or nodes.
*
* @param {NodePath} nodePath
* @param {Node|Array.} nodes
*/
Replacement.swaps = function(nodePath, nodes) {
if ({}.toString.call(nodes) !== '[object Array]') {
nodes = [nodes];
}
return new Replacement(nodePath, nodes);
};
/**
* Build replacements for each of the items in nodePaths by passing them to the
* given callback. If the callback returns null for a given node path then no
* replacement will be created for that node.
*
* @param {Array.} nodePaths
* @param {function(NodePath): ?Replacement} callback
* @returns {Replacement}
*/
Replacement.map = function(nodePaths, callback) {
var result = new Replacement();
nodePaths.each(function(nodePath) {
var replacement = callback(nodePath);
if (replacement) {
result.and(replacement);
}
});
return result;
};
module.exports = Replacement;
ast-util-0.6.0/lib/types.js 0000664 0000000 0000000 00000000601 12451033276 0015527 0 ustar 00root root 0000000 0000000 /**
* This file really only exists to make JSDoc/WebStorm happy.
*/
var types = require('ast-types');
var Scope = require('ast-types/lib/scope');
var NodePath = types.NodePath;
/** @typedef Object */
var Node;
/** @typedef Node */
var Expression;
/** @typedef Expression */
var CallExpression;
/** @typedef Expression */
var Literal;
/** @typedef Expression */
var Identifier;
ast-util-0.6.0/package.json 0000664 0000000 0000000 00000001353 12451033276 0015552 0 ustar 00root root 0000000 0000000 {
"name": "ast-util",
"version": "0.6.0",
"description": "Utilities for AST transformers.",
"main": "lib/index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha -R spec"
},
"repository": {
"type": "git",
"url": "git://github.com/eventualbuddha/ast-util.git"
},
"keywords": [
"ast",
"transform",
"esnext",
"es6",
"macros"
],
"author": "Brian Donovan",
"license": "Apache 2",
"bugs": {
"url": "https://github.com/eventualbuddha/ast-util/issues"
},
"homepage": "https://github.com/eventualbuddha/ast-util",
"devDependencies": {
"mocha": "~2.1.0",
"recast": "~0.9.11"
},
"dependencies": {
"ast-types": "~0.6.7",
"private": "~0.1.6"
}
} ast-util-0.6.0/test/ 0000775 0000000 0000000 00000000000 12451033276 0014241 5 ustar 00root root 0000000 0000000 ast-util-0.6.0/test/replacement_test.js 0000664 0000000 0000000 00000007662 12451033276 0020150 0 ustar 00root root 0000000 0000000 /* jshint node:true, mocha:true, undef:true, unused:true */
var Replacement = require('../lib').Replacement;
var assert = require('assert');
describe('Replacement', function() {
var nodePath;
function makeNodePath() {
var node = {};
return {
node: node,
replace: function() {
this.replacedWith = [].slice.call(arguments);
}
};
}
beforeEach(function() {
nodePath = makeNodePath();
});
describe('.removes', function() {
it('creates a Replacement that replaces with nothing', function() {
var repl = Replacement.removes(nodePath);
repl.replace();
assert.equal(nodePath.replacedWith.length, 0);
});
});
describe('.swaps', function() {
it('creates a Replacement that replaces with another', function() {
var swappedIn = {};
var repl = Replacement.swaps(nodePath, swappedIn);
repl.replace();
assert.equal(nodePath.replacedWith.length, 1);
assert.strictEqual(nodePath.replacedWith[0], swappedIn);
});
it('creates a Replacement that replaces with a list', function() {
var swappedIn1 = {};
var swappedIn2 = {};
var repl = Replacement.swaps(nodePath, [swappedIn1, swappedIn2]);
repl.replace();
assert.equal(nodePath.replacedWith.length, 2);
assert.strictEqual(nodePath.replacedWith[0], swappedIn1);
assert.strictEqual(nodePath.replacedWith[1], swappedIn2);
});
});
describe('.adds', function() {
it('creates a Replacement that adds additional nodes', function() {
var added1 = {};
var added2 = {};
var repl = Replacement.adds(nodePath, [added1, added2]);
repl.replace();
assert.equal(nodePath.replacedWith.length, 3);
assert.strictEqual(nodePath.replacedWith[0], nodePath.node);
assert.strictEqual(nodePath.replacedWith[1], added1);
assert.strictEqual(nodePath.replacedWith[2], added2);
});
});
describe('.map', function() {
var from1;
var from2;
var nodeArrayPath;
var to1;
var to2;
beforeEach(function() {
from1 = makeNodePath();
from2 = makeNodePath();
nodeArrayPath = [from1, from2];
nodeArrayPath.each = nodeArrayPath.forEach;
to1 = {};
to2 = {};
});
it('creates a Replacement with the results of the map', function() {
var repl = Replacement.map(nodeArrayPath, function(nodePath) {
if (nodePath === from1) {
return Replacement.swaps(nodePath, to1);
} else if (nodePath === from2) {
return Replacement.swaps(nodePath, to2);
} else {
assert.ok(false, 'unexpected argument to callback: ' + nodePath);
}
});
repl.replace();
assert.equal(from1.replacedWith.length, 1);
assert.strictEqual(from1.replacedWith[0], to1);
assert.equal(from2.replacedWith.length, 1);
assert.strictEqual(from2.replacedWith[0], to2);
});
it('ignores null returns from the callback', function() {
var repl = Replacement.map(nodeArrayPath, function(nodePath) {
if (nodePath === from1) {
return Replacement.swaps(nodePath, to1);
} else if (nodePath === from2) {
return null;
} else {
assert.ok(false, 'unexpected argument to callback: ' + nodePath);
}
});
repl.replace();
assert.equal(from1.replacedWith.length, 1);
assert.strictEqual(from1.replacedWith[0], to1);
assert.equal(from2.replacedWith, null);
});
});
describe('#and', function() {
it('adds the given Replacement to the receiver', function() {
var node1 = {};
var anotherNodePath = makeNodePath();
var repl = Replacement.swaps(
nodePath, node1
).and(
Replacement.removes(anotherNodePath)
);
repl.replace();
assert.equal(nodePath.replacedWith.length, 1);
assert.strictEqual(nodePath.replacedWith[0], node1);
assert.equal(anotherNodePath.replacedWith.length, 0);
});
});
});
ast-util-0.6.0/test/util_test.js 0000664 0000000 0000000 00000041723 12451033276 0016622 0 ustar 00root root 0000000 0000000 /* jshint node:true, mocha:true, undef:true, unused:true */
var util = require('../lib');
var recast = require('recast');
var esprima = require('esprima-fb');
var types = recast.types;
var n = types.namedTypes;
var b = types.builders;
var NodePath = types.NodePath;
var assert = require('assert');
function parse(source) {
return recast.parse(source, { esprima: esprima });
}
function normalize(source) {
return recast.prettyPrint(parse(source)).code;
}
function processIt(source, callback) {
var ast = parse(source);
types.visit(ast, {
visitIdentifier: function(path) {
if (path.value.name === 'IT') {
callback.call(path, path.value);
return false;
}
this.traverse(path);
}
});
return ast;
}
function sameSource(actual, expected, message) {
actual = (typeof actual === 'object') ?
recast.prettyPrint(actual).code :
normalize(actual);
expected = (typeof expected === 'object') ?
recast.prettyPrint(expected).code :
normalize(expected);
assert.equal(actual, expected, message);
}
describe('#uniqueIdentifier', function() {
// Looks for an `IT` identifier and asserts that the first valid unique
// identifier in that scope matches `expected`.
function check(source, expected, name) {
var identifier;
processIt(source, function() {
identifier = util.uniqueIdentifier(this.scope, name);
});
assert.equal(identifier.name, expected);
}
it('returns the first variable name when there are no variables', function() {
check('IT;', '$__0');
});
it('returns the first variable not already declared', function() {
check('var $__0, $__1; IT', '$__2');
});
it('returns the first variable not hoisted', function() {
check('IT; var $__0, $__1;', '$__2');
});
it('skips conflicting argument names', function() {
check('function foo(a, $__0, b) { IT; }', '$__1');
});
it('skips conflicting catch arguments', function() {
check('try {} catch ($__0) { IT; }', '$__1');
});
it('skips conflicts from parent scopes', function() {
check('var $__0; function outer($__1) { function $__2() { IT; } }', '$__3');
});
it('ignores variables that will shadow inner scopes', function() {
check('IT; (function($__0){ var $__1; })();', '$__0');
});
it('skips references to global scope', function() {
check('$__0; IT;', '$__1');
});
it('allows specifying a descriptive name', function() {
check('IT;', '$__aName', 'aName');
});
it('adds numeric suffixes to descriptive names if need be', function() {
check('var $__o; $__o0; (function($__o1){ IT; }); function $__o2(){}', '$__o3', 'o');
});
it('converts descriptive names to identifiers', function() {
check('IT;', '$__Hello$there$$how$are$you$', 'Hello there, how are you?');
});
});
describe('#isUsed', function() {
function check(source, names, expected) {
var ast = parse(source).program;
var rootPath = new NodePath({ root: ast });
var globalScope = rootPath.get('root').scope;
if (typeof names === 'string') {
names = [names];
}
names.forEach(function(name) {
var actual = util.isUsed(globalScope, name);
assert.ok(
actual === expected,
'expected `' + name + '` ' +
(expected ? '' : 'not ') +
'to be used globally by `' + JSON.stringify(source) + '`'
);
});
}
it('is true for globals declared at the top level', function() {
check('var a;', 'a', true);
});
it('is true for globals referenced at the top level', function() {
check('a;', 'a', true);
});
it('is false for a variable not declared in scope or referenced anywhere', function() {
check('var a, b; c;', 'd', false);
});
it('is false in the global scope for variables declared in inner scopes', function() {
check('function foo(a) { var b; }', ['a', 'b'], false);
});
it('is true for global references used within inner scopes', function() {
check('function foo() { return a + b; }', ['a', 'b'], true);
});
});
describe('#isReference', function() {
function check(source, expected, filter) {
processIt(source, function() {
if (!filter || filter(this)) {
assert.equal(
util.isReference(this),
expected,
'expected `IT` in `' + source + '` to ' +
(expected ? '' : 'not ') + 'be a reference'
);
}
});
}
it('is false for variable declaration identifiers', function() {
check('var IT;', false);
});
it('is true for global property accesses', function() {
check('IT;', true);
check('IT.foo', true);
});
it('is true for computed member expressions', function() {
check('foo[IT];', true);
});
it('is false for non-computed member expressions', function() {
check('foo.IT;', false);
});
it('is false for function parameters', function() {
check('(function(IT){})', false);
});
it('is false for catch arguments', function() {
check('try{}catch(IT){}', false);
});
it('is true for variable references', function() {
check('var IT; foo(IT);', true, function(path) {
return n.CallExpression.check(path.parent.value);
});
});
it('is false for property keys', function() {
check('({IT: 1})', false);
});
it('is true for property values', function() {
check('({a: IT})', true);
check('({IT: IT})', true, function(path) {
return path.parent.value === path.value;
});
});
it('is true for variable initial values', function() {
check('var a = IT;', true);
});
it('is false for labeled statements', function() {
check('IT: 1', false);
check('IT: IT', true, function(path) {
return n.ExpressionStatement.check(path.parent.value);
});
});
it('can check names', function() {
types.visit(parse('a'), {
visitIdentifier: function(path) {
assert.ok(util.isReference(path, 'a'));
assert.ok(!util.isReference(path, 'b'));
return false;
}
});
});
it('is false for class names', function() {
check('class IT {}', false);
check('var foo = class IT {};', false);
});
it('is false for method definition identifiers', function() {
check('class Foo { IT(){} }', false);
});
it('is false for function definition identifiers', function() {
check('function IT(){}', false);
});
it('is true for superclass identifiers', function() {
check('class Foo extends IT {}', true);
});
it('is false for name of import specifiers', function() {
check('import IT from "IT";', false);
check('import { IT } from "IT";', false);
check('import { foo as IT } from "IT";', false);
check('import { IT as foo } from "IT";', false);
});
it('is true for export default', function() {
check('export default IT;', true);
});
});
describe('#injectVariable', function() {
it('creates a variable in the scope node', function() {
var identifier = b.identifier('a');
var ast = processIt('function foo(){ IT; }', function() {
assert.strictEqual(
util.injectVariable(this.scope, identifier),
identifier
);
});
sameSource(ast, 'function foo(){ var a; IT; }');
});
it('marks the variable as bound in the given scope', function() {
var identifier = b.identifier('a');
var scope;
processIt('function foo(){ IT; }', function() {
scope = this.scope;
assert.strictEqual(
util.injectVariable(scope, identifier),
identifier
);
});
assert.ok(scope.declares('a'), 'injected variables should count as declared');
assert.ok(!scope.parent.declares('a'), 'injected variables should not pollute parent scopes');
});
it('can create a variable with an initial value', function() {
var ast = processIt('IT;', function() {
util.injectVariable(
this.scope,
b.identifier('hasOwnProp'),
b.memberExpression(
b.memberExpression(
b.identifier('Object'),
b.identifier('prototype'),
false
),
b.identifier('hasOwnProperty'),
false
)
);
});
sameSource(ast, 'var hasOwnProp = Object.prototype.hasOwnProperty; IT;');
});
it('can inject a variable in a scope at a position that is later replaced', function() {
var ast = parse('var a;');
types.visit(ast, {
visitVariableDeclaration: function(path) {
util.injectVariable(path.scope, b.identifier('b'));
return b.expressionStatement(
b.callExpression(b.identifier('replacement'), [])
);
}
});
sameSource(
ast,
'var b; replacement();'
);
});
it('injects after any global "use strict" pragma', function() {
var ast = processIt('"use strict"; IT;', function() {
util.injectVariable(
this.scope,
b.identifier('a'),
b.literal(1)
)
});
sameSource(ast, '"use strict"; var a = 1; IT;');
});
it('injects after any local "use strict" pragma', function() {
var ast = processIt('function getIt() { "use strict"; return IT; }', function() {
util.injectVariable(
this.scope,
b.identifier('a'),
b.literal(1)
)
});
sameSource(ast, 'function getIt() { "use strict"; var a = 1; return IT; }');
});
});
describe('#injectShared', function() {
var hasOwnPropAST = b.memberExpression(
b.memberExpression(
b.identifier('Object'),
b.identifier('prototype'),
false
),
b.identifier('hasOwnProperty'),
false
);
var arraySliceAST = b.memberExpression(
b.memberExpression(
b.identifier('Array'),
b.identifier('prototype'),
false
),
b.identifier('slice'),
false
);
it('can inject a shared value', function() {
var ast = processIt('IT;', function() {
assert.equal(
util.injectShared(
this.scope,
'hasOwnProperty',
hasOwnPropAST
).name,
'$__hasOwnProperty'
);
// do it again under the same name
assert.equal(
util.injectShared(
this.scope,
'hasOwnProperty',
hasOwnPropAST
).name,
'$__hasOwnProperty'
);
// add a different shared variable
assert.equal(
util.injectShared(
this.scope,
'arraySlice',
arraySliceAST
).name,
'$__arraySlice'
);
});
sameSource(
ast,
'var $__arraySlice = Array.prototype.slice;' +
'var $__hasOwnProperty = Object.prototype.hasOwnProperty;' +
'IT;'
);
});
});
describe('#callHasOwnProperty', function() {
it('returns a CallExpression with the given object and property', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callHasOwnProperty(this.scope, node, 'is'));
});
sameSource(
ast,
'var $__Object$prototype$hasOwnProperty = Object.prototype.hasOwnProperty;' +
'$__Object$prototype$hasOwnProperty.call(IT, "is");'
);
});
});
describe('#callGetOwnPropertyDescriptor', function() {
it('returns a CallExpression with the given object and property', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callGetOwnPropertyDescriptor(this.scope, node, 'is'));
});
sameSource(
ast,
'var $__Object$getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;' +
'$__Object$getOwnPropertyDescriptor(IT, "is");'
);
});
});
describe('#callGetPrototypeOf', function() {
it('returns a CallExpression with the given object', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callGetPrototypeOf(this.scope, node));
});
sameSource(
ast,
'var $__Object$getPrototypeOf = Object.getPrototypeOf;' +
'$__Object$getPrototypeOf(IT);'
);
});
});
describe('#callArraySlice', function() {
it('returns a CallExpression with the given object and omits missing begin/end', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callArraySlice(this.scope, node));
});
sameSource(
ast,
'var $__Array$prototype$slice = Array.prototype.slice;' +
'$__Array$prototype$slice.call(IT);'
);
});
it('returns a CallExpression with the given object and begin/end', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callArraySlice(this.scope, node, 1, 2));
});
sameSource(
ast,
'var $__Array$prototype$slice = Array.prototype.slice;' +
'$__Array$prototype$slice.call(IT, 1, 2);'
);
});
});
describe('#callFunctionBind', function() {
it('uses call when given args as an array', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callFunctionBind(
this.scope,
node,
b.thisExpression(),
[b.literal(1)]
));
});
sameSource(
ast,
'var $__Function$prototype$bind = Function.prototype.bind;' +
'$__Function$prototype$bind.call(IT, this, 1);'
);
});
it('uses apply when given args as an expression', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callFunctionBind(
this.scope,
node,
b.thisExpression(),
b.identifier('args')
));
});
sameSource(
ast,
'var $__Function$prototype$bind = Function.prototype.bind;' +
'$__Function$prototype$bind.apply(IT, [this].concat(args));'
);
});
});
describe('#callGetIterator', function() {
it('calls the get iterator helper', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callGetIterator(
this.scope, node
));
});
sameSource(
ast,
'var $__getIterator = function(iterable) {' +
' var sym = typeof Symbol === "function" && Symbol.iterator || "@@iterator";' +
' if (typeof iterable[sym] === "function") {' +
' return iterable[sym]();' +
' } else if (typeof iterable === "object" || typeof iterable === "function") {' +
' return $__getArrayIterator(iterable);' +
' } else {' +
' throw new TypeError();' +
' }' +
'};' +
'var $__getArrayIterator = function(array) {' +
' var index = 0;' +
' return {' +
' next: function() {' +
' if (index < array.length) {' +
' return {' +
' done: false,' +
' value: array[index++]' +
' };' +
' } else {' +
' return {' +
' done: true,' +
' value: void 0' +
' };' +
' }' +
' }' +
' };' +
'};' +
'$__getIterator(IT);'
);
});
});
describe('#callGetIteratorRange', function() {
it('calls the get iterator range helper', function() {
var ast = processIt('IT;', function(node) {
this.replace(util.callGetIteratorRange(
this.scope, node, b.literal(1), b.literal(2), b.literal(3)
));
});
sameSource(
ast,
'var $__getIteratorRange = function(iterator, index, begin, len) {' +
' if (index > begin) {' +
' throw new RangeError();' +
' }' +
' if (typeof len === \'undefined\') {' +
' len = Infinity;' +
' }' +
' var range = [], end = begin + len;' +
' while (index < end) {' +
' var next = iterator.next();' +
' if (next.done) {' +
' break;' +
' }' +
' if (index >= begin) {' +
' range.push(next.value);' +
' }' +
' index++;' +
' }' +
' return {' +
' range: range,' +
' index: index' +
' };' +
'};'+
'$__getIteratorRange(IT, 1, 2, 3);'
);
});
});
describe('#getGlobals', function() {
function check(source, globals) {
assert.deepEqual(
util.getGlobals(parse(source).program).map(function(identifier) {
return identifier.name;
}),
globals
);
}
it('is empty when there are no references', function() {
check('', []);
});
it('has references from the top level', function() {
check('a; b(c + d);', ['a', 'b', 'c', 'd']);
});
it('ignores references that have associated variable declarations', function() {
check('var a; a + b;', ['b']);
});
it('ignores references that have associated function params', function() {
check('function area(r){ return Math.PI * Math.pow(r, 2); }', ['Math']);
});
it('ignores references that have associated catch arguments', function() {
check('try {} catch (a) { a + b["c"]; }', ['b']);
});
it('ignores references to declared functions', function() {
check('foo(); function foo() {}', []);
});
it('ignores references to things declared in outer scopes', function() {
check('var a; function foo() { return a; }', []);
});
it('ignores references to function expression identifiers within the function', function() {
check('var a = function b(){ return b; };', []);
});
});