💻
How are you using Babel?
Programmatic API (babel.transform, babel.parse)
Input code
// WORKING_DIR_ROOT/test/custom_tests/remove.js
const traverse = require("../../packages/babel-traverse/lib").default;
const t = require("../../packages/babel-types/lib");
const generate = require("../../packages/babel-generator/lib");
const { parse } = require("../../packages/babel-parser");
const script = `
{
let a = 33;
}
let a = 42;
`
const AST = parse(script);
let found = false;
traverse(AST, {
/**
*
* @param {NodePath<t.VariableDeclarator>} path
* @returns
*/
VariableDeclarator(path){
if(!found && path.toString() === "a = 33"){
found = true;
path.remove();
return;
} else if(path.toString() === "a = 42") {
const binding = path.scope.getBinding("a");
// binding is undefined
console.log(binding);
// can't access path of undefined
console.log(binding.path.toString());
}
}
});
Configuration file name
No response
Configuration
No response
Current and expected behavior
The test script is comprised of a BlockStatement containing one VariableDeclaration with a VariableDeclarator named a having any type of value. Outside of the BlockStatement we have another VariableDeclaration, just as the last one, with a VariableDeclarator having the same name.
What babel is doing is that when we remove the VariableDeclarator (NOT VariableDeclaration) from inside the BlockStatement makes it so that both Bindings of a (the one from the scope of the BlockStatement, and also the one from the parent scope) are deleted.
The expected behavior would be that only the Binding of a in the BlockStatement's scope to be deleted, but that doesn't happen
Environment
System:
OS: Windows 10 10.0.19045
Binaries:
Node: 20.14.0 - C:\Program Files\nodejs\node.EXE
Yarn: 4.2.2 - C:\Program Files\nodejs\yarn.CMD
npm: 10.7.0 - C:\Program Files\nodejs\npm.CMD
Possible solution
The problem arises in the packages/babel-traverse/src/path/removal.ts > remove function, more specifically, when calling the this._callRemovalHooks() function. By the point the _callRemovalHooks function is called, the Binding of a is already removed, keep this in mind.
The first hook:
function (self: NodePath, parent: NodePath) {
const removeParent =
// while (NODE);
// removing the test of a while/switch, we can either just remove it entirely *or* turn the
// `test` into `true` unlikely that the latter will ever be what's wanted so we just remove
// the loop to avoid infinite recursion
(self.key === "test" && (parent.isWhile() || parent.isSwitchCase())) ||
// export NODE;
// just remove a declaration for an export as this is no longer valid
(self.key === "declaration" && parent.isExportDeclaration()) ||
// label: NODE
// stray labeled statement with no body
(self.key === "body" && parent.isLabeledStatement()) ||
// let NODE;
// remove an entire declaration if there are no declarators left
(self.listKey === "declarations" &&
parent.isVariableDeclaration() &&
parent.node.declarations.length === 1) ||
// NODE;
// remove the entire expression statement if there's no expression
(self.key === "expression" && parent.isExpressionStatement());
if (removeParent) {
parent.remove();
return true;
}
}
is the one we are going down on, which in turn calls parent.remove(), which is the same remove function that was previously mentioned.
Now, the problem lies when we call this._removeFromScope(), that is because the call to getBindingIdentifiers returns the bindings that we already removed.
Additional context
No response
💻
How are you using Babel?
Programmatic API (
babel.transform,babel.parse)Input code
Configuration file name
No response
Configuration
No response
Current and expected behavior
The test script is comprised of a BlockStatement containing one VariableDeclaration with a VariableDeclarator named
ahaving any type of value. Outside of the BlockStatement we have another VariableDeclaration, just as the last one, with a VariableDeclarator having the same name.What babel is doing is that when we remove the VariableDeclarator (NOT VariableDeclaration) from inside the BlockStatement makes it so that both Bindings of
a(the one from the scope of the BlockStatement, and also the one from the parent scope) are deleted.The expected behavior would be that only the Binding of
ain the BlockStatement's scope to be deleted, but that doesn't happenEnvironment
System:
OS: Windows 10 10.0.19045
Binaries:
Node: 20.14.0 - C:\Program Files\nodejs\node.EXE
Yarn: 4.2.2 - C:\Program Files\nodejs\yarn.CMD
npm: 10.7.0 - C:\Program Files\nodejs\npm.CMD
Possible solution
The problem arises in the
packages/babel-traverse/src/path/removal.ts > removefunction, more specifically, when calling thethis._callRemovalHooks()function. By the point the_callRemovalHooksfunction is called, the Binding ofais already removed, keep this in mind.The first hook:
is the one we are going down on, which in turn calls
parent.remove(), which is the sameremovefunction that was previously mentioned.Now, the problem lies when we call
this._removeFromScope(), that is because the call togetBindingIdentifiersreturns the bindings that we already removed.Additional context
No response