From 8a2dabc914b547a3d43cf3a24da35770652f55bd Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Sun, 18 Jan 2026 23:02:07 +0000 Subject: [PATCH] fix(linter/plugins): rule tester default to module source type in ESLint compat mode (#18195) #18124 altered parsing behavior to default to `script` / `module` source type depending on whether the file contains ESM syntax (e.g. `import` declarations). This broke a several hundred Oxlint JS plugins conformance tests, because ESLint's behavior is to treat files as `module` source type unless directed otherwise. Fix these conformance tests by reverting the "parse based on content" behavior in `RuleTester`, when `eslintCompat` option is enabled. --- apps/oxlint/src-js/package/rule_tester.ts | 10 +++- apps/oxlint/test/rule_tester.test.ts | 66 ++++++++++++++++++----- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/apps/oxlint/src-js/package/rule_tester.ts b/apps/oxlint/src-js/package/rule_tester.ts index c678174f1e927..ac553aed84ade 100644 --- a/apps/oxlint/src-js/package/rule_tester.ts +++ b/apps/oxlint/src-js/package/rule_tester.ts @@ -203,6 +203,9 @@ interface EcmaFeatures { */ type Language = "js" | "jsx" | "ts" | "tsx" | "dts"; +// Empty language options +const EMPTY_LANGUAGE_OPTIONS: LanguageOptionsInternal = {}; + // `RuleTester` uses this config as its default. Can be overwritten via `RuleTester.setDefaultConfig()`. let sharedConfig: Config = {}; @@ -1027,8 +1030,8 @@ function lint(test: TestCase, plugin: Plugin): Diagnostic[] { function getParseOptions(test: TestCase): ParseOptions { const parseOptions: ParseOptions = {}; - const languageOptions = test.languageOptions as LanguageOptionsInternal | undefined; - if (languageOptions == null) return parseOptions; + let languageOptions = test.languageOptions as LanguageOptionsInternal | undefined; + if (languageOptions == null) languageOptions = EMPTY_LANGUAGE_OPTIONS; // Throw error if custom parser is provided if (languageOptions.parser != null) throw new Error("Custom parsers are not supported"); @@ -1059,6 +1062,9 @@ function getParseOptions(test: TestCase): ParseOptions { } parseOptions.sourceType = sourceType; + } else if (test.eslintCompat === true) { + // ESLint defaults to `module` if no source type is specified + parseOptions.sourceType = "module"; } // Handle `languageOptions.parserOptions` diff --git a/apps/oxlint/test/rule_tester.test.ts b/apps/oxlint/test/rule_tester.test.ts index 57a15272a5519..076a842352113 100644 --- a/apps/oxlint/test/rule_tester.test.ts +++ b/apps/oxlint/test/rule_tester.test.ts @@ -941,21 +941,61 @@ describe("RuleTester", () => { describe("parsing options", () => { describe("sourceType", () => { - it("default (unambiguous)", () => { - const tester = new RuleTester(); - tester.run("no-foo", simpleRule, { - // with (obj) {} - no ESM syntax, parsed as script, `with` is allowed - // import x from 'foo'; - has ESM syntax, parsed as module - valid: ["with (obj) {}", "import x from 'foo';"], - invalid: [], + describe("default", () => { + const reportSourceTypeRule: Rule = { + create(context) { + return { + Program(node) { + context.report({ + message: `sourceType: ${context.languageOptions.sourceType}`, + node, + }); + }, + }; + }, + }; + + it("unambiguous without ESLint compatibility mode", () => { + const tester = new RuleTester(); + tester.run("source-type", reportSourceTypeRule, { + valid: [], + invalid: [ + // No ESM syntax, parsed as script, so `with` is allowed + { + code: "with (obj) {}", + errors: ["sourceType: script"], + }, + // Has ESM syntax, parsed as module + { + code: "import x from 'foo';", + errors: ["sourceType: module"], + }, + ], + }); + expect(runCases()).toEqual([null, null]); }); - expect(runCases()).toMatchInlineSnapshot(` - [ - null, - null, - ] - `); + it("module with ESLint compatibility mode", () => { + const tester = new RuleTester({ eslintCompat: true }); + tester.run("source-type", reportSourceTypeRule, { + // Parsed as module, `with` is not allowed, so parse error + valid: ["with (obj) {}"], + // Has ESM syntax, successfully parsed as module + invalid: [ + { + code: "import x from 'foo';", + errors: ["sourceType: module"], + }, + ], + }); + + expect(runCases()).toMatchInlineSnapshot(` + [ + [Error: Parsing failed], + null, + ] + `); + }); }); describe("module", () => {