diff --git a/apps/oxfmt/src/core/config.rs b/apps/oxfmt/src/core/config.rs index 9e8318978bf95..2a01bff71da05 100644 --- a/apps/oxfmt/src/core/config.rs +++ b/apps/oxfmt/src/core/config.rs @@ -35,6 +35,14 @@ const OXFMT_JS_CONFIG_NAME: &str = "oxfmt.config.ts"; #[cfg(feature = "napi")] const VITE_PLUS_CONFIG_NAME: &str = "vite.config.ts"; +/// Returns `true` when running inside Vite+ mode (`VITE_PLUS_VERSION` env var is set). +/// When true, only `vite.config.ts` is used as a config source. +/// When false, `vite.config.ts` is ignored and only oxfmt-specific configs are used. +#[cfg(feature = "napi")] +fn is_vp() -> bool { + std::env::var_os("VITE_PLUS_VERSION").is_some() +} + fn is_js_config_file(path: &Path) -> bool { path.extension().and_then(|e| e.to_str()).is_some_and(|ext| JS_CONFIG_EXTENSIONS.contains(&ext)) } @@ -45,17 +53,25 @@ fn is_vite_plus_config(path: &Path) -> bool { } /// Returns an iterator of all supported config file names, in priority order. +/// +/// In VP mode (`VITE_PLUS_VERSION` env var set), only `vite.config.ts` is returned. +/// In non-VP mode, `vite.config.ts` is excluded. pub fn all_config_file_names() -> impl Iterator { #[cfg(feature = "napi")] { + if is_vp() { + return vec![VITE_PLUS_CONFIG_NAME.to_string()].into_iter(); + } JSON_CONFIG_FILES .iter() .copied() - .chain([OXFMT_JS_CONFIG_NAME, VITE_PLUS_CONFIG_NAME]) + .chain([OXFMT_JS_CONFIG_NAME]) .map(ToString::to_string) + .collect::>() + .into_iter() } #[cfg(not(feature = "napi"))] - JSON_CONFIG_FILES.iter().map(|f| (*f).to_string()) + JSON_CONFIG_FILES.iter().map(|f| (*f).to_string()).collect::>().into_iter() } pub fn resolve_editorconfig_path(cwd: &Path) -> Option { diff --git a/apps/oxfmt/test/cli/utils.ts b/apps/oxfmt/test/cli/utils.ts index 510932b0e566b..e60b6ac2b0e46 100644 --- a/apps/oxfmt/test/cli/utils.ts +++ b/apps/oxfmt/test/cli/utils.ts @@ -15,11 +15,12 @@ declare global { const CLI_PATH = join(import.meta.dirname, "..", "..", "dist", "cli.js"); -export function runCli(cwd: string, args: string[]) { +export function runCli(cwd: string, args: string[], env: Record = {}) { return execa("node", [CLI_PATH, ...args, "--threads=1"], { cwd, reject: false, timeout: 5000, + env, }); } @@ -30,10 +31,14 @@ export function runCliStdin(input: string, filepath: string, pipe?: string) { } // Test function for running the CLI with various arguments -export async function runAndSnapshot(cwd: string, testCases: string[][]): Promise { +export async function runAndSnapshot( + cwd: string, + testCases: string[][], + env?: Record, +): Promise { const snapshot = []; for (const args of testCases) { - const result = await runCli(cwd, args); + const result = await runCli(cwd, args, env); snapshot.push(formatSnapshot(cwd, args, result)); } return normalizeOutput(snapshot.join("\n"), cwd); diff --git a/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap b/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap index 5359f420a541c..6fab0ec8ce9db 100644 --- a/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap +++ b/apps/oxfmt/test/cli/vite_config/__snapshots__/vite_config.test.ts.snap @@ -60,7 +60,37 @@ Expected a \`fmt\` field in the default export of /vite.config.ts --------------------" `; -exports[`vite_config > priority: oxfmt.config.ts takes precedence over vite.config.ts 1`] = ` +exports[`vite_config > ignored: not auto-discovered vite.config.ts that fails to load 1`] = ` +"-------------------- +arguments: --check test.ts +working directory: vite_config/fixtures/error_load_failure/child +exit code: 0 +--- STDOUT --------- +Checking formatting... + +All matched files use the correct format. +Finished in ms on 1 files using 1 threads. +--- STDERR --------- + +--------------------" +`; + +exports[`vite_config > ignored: vite.config.ts is not used without VITE_PLUS_VERSION 1`] = ` +"-------------------- +arguments: --check test.ts +working directory: vite_config/fixtures/basic +exit code: 0 +--- STDOUT --------- +Checking formatting... + +All matched files use the correct format. +Finished in ms on 1 files using 1 threads. +--- STDERR --------- + +--------------------" +`; + +exports[`vite_config > oxfmt: vite.config.ts is ignored, oxfmt.config.ts is used 1`] = ` "-------------------- arguments: --check test.ts working directory: vite_config/fixtures/priority @@ -107,17 +137,30 @@ Finished in ms on 1 files using 1 threads. --------------------" `; -exports[`vite_config > skip: parent config is found when vite.config.ts without fmt is skipped 1`] = ` +exports[`vite_config > vp: oxfmt.config.ts is ignored, vite.config.ts is used 1`] = ` "-------------------- arguments: --check test.ts -working directory: vite_config/fixtures/skip_finds_parent/child -exit code: 1 +working directory: vite_config/fixtures/priority +exit code: 0 --- STDOUT --------- Checking formatting... -test.ts (ms) +All matched files use the correct format. +Finished in ms on 1 files using 1 threads. +--- STDERR --------- -Format issues found in above 1 files. Run without \`--check\` to fix. +--------------------" +`; + +exports[`vite_config > vp: parent oxfmt config is not used when vite.config.ts without fmt is skipped 1`] = ` +"-------------------- +arguments: --check test.ts +working directory: vite_config/fixtures/skip_finds_parent/child +exit code: 0 +--- STDOUT --------- +Checking formatting... + +All matched files use the correct format. Finished in ms on 1 files using 1 threads. --- STDERR --------- diff --git a/apps/oxfmt/test/cli/vite_config/fixtures/error_load_failure/child/test.ts b/apps/oxfmt/test/cli/vite_config/fixtures/error_load_failure/child/test.ts index 54b82a09ad543..f36673f63f967 100644 --- a/apps/oxfmt/test/cli/vite_config/fixtures/error_load_failure/child/test.ts +++ b/apps/oxfmt/test/cli/vite_config/fixtures/error_load_failure/child/test.ts @@ -1 +1 @@ -const a = 1; +const a = 1 diff --git a/apps/oxfmt/test/cli/vite_config/vite_config.test.ts b/apps/oxfmt/test/cli/vite_config/vite_config.test.ts index 8597a9c1ed821..fadb6fb8ab9a1 100644 --- a/apps/oxfmt/test/cli/vite_config/vite_config.test.ts +++ b/apps/oxfmt/test/cli/vite_config/vite_config.test.ts @@ -4,8 +4,18 @@ import { runAndSnapshot } from "../utils"; const fixturesDir = join(import.meta.dirname, "fixtures"); +const VP_ENV = { VITE_PLUS_VERSION: "1" }; + describe("vite_config", () => { it("basic: reads fmt field from vite.config.ts", async () => { + const cwd = join(fixturesDir, "basic"); + const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]], VP_ENV); + expect(snapshot).toMatchSnapshot(); + }); + + it("ignored: vite.config.ts is not used without VITE_PLUS_VERSION", async () => { + // Same fixture as "basic" but without VITE_PLUS_VERSION env + // vite.config.ts has semi: false, but should be ignored → defaults (semi: true) → check passes const cwd = join(fixturesDir, "basic"); const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]]); expect(snapshot).toMatchSnapshot(); @@ -13,50 +23,72 @@ describe("vite_config", () => { it("error: explicit --config vite.config.ts without fmt field", async () => { const cwd = join(fixturesDir, "no_fmt_field"); - const snapshot = await runAndSnapshot(cwd, [ - ["--check", "--config", "vite.config.ts", "test.ts"], - ]); + const snapshot = await runAndSnapshot( + cwd, + [["--check", "--config", "vite.config.ts", "test.ts"]], + VP_ENV, + ); + expect(snapshot).toMatchSnapshot(); + }); + + it("skip: auto-discovered vite.config.ts without fmt field uses defaults", async () => { + const cwd = join(fixturesDir, "no_fmt_field"); + const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]], VP_ENV); expect(snapshot).toMatchSnapshot(); }); it("error: explicit --config vite.config.ts that fails to load", async () => { const cwd = join(fixturesDir, "error_load_failure", "child"); - const snapshot = await runAndSnapshot(cwd, [ - ["--check", "--config", "vite.config.ts", "test.ts"], - ]); + const snapshot = await runAndSnapshot( + cwd, + [["--check", "--config", "vite.config.ts", "test.ts"]], + VP_ENV, + ); expect(snapshot).toMatchSnapshot(); }); it("error: auto-discovered vite.config.ts that fails to load", async () => { const cwd = join(fixturesDir, "error_load_failure", "child"); - const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]]); + const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]], VP_ENV); expect(snapshot).toMatchSnapshot(); }); - it("skip: auto-discovered vite.config.ts without fmt field uses defaults", async () => { - const cwd = join(fixturesDir, "no_fmt_field"); + it("ignored: not auto-discovered vite.config.ts that fails to load", async () => { + // Without VITE_PLUS_VERSION, broken vite.config.ts is completely ignored + // Parent .oxfmtrc.json (semi: false) is found instead → `const a = 1` (no semi) matches → check passes + const cwd = join(fixturesDir, "error_load_failure", "child"); const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]]); expect(snapshot).toMatchSnapshot(); }); - it("skip: parent config is found when vite.config.ts without fmt is skipped", async () => { + it("vp: parent oxfmt config is not used when vite.config.ts without fmt is skipped", async () => { // child/ has vite.config.ts without .fmt → skipped - // parent has .oxfmtrc.json with semi: false - // So `const a = 1;` (with semicolon) should be flagged as mismatch + // parent has .oxfmtrc.json with semi: false, but it's ignored in VP mode + // So defaults (semi: true) apply → `const a = 1;` check passes const cwd = join(fixturesDir, "skip_finds_parent", "child"); - const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]]); + const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]], VP_ENV); expect(snapshot).toMatchSnapshot(); }); it("skip: auto-discovered vite.config.ts with function export uses defaults", async () => { const cwd = join(fixturesDir, "skip_fn_export"); - const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]]); + const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]], VP_ENV); + expect(snapshot).toMatchSnapshot(); + }); + + it("vp: oxfmt.config.ts is ignored, vite.config.ts is used", async () => { + // `vite.config.ts` has `semi: true`, `oxfmt.config.ts` has `semi: false` + // In VP mode, oxfmt.config.ts is ignored and vite.config.ts is used + // So `const a = 1;` (with semicolon) matches semi: true → check passes + const cwd = join(fixturesDir, "priority"); + const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]], VP_ENV); expect(snapshot).toMatchSnapshot(); }); - it("priority: oxfmt.config.ts takes precedence over vite.config.ts", async () => { - // `oxfmt.config.ts` has `semi: false`, `vite.config.ts` has `semi: true` - // oxfmt.config.ts should win, so `const a = 1;` (with semicolon) should be flagged + it("oxfmt: vite.config.ts is ignored, oxfmt.config.ts is used", async () => { + // `vite.config.ts` has `semi: true`, `oxfmt.config.ts` has `semi: false` + // In non-VP mode, vite.config.ts is ignored and oxfmt.config.ts is used + // So `const a = 1;` (with semicolon) does not match semi: false → check fails const cwd = join(fixturesDir, "priority"); const snapshot = await runAndSnapshot(cwd, [["--check", "test.ts"]]); expect(snapshot).toMatchSnapshot(); diff --git a/apps/oxfmt/test/lsp/format/format.test.ts b/apps/oxfmt/test/lsp/format/format.test.ts index a5e19d28de449..a0f98d71bc5f6 100644 --- a/apps/oxfmt/test/lsp/format/format.test.ts +++ b/apps/oxfmt/test/lsp/format/format.test.ts @@ -24,7 +24,6 @@ describe("LSP formatting", () => { it.each([ ["config-semi/test.ts", "typescript"], ["config-js-semi/test.ts", "typescript"], - ["config-vite-semi/test.ts", "typescript"], ["config-no-sort-package-json/package.json", "json"], ["config-vue-indent/test.vue", "vue"], ["config-sort-imports/test.js", "javascript"], @@ -36,6 +35,16 @@ describe("LSP formatting", () => { ])("should apply config from %s", async (path, languageId) => { expect(await formatFixture(FIXTURES_DIR, path, languageId)).toMatchSnapshot(); }); + + it("should apply config from config-vite-semi/test.ts", async () => { + await using client = createLspConnection({ VITE_PLUS_VERSION: "1" }); + const path = "config-vite-semi/test.ts"; + const dirPath = dirname(join(FIXTURES_DIR, path)); + await client.initialize([{ uri: pathToFileURL(dirPath).href, name: "test" }], {}, [ + { workspaceUri: pathToFileURL(dirPath).href, options: null }, + ]); + expect(await formatFixture(FIXTURES_DIR, path, "typescript", client)).toMatchSnapshot(); + }); }); describe("config options in nested workspace folders", () => { diff --git a/apps/oxfmt/test/lsp/init/init.test.ts b/apps/oxfmt/test/lsp/init/init.test.ts index a18659529c6dd..01e6f1408172f 100644 --- a/apps/oxfmt/test/lsp/init/init.test.ts +++ b/apps/oxfmt/test/lsp/init/init.test.ts @@ -16,13 +16,10 @@ describe("LSP initialization", () => { }); it.each([ - [ - undefined, - [".oxfmtrc.json", ".oxfmtrc.jsonc", "oxfmt.config.ts", "vite.config.ts", ".editorconfig"], - ], + [undefined, [".oxfmtrc.json", ".oxfmtrc.jsonc", "oxfmt.config.ts", ".editorconfig"]], [ { "fmt.configPath": "" }, - [".oxfmtrc.json", ".oxfmtrc.jsonc", "oxfmt.config.ts", "vite.config.ts", ".editorconfig"], + [".oxfmtrc.json", ".oxfmtrc.jsonc", "oxfmt.config.ts", ".editorconfig"], ], [{ "fmt.configPath": "./custom-config.json" }, ["./custom-config.json", ".editorconfig"]], ])( diff --git a/apps/oxfmt/test/lsp/utils.ts b/apps/oxfmt/test/lsp/utils.ts index 9c62741026aec..74c2c2d9af460 100644 --- a/apps/oxfmt/test/lsp/utils.ts +++ b/apps/oxfmt/test/lsp/utils.ts @@ -26,8 +26,9 @@ import type { const CLI_PATH = join(import.meta.dirname, "..", "..", "dist", "cli.js"); -export function createLspConnection() { +export function createLspConnection(env: Record = {}) { const proc = spawn("node", [CLI_PATH, "--lsp"], { + env: { ...process.env, ...env }, // env: { ...process.env, OXC_LOG: "info" }, for debugging }); diff --git a/apps/oxlint/src/config_loader.rs b/apps/oxlint/src/config_loader.rs index f42e67b1fc992..57e35a17ff6d8 100644 --- a/apps/oxlint/src/config_loader.rs +++ b/apps/oxlint/src/config_loader.rs @@ -14,6 +14,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use crate::{ DEFAULT_JSONC_OXLINTRC_NAME, DEFAULT_OXLINTRC_NAME, DEFAULT_TS_OXLINTRC_NAME, VITE_CONFIG_NAME, + is_vp, }; #[cfg(feature = "napi")] @@ -146,6 +147,7 @@ fn to_discovered_config(entry: &DirEntry) -> Option { return None; } let file_name = entry.path().file_name()?; + if file_name == DEFAULT_OXLINTRC_NAME { Some(DiscoveredConfig::Json(entry.path().to_path_buf())) } else if file_name == DEFAULT_JSONC_OXLINTRC_NAME { @@ -464,7 +466,18 @@ impl<'a> ConfigLoader<'a> { /// /// Checks for both `.oxlintrc.json` and `oxlint.config.ts` files in the given directory. /// Returns `Ok(Some(config))` if found, `Ok(None)` if not found, or `Err` on error. + /// + /// In VP mode (`VITE_PLUS_VERSION` env var set), only `vite.config.ts` is checked. + /// In non-VP mode, only oxlint-specific config files are checked. fn try_load_config_from_dir(&self, dir: &Path) -> Result, OxcDiagnostic> { + if is_vp() { + let vite_config_path = dir.join(VITE_CONFIG_NAME); + if vite_config_path.is_file() { + return self.load_root_js_config(&vite_config_path); + } + return Ok(None); + } + let json_path = dir.join(DEFAULT_OXLINTRC_NAME); let jsonc_path = dir.join(DEFAULT_JSONC_OXLINTRC_NAME); let ts_path = dir.join(DEFAULT_TS_OXLINTRC_NAME); @@ -494,13 +507,6 @@ impl<'a> ConfigLoader<'a> { return Oxlintrc::from_file(&jsonc_path).map(Some); } - // Fallback: check for vite.config.ts with .lint field (lowest priority) - // If .lint field is missing, `load_root_js_config` returns `Ok(None)` to skip. - let vite_config_path = dir.join(VITE_CONFIG_NAME); - if vite_config_path.is_file() { - return self.load_root_js_config(&vite_config_path); - } - Ok(None) } @@ -624,7 +630,8 @@ impl<'a> ConfigLoader<'a> { Err(err) => return Err(CliConfigLoadError::RootConfig(err)), }; - if !search_for_nested_configs { + // NOTE: For now, nested config support for Vite+ is unstated + if !search_for_nested_configs || is_vp() { return Ok(LoadedConfigs { root: oxlintrc, nested: FxHashMap::default(), diff --git a/apps/oxlint/src/lib.rs b/apps/oxlint/src/lib.rs index 4e7e816978cd6..e7925d556815e 100644 --- a/apps/oxlint/src/lib.rs +++ b/apps/oxlint/src/lib.rs @@ -62,6 +62,13 @@ const DEFAULT_TS_OXLINTRC_NAME: &str = "oxlint.config.ts"; /// Vite config file that may contain oxlint config under a `.lint` field. const VITE_CONFIG_NAME: &str = "vite.config.ts"; +/// Returns `true` when running inside Vite+ mode (`VITE_PLUS_VERSION` env var is set). +/// When true, only `vite.config.ts` is used as a config source. +/// When false, `vite.config.ts` is ignored and only oxlint-specific configs are used. +fn is_vp() -> bool { + std::env::var_os("VITE_PLUS_VERSION").is_some() +} + /// Return a JSON blob containing metadata for all available oxlint rules. /// /// This uses the internal JSON output formatter to generate the full list. diff --git a/apps/oxlint/src/lsp/server_linter.rs b/apps/oxlint/src/lsp/server_linter.rs index 0162d51dafd0a..26c21e1ceb306 100644 --- a/apps/oxlint/src/lsp/server_linter.rs +++ b/apps/oxlint/src/lsp/server_linter.rs @@ -30,6 +30,7 @@ use oxc_language_server::{ use crate::{ config_loader::{ConfigLoader, build_nested_configs, discover_configs_in_tree}, + is_vp, lsp::{ code_actions::{ CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, apply_all_fix_code_action, apply_fix_code_actions, @@ -92,7 +93,8 @@ impl ServerLinterBuilder { let mut nested_ignore_patterns = Vec::new(); let mut extended_paths = FxHashSet::default(); - let nested_configs = if options.use_nested_configs() { + // NOTE: For now, nested config support for Vite+ is unstated + let nested_configs = if options.use_nested_configs() && !is_vp() { self.create_nested_configs( &root_path, &mut external_plugin_store, @@ -489,13 +491,16 @@ impl Tool for ServerLinter { }; let mut watchers = match options.config_path.as_deref() { Some("") | None => { - // Watch both JSON/JSONC and TS config files - vec![ - "**/.oxlintrc.json".to_string(), - "**/.oxlintrc.jsonc".to_string(), - "**/oxlint.config.ts".to_string(), - "**/vite.config.ts".to_string(), - ] + if is_vp() { + // NOTE: For now, nested config support for Vite+ is unstated + vec!["vite.config.ts".to_string()] + } else { + vec![ + "**/.oxlintrc.json".to_string(), + "**/.oxlintrc.jsonc".to_string(), + "**/oxlint.config.ts".to_string(), + ] + } } Some(v) => vec![v.to_string()], }; @@ -1086,11 +1091,10 @@ mod test_watchers { let patterns = Tester::new("fixtures/lsp/watchers/default", json!({})).get_watcher_patterns(); - assert_eq!(patterns.len(), 4); + assert_eq!(patterns.len(), 3); assert_eq!(patterns[0], "**/.oxlintrc.json".to_string()); assert_eq!(patterns[1], "**/.oxlintrc.jsonc".to_string()); assert_eq!(patterns[2], "**/oxlint.config.ts".to_string()); - assert_eq!(patterns[3], "**/vite.config.ts".to_string()); } #[test] @@ -1103,11 +1107,10 @@ mod test_watchers { ) .get_watcher_patterns(); - assert_eq!(patterns.len(), 4); + assert_eq!(patterns.len(), 3); assert_eq!(patterns[0], "**/.oxlintrc.json".to_string()); assert_eq!(patterns[1], "**/.oxlintrc.jsonc".to_string()); assert_eq!(patterns[2], "**/oxlint.config.ts".to_string()); - assert_eq!(patterns[3], "**/vite.config.ts".to_string()); } #[test] @@ -1129,13 +1132,12 @@ mod test_watchers { let patterns = Tester::new("fixtures/lsp/watchers/linter_extends", json!({})) .get_watcher_patterns(); - // The `.oxlintrc.json` extends `./lint.json` -> 5 watchers (json, jsonc, ts, vite, lint.json) - assert_eq!(patterns.len(), 5); + // The `.oxlintrc.json` extends `./lint.json` -> 4 watchers (json, jsonc, ts, lint.json) + assert_eq!(patterns.len(), 4); assert_eq!(patterns[0], "**/.oxlintrc.json".to_string()); assert_eq!(patterns[1], "**/.oxlintrc.jsonc".to_string()); assert_eq!(patterns[2], "**/oxlint.config.ts".to_string()); - assert_eq!(patterns[3], "**/vite.config.ts".to_string()); - assert_eq!(patterns[4], "lint.json".to_string()); + assert_eq!(patterns[3], "lint.json".to_string()); } #[test] @@ -1163,12 +1165,11 @@ mod test_watchers { ) .get_watcher_patterns(); - assert_eq!(patterns.len(), 5); + assert_eq!(patterns.len(), 4); assert_eq!(patterns[0], "**/.oxlintrc.json".to_string()); assert_eq!(patterns[1], "**/.oxlintrc.jsonc".to_string()); assert_eq!(patterns[2], "**/oxlint.config.ts".to_string()); - assert_eq!(patterns[3], "**/vite.config.ts".to_string()); - assert_eq!(patterns[4], "**/tsconfig*.json".to_string()); + assert_eq!(patterns[3], "**/tsconfig*.json".to_string()); } } @@ -1219,12 +1220,11 @@ mod test_watchers { "typeAware": true })); assert!(watch_patterns.is_some()); - assert_eq!(watch_patterns.as_ref().unwrap().len(), 5); + assert_eq!(watch_patterns.as_ref().unwrap().len(), 4); assert_eq!(watch_patterns.as_ref().unwrap()[0], "**/.oxlintrc.json".to_string()); assert_eq!(watch_patterns.as_ref().unwrap()[1], "**/.oxlintrc.jsonc".to_string()); assert_eq!(watch_patterns.as_ref().unwrap()[2], "**/oxlint.config.ts".to_string()); - assert_eq!(watch_patterns.as_ref().unwrap()[3], "**/vite.config.ts".to_string()); - assert_eq!(watch_patterns.as_ref().unwrap()[4], "**/tsconfig*.json".to_string()); + assert_eq!(watch_patterns.as_ref().unwrap()[3], "**/tsconfig*.json".to_string()); } } } diff --git a/apps/oxlint/test/fixtures/vite_config_basic/options.json b/apps/oxlint/test/fixtures/vite_config_basic/options.json index c6d966f1b525b..ce97dcda2b16e 100644 --- a/apps/oxlint/test/fixtures/vite_config_basic/options.json +++ b/apps/oxlint/test/fixtures/vite_config_basic/options.json @@ -1,3 +1,4 @@ { - "singleThread": true + "singleThread": true, + "env": { "VITE_PLUS_VERSION": "1" } } diff --git a/apps/oxlint/test/fixtures/vite_config_explicit_no_lint_field/options.json b/apps/oxlint/test/fixtures/vite_config_explicit_no_lint_field/options.json index 6c05a413a203e..a38085f9ced21 100644 --- a/apps/oxlint/test/fixtures/vite_config_explicit_no_lint_field/options.json +++ b/apps/oxlint/test/fixtures/vite_config_explicit_no_lint_field/options.json @@ -1,4 +1,5 @@ { "singleThread": true, - "args": ["--config", "vite.config.ts"] + "args": ["--config", "vite.config.ts"], + "env": { "VITE_PLUS_VERSION": "1" } } diff --git a/apps/oxlint/test/fixtures/vite_config_fn_export/options.json b/apps/oxlint/test/fixtures/vite_config_fn_export/options.json index c6d966f1b525b..ce97dcda2b16e 100644 --- a/apps/oxlint/test/fixtures/vite_config_fn_export/options.json +++ b/apps/oxlint/test/fixtures/vite_config_fn_export/options.json @@ -1,3 +1,4 @@ { - "singleThread": true + "singleThread": true, + "env": { "VITE_PLUS_VERSION": "1" } } diff --git a/apps/oxlint/test/fixtures/vite_config_priority/files/test.js b/apps/oxlint/test/fixtures/vite_config_ignored_without_vp/files/test.js similarity index 100% rename from apps/oxlint/test/fixtures/vite_config_priority/files/test.js rename to apps/oxlint/test/fixtures/vite_config_ignored_without_vp/files/test.js diff --git a/apps/oxlint/test/fixtures/vite_config_priority/options.json b/apps/oxlint/test/fixtures/vite_config_ignored_without_vp/options.json similarity index 100% rename from apps/oxlint/test/fixtures/vite_config_priority/options.json rename to apps/oxlint/test/fixtures/vite_config_ignored_without_vp/options.json diff --git a/apps/oxlint/test/fixtures/vite_config_priority/output.snap.md b/apps/oxlint/test/fixtures/vite_config_ignored_without_vp/output.snap.md similarity index 70% rename from apps/oxlint/test/fixtures/vite_config_priority/output.snap.md rename to apps/oxlint/test/fixtures/vite_config_ignored_without_vp/output.snap.md index 04520bd567524..ffc1535c85a49 100644 --- a/apps/oxlint/test/fixtures/vite_config_priority/output.snap.md +++ b/apps/oxlint/test/fixtures/vite_config_ignored_without_vp/output.snap.md @@ -1,9 +1,9 @@ # Exit code -1 +0 # stdout ``` - x eslint(no-debugger): `debugger` statement is not allowed + ! eslint(no-debugger): `debugger` statement is not allowed ,-[files/test.js:1:1] 1 | debugger; : ^^^^^^^^^ @@ -11,7 +11,7 @@ `---- help: Remove the debugger statement -Found 0 warnings and 1 error. +Found 1 warning and 0 errors. Finished in Xms on 1 file with 93 rules using X threads. ``` diff --git a/apps/oxlint/test/fixtures/vite_config_ignored_without_vp/vite.config.ts b/apps/oxlint/test/fixtures/vite_config_ignored_without_vp/vite.config.ts new file mode 100644 index 0000000000000..bbcc165cfb347 --- /dev/null +++ b/apps/oxlint/test/fixtures/vite_config_ignored_without_vp/vite.config.ts @@ -0,0 +1,8 @@ +export default { + lint: { + rules: { + "no-debugger": "error", + eqeqeq: "warn", + }, + }, +}; diff --git a/apps/oxlint/test/fixtures/vite_config_no_lint_field/options.json b/apps/oxlint/test/fixtures/vite_config_no_lint_field/options.json index c6d966f1b525b..ce97dcda2b16e 100644 --- a/apps/oxlint/test/fixtures/vite_config_no_lint_field/options.json +++ b/apps/oxlint/test/fixtures/vite_config_no_lint_field/options.json @@ -1,3 +1,4 @@ { - "singleThread": true + "singleThread": true, + "env": { "VITE_PLUS_VERSION": "1" } } diff --git a/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/files/test.js b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/files/test.js new file mode 100644 index 0000000000000..d4e2f029dd793 --- /dev/null +++ b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/files/test.js @@ -0,0 +1,3 @@ +debugger; +if (x == 1) { +} diff --git a/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/options.json b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/options.json new file mode 100644 index 0000000000000..ce97dcda2b16e --- /dev/null +++ b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/options.json @@ -0,0 +1,4 @@ +{ + "singleThread": true, + "env": { "VITE_PLUS_VERSION": "1" } +} diff --git a/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/output.snap.md b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/output.snap.md new file mode 100644 index 0000000000000..e26225d4078af --- /dev/null +++ b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/output.snap.md @@ -0,0 +1,29 @@ +# Exit code +0 + +# stdout +``` + ! eslint(no-debugger): `debugger` statement is not allowed + ,-[files/test.js:1:1] + 1 | debugger; + : ^^^^^^^^^ + 2 | if (x == 1) { + `---- + help: Remove the debugger statement + + ! eslint(eqeqeq): Expected === and instead saw == + ,-[files/test.js:2:7] + 1 | debugger; + 2 | if (x == 1) { + : ^^ + 3 | } + `---- + help: Prefer === operator + +Found 2 warnings and 0 errors. +Finished in Xms on 1 file with 94 rules using X threads. +``` + +# stderr +``` +``` diff --git a/apps/oxlint/test/fixtures/vite_config_priority/oxlint.config.ts b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/oxlint.config.ts similarity index 100% rename from apps/oxlint/test/fixtures/vite_config_priority/oxlint.config.ts rename to apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/oxlint.config.ts diff --git a/apps/oxlint/test/fixtures/vite_config_priority/vite.config.ts b/apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/vite.config.ts similarity index 100% rename from apps/oxlint/test/fixtures/vite_config_priority/vite.config.ts rename to apps/oxlint/test/fixtures/vite_config_vp_ignores_oxlint_config/vite.config.ts diff --git a/apps/oxlint/test/lsp/init/init.test.ts b/apps/oxlint/test/lsp/init/init.test.ts index 33372461f95ac..e4379c5dfab00 100644 --- a/apps/oxlint/test/lsp/init/init.test.ts +++ b/apps/oxlint/test/lsp/init/init.test.ts @@ -37,14 +37,8 @@ describe("LSP initialization", () => { }); it.each([ - [ - undefined, - ["**/.oxlintrc.json", "**/.oxlintrc.jsonc", "**/oxlint.config.ts", "**/vite.config.ts"], - ], - [ - { configPath: "" }, - ["**/.oxlintrc.json", "**/.oxlintrc.jsonc", "**/oxlint.config.ts", "**/vite.config.ts"], - ], + [undefined, ["**/.oxlintrc.json", "**/.oxlintrc.jsonc", "**/oxlint.config.ts"]], + [{ configPath: "" }, ["**/.oxlintrc.json", "**/.oxlintrc.jsonc", "**/oxlint.config.ts"]], [{ configPath: "./custom-config.json" }, ["./custom-config.json"]], ])( "should send correct dynamic watch pattern registration for config: %s", diff --git a/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap b/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap index 89dd14fa3eab0..4a470cbdfcdd8 100644 --- a/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap +++ b/apps/oxlint/test/lsp/lint/__snapshots__/lint.test.ts.snap @@ -155,14 +155,8 @@ exports[`LSP linting > config options > should apply config from vite-config-ski "--- FILE ----------- vite-config-skip-finds-parent/child/test.js --- Diagnostics --------- - 1 | debugger; -> 2 | if (x == 1) { - | ^^ Warning: Expected === and instead saw == -help: Prefer === operator - 3 | } - 4 | ---------------------> 1 | debugger; - | ^^^^^^^^^ Error: \`debugger\` statement is not allowed +> 1 | debugger; + | ^^^^^^^^^ Warning: \`debugger\` statement is not allowed help: Remove the debugger statement 2 | if (x == 1) { 3 | } diff --git a/apps/oxlint/test/lsp/lint/lint.test.ts b/apps/oxlint/test/lsp/lint/lint.test.ts index f9cc729afed59..dd6e20c57d8ae 100644 --- a/apps/oxlint/test/lsp/lint/lint.test.ts +++ b/apps/oxlint/test/lsp/lint/lint.test.ts @@ -24,12 +24,23 @@ describe("LSP linting", () => { ["config-ts-type-aware/test.ts", "typescript"], ["config-ts-nested-type-aware-invalid/nested/test.ts", "typescript"], ["unused-disable-directive-from-config/test.ts", "typescript"], - ["vite-config-skip-finds-parent/child/test.js", "javascript"], ["config-ts-stdout-pollution/test.js", "javascript"], ])("should apply config from %s", async (path, languageId) => { expect(await lintFixture(FIXTURES_DIR, path, languageId)).toMatchSnapshot(); }); + it("should apply config from vite-config-skip-finds-parent/child/test.js", async () => { + expect( + await lintFixture( + FIXTURES_DIR, + "vite-config-skip-finds-parent/child/test.js", + "javascript", + undefined, + { VITE_PLUS_VERSION: "1" }, + ), + ).toMatchSnapshot(); + }); + it("should allow LSP typeAware option to override ts config", async () => { expect( await lintFixture( diff --git a/apps/oxlint/test/lsp/utils.ts b/apps/oxlint/test/lsp/utils.ts index cccd8fe8345df..6880449b5cfc2 100644 --- a/apps/oxlint/test/lsp/utils.ts +++ b/apps/oxlint/test/lsp/utils.ts @@ -46,11 +46,12 @@ const PULL_DIAGNOSTICS_CAPABILITY = { }, }; -export function createLspConnection() { +export function createLspConnection(env: Record = {}) { const proc = spawn(process.execPath, [CLI_PATH, "--lsp"], { env: { ...process.env, OXC_LOG: "debug", + ...env, }, }); @@ -154,11 +155,13 @@ export async function lintFixture( fixturePath: string, languageId: string, initializationOptions?: OxlintLSPConfig, + env: Record = {}, ): Promise { return lintMultiWorkspaceFixture( fixturesDir, [{ path: fixturePath, languageId }], initializationOptions ? [initializationOptions] : undefined, + env, ); } @@ -169,11 +172,12 @@ export async function lintMultiWorkspaceFixture( languageId: string; }[], initializationOptions?: OxlintLSPConfig[], + env: Record = {}, ): Promise { const workspaceUris = fixturePaths.map( ({ path }) => pathToFileURL(dirname(join(fixturesDir, path))).href, ); - await using client = createLspConnection(); + await using client = createLspConnection(env); await client.initialize( workspaceUris.map((uri, index) => ({ uri, name: `workspace-${index}` })), diff --git a/apps/oxlint/test/utils.ts b/apps/oxlint/test/utils.ts index 386b055ada70d..f0c88eb796907 100644 --- a/apps/oxlint/test/utils.ts +++ b/apps/oxlint/test/utils.ts @@ -38,6 +38,8 @@ export interface Fixture { // Additional arguments to run Oxlint with. Default: `[]`. args: string[]; + // Additional environment variables to set when running. Default: `{}`. + env: Record; }; } @@ -49,6 +51,7 @@ const DEFAULT_OPTIONS: Fixture["options"] = { singleThread: false, cwd: null, args: [], + env: {}, }; /** @@ -96,6 +99,14 @@ export function getFixtures(): Fixture[] { if (!Array.isArray(options.args) || !options.args.every((arg) => typeof arg === "string")) { throw new TypeError("`args` property in `options.json` must be an array of strings"); } + if ( + typeof options.env !== "object" || + options.env === null || + Array.isArray(options.env) || + !Object.values(options.env).every((v) => typeof v === "string") + ) { + throw new TypeError("`env` property in `options.json` must be an object of string values"); + } fixtures.push({ name, dirPath, options }); } @@ -150,6 +161,7 @@ export async function testFixtureWithCommand(options: TestFixtureOptions): Promi let { stdout, stderr, exitCode } = await execa(options.command, options.args, { cwd, reject: false, + env: fixtureOptions.env, }); // Build snapshot `.snap.md` file