Skip to content

fix: fixed issue16583 + test#16587

Merged
JLHwung merged 12 commits intobabel:mainfrom
nerodesu017:issue16583
Jul 27, 2024
Merged

fix: fixed issue16583 + test#16587
JLHwung merged 12 commits intobabel:mainfrom
nerodesu017:issue16583

Conversation

@nerodesu017
Copy link
Contributor

@nerodesu017 nerodesu017 commented Jun 24, 2024

Q                       A
Fixed Issues? Fixes #16583
Patch: Bug Fix? Yes, hotfix
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

I have redone the solution for issue #16583.
The problem with it was that when removing a VariableDeclarator, the binding related to it was getting removed. If, after this, the VariableDeclaration parent was empty (check from removal hooks), the binding was removed a second time. If no binding with the same name was to be found in parent scopes, it wasn't problematic, but if a binding with the same name was in parent scopes, it was getting removed erroneously.

@babel-bot
Copy link
Collaborator

babel-bot commented Jun 24, 2024

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/57493

@nerodesu017
Copy link
Contributor Author

I have successfully managed to solve the problem, but I still need an opinion over modifying the getBinding to getOwnBinding change. The former would mean traversing up the scopes, which is, from my point of view, not breaking. But please prove me otherwise, if it is.

@liuxingbaoyu
Copy link
Member

liuxingbaoyu commented Jun 24, 2024

Thanks for the PR!

I think this breaks the usage of removeBinding.
Perhaps as a quick fix, we can modify _removeFromScope, which should be safer.
image

In addition, we can modify parent.remove(); in hooks to avoid removing the scope information of the VariableDeclaration node.

@nerodesu017
Copy link
Contributor Author

nerodesu017 commented Jun 24, 2024

Thanks for the PR!

I think this breaks the usage of removeBinding. Perhaps as a quick fix, we can modify _removeFromScope, which should be safer. image

In addition, we can modify parent.remove(); in hooks to avoid removing the scope information of the VariableDeclaration node.

Hi, thank you for feedback!
And you are indeed right, modifying removeBinding directly is not desired, will modify _removeFromScope as you pointed out.
Still have to tinker with the specific hook, as I don't seem to get it right at the moment (test fails), but I got _removeFromScope right!

Thank you, once again, will run tests one more time and push

@liuxingbaoyu
Copy link
Member

liuxingbaoyu commented Jun 24, 2024

Sorry, I realize this still to break the example.

{
	var x = 1;
}

Because var is hoisted to a higher scope.

image
Something like this seems to work, but note that this is just an example and it doesn't handle the case where opts is null.
Perhaps we could add a _remove(noScope: boolean) for internal use, but I'd like to hear from other members before doing so.

@liuxingbaoyu liuxingbaoyu added PR: Bug Fix 🐛 A type of pull request used for our changelog categories pkg: traverse (scope) labels Jul 8, 2024
path.remove();

case "a = 42":
expect(path.scope.getBinding("a")).toBeDefined();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this line never runs it will not fail. We should push a comment in the output saying "// a is defined? " + path.scope.getBinding("a"), so that if the comment is missing the test fails.

Also, could you use an if to make this slightly easier to follow?

const { value } = path.node.init;
if (value === 33) path.remove();
t.addComment(
  path.getProgramParent().path.node,
  "trailing",
  `// a is defined when visiting a=${value}? ${path.scope.hasBinding("a")}` 
);
``

…path/plugin.js

Co-authored-by: Nicolò Ribaudo <hello@nicr.dev>
Copy link
Contributor

@JLHwung JLHwung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this bug should be fixed in Scope#removeBinding:

removeBinding(name: string) {
// clear literal binding
this.getBinding(name)?.scope.removeOwnBinding(name);
// clear uids with this name - https://github.com/babel/babel/issues/2101

The root cause of this bug is that currently the scope.removeBinding() method is not idempotent: When we call scope.removeBinding() on the same scope twice, we end up removing the binding of the outer scope, because the getBinding returns the binding info of the outer scope after the inner binding is removed.

To fix this, we can

  1. call removeOwnBinding on current scope if the current scope path is a function parent (a var scope). Test case: calling inner scope.removeBinding() twice from an inner var scope should not result to deletion of a in the upper scope:
function outer() {
  var a;
  function inner() {
    var a;
  }
}
  1. For let-scope, we can check the binding info. If the binding kind is var, we call removeOwnBinding on the scope's function parent, we should not call the removal on binding scope because it may be the outer var scope. e.g. calling scope.removeBinding from inner block statement should not result to deletion of a in the upper scope:
function outer() {
  var a;
  function inner() {
    {
      var a;
    }
  }
}
  1. For let-scope and other binding kinds, simply call removeOwnBinding on current scope.

The new approach will only manipulate current scope or its closest var-scope. With the new approach, the path.remove() should work as intended with current logic.

@liuxingbaoyu
Copy link
Member

As I understand this method, it removes a binding that is accessible in the current scope.

I think this method is not expected to be idempotent because it takes a string parameter and when people call it twice, they will expect to remove two bindings named x.

If it were made idempotent, it would be a breaking change and break some uses. Also, it is confusing that getBinding can get a binding but cannot be removed using removeBinding.

Rather than making this method idempotent, I would rather people call this method deterministically to avoid removing unexpected variables.

@JLHwung
Copy link
Contributor

JLHwung commented Jul 26, 2024

As I understand this method, it removes a binding that is accessible in the current scope.

I think this method is not expected to be idempotent because it takes a string parameter and when people call it twice, they will expect to remove two bindings named x.

If it were made idempotent, it would be a breaking change and break some uses. Also, it is confusing that getBinding can get a binding but cannot be removed using removeBinding.

Rather than making this method idempotent, I would rather people call this method deterministically to avoid removing unexpected variables.

Understood. The getBinding/removeBinding symmetry is persuasive. In that case:

export function remove(this: NodePath) {
_assertUnremoved.call(this);
this.resync();
if (!this.opts?.noScope) {
_removeFromScope.call(this);
}
if (_callRemovalHooks.call(this)) {
_markRemoved.call(this);
return;
}
this.shareCommentsWithSiblings();
_remove.call(this);
_markRemoved.call(this);
}

How about moving the !this.opts?.noScope branch after the if (_callRemovalHooks.call(this)) branch? Doing so will also pass all the tests in this PR. The rationale here is that if the hook delegate the removal to the node's parent, the parent path.remove should take care of the scope update as well. Other than deletion delegate, there are also some replacements involved in the removal hooks, such as unwrapping a single-element sequence expression, but none of these AST types introduce new bindings so getBindingIdentifiers will be empty anyway, which means it does not introduce any changes if _removeFromScope is not called for these cases.

I personally don't like a boolean flag in a hot path such as the removeInternal function, so I am trying my best to avoid it.

@JLHwung JLHwung self-requested a review July 26, 2024 21:37
@JLHwung JLHwung merged commit 801d3cb into babel:main Jul 27, 2024
Vylpes pushed a commit to Vylpes/random-bunny that referenced this pull request Sep 2, 2024
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@babel/traverse](https://babel.dev/docs/en/next/babel-traverse) ([source](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse)) | resolutions | minor | [`7.24.8` -> `7.25.6`](https://renovatebot.com/diffs/npm/@babel%2ftraverse/7.24.8/7.25.6) |

---

### Release Notes

<details>
<summary>babel/babel (@&#8203;babel/traverse)</summary>

### [`v7.25.6`](https://github.com/babel/babel/blob/HEAD/CHANGELOG.md#v7256-2024-08-29)

[Compare Source](babel/babel@v7.25.4...v7.25.6)

##### 🐛 Bug Fix

-   `babel-generator`
    -   [#&#8203;16783](babel/babel#16783) Properly print inner comments in TS array types ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
    -   [#&#8203;16775](babel/babel#16775) fix: jsx whitespace is not properly preserved when retainLines ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))
-   `babel-traverse`
    -   [#&#8203;16727](babel/babel#16727) fix: `path.getAssignmentIdentifiers` may be `undefined` ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))
-   `babel-parser`
    -   [#&#8203;16761](babel/babel#16761) fix: improve static canFollowModifier checks ([@&#8203;JLHwung](https://github.com/JLHwung))
-   `babel-helpers`, `babel-plugin-transform-optional-chaining`, `babel-runtime-corejs3`
    -   [#&#8203;16769](babel/babel#16769) Only wrap functions in `superPropertyGet` helper ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

##### 💅 Polish

-   `babel-generator`, `babel-plugin-transform-async-to-generator`, `babel-plugin-transform-block-scoping`, `babel-plugin-transform-class-properties`, `babel-plugin-transform-classes`, `babel-plugin-transform-duplicate-named-capturing-groups-regex`, `babel-plugin-transform-named-capturing-groups-regex`, `babel-plugin-transform-react-jsx-development`, `babel-plugin-transform-react-jsx`, `babel-plugin-transform-react-pure-annotations`, `babel-plugin-transform-regenerator`, `babel-plugin-transform-runtime`, `babel-preset-env`
    -   [#&#8203;16780](babel/babel#16780) Do not enforce printing space between `(` and comments ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-plugin-syntax-import-assertions`, `babel-plugin-syntax-import-attributes`
    -   [#&#8203;16781](babel/babel#16781) Don't throw when enabling both syntax-import-{assertions,attributes} ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-generator`
    -   [#&#8203;16782](babel/babel#16782) TS union/intersection nested in union does not need parens ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

##### 🏠 Internal

-   `babel-generator`
    -   [#&#8203;16777](babel/babel#16777) Remove unused `parent` params in the generator ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

### [`v7.25.4`](https://github.com/babel/babel/blob/HEAD/CHANGELOG.md#v7254-2024-08-22)

[Compare Source](babel/babel@v7.25.3...v7.25.4)

##### 🐛 Bug Fix

-   `babel-traverse`
    -   [#&#8203;16756](babel/babel#16756) fix: Skip computed key when renaming ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))
-   `babel-helper-create-class-features-plugin`, `babel-plugin-proposal-decorators`
    -   [#&#8203;16755](babel/babel#16755) fix: Decorator 2018-09 may throw an exception ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))
-   `babel-types`
    -   [#&#8203;16710](babel/babel#16710) Visit AST fields nodes according to their syntactical order ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-generator`
    -   [#&#8203;16709](babel/babel#16709) Print semicolon after TS `export namespace as A` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

##### 💅 Polish

-   `babel-generator`, `babel-plugin-proposal-decorators`, `babel-plugin-proposal-destructuring-private`, `babel-plugin-proposal-pipeline-operator`, `babel-plugin-transform-class-properties`, `babel-plugin-transform-destructuring`, `babel-plugin-transform-optional-chaining`, `babel-plugin-transform-private-methods`, `babel-plugin-transform-private-property-in-object`, `babel-plugin-transform-typescript`, `babel-runtime-corejs2`, `babel-runtime`, `babel-traverse`
    -   [#&#8203;16722](babel/babel#16722) Avoid unnecessary parens around sequence expressions ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-generator`, `babel-plugin-transform-class-properties`
    -   [#&#8203;16714](babel/babel#16714) Avoid unnecessary parens around exported arrow functions ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-generator`, `babel-plugin-proposal-decorators`, `babel-plugin-proposal-destructuring-private`, `babel-plugin-transform-object-rest-spread`
    -   [#&#8203;16712](babel/babel#16712) Avoid printing unnecessary parens around object destructuring ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

##### 🔬 Output optimization

-   `babel-generator`
    -   [#&#8203;16740](babel/babel#16740) Avoid extra spaces between comments/regexps in compact mode ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

### [`v7.25.3`](https://github.com/babel/babel/blob/HEAD/CHANGELOG.md#v7253-2024-07-31)

[Compare Source](babel/babel@v7.25.2...v7.25.3)

##### 🐛 Bug Fix

-   `babel-plugin-bugfix-firefox-class-in-computed-class-key`, `babel-traverse`
    -   [#&#8203;16699](babel/babel#16699) Avoid validating visitors produced by `traverse.visitors.merge` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

##### 🏠 Internal

-   `babel-parser`
    -   [#&#8203;16688](babel/babel#16688) Add `@babel/types` as a dependency of `@babel/parser` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

### [`v7.25.2`](https://github.com/babel/babel/blob/HEAD/CHANGELOG.md#v7252-2024-07-30)

[Compare Source](babel/babel@v7.25.1...v7.25.2)

##### 🐛 Bug Fix

-   `babel-core`, `babel-traverse`
    -   [#&#8203;16695](babel/babel#16695) Ensure that `requeueComputedKeyAndDecorators` is available ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

### [`v7.25.1`](https://github.com/babel/babel/blob/HEAD/CHANGELOG.md#v7251-2024-07-28)

[Compare Source](babel/babel@v7.25.0...v7.25.1)

##### 🐛 Bug Fix

-   `babel-plugin-transform-function-name`
    -   [#&#8203;16683](babel/babel#16683) fix: `ensureFunctionName` may be undefined ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))
-   `babel-plugin-transform-react-constant-elements`
    -   [#&#8203;16582](babel/babel#16582) fix plugin-transform-react-constant-elements transform JSXFrament but not add JSXExpressionContainer ([@&#8203;keiseiTi](https://github.com/keiseiTi))
-   `babel-traverse`
    -   [#&#8203;16587](babel/babel#16587) fix: fixed issue16583 + test ([@&#8203;nerodesu017](https://github.com/nerodesu017))

##### 🏠 Internal

-   [#&#8203;16663](babel/babel#16663) Test eslint plugin against eslint 9 ([@&#8203;JLHwung](https://github.com/JLHwung))

### [`v7.25.0`](https://github.com/babel/babel/blob/HEAD/CHANGELOG.md#v7250-2024-07-26)

[Compare Source](babel/babel@v7.24.8...v7.25.0)

##### 👓 Spec Compliance

-   `babel-helpers`, `babel-plugin-proposal-explicit-resource-management`, `babel-runtime-corejs3`
    -   [#&#8203;16537](babel/babel#16537) `await using` normative updates ([@&#8203;JLHwung](https://github.com/JLHwung))
-   `babel-plugin-transform-typescript`
    -   [#&#8203;16602](babel/babel#16602) Ensure enum members syntactically determinable to be strings do not get reverse mappings ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))

##### 🚀 New Feature

-   `babel-helper-create-class-features-plugin`, `babel-helper-function-name`, `babel-helper-plugin-utils`, `babel-helper-wrap-function`, `babel-plugin-bugfix-safari-class-field-initializer-scope`, `babel-plugin-bugfix-safari-id-destructuring-collision-in-function-expression`, `babel-plugin-transform-classes`, `babel-plugin-transform-function-name`, `babel-preset-env`, `babel-traverse`, `babel-types`
    -   [#&#8203;16658](babel/babel#16658) Move `ensureFunctionName` to `NodePath.prototype` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-helper-hoist-variables`, `babel-helper-plugin-utils`, `babel-plugin-proposal-async-do-expressions`, `babel-plugin-transform-modules-systemjs`, `babel-traverse`
    -   [#&#8203;16644](babel/babel#16644) Move `hoistVariables` to `Scope.prototype` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-helper-create-class-features-plugin`, `babel-helper-module-transforms`, `babel-helper-plugin-utils`, `babel-helper-split-export-declaration`, `babel-plugin-transform-classes`, `babel-traverse`, `babel-types`
    -   [#&#8203;16645](babel/babel#16645) Move `splitExportDeclaration` to `NodePath.prototype` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-helper-create-class-features-plugin`, `babel-helper-environment-visitor`, `babel-helper-module-transforms`, `babel-helper-plugin-utils`, `babel-helper-remap-async-to-generator`, `babel-helper-replace-supers`, `babel-plugin-bugfix-firefox-class-in-computed-class-key`, `babel-plugin-bugfix-v8-static-class-fields-redefine-readonly`, `babel-plugin-transform-async-generator-functions`, `babel-plugin-transform-classes`, `babel-traverse`
    -   [#&#8203;16649](babel/babel#16649) Move `environment-visitor` helper into `@babel/traverse` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-core`, `babel-parser`
    -   [#&#8203;16480](babel/babel#16480) Expose wether a module has TLA or not as `.extra.async` ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-compat-data`, `babel-plugin-bugfix-safari-class-field-initializer-scope`, `babel-preset-env`
    -   [#&#8203;16569](babel/babel#16569) Introduce `bugfix-safari-class-field-initializer-scope` ([@&#8203;davidtaylorhq](https://github.com/davidtaylorhq))
-   `babel-plugin-transform-block-scoping`, `babel-traverse`, `babel-types`
    -   [#&#8203;16551](babel/babel#16551) Add `NodePath#getAssignmentIdentifiers` ([@&#8203;JLHwung](https://github.com/JLHwung))
-   `babel-helper-import-to-platform-api`, `babel-plugin-proposal-json-modules`
    -   [#&#8203;16579](babel/babel#16579) Add `uncheckedRequire` option for JSON imports to CJS ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-helper-transform-fixture-test-runner`, `babel-node`
    -   [#&#8203;16642](babel/babel#16642) Allow using custom config in `babel-node --eval` ([@&#8203;slatereax](https://github.com/slatereax))
-   `babel-compat-data`, `babel-helper-create-regexp-features-plugin`, `babel-plugin-proposal-duplicate-named-capturing-groups-regex`, `babel-plugin-transform-duplicate-named-capturing-groups-regex`, `babel-preset-env`, `babel-standalone`
    -   [#&#8203;16445](babel/babel#16445) Add `duplicate-named-capturing-groups-regex` to `preset-env` ([@&#8203;JLHwung](https://github.com/JLHwung))

##### 🐛 Bug Fix

-   `babel-generator`
    -   [#&#8203;16678](babel/babel#16678) Print parens around as expressions on the LHS ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-template`, `babel-types`
    -   [#&#8203;15286](babel/babel#15286) fix: Props are lost when the template replaces the node ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))

##### 🏠 Internal

-   Other
    -   [#&#8203;16674](babel/babel#16674) bump gulp to 5 ([@&#8203;JLHwung](https://github.com/JLHwung))
-   `babel-generator`
    -   [#&#8203;16651](babel/babel#16651) Simplify the printing logic for `(` before ambiguous tokens ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))
-   `babel-helper-function-name`, `babel-plugin-transform-arrow-functions`, `babel-plugin-transform-function-name`, `babel-preset-env`, `babel-traverse`
    -   [#&#8203;16652](babel/babel#16652) Simplify `helper-function-name` logic ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

##### 🏃‍♀️ Performance

-   `babel-parser`, `babel-plugin-proposal-pipeline-operator`
    -   [#&#8203;16461](babel/babel#16461) Some minor parser performance improvements for ts ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))

##### 🔬 Output optimization

-   `babel-plugin-transform-classes`
    -   [#&#8203;16670](babel/babel#16670) Reduce redundant `assertThisInitialized` ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))
-   `babel-helper-create-class-features-plugin`, `babel-helper-replace-supers`, `babel-helpers`, `babel-plugin-proposal-decorators`, `babel-plugin-transform-class-properties`, `babel-plugin-transform-classes`, `babel-plugin-transform-exponentiation-operator`, `babel-plugin-transform-object-super`, `babel-plugin-transform-private-methods`, `babel-runtime-corejs2`, `babel-runtime-corejs3`, `babel-runtime`
    -   [#&#8203;16374](babel/babel#16374) Improve `super.x` output ([@&#8203;liuxingbaoyu](https://github.com/liuxingbaoyu))
-   `babel-plugin-transform-class-properties`, `babel-plugin-transform-classes`
    -   [#&#8203;16656](babel/babel#16656) Simplify output for anonymous classes with no methods ([@&#8203;nicolo-ribaudo](https://github.com/nicolo-ribaudo))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MzEuNCIsInVwZGF0ZWRJblZlciI6IjM3LjQzMS40IiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6WyJ0eXBlL2RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: https://git.vylpes.xyz/RabbitLabs/random-bunny/pulls/212
Reviewed-by: Vylpes <ethan@vylpes.com>
Co-authored-by: Renovate Bot <renovate@vylpes.com>
Co-committed-by: Renovate Bot <renovate@vylpes.com>
@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 27, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: traverse (scope) PR: Bug Fix 🐛 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: variable declarator remove bug

5 participants