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
letdoes not fail. In sloppy mode the assignment quietly attaches the name to the global object, soiis 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'throwsReferenceError: i is not definedinstead. 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