@@ -9,10 +9,13 @@ use vite_path::{AbsolutePath, AbsolutePathBuf};
99use vite_shared:: output;
1010use vite_task:: ExitStatus ;
1111
12- use self :: analysis:: {
13- LintMessageKind , analyze_fmt_check_output, analyze_lint_output, format_count, format_elapsed,
14- lint_config_type_check_enabled, print_error_block, print_pass_line, print_stdout_block,
15- print_summary_line,
12+ use self :: {
13+ analysis:: {
14+ LintMessageKind , analyze_fmt_check_output, analyze_lint_output, format_count,
15+ format_elapsed, lint_config_type_check_enabled, print_error_block, print_pass_line,
16+ print_stdout_block, print_summary_line,
17+ } ,
18+ sidecar:: write_no_type_check_sidecar,
1619} ;
1720use crate :: cli:: {
1821 CapturedCommandOutput , SubcommandResolver , SynthesizableSubcommand , resolve_and_capture_output,
@@ -31,14 +34,6 @@ pub(crate) async fn execute_check(
3134 cwd : & AbsolutePathBuf ,
3235 cwd_arc : & Arc < AbsolutePath > ,
3336) -> Result < ExitStatus , Error > {
34- if no_fmt && no_lint {
35- output:: error ( "No checks enabled" ) ;
36- print_summary_line (
37- "`vp check` did not run because both `--no-fmt` and `--no-lint` were set" ,
38- ) ;
39- return Ok ( ExitStatus ( 1 ) ) ;
40- }
41-
4237 let mut status = ExitStatus :: SUCCESS ;
4338 let has_paths = !paths. is_empty ( ) ;
4439 // In --fix mode with file paths (the lint-staged use case), implicitly suppress
@@ -50,13 +45,22 @@ pub(crate) async fn execute_check(
5045 let resolved_vite_config = resolver. resolve_universal_vite_config ( ) . await ?;
5146
5247 // Per-phase enabled booleans derived from the raw flags plus the resolved
53- // config's `typeCheck` setting. Currently only the guard immediately below
54- // reads these; the fmt/lint phase conditions further down still consult the
55- // raw `no_fmt`/`no_lint` flags .
48+ // config's `typeCheck` setting. `run_lint_phase` drives whether the lint
49+ // subprocess starts at all — true when lint rules should run, or when
50+ // type-check should run via oxlint's type-check-only path .
5651 let config_type_check_enabled =
5752 lint_config_type_check_enabled ( resolved_vite_config. lint . as_ref ( ) ) ;
5853 let type_check_enabled = !no_type_check && config_type_check_enabled;
5954 let lint_enabled = !no_lint;
55+ let run_lint_phase = lint_enabled || type_check_enabled;
56+
57+ if no_fmt && !run_lint_phase {
58+ output:: error ( "No checks enabled" ) ;
59+ print_summary_line (
60+ "`vp check` did not run because all checks were disabled by the provided flags" ,
61+ ) ;
62+ return Ok ( ExitStatus ( 1 ) ) ;
63+ }
6064
6165 // Reject `--fix --no-lint` when the project enables type-check. With lint
6266 // rules skipped, oxlint would take the type-check-only path which it
@@ -74,6 +78,18 @@ pub(crate) async fn execute_check(
7478 return Ok ( ExitStatus ( 1 ) ) ;
7579 }
7680
81+ // Build the `--no-type-check` sidecar up front, before any fmt side effects.
82+ // If temp-dir write fails (full tmpfs, read-only mount, permission denied),
83+ // we surface the error before fmt modifies files, mirroring the
84+ // transactional guarantee of the hard-error guard above. The returned
85+ // guard lives through both fmt and lint phases and is dropped at function
86+ // exit, cleaning up the temp file.
87+ let sidecar = if lint_enabled && no_type_check && config_type_check_enabled {
88+ write_no_type_check_sidecar ( & resolved_vite_config) ?
89+ } else {
90+ None
91+ } ;
92+
7793 if !no_fmt {
7894 let mut args = if fix { vec ! [ ] } else { vec ! [ "--check" . to_string( ) ] } ;
7995 if suppress_unmatched {
@@ -137,7 +153,7 @@ pub(crate) async fn execute_check(
137153 }
138154 }
139155
140- if fix && no_lint && status == ExitStatus :: SUCCESS {
156+ if fix && !run_lint_phase && status == ExitStatus :: SUCCESS {
141157 print_pass_line (
142158 "Formatting completed for checked files" ,
143159 Some ( & format ! ( "({})" , format_elapsed( fmt_start. elapsed( ) ) ) ) ,
@@ -155,13 +171,23 @@ pub(crate) async fn execute_check(
155171 }
156172 }
157173
158- if !no_lint {
159- let lint_message_kind =
160- LintMessageKind :: from_lint_config ( resolved_vite_config. lint . as_ref ( ) ) ;
174+ if run_lint_phase {
175+ let lint_message_kind = LintMessageKind :: from_flags ( lint_enabled, type_check_enabled) ;
161176 let mut args = Vec :: new ( ) ;
162- if fix {
177+ // Hard-error guard above rejects (fix && !lint_enabled && type_check_enabled),
178+ // so when this branch runs with `fix`, lint_enabled is always true. The
179+ // `lint_enabled` check is defense-in-depth against future guard changes.
180+ if fix && lint_enabled {
163181 args. push ( "--fix" . to_string ( ) ) ;
164182 }
183+ // `--type-check-only` suppresses lint rules and runs only type-check
184+ // diagnostics. oxlint accepts this as a hidden flag (oxc#21184). When
185+ // config `typeCheck` is false this flag forces type-check ON, so we
186+ // only emit it on the `--no-lint` + `typeCheck: true` path and skip
187+ // the lint phase entirely when type_check_enabled is false.
188+ if !lint_enabled && type_check_enabled {
189+ args. push ( "--type-check-only" . to_string ( ) ) ;
190+ }
165191 // `vp check` parses oxlint's human-readable summary output to print
166192 // unified pass/fail lines. When `GITHUB_ACTIONS=true`, oxlint auto-switches
167193 // to the GitHub reporter, which omits that summary on success and makes the
@@ -174,10 +200,17 @@ pub(crate) async fn execute_check(
174200 if has_paths {
175201 args. extend ( paths. iter ( ) . cloned ( ) ) ;
176202 }
203+
204+ // `sidecar` was built up front to surface temp-dir write failures
205+ // before fmt made any changes. Borrow its config here to route oxlint
206+ // through the override when present; otherwise use the resolved
207+ // config unchanged.
208+ let lint_vite_config = sidecar. as_ref ( ) . map ( |s| & s. config ) . unwrap_or ( & resolved_vite_config) ;
209+
177210 let captured = resolve_and_capture_output (
178211 resolver,
179212 SynthesizableSubcommand :: Lint { args } ,
180- Some ( & resolved_vite_config ) ,
213+ Some ( lint_vite_config ) ,
181214 envs,
182215 cwd,
183216 cwd_arc,
0 commit comments