diff --git a/apps/oxlint/src-js/plugins/context.ts b/apps/oxlint/src-js/plugins/context.ts index 23da8e59d67ff..922142ce3cd28 100644 --- a/apps/oxlint/src-js/plugins/context.ts +++ b/apps/oxlint/src-js/plugins/context.ts @@ -31,7 +31,7 @@ import { report } from "./report.ts"; import { settings, initSettings } from "./settings.ts"; import visitorKeys from "../generated/keys.ts"; import { debugAssertIsNonNull } from "../utils/asserts.ts"; -import { EMPTY_GLOBALS, Globals, globals, initGlobals } from "./globals.ts"; +import { Globals, globals, initGlobals } from "./globals.ts"; import type { RuleDetails } from "./load.ts"; import type { Options } from "./options.ts"; @@ -182,12 +182,13 @@ const LANGUAGE_OPTIONS = { /** * Globals defined for the file being linted. */ - get globals(): Readonly | null { + get globals(): Readonly { + // ESLint does not define `globals` property on `LanguageOptions` if no globals are defined. + // We have no way to distinguish between "no globals defined" and "globals defined as empty object", + // so we behave slightly differently from ESLint here, but it should make no practical difference. if (globals === null) initGlobals(); debugAssertIsNonNull(globals); - - // ESLint has `globals` as `null`, not empty object, if no globals are defined - return globals === EMPTY_GLOBALS ? null : globals; + return globals; }, }; diff --git a/apps/oxlint/src-js/plugins/globals.ts b/apps/oxlint/src-js/plugins/globals.ts index 2dd4d36165c65..9a25091951eed 100644 --- a/apps/oxlint/src-js/plugins/globals.ts +++ b/apps/oxlint/src-js/plugins/globals.ts @@ -13,8 +13,8 @@ import { debugAssert, debugAssertIsNonNull } from "../utils/asserts.ts"; export type Globals = Record; // Empty globals object. -// No need to freeze this object, as it's never passed to user. -export const EMPTY_GLOBALS: Globals = {}; +// When globals are empty, we use this singleton object to avoid allocating a new object each time. +const EMPTY_GLOBALS: Globals = Object.freeze({}); // Globals for current file. // `globalsJSON` is set before linting a file by `setGlobalsForFile`. @@ -43,9 +43,7 @@ export function initGlobals(): void { debugAssertIsNonNull(globalsJSON); if (globalsJSON === "{}") { - // `EMPTY_GLOBALS` is a placeholder meaning "no globals defined". - // `globals` getter on `LanguageOptions` returns `null` if `globals === EMPTY_GLOBALS`. - // No need to freeze `EMPTY_GLOBALS`, since it's never passed to user. + // Re-use a single object for empty globals as an optimization globals = EMPTY_GLOBALS; } else { globals = JSON.parse(globalsJSON); diff --git a/apps/oxlint/test/fixtures/languageOptions/output.snap.md b/apps/oxlint/test/fixtures/languageOptions/output.snap.md index fd2b6753c9a20..107ada8823377 100644 --- a/apps/oxlint/test/fixtures/languageOptions/output.snap.md +++ b/apps/oxlint/test/fixtures/languageOptions/output.snap.md @@ -7,7 +7,7 @@ | sourceType: script | ecmaVersion: 2026 | parserOptions: {"sourceType":"script"} - | globals: null + | globals: {} ,-[files/index.cjs:1:1] 1 | let x; : ^ @@ -794,7 +794,7 @@ | sourceType: module | ecmaVersion: 2026 | parserOptions: {"sourceType":"module"} - | globals: null + | globals: {} ,-[files/index.js:1:1] 1 | let x; : ^ @@ -804,7 +804,7 @@ | sourceType: module | ecmaVersion: 2026 | parserOptions: {"sourceType":"module"} - | globals: null + | globals: {} ,-[files/index.mjs:1:1] 1 | let x; : ^