From 93ccea5cd5583705da0788d4855f75b4820d6098 Mon Sep 17 00:00:00 2001 From: Dave Henton Date: Fri, 10 Apr 2026 21:16:38 -0500 Subject: [PATCH 1/3] fix(prettier): honor .prettierignore during checks --- .../cmd/network/check/prettier_ignore.in/.gitignore | 1 + .../network/check/prettier_ignore.in/.prettierignore | 1 + .../network/check/prettier_ignore.in/.qlty/qlty.toml | 8 ++++++++ .../check/prettier_ignore.in/ignored/ignored.js | 1 + .../cmd/network/check/prettier_ignore.in/included.js | 1 + .../tests/cmd/network/check/prettier_ignore.stderr | 2 ++ .../tests/cmd/network/check/prettier_ignore.stdout | 11 +++++++++++ qlty-cli/tests/cmd/network/check/prettier_ignore.toml | 3 +++ qlty-plugins/plugins/linters/prettier/plugin.toml | 4 ++-- 9 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.in/.gitignore create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.in/.prettierignore create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.in/.qlty/qlty.toml create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.in/ignored/ignored.js create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.in/included.js create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.stderr create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.stdout create mode 100644 qlty-cli/tests/cmd/network/check/prettier_ignore.toml diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.gitignore b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.gitignore new file mode 100644 index 000000000..7496023e5 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.gitignore @@ -0,0 +1 @@ +.qlty/ diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.prettierignore b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.prettierignore new file mode 100644 index 000000000..ea10ec85c --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.prettierignore @@ -0,0 +1 @@ +ignored diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.qlty/qlty.toml b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.qlty/qlty.toml new file mode 100644 index 000000000..b7cb4595b --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/.qlty/qlty.toml @@ -0,0 +1,8 @@ +config_version = "0" + +[[source]] +name = "default" +default = true + +[[plugin]] +name = "prettier" diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.in/ignored/ignored.js b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/ignored/ignored.js new file mode 100644 index 000000000..5b77d6139 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/ignored/ignored.js @@ -0,0 +1 @@ +const thing={answer:42} diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.in/included.js b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/included.js new file mode 100644 index 000000000..5b77d6139 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.in/included.js @@ -0,0 +1 @@ +const thing={answer:42} diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.stderr b/qlty-cli/tests/cmd/network/check/prettier_ignore.stderr new file mode 100644 index 000000000..c3a5e6b35 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.stderr @@ -0,0 +1,2 @@ +✖ 1 issue +✖ 1 unformatted file diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.stdout b/qlty-cli/tests/cmd/network/check/prettier_ignore.stdout new file mode 100644 index 000000000..30d097aa1 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.stdout @@ -0,0 +1,11 @@ + + UNFORMATTED FILES: 1 + +✖ included.js + + + ISSUES: 1 + +included.js:0:0 + 0:0 fmt Incorrect formatting, autoformat by running `qlty fmt`. prettier:fmt + diff --git a/qlty-cli/tests/cmd/network/check/prettier_ignore.toml b/qlty-cli/tests/cmd/network/check/prettier_ignore.toml new file mode 100644 index 000000000..ca7a5c984 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_ignore.toml @@ -0,0 +1,3 @@ +bin.name = "qlty" +args = ["check", "--all", "--no-cache"] +status.code = 1 diff --git a/qlty-plugins/plugins/linters/prettier/plugin.toml b/qlty-plugins/plugins/linters/prettier/plugin.toml index c8a6b0a84..ddd7c1f12 100644 --- a/qlty-plugins/plugins/linters/prettier/plugin.toml +++ b/qlty-plugins/plugins/linters/prettier/plugin.toml @@ -30,14 +30,14 @@ config_files = [ "prettier.config.cjs", "prettier.config.js", ] -affects_cache = ["package.json", ".editorconfig", ".prettierignore"] +affects_cache = ["package.json", ".editorconfig", ".gitignore", ".prettierignore"] description = "JS, CSS, HTML, JSON, TS, GraphQL, MD and YML formatter" package_file_candidate = "package.json" package_file_candidate_filters = ["prettier"] [plugins.definitions.prettier.drivers.format] script = "prettier --config ${config_file} -w ${target}" -runs_from = { type = "tool_directory" } +runs_from = { type = "root" } batch_by = "config_file" success_codes = [0] # https://prettier.io/docs/cli#exit-codes cache_results = true From bd5b3726767a67278feb7c6f682ce61faf7630cd Mon Sep 17 00:00:00 2001 From: Dave Henton Date: Tue, 14 Apr 2026 21:57:23 -0500 Subject: [PATCH 2/3] fix(prettier): resolve ignore files explicitly --- qlty-check/src/cache.rs | 5 + qlty-check/src/executor/invocation_script.rs | 160 +++++++++++++++++- .../prettier_nested_ignore.in/.gitignore | 1 + .../prettier_nested_ignore.in/.qlty/qlty.toml | 8 + .../packages/app/.prettierignore | 1 + .../packages/app/.prettierrc.json | 1 + .../packages/app/ignored.js | 1 + .../packages/app/included.js | 1 + .../check/prettier_nested_ignore.stderr | 2 + .../check/prettier_nested_ignore.stdout | 11 ++ .../network/check/prettier_nested_ignore.toml | 3 + qlty-config/src/config/plugin.rs | 3 + .../plugins/linters/prettier/plugin.toml | 5 +- 13 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.gitignore create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.qlty/qlty.toml create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierignore create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierrc.json create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/ignored.js create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/included.js create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stderr create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stdout create mode 100644 qlty-cli/tests/cmd/network/check/prettier_nested_ignore.toml diff --git a/qlty-check/src/cache.rs b/qlty-check/src/cache.rs index 7bc4bebdc..8fa55a958 100644 --- a/qlty-check/src/cache.rs +++ b/qlty-check/src/cache.rs @@ -243,6 +243,11 @@ impl InvocationCacheKey { &driver.copy_configs_into_tool_install.to_string(), ); + digest.add( + "plugin.driver.ignore_files", + &serde_yaml::to_string(&driver.ignore_files).unwrap(), + ); + digest.add("qlty_version", &self.qlty_version); digest.add("tool", &self.tool.directory()); digest.add("driver_name", &self.driver_name); diff --git a/qlty-check/src/executor/invocation_script.rs b/qlty-check/src/executor/invocation_script.rs index 6722469c4..22af9b759 100644 --- a/qlty-check/src/executor/invocation_script.rs +++ b/qlty-check/src/executor/invocation_script.rs @@ -4,6 +4,7 @@ use itertools::Itertools; use qlty_analysis::utils::fs::path_to_string; use qlty_analysis::{join_path_string, utils::fs::path_to_native_string}; use qlty_config::config::InvocationDirectoryType; +use std::path::{Path, PathBuf}; use tracing::{error, trace}; #[cfg(unix)] @@ -18,6 +19,7 @@ pub fn compute_invocation_script(plan: &InvocationPlan) -> Result { // Autoload script first in case it has variables that need to be interpolated base_script = replace_autoload_script(plan, base_script); base_script = replace_config_script(plan, base_script); + base_script = replace_ignore_paths(plan, base_script); base_script = plan.tool.interpolate_variables(&base_script); base_script = replace_target_variable(plan, base_script); base_script = replace_tmpfile_variable(plan, base_script); @@ -53,6 +55,22 @@ fn replace_config_script(plan: &InvocationPlan, script: String) -> String { } } +fn replace_ignore_paths(plan: &InvocationPlan, script: String) -> String { + if !script.contains("${ignore_paths}") { + return script; + } + + let ignore_paths = get_ignore_paths(plan) + .into_iter() + .map(|path| { + let escaped = escape(path_to_native_string(path).into()).into_owned(); + format!("--ignore-path={escaped}") + }) + .join(" "); + + script.replace("${ignore_paths}", &ignore_paths) +} + fn replace_target_variable(plan: &InvocationPlan, script: String) -> String { if script.contains("${target}") { let targets_list = plan_target_list(plan); @@ -108,6 +126,39 @@ fn get_config_file_paths(plan: &InvocationPlan) -> Vec { .collect() } +fn get_ignore_paths(plan: &InvocationPlan) -> Vec { + let search_directory = ignore_search_directory(plan); + + plan.driver + .ignore_files + .iter() + .map(|ignore_file| search_directory.join(ignore_file)) + .filter(|ignore_path| ignore_path.exists()) + .collect() +} + +fn ignore_search_directory(plan: &InvocationPlan) -> PathBuf { + plan.plugin_configs + .first() + .and_then(|config| effective_config_directory(plan, &config.path)) + .unwrap_or_else(|| plan.target_root.clone()) +} + +fn effective_config_directory(plan: &InvocationPlan, config_path: &Path) -> Option { + if plan.target_root == plan.workspace.root { + return config_path.parent().map(Path::to_path_buf); + } + + let relative_path = config_path.strip_prefix(&plan.workspace.root).ok()?; + Some( + plan.target_root + .join(relative_path) + .parent() + .unwrap() + .to_path_buf(), + ) +} + pub fn plan_target_list(plan: &InvocationPlan) -> String { plan.targets .iter() @@ -152,7 +203,8 @@ mod test { Workspace, }; use qlty_types::analysis::v1::ExecutionVerb; - use std::{path::PathBuf, sync::Arc, time::SystemTime}; + use std::{fs, path::PathBuf, sync::Arc, time::SystemTime}; + use tempfile::tempdir; #[test] fn test_target_list() { @@ -332,4 +384,110 @@ mod test { assert_eq!(script, "autoload_script: autoload.php"); } + + #[test] + fn test_replace_ignore_paths_uses_root_ignore_files_without_config() { + let temp_dir = tempdir().unwrap(); + let workspace_root = temp_dir.path().to_path_buf(); + + fs::write(workspace_root.join(".prettierignore"), "ignored.js\n").unwrap(); + fs::write(workspace_root.join(".gitignore"), "build/\n").unwrap(); + + let mut driver = build_driver(vec![], vec![]); + driver.ignore_files = vec![ + PathBuf::from(".prettierignore"), + PathBuf::from(".gitignore"), + ]; + + let plan = InvocationPlan { + target_root: workspace_root.clone(), + workspace_entries: Arc::new(vec![]), + invocation_id: "".to_string(), + verb: ExecutionVerb::Check, + workspace: Workspace { + root: workspace_root.clone(), + }, + settings: Default::default(), + runtime: None, + runtime_version: None, + plugin_name: "prettier".to_string(), + plugin: PluginDef::default(), + tool: Ruby::new_tool(""), + driver_name: "format".to_string(), + driver, + plugin_configs: vec![], + targets: vec![], + invocation_directory: PathBuf::from("/tool"), + invocation_directory_def: InvocationDirectoryDef { + kind: InvocationDirectoryType::ToolDir, + path: None, + }, + }; + + let script = replace_ignore_paths(&plan, "prettier ${ignore_paths} ${target}".to_string()); + + assert!(script.contains("--ignore-path=")); + assert!(script.contains(".prettierignore")); + assert!(script.contains(".gitignore")); + } + + #[test] + fn test_replace_ignore_paths_uses_staged_config_directory() { + let workspace_dir = tempdir().unwrap(); + let staging_dir = tempdir().unwrap(); + let workspace_root = workspace_dir.path().to_path_buf(); + let target_root = staging_dir.path().to_path_buf(); + let config_path = workspace_root.join("packages/app/.prettierrc"); + + fs::create_dir_all(config_path.parent().unwrap()).unwrap(); + fs::create_dir_all(target_root.join("packages/app")).unwrap(); + fs::write(&config_path, "{}\n").unwrap(); + fs::write( + target_root.join("packages/app/.prettierignore"), + "ignored.js\n", + ) + .unwrap(); + fs::write(target_root.join("packages/app/.gitignore"), "dist/\n").unwrap(); + fs::write(target_root.join(".prettierignore"), "wrong.js\n").unwrap(); + + let mut driver = build_driver(vec![], vec![]); + driver.ignore_files = vec![ + PathBuf::from(".prettierignore"), + PathBuf::from(".gitignore"), + ]; + + let plan = InvocationPlan { + target_root, + workspace_entries: Arc::new(vec![]), + invocation_id: "".to_string(), + verb: ExecutionVerb::Check, + workspace: Workspace { + root: workspace_root, + }, + settings: Default::default(), + runtime: None, + runtime_version: None, + plugin_name: "prettier".to_string(), + plugin: PluginDef::default(), + tool: Ruby::new_tool(""), + driver_name: "format".to_string(), + driver, + plugin_configs: vec![crate::planner::config_files::PluginConfigFile { + path: config_path, + contents: "{}".to_string(), + }], + targets: vec![], + invocation_directory: PathBuf::from("/tool"), + invocation_directory_def: InvocationDirectoryDef { + kind: InvocationDirectoryType::ToolDir, + path: None, + }, + }; + + let script = replace_ignore_paths(&plan, "prettier ${ignore_paths} ${target}".to_string()); + + assert!(script.contains("packages/app/.prettierignore")); + assert!(script.contains("packages/app/.gitignore")); + assert!(!script.contains("wrong.js")); + } } diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.gitignore b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.gitignore new file mode 100644 index 000000000..3fec32c84 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.qlty/qlty.toml b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.qlty/qlty.toml new file mode 100644 index 000000000..b7cb4595b --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/.qlty/qlty.toml @@ -0,0 +1,8 @@ +config_version = "0" + +[[source]] +name = "default" +default = true + +[[plugin]] +name = "prettier" diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierignore b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierignore new file mode 100644 index 000000000..1615b0c68 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierignore @@ -0,0 +1 @@ +ignored.js diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierrc.json b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierrc.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/ignored.js b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/ignored.js new file mode 100644 index 000000000..f911b4353 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/ignored.js @@ -0,0 +1 @@ +const ignored = [1,2,3] diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/included.js b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/included.js new file mode 100644 index 000000000..8714459f3 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.in/packages/app/included.js @@ -0,0 +1 @@ +const included = [1,2,3] diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stderr b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stderr new file mode 100644 index 000000000..c3a5e6b35 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stderr @@ -0,0 +1,2 @@ +✖ 1 issue +✖ 1 unformatted file diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stdout b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stdout new file mode 100644 index 000000000..3d97859d9 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.stdout @@ -0,0 +1,11 @@ + + UNFORMATTED FILES: 1 + +✖ packages/app/included.js + + + ISSUES: 1 + +packages/app/included.js:0:0 + 0:0 fmt Incorrect formatting, autoformat by running `qlty fmt`. prettier:fmt + diff --git a/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.toml b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.toml new file mode 100644 index 000000000..ca7a5c984 --- /dev/null +++ b/qlty-cli/tests/cmd/network/check/prettier_nested_ignore.toml @@ -0,0 +1,3 @@ +bin.name = "qlty" +args = ["check", "--all", "--no-cache"] +status.code = 1 diff --git a/qlty-config/src/config/plugin.rs b/qlty-config/src/config/plugin.rs index 956e38752..4809d87e9 100644 --- a/qlty-config/src/config/plugin.rs +++ b/qlty-config/src/config/plugin.rs @@ -102,6 +102,9 @@ pub struct DriverDef { #[serde(default)] pub config_files: Vec, + #[serde(default)] + pub ignore_files: Vec, + #[serde(default)] pub suggested: SuggestionMode, diff --git a/qlty-plugins/plugins/linters/prettier/plugin.toml b/qlty-plugins/plugins/linters/prettier/plugin.toml index ddd7c1f12..b5a59a2ac 100644 --- a/qlty-plugins/plugins/linters/prettier/plugin.toml +++ b/qlty-plugins/plugins/linters/prettier/plugin.toml @@ -36,8 +36,9 @@ package_file_candidate = "package.json" package_file_candidate_filters = ["prettier"] [plugins.definitions.prettier.drivers.format] -script = "prettier --config ${config_file} -w ${target}" -runs_from = { type = "root" } +script = "prettier ${ignore_paths} --config ${config_file} -w ${target}" +runs_from = { type = "tool_directory" } +ignore_files = [".prettierignore", ".gitignore"] batch_by = "config_file" success_codes = [0] # https://prettier.io/docs/cli#exit-codes cache_results = true From c34d896f38a76f20173ae1b1e965a657e38aa7c5 Mon Sep 17 00:00:00 2001 From: Dave Henton Date: Tue, 14 Apr 2026 22:31:27 -0500 Subject: [PATCH 3/3] fix(prettier): make ignore path test platform-safe --- qlty-check/src/executor/invocation_script.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qlty-check/src/executor/invocation_script.rs b/qlty-check/src/executor/invocation_script.rs index 22af9b759..9b8764b8f 100644 --- a/qlty-check/src/executor/invocation_script.rs +++ b/qlty-check/src/executor/invocation_script.rs @@ -486,8 +486,12 @@ mod test { let script = replace_ignore_paths(&plan, "prettier ${ignore_paths} ${target}".to_string()); - assert!(script.contains("packages/app/.prettierignore")); - assert!(script.contains("packages/app/.gitignore")); + assert!(script.contains(&path_to_native_string( + plan.target_root.join("packages/app/.prettierignore") + ))); + assert!(script.contains(&path_to_native_string( + plan.target_root.join("packages/app/.gitignore") + ))); assert!(!script.contains("wrong.js")); } }