Skip to content

Commit 507a843

Browse files
committed
feat(check): support --no-error-on-unmatched-pattern flag
In `--fix` mode with file paths, implicitly suppress "no matching files" errors for both fmt and lint. This fixes the lint-staged use case where staged files may be excluded by ignorePatterns (e.g. package-lock.json excluded by fmt ignorePatterns). Also expose `--no-error-on-unmatched-pattern` as an explicit CLI flag for non-fix use cases. oxfmt supports this flag natively; for oxlint (which does not), vp check handles it by treating unparseable lint output as a pass when the flag is active. Closes #1210
1 parent 08a95ab commit 507a843

File tree

11 files changed

+114
-26
lines changed

11 files changed

+114
-26
lines changed

crates/vite_global_cli/src/help.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,10 @@ fn delegated_help_doc(command: &str) -> Option<HelpDoc> {
759759
row("--fix", "Auto-fix format and lint issues"),
760760
row("--no-fmt", "Skip format check"),
761761
row("--no-lint", "Skip lint check"),
762+
row(
763+
"--no-error-on-unmatched-pattern",
764+
"Do not exit with error when pattern is unmatched",
765+
),
762766
row("-h, --help", "Print help"),
763767
],
764768
),

packages/cli/binding/src/check/mod.rs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub(crate) async fn execute_check(
2222
fix: bool,
2323
no_fmt: bool,
2424
no_lint: bool,
25+
no_error_on_unmatched_pattern: bool,
2526
paths: Vec<String>,
2627
envs: &Arc<FxHashMap<Arc<OsStr>, Arc<OsStr>>>,
2728
cwd: &AbsolutePathBuf,
@@ -37,14 +38,20 @@ pub(crate) async fn execute_check(
3738

3839
let mut status = ExitStatus::SUCCESS;
3940
let has_paths = !paths.is_empty();
41+
// In --fix mode with file paths (the lint-staged use case), implicitly suppress
42+
// "no matching files" errors. This is also available as an explicit flag for
43+
// non-fix use cases.
44+
let suppress_unmatched = no_error_on_unmatched_pattern || (fix && has_paths);
4045
let mut fmt_fix_started: Option<Instant> = None;
4146
let mut deferred_lint_pass: Option<(String, String)> = None;
4247
let resolved_vite_config = resolver.resolve_universal_vite_config().await?;
4348

4449
if !no_fmt {
4550
let mut args = if fix { vec![] } else { vec!["--check".to_string()] };
46-
if has_paths {
51+
if suppress_unmatched {
4752
args.push("--no-error-on-unmatched-pattern".to_string());
53+
}
54+
if has_paths {
4855
args.extend(paths.iter().cloned());
4956
}
5057
let fmt_start = Instant::now();
@@ -87,11 +94,17 @@ pub(crate) async fn execute_check(
8794
));
8895
}
8996
None => {
90-
print_error_block(
91-
"Formatting could not start",
92-
&combined_output,
93-
"Formatting failed before analysis started",
94-
);
97+
if suppress_unmatched && status == ExitStatus::SUCCESS {
98+
// No files matched fmt patterns — treat as pass when
99+
// --no-error-on-unmatched-pattern is active (explicit or
100+
// implicit via --fix with paths).
101+
} else {
102+
print_error_block(
103+
"Formatting could not start",
104+
&combined_output,
105+
"Formatting failed before analysis started",
106+
);
107+
}
95108
}
96109
}
97110
}
@@ -177,11 +190,18 @@ pub(crate) async fn execute_check(
177190
));
178191
}
179192
None => {
180-
output::error("Linting could not start");
181-
if !combined_output.trim().is_empty() {
182-
print_stdout_block(&combined_output);
193+
if suppress_unmatched {
194+
// oxlint does not support --no-error-on-unmatched-pattern natively,
195+
// so we handle it here: when all files are excluded by ignorePatterns
196+
// and the flag is active, treat it as a pass instead of an error.
197+
status = ExitStatus::SUCCESS;
198+
} else {
199+
output::error("Linting could not start");
200+
if !combined_output.trim().is_empty() {
201+
print_stdout_block(&combined_output);
202+
}
203+
print_summary_line("Linting failed before analysis started");
183204
}
184-
print_summary_line("Linting failed before analysis started");
185205
}
186206
}
187207
if status != ExitStatus::SUCCESS {
@@ -193,8 +213,10 @@ pub(crate) async fn execute_check(
193213
// (e.g. the curly rule adding braces to if-statements)
194214
if fix && !no_fmt && !no_lint {
195215
let mut args = Vec::new();
196-
if has_paths {
216+
if suppress_unmatched {
197217
args.push("--no-error-on-unmatched-pattern".to_string());
218+
}
219+
if has_paths {
198220
args.extend(paths.into_iter());
199221
}
200222
let captured = resolve_and_capture_output(

packages/cli/binding/src/cli/mod.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,23 @@ async fn execute_direct_subcommand(
6363
let cwd_arc: Arc<AbsolutePath> = cwd.clone().into();
6464

6565
let status = match subcommand {
66-
SynthesizableSubcommand::Check { fix, no_fmt, no_lint, paths } => {
66+
SynthesizableSubcommand::Check {
67+
fix,
68+
no_fmt,
69+
no_lint,
70+
no_error_on_unmatched_pattern,
71+
paths,
72+
} => {
6773
return crate::check::execute_check(
68-
&resolver, fix, no_fmt, no_lint, paths, &envs, cwd, &cwd_arc,
74+
&resolver,
75+
fix,
76+
no_fmt,
77+
no_lint,
78+
no_error_on_unmatched_pattern,
79+
paths,
80+
&envs,
81+
cwd,
82+
&cwd_arc,
6983
)
7084
.await;
7185
}

packages/cli/binding/src/cli/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ pub enum SynthesizableSubcommand {
9393
/// Skip lint check
9494
#[arg(long = "no-lint")]
9595
no_lint: bool,
96+
/// Do not exit with error when pattern is unmatched
97+
#[arg(long = "no-error-on-unmatched-pattern")]
98+
no_error_on_unmatched_pattern: bool,
9699
/// File paths to check (passed through to fmt and lint)
97100
#[arg(trailing_var_arg = true)]
98101
paths: Vec<String>,

packages/cli/snap-tests-global/command-check-help/snap.txt

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ Usage: vp check [OPTIONS] [PATHS]...
66
Run format, lint, and type checks.
77

88
Options:
9-
--fix Auto-fix format and lint issues
10-
--no-fmt Skip format check
11-
--no-lint Skip lint check
12-
-h, --help Print help
9+
--fix Auto-fix format and lint issues
10+
--no-fmt Skip format check
11+
--no-lint Skip lint check
12+
--no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched
13+
-h, --help Print help
1314

1415
Examples:
1516
vp check
@@ -27,10 +28,11 @@ Usage: vp check [OPTIONS] [PATHS]...
2728
Run format, lint, and type checks.
2829

2930
Options:
30-
--fix Auto-fix format and lint issues
31-
--no-fmt Skip format check
32-
--no-lint Skip lint check
33-
-h, --help Print help
31+
--fix Auto-fix format and lint issues
32+
--no-fmt Skip format check
33+
--no-lint Skip lint check
34+
--no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched
35+
-h, --help Print help
3436

3537
Examples:
3638
vp check
@@ -48,10 +50,11 @@ Usage: vp check [OPTIONS] [PATHS]...
4850
Run format, lint, and type checks.
4951

5052
Options:
51-
--fix Auto-fix format and lint issues
52-
--no-fmt Skip format check
53-
--no-lint Skip lint check
54-
-h, --help Print help
53+
--fix Auto-fix format and lint issues
54+
--no-fmt Skip format check
55+
--no-lint Skip lint check
56+
--no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched
57+
-h, --help Print help
5558

5659
Examples:
5760
vp check
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "check-fix-no-error-unmatched",
3+
"version": "0.0.0",
4+
"private": true
5+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
> vp check --fix src/ignored/index.js # all files excluded by ignorePatterns, should pass in --fix mode
2+
pass: Formatting completed for checked files (<variable>ms)
3+
pass: Found no warnings or lint errors in 0 files (<variable>ms, <variable> threads)
4+
5+
> vp check --no-error-on-unmatched-pattern src/ignored/index.js # explicit flag without --fix, should also pass
6+
pass: Found no warnings or lint errors in 0 files (<variable>ms, <variable> threads)
7+
8+
> vp check --fix --no-error-on-unmatched-pattern src/ignored/index.js # both flags set, should pass
9+
pass: Formatting completed for checked files (<variable>ms)
10+
pass: Found no warnings or lint errors in 0 files (<variable>ms, <variable> threads)
11+
12+
[2]> vp check src/ignored/index.js # without --fix or explicit flag, should exit non-zero
13+
error: Formatting could not start
14+
Expected at least one target file
15+
16+
Formatting failed before analysis started
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// This file is excluded by both fmt and lint ignorePatterns.
2+
export const hello = 'world';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"commands": [
3+
"vp check --fix src/ignored/index.js # all files excluded by ignorePatterns, should pass in --fix mode",
4+
"vp check --no-error-on-unmatched-pattern src/ignored/index.js # explicit flag without --fix, should also pass",
5+
"vp check --fix --no-error-on-unmatched-pattern src/ignored/index.js # both flags set, should pass",
6+
"vp check src/ignored/index.js # without --fix or explicit flag, should exit non-zero"
7+
]
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default {
2+
fmt: {
3+
ignorePatterns: ['src/ignored/**/*'],
4+
},
5+
lint: {
6+
ignorePatterns: ['src/ignored/**/*'],
7+
},
8+
};

0 commit comments

Comments
 (0)