estools / escope Goto Github PK
View Code? Open in Web Editor NEWEscope: ECMAScript scope analyzer
License: BSD 2-Clause "Simplified" License
Escope: ECMAScript scope analyzer
License: BSD 2-Clause "Simplified" License
From https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API#Expressions
interface ObjectExpression <: Expression {
type: "ObjectExpression";
properties: [ { key: Literal | Identifier,
value: Expression,
kind: "init" | "get" | "set" } ];
}
esprima
adds a non-standard type: "Property"
field to this declaration, which escope
relies on.
This is pretty pedantic, I know, but I'm reporting it because it causes esmangle
to give bad output using the acorn
parser. Specifically:
var acorn = require('acorn');
var esmangle = require('esmangle');
var escodegen = require('escodegen');
var ast = acorn.parse('(function() {var foo = {a: bar}; function bar() {};})();');
console.log(escodegen.generate(esmangle.mangle(ast)));
Results in
(function() {
var a = {a:bar};
function b() {};
})();
Use RestElement
and drop function's rest support.
/cc @nzakas
related to estools/esmangle#25
Is the scope tree that is output based on a standard or is it custom to this project?
I'm trying to identify the variables that are exported, by parsed code, to the global object.
On a trivial example reading the variables array of the global scope object works.
However, when I parse a file that passes in the context I see an empty variables array in the global object, and I'm not sure how to determine what would be available in the global scope if that code was executed.
i.e.
(function(){
...
}).call(this);
Any thoughts?
Please take a look at the illustration
Right: the sample code
Left (pseudocode): each(escope.analyze(code).scopes, function(scope) { return scope.block }
function Bear() {
this.size = 'large'
}
Bear.prototype.say = function () {
return 'roar'
}
Bear.prototype.move = function move() {
return 'run'
}
Bear.prototype.attack = function attack() {
function rage() {
}
return 'claw'
}
var crazyBear = new Bear();
Currently Escope analysis is pessimistic.
For example, with statement / direct call to eval makes references unreachable.
(function () {
var ref;
eval(str); // direct call to eval
print(ref);
}());
This is because originally Escope is extracted from Esmangle project. Esmangle takes conservative approach on minification, that is Esmangle must not break any JS code semantics.
But in the other fields such as syntax coloring, optimistic scope analysis is useful.
So I'm planning to add optimistic
option to Escope. This ignores eval and with effect.
Sometimes ignoring with is useful.
Current implementation relies on using es6-map
and es6-weak-map
unconditionally which sometimes conflicts with other already injected polyfills or native implementations.
Please make those imports conditional (depending on whether Map
and WeakMap
already exist in global scope).
Now estraverse
, esrecurse
used in escope are upgraded and there's added/removed AST nodes,
so there're breaking changes. So I think it's time to upgrade major version.
@nzakas
Is it possible to upgrade the major version of escope on whcih eslint depends?
If it's too difficult, we need to consider another way.
I sent PR #38 to take care of the JSDoc link in the README, but there are broken links in the docs themselves still referencing constellation/escope. GitHub is smart enough to redirect some of these, but external links to travis for example won't work until they're updated.
If you look at https://github.com/estools/escope/blob/master/test/es6-import.coffee#L25 you see that the tests for es6 uses an esprima version that does not generate https://github.com/estree/estree spec compliant ast
For example:
ast.body[0].specifiers for code 'import v from "mod"'
is expected to output:
[ { type: 'ImportDefaultSpecifier', local: { type: 'Identifier', name: 'v' } } ]
and not
[ { type: 'ImportDefaultSpecifier', id: { type: 'Identifier', name: 'v' } } ]
The 2.0.5 version misses references to classes, such as:
class Shoe {
constructor() {
//Shoe.x = true;
}
}
let s = new Shoe();
escope fails to identify new Shoe()
as a reference.
If x
is not otherwise defined, doesn't for (x in y)
create an implicit global variable just like x = y
does?
(Arguably ++
/--
and the +=
class of operators do too, but they don't really create something from nothing. for (x in y)
does.)
Commit b7a76d9
indicates that development has started on version 1.0.2 but there is no tag for the 1.0.1 version release.
Would you consider supporting Node.js scoping? Node.js does a weird thing where the module code is actually run inside a function, so this:
return false;
is legal even though it's not in a function because it's actually interpreted as:
(function(require, module, process) {
return false;
}());
What I'm proposing is a new flag nodejsScope
that can be set to true
to artificially add a function scope immediately following the global scope.
That way, tools like ESLint can properly maintain scope even when evaluating Node.js files.
See for example this test:
escope/test/es6-destructuring-assignments.coffee
Lines 340 to 346 in f237eef
Note that there are two references with name c
and isWrite()
true: that is, two write references to c
. This despite the fact that c
will be written precisely once by the sample code. This is especially confusing if you're looking at const-correctness.
Is this intentional? If so, what's the reasoning behind it?
Both ObjectPattern
and ObjectExpression
assume that all nodes in properties
will be of type Property
, and that causes an error if there's ever a different node type present (such as with spread properties or rest properties).
Around here:
https://github.com/estools/escope/blob/master/src/referencer.js#L50
And here:
https://github.com/estools/escope/blob/master/src/referencer.js#L112
It should instead visit()
each property so it can handle any node type that is present.
Hi Yusuke!
First, let me thank you for all projects you have published on github, I am finding them quite interesting and useful. Also, let me know if this is not the best channel for communication in regards to these libraries.
While running escope.analize on the following code:
function getAdd1toX(x) {
return function add1toX() {
x = x + 1;
return x;
}
};
Although I would expect to get 3 scopes, 4 Scope objects are returned. The last two scopes, point to the same block (The add1ToX function), with the only difference being that in the "variables" array of their Scope object, one holds a variable with the name of the function (add1ToX) and the other holds a variable with the "arguments" (To be expected).
The interesting part is that the following representations of the same function, both return 3 scopes as expected:
function getAdd1toX2(x) {
return function () {
x = x + 1;
return x;
};
}
function getAdd1toX3(x) {
function add1toX() {
x = x + 1;
return x;
}
return add1toX;
}
When I compare the 3 functions execution context within chrome developer tools, I see only 3 scopes for the 3 versions of the function (global, getAdd1toX and add1toX), so I am assuming 3 scopes is the right behaviour.
Can you point me to the right direction on how to fix this bug?
Thanks!
Best regards,
Jorge C.
In the following code:
const x = 1;
const {y = x} = {};
The x
on the second line isn't recognized by escope as a reference to x
on the first line.
Tested with 3.0.1 using ES6 mode.
I'm not well versed with the ES6 spec details, but from my understanding, there is some optional behavior for block-level function declarations that escope
does not take into account.
In the following code:
// ES6
function foo() {
{
function bar() {};
}
bar(1);
}
the call to bar
would not throw a ReferenceError
if the web compatibility semantics extension is in effect.
The escope
output for this code with {ecmaVersion: 6}
does not link the bar
reference with the bar
declaration at all. Maybe an additional webExtensions
flag for escope.analyze
is needed.
The following code:
function evilEval(stuffToEval)
{
var ultimateAnswer;
ultimateAnswer = 42;
alert(stuffToEval);
}
Will put one variable into through
array - alert
. However, if you change alert
to eval
like so:
function evilEval(stuffToEval)
{
var ultimateAnswer;
ultimateAnswer = 42;
eval(stuffToEval);
}
There are now 3 variables in through
. eval
, ultimateAnswer
and stuffToEval
.
This was found and reported here: eslint/eslint#376
I think this would help with readability.
It's easy to mistake it with the local variable in certain prototype functions such as ScopeManager.prototype.__get
or ScopeManager.prototype.release
Scope Objects Visualization โ could be a useful tool for escope users.
I want to customize escope's referencer logic in order to use ES7 features.
However, PatternVisitor
is not exposed, so I cannot do.
Could escope export that?
For my task, I need to collect & pass minimum of data referenced by the given function from outer scope i.e. if it references globalObj.prop1
and globalObj.prop2
I'd like to get list of those props and not just globalObj
.
Do you think it would be possible / easy to extend escope to support collection of such property references?
Give the following code and using http://mazurov.github.io/escope-demo/;
const a = 'b';
a = 'c';
testing(a);
You can see the variable a
has an empty references property here, The references are available under scope.references
just not attached to the variable.
Example code:
var obj = {};
[obj.prop] = [1];
The output includes prop
as a reference:
// globalScope.references[2]
{ identifier: { type: 'Identifier', name: 'prop' },
from:
{ type: 'global',
// ...
I'm not sure whether this is the intended behaviour.
I've just upgraded to EScope 3.0.0 for ESLint. Here are some issues I've encountered:
npm i escope@latest
still pulls in 1.0.3 instead of 3.0.0, making it hard to know the latest available versionSuper
instead of SuperExpression
Otherwise, very smooth upgrade!
When I try to analyze smth as simple as
var localVar = globalObj.prop1 + globalObj.prop2;
I get localVar
in globalScope.through
and it's resolved
property equals to null
(while the var is obviously local and shouldn't be among unresolved references).
For ESLint, it would be awesome if we could have incremental scope generation. What I mean is that we are already doing a traversal, but right now we have to do at least two: one for escope and then one for what ESLint is doing. If there was a way to hook escope into using the traversal that ESLint is doing, it would dramatically decrease the running time.
Is this possible?
While parsing the following example:
function a() {
var test = 0;
(function() {
test = test + 1;
})();
}
Escope incorrectly moves "test" variables from anonymous function into global scope. Since anonymous function is called from within function a, and a has "test" variables declared, variable will not be in the global scope.
var obj = {
foo() { /* here is strict mode? */ }
};
As far as I know, class bodies are always strict mode, but object literal shorthands are not always strict mode.
But currently, Method Initializer Shorthand makes always strict mode.
Is this correct behavior?
See Also: http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code
In class methods, there should be a super
identifier prepopulated in the scope (similar to this
and arguments
).
I'm not sure if this is a bug or not, but this behavior is unexpected. If you can let me know if it's a bug, I can try to submit a patch.
If I call this in 3.0.0:
var scope = scopeManager.acquire(node);
And the node
is a SwitchStatement
, scope
is undefined. However, there actually is a scope of type "switch"
that I expected to be returned by this.
Is this behavior expected or a bug?
This is probably a regression bug for the fix introduced in #21
Test script:
var escope = require("escope"),
esprima = require("esprima");
var src = require("fs").readFileSync("source.js");
var ast = esprima.parse(src),
scopes = escope.analyze(ast).scopes;
console.dir(scopes[0].variables[0].defs.map(function (def) {
return def.type
}));
Input source:
var a; a = 42;
Output with escope 0.14.0:
[ 'Variable' ]
Output with escope 0.15.0 and 0.16.0:
[ 'Variable', 'ImplicitGlobalVariable' ]
Has attach
been deprecated? If so, what should I be using instead?
Sometimes ignoring eval is useful.
Use ES6 to write escope.
One technical note is that, current jsdoc
doesn't support it...
This can be reproduced with the following program:
function a() {
with(a) {
var b, c = 0;
}
}
c
is a through
reference in the while
scope, but b
is not.
Consider the following source:
function a() {
var x;
function b() {
x = 42;
}
}
When I run escope via this script:
var escope = require("escope"),
esprima = require("esprima");
var src = require("fs").readFileSync("source.js");
var ast = esprima.parse(src),
scopes = escope.analyze(ast).scopes;
// dump names of variables in the global scope
console.dir(scopes[0].variables.map(function (variable) {
return variable.name
}));
I get the following outpout: ["a", "x"]
Interestingly, if I change x = 42
to x()
or simply x
, it does not appear in the output.
Am I using escope incorrectly or why is x
in the global scope?
As far as I can tell after peaking at the spec, try
blocks should also be included in the scopes while catch
and finally
shouldn't. However escope
only creates adds a scope for catch
blocks
import {parse} from 'acorn';
import escope from 'escope';
let ast = parse(`
const x = 2;
try {
const x = 1;
[1, 2, 3].map(x => x);
} catch(o_O) {
a();
} finally {
console.log(2);
}
`, { ecmaVersion: 6});
escope.analyze(ast);
// [{global scope}, {catch scope}]
I can't find any references to it within the escope code.
Supporting ES6 block scopes and some special scopes (such as arraw function and method)
Perhaps I'm just not understanding the documentation on references and through, but the following:
var y = 2; var u; var z;
If I change "var u" to "var u = 2", all of a sudden u shows up both in references and through.
I can't find a reference to this property in the escope code.
Something like this:
Scope.prototype.isUnused = function(name){
if(this.set.has(name)) return false;
for (var i = 0, iz = this.through.length; i < iz; ++i) {
if (this.through[i].identifier.name === name) {
return false;
}
}
return true;
};
I needed to implement something like this in michaelficarra/brushtail@532d09f2dde8d7b1686c739154c226f0d0217dd1
I figured it was more appropriate to pull this into escope. An alternative would be a function that takes a name-generating function (which takes a seed) and runs it until the name is safe to use.
safeName = Scope.helpMeGenerateASafeName(function iWillGenerateNames(attemptNumber){
return "somePrefixThatILike" + Array(attemptNumber + 1).join("$");
});
edit: Updated first code sample, boolean logic was backwards.
In the latest escope with ecmaVersion: 6
, rest arguments are ignored. For example:
function f(...b) {
}
In this code, the b
argument isn't found by escope.
I believe I have a fix for this.
Related: eslint/eslint#1543
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.