[Bug]: Function declarations not hoisted to upper scope in sloppy mode code
- Dominant language
- TypeScript
- Stars
- 44k
- Forks
- 6k
- Avg merge
- 5d 15h
- Merged PRs (30d)
- 23
Description
### 💻
- [X] Would you like to work on a fix?
### How are you using Babel?
Programmatic API (`babel.transform`, `babel.parse`)
### Input code
```js
{
function f() {}
}
f();
```
### Configuration file name
babel.config.json
### Configuration
n/a
### Current and expected behavior
In strict mode, function declarations are scoped to the statement block in which they're defined (i.e. behave like `let`). In sloppy mode they are scoped to the next enclosing function / top-level (i.e. behave like `var`).
This code is valid in sloppy mode and will execute without an error:
```js
{
function f() {}
}
f();
```
Babel appears not to take strict/sloppy mode into account, and applies the strict mode behavior regardless.
```js
const srcCode = `
{
function f() {}
}
f();
`;
const { code } = transformSync( srcCode, {
sourceType: 'script',
plugins: [
() => ( {
visitor: {
Identifier( path ) {
const binding = path.scope.getBinding( path.node.name );
if ( !binding ) path.node.name = `unreferenced_${ path.node.name }`;
}
}
} )
]
} );
console.log(code);
```
This outputs:
```js
{
function f() {}
}
unreferenced_f();
```
i.e. `path.scope.getBinding('f')` did not identify that `function f` is in scope of `f()`.
NB As far as I can see from testing in NodeJS, the function declaration's scope is hoisted up to enclosing function's scope/top-level scope if its *surrounding environment* is sloppy mode, regardless of whether the function itself is strict. i.e. `f` is still defined in top level scope in this case:
```js
// Sloppy mode
{
function f() {
'use strict';
}
}
f();
```
### Environment
```
System:
OS: macOS 10.15.7
Binaries:
Node: 16.4.2 - ~/.nvm/versions/node/v16.4.2/bin/node
Yarn: 1.22.10 - ~/.nvm/versions/node/v16.4.2/bin/yarn
npm: 7.18.1 - ~/.nvm/versions/node/v16.4.2/bin/npm
npmPackages:
@babel/core: ^7.14.6 => 7.14.6
@babel/generator: ^7.14.5 => 7.14.5
@babel/helper-module-transforms: ^7.14.5 => 7.14.5
@babel/parser: ^7.14.7 => 7.14.7
@babel/plugin-transform-arrow-functions: ^7.14.5 => 7.14.5
@babel/plugin-transform-modules-commonjs: ^7.14.5 => 7.14.5
@babel/plugin-transform-react-jsx: ^7.14.5 => 7.14.5
@babel/plugin-transform-strict-mode: ^7.14.5 => 7.14.5
@babel/register: ^7.14.5 => 7.14.5
@babel/traverse: ^7.14.7 => 7.14.7
@babel/types: ^7.14.5 => 7.14.5
babel-jest: ^27.0.6 => 27.0.6
babel-plugin-dynamic-import-node: ^2.3.3 => 2.3.3
eslint: ^7.30.0 => 7.30.0
jest: ^27.0.6 => 27.0.6
```
### Possible solution
_No response_
### Additional context
I'm happy to work on a fix but would appreciate it if someone can point me in right direction. Last time I looked at the scope logic in Babel, I had trouble getting my head around it.
Contributor guide
Research direction
Reproduce the behavior with the issue's transformSync example using sourceType: 'script', focusing on path.scope.getBinding('f') for the block function and later call. Read Babel's scope logic and compare binding resolution in sloppy and strict contexts; done means the outer f() resolves to the block declaration only in sloppy mode, while strict mode remains block-scoped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100