babel: [Bug]: traverse calculates scope incorrectly for var in function expression with same name as var
💻
- Would you like to work on a fix?
How are you using Babel?
Programmatic API (babel.transform
, babel.parse
)
Input code
const {transformSync} = require('@babel/core');
function transformWithPlugin(code) {
return transformSync(code, {
plugins: [
() => ({
visitor: {
Identifier(path) {
const binding = path.scope.getBinding(path.node.name);
path.node.leadingComments = [{type: 'CommentBlock', value: binding.path.type}];
}
}
})
],
retainLines: true
}).code;
}
// Correct
console.log(transformWithPlugin('(function f() { let x = 1; return x; })'));
// (function /*FunctionExpression*/f() {let /*VariableDeclarator*/x = 1;return (/*VariableDeclarator*/x);});
// Wrong
console.log(transformWithPlugin('(function x() { let x = 1; return x; })'));
// (function /*FunctionExpression*/x() {let /*FunctionExpression*/x = 1;return (/*FunctionExpression*/x);});
Configuration file name
n/a
Configuration
n/a
Current and expected behavior
In the example above, the first transform is correct:
(function f() { let x = 1; return x; })
is transformed to:
(function /*FunctionExpression*/f() {let /*VariableDeclarator*/x = 1;return (/*VariableDeclarator*/x);});
path.scope.getBinding('x').path
points to the variable let x
.
But if the function expression is named x
, same as the var, it’s incorrect (2nd transform):
(function x() { let x = 1; return x; })
is transformed to:
(function /*FunctionExpression*/x() {let /*FunctionExpression*/x = 1;return (/*FunctionExpression*/x);});
Here path.scope.getBinding('x').path
erroneously points to function x
.
Same problem occurs if let x
is replaced with const x
, var x
, function x() {}
or class x {}
.
Environment
System:
OS: macOS 10.15.7
Binaries:
Node: 16.13.1 - ~/.nvm/versions/node/v16.13.1/bin/node
npm: 8.1.2 - ~/.nvm/versions/node/v16.13.1/bin/npm
npmPackages:
@babel/core: ^7.16.0 => 7.16.0
@babel/generator: ^7.16.0 => 7.16.0
@babel/helper-module-transforms: ^7.16.0 => 7.16.0
@babel/helper-plugin-utils: ^7.14.5 => 7.14.5
@babel/parser: ^7.16.4 => 7.16.4
@babel/plugin-transform-arrow-functions: ^7.16.0 => 7.16.0
@babel/plugin-transform-modules-commonjs: ^7.16.0 => 7.16.0
@babel/plugin-transform-react-jsx: ^7.16.0 => 7.16.0
@babel/plugin-transform-strict-mode: ^7.16.0 => 7.16.0
@babel/register: ^7.16.0 => 7.16.0
@babel/traverse: ^7.16.3 => 7.16.3
@babel/types: ^7.16.0 => 7.16.0
babel-jest: ^27.4.2 => 27.4.2
babel-plugin-dynamic-import-node: ^2.3.3 => 2.3.3
eslint: ^7.32.0 => 7.32.0
jest: ^27.4.3 => 27.4.3
Possible solution
Not specifically. I assume cause is that the scopes tracker is seeing the binding to function expression before the binding to let x
.
Additional context
I would be happy to work on a fix. Any pointers on where to look in the codebase would be appreciated.
About this issue
- Original URL
- State: open
- Created 3 years ago
- Comments: 15 (14 by maintainers)
@overlookmotel you’re right. I was misreading the function/class expression id part. Now it makes sense, renaming the id won’t touch params, nor local vars.
NB: that last example does not actually illustrate the issue, because function declaration binds
x/someOtherName
in outer scope. Function expression is different, there the name is bound inside: