no-implicit-globals

Disallow declarations and assignments that create global variables by accident

In a classic script (one loaded with <script> and no type="module"), every top-level var and function becomes a global that all scripts on the page share. Two scripts that both declare var config overwrite each other. Outside strict mode, assigning to a name that was never declared creates a global too.

Modules and CommonJS files have their own top-level scope, so declarations in them are never reported. Bun lints .js files as modules and .cjs files as CommonJS. To lint files as scripts, set sourceType to "script" in an ESLint config, as the examples do.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"no-implicit-globals" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"no-implicit-globals" = ["error", {
  lexicalBindings = false,
}]

Examples#

Reported in a script:

eslint.config.js
export default [{ languageOptions: { sourceType: "script" } }];
example.js2 problems
var config = { debug: false };

function start() {
  console.log(config.debug);
}

Not reported. The function keeps config and start private, and anything other scripts need is set on window explicitly:

eslint.config.js
export default [{ languageOptions: { sourceType: "script" } }];
example.jsNo problems
(function () {
  var config = { debug: false };

  function start() {
    console.log(config.debug);
  }

  window.start = start;
})();

Reported in a CommonJS file, which isn't strict unless it says "use strict". total is never declared:

sum.cjs1 problem
module.exports = function sum(prices) {
  total = 0;
  for (const price of prices) total += price;
  return total;
};

Reported in every kind of file, because Promise is a read-only built-in:

example.js1 problem
import Bluebird from "bluebird";

Promise = Bluebird;

Options#

OptionDefaultWhat it does
lexicalBindingsfalseAlso report let, const and class at the top level of a script.

These don't become properties of window, but other scripts still see them, and a second script that declares the same name fails to load. Wrap them in a block or a function to keep them private.

eslint.config.js
export default [{ languageOptions: { sourceType: "script" } }];
example.jswith { "lexicalBindings": true }2 problems
const config = { debug: false };
class Store {}

When not to use it#

If all your files are modules, the only thing left is the report about built-ins such as Promise.