[Bug]: plugin-transform-modules-commonjs does not transform `arguments` outside function
- Dominant language
- TypeScript
- Stars
- 44k
- Forks
- 6k
- Avg merge
- 5d 15h
- Merged PRs (30d)
- 23
Description
### 💻
- [ ] Would you like to work on a fix?
### How are you using Babel?
@babel/register or @babel/node
### Input code
```js
// ES Module
const a = arguments;
```
[REPL](https://babeljs.io/repl/#?browsers=&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=MYewdgzgLgBAhjAvPATgcwK4FsCmYoQDcAUEA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=&prettier=true&targets=Node-16&version=7.14.7&externalPlugins=%40babel%2Fplugin-transform-modules-commonjs%407.14.5)
### Configuration file name
babel.config.json
### Configuration
```json
{
"sourceType": "module",
"plugins": [ "@babel/plugin-transform-modules-commonjs" ]
}
```
### Current and expected behavior
In an ES Module, `arguments` outside of a function evaluates to `globalThis.arguments` if it's defined, or throws a runtime error `ReferenceError: arguments is not defined`.
In CommonJS, `arguments` evaluates to the arguments object of the CommonJS wrapper function (`module`, `exports`, `require` etc).
[@babel/plugin-transform-modules-commonjs](https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs) does not take this difference into account. `arguments` is passed through unchanged.
### Environment
```
System:
OS: macOS 10.15.7
Binaries:
Node: 16.4.2 - ~/.nvm/versions/node/v16.4.2/bin/node
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
A more correct transformation would be:
```js
// ESM input
const a = arguments;
```
```js
// CommonJS output
const a = (0, eval)("arguments");
```
Only problem with above implementation is that `eval()` can be blocked by CSP in some environments. An alternative:
```js
// CommonJS output
const a = _helper_getArguments();
function _helper_getArguments() {
if ( 'arguments' in globalThis ) return globalThis.arguments;
throw new ReferenceError( "arguments is not defined" );
}
```
But then you need a cross-platform implementation of `globalThis` which is [surprisingly tricky](https://mathiasbynens.be/notes/globalthis).
Maybe, given how rare it would be to define `globalThis.arguments`, better to always throw.
### Additional context
_No response_
Contributor guide
Research direction
Start at the @babel/plugin-transform-modules-commonjs entry point and reproduce the issue with the provided ES module input, babel.config.json, and Babel REPL configuration. Compare the generated CommonJS behavior with ES module semantics for top-level `arguments`, then determine and test a transformation that handles both the global value and the required ReferenceError behavior without changing function-scoped `arguments`.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100