Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/vite_global_cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,10 @@ fn delegated_help_doc(command: &str) -> Option<HelpDoc> {
row("--fix", "Auto-fix format and lint issues"),
row("--no-fmt", "Skip format check"),
row("--no-lint", "Skip lint check"),
row(
"--no-error-on-unmatched-pattern",
"Do not exit with error when pattern is unmatched",
),
row("-h, --help", "Print help"),
],
),
Expand Down
44 changes: 33 additions & 11 deletions packages/cli/binding/src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) async fn execute_check(
fix: bool,
no_fmt: bool,
no_lint: bool,
no_error_on_unmatched_pattern: bool,
paths: Vec<String>,
envs: &Arc<FxHashMap<Arc<OsStr>, Arc<OsStr>>>,
cwd: &AbsolutePathBuf,
Expand All @@ -37,14 +38,20 @@ pub(crate) async fn execute_check(

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

if !no_fmt {
let mut args = if fix { vec![] } else { vec!["--check".to_string()] };
if has_paths {
if suppress_unmatched {
args.push("--no-error-on-unmatched-pattern".to_string());
}
if has_paths {
args.extend(paths.iter().cloned());
}
let fmt_start = Instant::now();
Expand Down Expand Up @@ -87,11 +94,17 @@ pub(crate) async fn execute_check(
));
}
None => {
print_error_block(
"Formatting could not start",
&combined_output,
"Formatting failed before analysis started",
);
// oxfmt handles --no-error-on-unmatched-pattern natively and
// exits 0 when no files match, so we only need to guard
// against the edge case where output is unparsable but the
// process still succeeded.
if !(suppress_unmatched && status == ExitStatus::SUCCESS) {
print_error_block(
"Formatting could not start",
&combined_output,
"Formatting failed before analysis started",
);
}
}
}
}
Expand Down Expand Up @@ -177,11 +190,18 @@ pub(crate) async fn execute_check(
));
}
None => {
output::error("Linting could not start");
if !combined_output.trim().is_empty() {
print_stdout_block(&combined_output);
// Only suppress when the output is empty (no files to lint).
// If oxlint produced error output (config error, crash, etc.),
// surface it even when suppress_unmatched is active.
if suppress_unmatched && combined_output.trim().is_empty() {
status = ExitStatus::SUCCESS;
} else {
output::error("Linting could not start");
if !combined_output.trim().is_empty() {
print_stdout_block(&combined_output);
}
print_summary_line("Linting failed before analysis started");
}
print_summary_line("Linting failed before analysis started");
}
}
if status != ExitStatus::SUCCESS {
Expand All @@ -193,8 +213,10 @@ pub(crate) async fn execute_check(
// (e.g. the curly rule adding braces to if-statements)
if fix && !no_fmt && !no_lint {
let mut args = Vec::new();
if has_paths {
if suppress_unmatched {
args.push("--no-error-on-unmatched-pattern".to_string());
}
if has_paths {
args.extend(paths.into_iter());
}
let captured = resolve_and_capture_output(
Expand Down
18 changes: 16 additions & 2 deletions packages/cli/binding/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,23 @@ async fn execute_direct_subcommand(
let cwd_arc: Arc<AbsolutePath> = cwd.clone().into();

let status = match subcommand {
SynthesizableSubcommand::Check { fix, no_fmt, no_lint, paths } => {
SynthesizableSubcommand::Check {
fix,
no_fmt,
no_lint,
no_error_on_unmatched_pattern,
paths,
} => {
return crate::check::execute_check(
&resolver, fix, no_fmt, no_lint, paths, &envs, cwd, &cwd_arc,
&resolver,
fix,
no_fmt,
no_lint,
no_error_on_unmatched_pattern,
paths,
&envs,
cwd,
&cwd_arc,
)
.await;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/binding/src/cli/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ pub enum SynthesizableSubcommand {
/// Skip lint check
#[arg(long = "no-lint")]
no_lint: bool,
/// Do not exit with error when pattern is unmatched
#[arg(long = "no-error-on-unmatched-pattern")]
no_error_on_unmatched_pattern: bool,
/// File paths to check (passed through to fmt and lint)
#[arg(trailing_var_arg = true)]
paths: Vec<String>,
Expand Down
27 changes: 15 additions & 12 deletions packages/cli/snap-tests-global/command-check-help/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ Usage: vp check [OPTIONS] [PATHS]...
Run format, lint, and type checks.

Options:
--fix Auto-fix format and lint issues
--no-fmt Skip format check
--no-lint Skip lint check
-h, --help Print help
--fix Auto-fix format and lint issues
--no-fmt Skip format check
--no-lint Skip lint check
--no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched
-h, --help Print help

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

Options:
--fix Auto-fix format and lint issues
--no-fmt Skip format check
--no-lint Skip lint check
-h, --help Print help
--fix Auto-fix format and lint issues
--no-fmt Skip format check
--no-lint Skip lint check
--no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched
-h, --help Print help

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

Options:
--fix Auto-fix format and lint issues
--no-fmt Skip format check
--no-lint Skip lint check
-h, --help Print help
--fix Auto-fix format and lint issues
--no-fmt Skip format check
--no-lint Skip lint check
--no-error-on-unmatched-pattern Do not exit with error when pattern is unmatched
-h, --help Print help

Examples:
vp check
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "check-fix-lint-error-not-swallowed",
"version": "0.0.0",
"private": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[1]> vp check --fix src/index.js # real lint error with --fix and paths (suppress_unmatched active), error must not be swallowed
error: Lint issues found
× eslint(no-eval): eval can be harmful.
╭─[src/index.js:2:3]
1 │ function hello() {
2 │ eval("code");
· ────
3 │ return "hello";
╰────
help: Avoid eval(). For JSON parsing use JSON.parse(); for dynamic property access use bracket notation (obj[key]); for other cases refactor to avoid evaluating strings as code.

Found 1 error and 0 warnings in 1 file (<variable>ms, <variable> threads)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function hello() {
eval("code");
return "hello";
}

export { hello };
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"commands": [
"vp check --fix src/index.js # real lint error with --fix and paths (suppress_unmatched active), error must not be swallowed"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
lint: {
rules: {
"no-eval": "error",
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "check-fix-no-error-unmatched",
"version": "0.0.0",
"private": true
}
16 changes: 16 additions & 0 deletions packages/cli/snap-tests/check-fix-no-error-unmatched/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
> vp check --fix src/ignored/index.js # all files excluded by ignorePatterns, should pass in --fix mode
pass: Formatting completed for checked files (<variable>ms)
pass: Found no warnings or lint errors in 0 files (<variable>ms, <variable> threads)

> vp check --no-error-on-unmatched-pattern src/ignored/index.js # explicit flag without --fix, should also pass
pass: Found no warnings or lint errors in 0 files (<variable>ms, <variable> threads)

> vp check --fix --no-error-on-unmatched-pattern src/ignored/index.js # both flags set, should pass
pass: Formatting completed for checked files (<variable>ms)
pass: Found no warnings or lint errors in 0 files (<variable>ms, <variable> threads)

[2]> vp check src/ignored/index.js # without --fix or explicit flag, should exit non-zero
error: Formatting could not start
Expected at least one target file

Formatting failed before analysis started
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This file is excluded by both fmt and lint ignorePatterns.
export const hello = 'world';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"commands": [
"vp check --fix src/ignored/index.js # all files excluded by ignorePatterns, should pass in --fix mode",
"vp check --no-error-on-unmatched-pattern src/ignored/index.js # explicit flag without --fix, should also pass",
"vp check --fix --no-error-on-unmatched-pattern src/ignored/index.js # both flags set, should pass",
"vp check src/ignored/index.js # without --fix or explicit flag, should exit non-zero"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
fmt: {
ignorePatterns: ['src/ignored/**/*'],
},
lint: {
ignorePatterns: ['src/ignored/**/*'],
},
};
5 changes: 4 additions & 1 deletion rfcs/check-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ vp check --no-type-check
| `--lint` / `--no-lint` | ON | Run lint check (`vp lint`) |
| `--type-aware` / `--no-type-aware` | ON | Enable type-aware lint rules (oxlint `--type-aware`) |
| `--type-check` / `--no-type-check` | ON | Enable TypeScript type checking (oxlint `--type-check`) |
| `--no-error-on-unmatched-pattern` | OFF | Do not exit with error when pattern is unmatched |

**Flag dependency:** `--type-check` requires `--type-aware` as a prerequisite.

Expand All @@ -73,8 +74,9 @@ vp check --fix src/index.ts src/utils.ts

When file paths are provided:

- `--no-error-on-unmatched-pattern` is automatically added to `fmt` args (prevents errors when paths don't match fmt patterns)
- Paths are appended to both `fmt` and `lint` sub-commands
- In `--fix` mode, `--no-error-on-unmatched-pattern` is implicitly enabled for both `fmt` and `lint`, preventing errors when all provided paths are excluded by ignorePatterns. This is the common lint-staged use case where staged files may not match tool-specific patterns.
- Without `--fix`, unmatched patterns are reported as errors unless `--no-error-on-unmatched-pattern` is explicitly passed. Note that oxfmt supports this flag natively, while oxlint does not — `vp check` handles the lint side by treating unparsable lint output as a pass when the flag is active.

This enables lint-staged integration:

Expand Down Expand Up @@ -208,6 +210,7 @@ Options:
--lint Run lint check [default: true]
--type-aware Enable type-aware linting [default: true]
--type-check Enable TypeScript type checking [default: true]
--no-error-on-unmatched-pattern Do not exit with error when no files match
-h, --help Print help
```

Expand Down
Loading