estools / estraverse Goto Github PK
View Code? Open in Web Editor NEWECMAScript JS AST traversal functions
License: BSD 2-Clause "Simplified" License
ECMAScript JS AST traversal functions
License: BSD 2-Clause "Simplified" License
Hi,
It seems like using this.remove
on an expression which is itself set as expression
property of it's parent throws an error "Cannot read property 'type' of undefined". In this case, the parent should be removed with the child.
Hey,
what about map
method based on traverse
method?
now i have to write in none FP style:
const retrieveBemEntities = (fileContent) => {
var bemjsons = [];
return new Promise(function(res, err) {
const ast = parse.parse(fileContent);
traverse.traverse(ast, {
enter: node => isObject(node) && isBemjson(node) && bemjsons.push(nodeToObject(node)),
onEnd: () => res(bemjsons)
});
})
}
'll be cool write like this:
/**
* @returns {Promise<BEMJSON[]>}
*/
const retrieveBemEntities = (fileContent) =>
traverse.map(parse.parse(fileContent),
node => isObject(node) && isBemjson(node) && nodeToObject(node));
Consider the following example:
var escodegen = require('escodegen');
var esprima = require('esprima');
var estraverse = require('estraverse');
var code = 'function foo() { debug("bar"); debug("baz"); }';
var ast = esprima.parse(code);
estraverse.replace(ast, {
enter: function enter(node) {
if (
'ExpressionStatement' === node.type
&& 'CallExpression' === node.expression.type
&& 'debug' === node.expression.callee.name
) {
return this.remove();
}
}
});
code = escodegen.generate(ast, {
format: {
indent: { style: ' ' }
}
});
console.log(code);
This returns:
function foo() {
debug('baz');
}
but the expected result is:
function foo() {
}
Am i doing something wrong?
The versions of the modules used:
├─┬ [email protected]
│ ├── [email protected]
│ └─┬ [email protected]
│ └── [email protected]
├── [email protected]
└── [email protected]
/cc @RReverser
While updating Eclipse Orion to use version 1.5.0 of Estraverse I noticed that the exported version is '1.3.3-dev' for the1.5 tag in the repo. Is this intentional? It does not cause any issues for us, just wondering why the source version and the tagged version differ.
Hey developers
I'm wondering does "estraverse replace method" deals with promises ?
sorry for marking this is an issue :(
I don't understand the whole concept of estraverse. I mean it's like creating a SAX parser which iterates over the DOM. If I understand well, all of this is because the AST nodes do not have an uniform interface, like DOM nodes have e.g. childNodes, setAttribute, appendChild, etc.. Why don't you just fix the standard instead of creating such workarounds because of its errors?
This was just added to ESTree for module syntax.
estraverse.Syntax.ExportNamedDeclaration = "ExportNamedDeclaration";
estraverse.VisitorKeys.ExportNamedDeclaration = ["declaration", "specifies", "source"];
https://github.com/estree/estree/blob/master/es6.md#exportnameddeclaration
estraverse fails with any AST that has this new structure at the moment.
From time to time, there are new node types that are not yet supported by estraverse. Currently, estraverse just throws error on such nodes, what makes it less flexible compared to other AST traversal libraries (while I do understand reasoning behind strict set of properties).
But what if, instead of throwing, we would allow iterating over all sub-properties of unknown nodes? This would allow to use estraverse on new node types without waiting for new versions.
The use of candidates[current]
instead of key
is confusing, but candidates[current].type
seems nonsensical (it will always be undefined
, unless I'm mistaken):
if (nodeType === Syntax.ObjectExpression &&
'properties' === candidates[current] && // Why not 'properties' === key?
null == candidates[current].type) { // Do you mean node.type or candidate.type?
element = new Element(candidate[current2], [key, current2], 'Property', null);
}
As seen here: https://github.com/Constellation/estraverse/blob/e8c74ab23dde58153fd2ff4d9c02f45877162963/estraverse.js#L340
First, thank you for very elegant, concise, and useful tool. I use both tools: estraverse and recently shift-traverse for my projects.
One thing that I am missing is backward AST traversal. IMHO It will be useful for the static code analysis algorithms like lazy SSA construction or definition search.
What do you think?
I suggest making the .traverse method more polymorphic.
is it cool to write:
traverse.traverse(ast, node => isBemjson(node) && bemjsons.push(this.nodeToObject(node)));
insted of:
traverse.traverse(ast, {
enter: node => isBemjson(node) && bemjsons.push(this.nodeToObject(node))
});
I use estraverse
look for a specific node and I return the promise.
I cannot throw reject on traverse complete without workarounds.
function retrieveBemEntity (fileContent, offset) {
return new Promise(function (res, err) {
var ast = parse.parseExpressionAt(fileContent.substr(offset));
traverse.traverse(ast, {enter: function (node) {
var bemEntity = _onNode(node);
if (bemEntity) res(bemEntity), this.break();
}});
setTimeout(()=>err('not found'), 100); // todo add traverse.traverse on end handler
});
}
Consider the following snippet:
/**
*
*/
function f1(o1, o2, o3) {
return;
};
If you call estraverse.attachComments on the AST for it, the block comment is attached to the 'Program' node and not the 'FunctionDeclaration' node as you would expect.
I'm assuming this is due to the fact that the 'Program' and 'FunctionDeclaration' start range is the same.
We have the below sample code:
function dec() {
}
export default class Foo {
@dec
bar() {}
}
After parsed with babel-eslint
, it contains a MethodDefinition
node at body[1].declaration.body.body[0]
, it has a decorators
property, however estraverse.traverse
just ignore this property
Is it possible that estraverse support the edge ECMAScript feature to visit Decorator
node?
Esprima can include source code comments in its AST. For example, when parsing this code with the comment:true
flag:
function f(){
/*hello*/
//world
}
Esprima returns this AST:
{
"type": "Program",
"body": [ /* omitted to save space */ ],
"comments": [
{
"type": "Block",
"value": "hello"
},
{
"type": "Line",
"value": "world"
},
]
}
It would be helpful for estraverse to visit the comment nodes. Currently it does not.
(I believe comments are an Esprima extension to the Spidermonkey API, so this may be non-standard. But it would be really useful for downstream projects. For example, I'm currently trying to write a linting rule for ESLint that deals with comments, and fixing this issue is a prerequisite.)
When using estraverse.replace(), and I want to make new nodes, what's the best way to traverse down those new nodes?
For instance:
estraverse.replace(tree, {
enter: function (node, parent) {
if (/* something */) {
var newStatement = { type: 'FunctionDeclaration', body: /* ... */ };
parent.body.push(newStatement);
// ...recurse into newStatement
}
}
});
...currently, I'm just invoking estraverse.replace(newroot, { ... })
again with the same enter/exit
s, but I was wondering if there would be strange side effects to this of if there would be better ways.
escodegen references version ^1.9.1 as a dependency but there is no release for this even though the latest HEAD is tagged "version 1.9.2 starts". Could this be release be tagged?
I think it's reasonable to assume that someone using estraverse would also be using esprima.
They both define the same object for Syntax, would it be ok to remove this duplicate from estraverse?
Not sure what changed with the new minor version but it's breaking.
Error: Unknown node type SpreadProperty.
[email protected]
[email protected]
[email protected]
[email protected]
Pinning to 4.1.1 fix it
I posted a question on SO, but then thought I might also ask here.
Does estraverse support making a source map? Something like estraverse.replace(ast, transform, sourceMap)
would be awesome. Maybe in the returned AST object the root node can have a sourceMap
property? Or something along the lines of being able to supply a source map for the original AST in order to get the resulting source map. If no sourcemap is provided, then it would just return a sourcemap as if this is the first transform on the given code.
Would something like that be doable? What are the limitations? For example, I know the AST doesn't contain info about semi colons or the number and ordering of whitespace characters, so it may be difficult to map that.
Is there any existing recommended way to handle source maps with esprima+estraverse?
When we replace a node, do we expect the whole sub-tree of that node to be replaced?
I think that is the case, just wondering. Haven't tried it yet.
Another possibility could be that a returned node with a matching type is replaced in-place.
What about nodeToObject
method?
now I have to use workaroud with patched escodegen
and json.parse
:
const nodeToObject = node => JSON.parse(codegen.generate(node, {format: {realjson: true}}));
traverse.traverse(ast, {
enter: node => isBemjson(node) && bemjsons.push(this.nodeToObject(node))
});
I use estraverse.traverse in oj to iterate over the esprima AST. I'd like to use estraverse.Controller
directly, so I can use Controller#path
and Controller#parents
.
In estraverse.js, these methods are commented as API. However, no documentation or usage information exists in the README or wiki.
Just to make sure: Controller is intended to be API, right?
I see that estraverse.replace
and estraverse.traverse
return a tree, but it seems to be the same tree that is passed in, so it seems that the original tree is modified in place. Is the return value needed? What if someone doesn't want to modify the original tree, should they copy duplicate the tree in their own way first?
Parsing "var a = {a: 2}" with either acorn or esprima results in an AST which cannot be traversed by estraverse because ObjectExpression properties doesn't have a type.
According to https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API#Expressions, there is no type field on ObjectExpression.properties.
See estools/escodegen#80 for a similar bug in escodegen.
Does skip
stop recursion down its children? does break
only stop the traversal of the current node's siblings, or will it halt the entire operation entirely? These behaviors aren't documented and aren't immediately apparent.
Code:
var x = function (/* signature comment */) {
return 'foo' + 'bar';
};
/* raz */
x(function (other) { return other });
Handling:
var ast = esprima.parse(code, { comment: true, range: true, tokens: true });
console.log(ast.comments.length); // 2, both comments are referenced
ast = escodegen.attachComments(ast, ast.comments, ast.tokens);
console.log(escodegen.generate(ast, { comment: true });
Result:
var x = function (foo) {
return 'foo' + 'raz'
};
/* raz */
x(function (other) {
return other
})
We miss comment in function signature
I'm trying to traverse the AST of a JS file with 32482 LOC. I can see that lines after line 27860 has not been traversed. I can see the FunctionExpression node at that line has been entered but not left. Is this a known limitation in estraverse? Is there a way around this? Here is the file I'm trying to analyze.
Right now, the option for fallback: "iteration"
works great so long as all properties containing arrays/objects are intended to be traversed. However, there are some cases where extra information has been added into the AST (such as a parent
property) where traversing according to regular rules isn't desirable.
I'd like to add an option that works only when fallback: "iteration"
is set that allows us to ignore specific properties by name. So something like:
controller.traverse(tree, {
enter: function() {},
exit: function() {},
fallback: "iteration",
ignoreKeys: [ "parent" ]
});
So ignoreKeys
would be an array of keys that should never be traversed regardless of where they appear in the tree.
VisitorKeys = {
...
ExportSpecifier: ['exported', 'local'],
The keys should be in the reverse order because the 'local' identifier node will come before the 'exported' node. For example:
var a; export { a as b };
'local' is 'a'
'exported' is 'b'
This is the opposite for ImportSpecifier so its keys are correct.
Would you be open to make estraverse to support non-standard nodes?
I'm trying to use estraverse.replace
in js2coffee to mangle the JS AST into a CoffeeScript AST. This involves things such as, say, rewriting an undefined
to be surrounded by backticks. This means replacing { type: 'Identifier', name: 'undefined' }
with a custom CoffeeScript node, such as { type: 'CoffeeEscapedExpression', argument: 'undefined' }
.
This makes estraverse choke in these lines:
Controller.prototype.traverse = function traverse() {
...
candidates = VisitorKeys[nodeType];
current = candidates.length; /* <--- */
It would be nice to:
What do you think?
Hello,
I would like a node to become a comment.
Is it possible to do using estraverse ?
Thanks in advance for your help.
I am trying to get all the comments that are preceding/before the current node while traversing. I notice the comment here: #9 (comment), but I am not exactly sure how to do it.
If I first parse my source code using esprima, I will get the AST, where comments are separate from the tree, inside ast.comments
.
Then, when I traverse the tree using estraverse, how and when should I attachComments
so that I can access them in leadingComments?
I tried something like this:
var ast = esprima.parse(...);
estraverse.traverse(ast, {
cursor: 0,
enter: function (node) {
node = estraverse.attachComments( node, ast.comments, [] );
console.log('NODE', node)
}
});
But it doesn't work - it simply adds all comments to the leadingComments property of a node.
Generate AST spec from estree.
Would it be possible to replace a node with multiple statements? I understand this can't work on all places (only in body
s), but it'd be useful for some cases.
estraverse.replace(tree, {
enter: function (node, parent) {
if (/* something */) {
return [
{ type: 'FunctionDeclaration', body: /* ... */ },
{ type: 'FunctionDeclaration', body: /* ... */ } ];
}
}
});
An example use case would be to to change a ForStatement
(for (x;y;z){a}
) into a WhileStatement
(x; while (y) { z; a; }
), which will be useful for js2coffee.
We should add test cases.
Is needed from time to time (for example, to eliminate debugger statements in AST), but the only option for now is replacing them with EmptyStatement
which is not exactly the same as generates extra line with ;
.
I do understand that removing nodes is not always correct (i.e., not an option for parts of expressions), but this responsibility should be on developer's shoulders.
I'd like to add a pubic property, estraverse.Operators
I find this useful for debugging and using the english names for valid element classnames.
estraverse.Operators = {
'=': 'equal-assign',
'+=': 'plus-assign',
'-=': 'minus-assign',
'*=': 'times-assign',
'/=': 'divide-assign',
'%=': 'modulus-assign',
'<<=': 'left-shift-assign',
'>>=': 'right-shift-assign',
'>>>=': 'zero-right-shift-assign',
'&=': 'and-assign',
'^=': 'xor-assign',
'|=': 'or-assign',
'==': 'equal',
'!=': 'not-equal',
'===': 'strict-equal',
'!==': 'strict-not-equal',
'>': 'greater-than',
'>=': 'greater-than-or-equal',
'<': 'less-than',
'<=': 'less-than-or-equal',
'%': 'modulus',
'++': 'increment',
'--': 'decrement',
'-': 'negate',
'&': 'bitwise-and',
'|': 'bitwise-or',
'~': 'bitwise-not',
'^': 'xor',
'<<': 'left-shift',
'>>': 'right-shift',
'>>>': 'zero-right-shift',
'&&': 'logical-and',
'||': 'logical-or',
'!': 'logical-not',
'?': 'trinary-consequent',
':': 'trinary-alternate'
}
Love this library, I have a hacked branch that makes this "sort of work", but would love real es6 module support.
When requiring estraverse for the browser via browserify/webpack, it includes the package.json in the build output. This can be fixed by a shim of some sort, but its mildly annoying.
I found this old comment 36a40ad#commitcomment-3732396 but I can't figure out why shallowCopy
and lowerBound
are even in the code. Searching for them turns up nothing and removing them doesn't fail the tests.
c074451 fixes a very fatal bug in ESLint where parsing basic stuff like export default
would crash. It would be so awesome is this change is released soon 👍
interface Function <: Node {
id: Identifier | null;
params: [ Pattern ];
defaults: [ Expression ];
rest: Identifier | null;
body: BlockStatement | Expression;
generator: boolean;
expression: boolean;
}
As you can see, id
, params
, defaults
, rest
, and body
may have values that are Nodes, but we currently only traverse these members:
['params', 'body']
['id', 'params', 'body']
['id', 'params', 'body']
I saw this issue working inside eslint
and it did not emit event for BlockStatement
for the following code:
Code:
var a = function() {
a++;
b++;
c++;
},
b;
I listen for 2 nodes VariableDeclaration
and BlockStatement
.
My function gets called for VariableDeclaration
but never get called for BlockStatement
.
I expect that to happen.
Is this an known issue or my expectations are wrong.
UPDATE
If I stop listening for VariableDeclaration
then its works fine and the function gets called for BlockStatement
.
I'm not completely sure if the problem is with estraverse or eslint, but the fact is that eslint works just fine for us over estraverse 4.1.0, bur over estraverse 4.1.1 it does that:
> [email protected] lint /home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app
> eslint ./src ./index.js ./tests
/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/estraverse/estraverse.js:411
current = candidates.length;
^
TypeError: Cannot read property 'length' of undefined
at Controller.traverse (/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/estraverse/estraverse.js:411:37)
at EventEmitter.module.exports.api.verify (/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/eslint/lib/eslint.js:779:24)
at processText (/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/eslint/lib/cli-engine.js:221:27)
at processFile (/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/eslint/lib/cli-engine.js:258:18)
at executeOnFile (/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/eslint/lib/cli-engine.js:604:23)
at Array.forEach (native)
at /home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/eslint/lib/cli-engine.js:630:49
at Array.forEach (native)
at CLIEngine.executeOnFiles (/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/eslint/lib/cli-engine.js:626:18)
at Object.cli.execute (/home/gitlab-runner/builds/be2d88b2/0/stc-b2b/browser-app/node_modules/eslint/lib/cli.js:159:95)
At https://www.npmjs.com/package/estraverse, it says there is no readme:
This is because the published package does not include README.md:
$ tar tvf $(npm pack estraverse)
-rw-rw-rw- 0 0 0 1016 Mar 10 2016 package/package.json
-rw-rw-rw- 0 0 0 27642 Mar 10 2016 package/estraverse.js
-rw-rw-rw- 0 0 0 2790 Mar 7 2016 package/gulpfile.js
-rw-rw-rw- 0 0 0 1231 Mar 7 2016 package/LICENSE.BSD
-rw-rw-rw- 0 0 0 242 Mar 7 2016 package/.jshintrc
-rw-rw-rw- 0 0 0 29 Mar 7 2016 package/.babelrc
$
According to https://github.com/npm/registry/issues/120#issuecomment-276157329, old versions of npm didn't include README.md. If you upgrade your npm version, and publish a patch release of this module, the readme should appear on npmjs.com.
(issue markdown adapted from domenic/opener#24)
Hi there,
it seems that the most recent version of estraverse has a more simplified interface compared to older versions. The enter callback of the visitor does not include the third 'cb' argument, previously used to skip traversal.
With the new method of returning the SKIP or BREAK value, there is no way to replace a node in the enter stage, while skipping the traversal of the children, which is a feature we're currently using in our project.
Are there any plans to add support for this (again)?
Second Example in the README.md: Shouldn't it be "return this.break();" instead of only "this.break();" in order for the traversal to end at the first node?
Hi
I tried to use estraverse as external javascript library for a website that I develop. Whenever I call estraverse (estraverse.traverse), it gaves me an error: 'exports' is undefined. Can someone help with this?
Here are my code:
<script type="text/javascript" src="plugins/esprima.js"></script> <script type="text/javascript" src="plugins/estraverse.js"></script>var ast;
ast = esprima.parse('var answer = 42;var answer2 = 45;function f(){var answer3;}');
ast = JSON.stringify(ast);
var test;
estraverse.traverse(ast, {
enter: function (node, parent) {
if (node.type == 'FunctionExpression' || node.type == 'FunctionDeclaration')
return estraverse.VisitorOption.Skip;
},
leave: function (node, parent) {
if (node.type == 'VariableDeclarator')
test += node.id.name;
}
});
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.