Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't look up variables past hoisting scope #2042

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

indutny-signal
Copy link

@indutny-signal indutny-signal commented Feb 23, 2022

When we are renaming variables - we don't need to check for the
conflicts past the closest scope that stops the hoisting. Additionally,
function expression's name should be put into function's scope, and not
outside of the function.

Fix: #1147

# Caveat

I might need some guidance with regards to generateTempRef and generateTopLevelTempRef. They rely on the current behavior of the parser and it breaks tests for private properties because of the name clash. Given this the PR is in a draft state, even though all other tests pass.

cc @evanw

@indutny-signal indutny-signal marked this pull request as ready for review February 23, 2022 04:42
}

func (r *NumberRenamer) assignName(scope *numberScope, ref js_ast.Ref) {
func (r *NumberRenamer) assignName(scope *numberScope, ref js_ast.Ref, shallowLookup bool) {
Copy link
Author

Choose a reason for hiding this comment

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

I'm open to variable name suggestions 😂

p.recordDeclaredSymbol(fn.Name.Ref)
}

p.pushScopeForVisitPass(js_ast.ScopeFunctionArgs, scopeLoc)
if fn.Name != nil && isExpr {
Copy link
Author

Choose a reason for hiding this comment

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

I think this is correct scope, but let me know if I should move it to function body instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Added a test to verify that it is indeed the correct scope.

default_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
export const a = function a(b) {
Copy link
Author

Choose a reason for hiding this comment

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

Added a few tests. Let me know if you'd like to see more of them.

@indutny
Copy link
Contributor

indutny commented Feb 26, 2022

(This is actually still me, above is my work account)

When we are renaming variables - we don't need to check for the
conflicts past the closest scope that stops the hoisting. Additionally,
function expression's name should be put into function's scope, and not
outside of the function.

Fix: evanw#1147
@evanw
Copy link
Owner

evanw commented Mar 2, 2022

This PR introduces name collisions into the bundler. Here's an example, which should print 444 when run:

import { bar as baz } from 'data:text/javascript,export let bar = 123'
function foo(bar) {
  return bar + baz
}
console.log(foo(321))

Bundling this with the latest release of esbuild results in the following, which prints 444 (correct):

(() => {
  // <data:text/javascript,export let bar = 123>
  var bar = 123;

  // example.ts
  function foo(bar2) {
    return bar2 + bar;
  }
  console.log(foo(321));
})();

Bundling this with your PR gives this, which prints 642 instead (incorrect):

(() => {
  // <data:text/javascript,export let bar = 123>
  var bar = 123;

  // example.ts
  function foo(bar) {
    return bar + bar;
  }
  console.log(foo(321));
})();

@indutny-signal
Copy link
Author

Sorry about that. Let me see...

@indutny-signal
Copy link
Author

indutny-signal commented Mar 2, 2022

Alright, it took me awhile to find a way, but I think I got something far enough to request additional feedback @evanw .

The idea is to mark symbols post merge with WasMerged flag and place them in a numberScope's newly introduced map. If we ever find a merged symbol in the scope-chain that has the same name, but different source id - we do the rename.

It is possible to drop WasMerged flag, but at the cost of introducing extraneous renames. As an example:

--- FAIL: TestPackageJsonBrowserMapModuleDisabled (0.00s)
    --- FAIL: TestPackageJsonBrowserMapModuleDisabled/#00 (0.00s)
        bundler_packagejson_test.go:372:  ---------- /Users/user/project/out.js ----------
             // (disabled):Users/user/project/node_modules/node-pkg/index.js
             var require_node_pkg = __commonJS({
               "(disabled):Users/user/project/node_modules/node-pkg/index.js"() {
               }
             });
             
             // Users/user/project/node_modules/demo-pkg/index.js                  7/package.json imported from /Users/indutny/Code/evanw/esbuild/scripts/.end-to-end-tests/1187/node.js.
             var require_demo_pkg = __commonJS({
               "Users/user/project/node_modules/demo-pkg/index.js"(exports, module) {
            -    var fn = require_node_pkg();
            +    var fn2 = require_node_pkg();
                 module.exports = function() {
            -      return fn();
            +      return fn2();
                 };
               }
             });
             
             // Users/user/project/src/entry.js
             var import_demo_pkg = __toESM(require_demo_pkg());
             console.log((0, import_demo_pkg.default)());

I think this flag is very much needed because it tells that the symbol is used outside of the source where it was defined. Maybe I should rename flag to UsedExternally?

@jakebailey
Copy link

jakebailey commented Oct 22, 2022

I merged master into this PR to give it a test on some codebases; I found this case to be one that is also not handled.

// index.ts
import * as mod from "./mod";

export function doSomething() {
    return foo();
    function foo() {
        return mod.foo();
    }
}

// mod.ts
export function foo() {}

The correct output would be something like:

// project/mod.ts
function foo() {
}

// project/index.ts
function doSomething() {
  return foo2();
  function foo2() {
    return foo();
  }
}

However, this PR changes it to:

// project/mod.ts
function foo() {
}

// project/index.ts
function doSomething() {
  return foo();
  function foo() {
    return foo();
  }
}

That being said, I really like the affect this PR has on my outputs; it gets rid of a load of renames, mainly due to parameters not being renamed (since they are in a different scope entirely).

@kaatt
Copy link

kaatt commented Dec 22, 2022

@evanw please take another look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unneded functions renaming, keepNames and polyfills
5 participants