Implicit Global Variable

ID

javascript.implicit_global_variable

Severity

critical

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

reliability, scoping, state

Description

Reports an assignment to a bare name the file never declares, which creates a property on the global object instead of a variable.

Only the assignment form is checked. Reading a name that has no declaration is javascript.no_use_before_define where the declaration exists later in the file, and a reference to something the environment provides where it does not.

Declarations are collected from the whole file rather than from the enclosing scopes only. That deliberately errs towards silence: a name declared in a sibling function suppresses the finding even though it is not in scope at the assignment. The alternative — resolving scopes per assignment — buys a defect class that javascript.variable_redeclaration and javascript.no_use_before_define already speak about, at the price of false positives on every shadowing pattern.

Names the environment provides are held in an allow list covering the standard library, the browser and Node, extendable through the additionalGlobals property. Assigning to one of those is normally a polyfill (Promise = Promise || Bluebird;), which is deliberate. Each name is reported once, at the assignment that creates the global.

Rationale

// Bad
function tally(rows) {
  for (i = 0; i < rows.length; i++) {
    total += rows[i].amount;
  }
  return total;
}
  • A missing let does not fail. In sloppy mode the assignment quietly attaches the name to the global object, so i is shared by every function in the program.

  • Two calls that interleave — a callback, a generator, a recursive walk — overwrite each other’s position, and the bug appears as wrong numbers rather than as an error.

  • The name outlives the call, so it keeps whatever the last run left in it, and anything else in the program using the same name reads it.

  • The same line in a module or under 'use strict' throws ReferenceError: i is not defined instead. That is better, but it means the defect stays invisible until the file is made strict, and then it surfaces as a crash in whichever branch the tests did not cover.

Remediation

Declare the binding. const where the value never changes, let where it does.

// Good
function tally(rows) {
  let total = 0;
  for (let i = 0; i < rows.length; i++) {
    total += rows[i].amount;
  }
  return total;
}

If a value really has to be global, say so at the assignment so the intent is visible and a strict-mode migration does not break it.

// Good — deliberate, and legal under 'use strict'
globalThis.__APP_VERSION__ = version;

Configuration

The allow list of names the environment provides — the standard library, the browser and Node — is built in. A project that has its own ambient globals (a test harness, a script tag loaded before the bundle, a legacy namespace) adds them through the additionalGlobals property, which extends the built-in list rather than replacing it:

properties:
  additionalGlobals:
    - jQuery
    - __webpack_public_path__
    - APP_CONFIG

© 2026 Xygeni. All rights reserved