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. Without a config, bun lint takes every file for one or the other. To lint files as scripts, set sourceType to "script" under [lint.parserOptions] in bunfig.toml, or 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.