Skip to content
Merged
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 @@ -760,6 +760,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
46 changes: 35 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 @@ -127,6 +140,9 @@ pub(crate) async fn execute_check(
// parser think linting never started. Force the default reporter here so the
// captured output is stable across local and CI environments.
args.push("--format=default".to_string());
if suppress_unmatched {
args.push("--no-error-on-unmatched-pattern".to_string());
}
if has_paths {
args.extend(paths.iter().cloned());
}
Expand Down Expand Up @@ -177,11 +193,17 @@ pub(crate) async fn execute_check(
));
}
None => {
output::error("Linting could not start");
if !combined_output.trim().is_empty() {
print_stdout_block(&combined_output);
// oxlint 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) {
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");
Comment thread
fengmk2 marked this conversation as resolved.
}
print_summary_line("Linting failed before analysis started");
}
}
if status != ExitStatus::SUCCESS {
Expand All @@ -193,8 +215,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)

> vp check --fix package.json # non-lintable file, should pass in --fix mode
pass: Formatting completed for checked files (<variable>ms)

> 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
pass: Formatting completed for checked files (<variable>ms)

[2]> vp check src/ignored/index.js # without --fix or explicit flag, should exit non-zero
Comment thread
leaysgur marked this conversation as resolved.
error: Formatting could not start
Checking formatting...
Expected at least one target file. All matched files may have been excluded by ignore rules.

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,9 @@
{
"commands": [
"vp check --fix src/ignored/index.js # all files excluded by ignorePatterns, should pass in --fix mode",
"vp check --fix package.json # non-lintable file, 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: 5 additions & 0 deletions packages/cli/snap-tests/lint-unmatched-pattern/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "lint-unmatched-pattern",
"version": "0.0.0",
"private": true
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/lint-unmatched-pattern/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[1]> vp lint package.json # non-lintable file, should exit non-zero
No files found to lint. Please check your paths and ignore patterns.
Finished in <variable>ms on 0 files with <variable> rules using <variable> threads.
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/lint-unmatched-pattern/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"commands": ["vp lint package.json # non-lintable file, should exit non-zero"]
}
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. Both oxfmt and oxlint support this flag natively.

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