Skip to content

Commit c19ccc2

Browse files
committed
feat(check): wire lint phase to 3-phase matrix via from_flags and sidecar
1 parent 3dd0bf7 commit c19ccc2

3 files changed

Lines changed: 55 additions & 63 deletions

File tree

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

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,12 @@ pub(super) enum LintMessageKind {
4040
LintOnly,
4141
LintAndTypeCheck,
4242
// Used when lint rules are skipped but type-check still runs.
43-
#[allow(dead_code)]
4443
TypeCheckOnly,
4544
}
4645

4746
impl LintMessageKind {
48-
pub(super) fn from_lint_config(lint_config: Option<&serde_json::Value>) -> Self {
49-
if lint_config_type_check_enabled(lint_config) {
50-
Self::LintAndTypeCheck
51-
} else {
52-
Self::LintOnly
53-
}
54-
}
55-
5647
// Selects a variant from the `(lint, type-check)` enabled tuple callers have
5748
// already resolved, avoiding a second config lookup inside the helper.
58-
#[allow(dead_code)]
5949
pub(super) fn from_flags(lint_enabled: bool, type_check_enabled: bool) -> Self {
6050
match (lint_enabled, type_check_enabled) {
6151
(true, true) => Self::LintAndTypeCheck,
@@ -250,34 +240,3 @@ pub(super) fn analyze_lint_output(output: &str) -> Option<Result<LintSuccess, Li
250240

251241
Some(Err(LintFailure { summary, warnings, errors, diagnostics }))
252242
}
253-
254-
#[cfg(test)]
255-
mod tests {
256-
use serde_json::json;
257-
258-
use super::LintMessageKind;
259-
260-
#[test]
261-
fn lint_message_kind_defaults_to_lint_only_without_typecheck() {
262-
assert_eq!(LintMessageKind::from_lint_config(None), LintMessageKind::LintOnly);
263-
assert_eq!(
264-
LintMessageKind::from_lint_config(Some(&json!({ "options": {} }))),
265-
LintMessageKind::LintOnly
266-
);
267-
}
268-
269-
#[test]
270-
fn lint_message_kind_detects_typecheck_from_vite_config() {
271-
let kind = LintMessageKind::from_lint_config(Some(&json!({
272-
"options": {
273-
"typeAware": true,
274-
"typeCheck": true
275-
}
276-
})));
277-
278-
assert_eq!(kind, LintMessageKind::LintAndTypeCheck);
279-
assert_eq!(kind.success_label(), "Found no warnings, lint errors, or type errors");
280-
assert_eq!(kind.warning_heading(), "Lint or type warnings found");
281-
assert_eq!(kind.issue_heading(), "Lint or type issues found");
282-
}
283-
}

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

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ use vite_path::{AbsolutePath, AbsolutePathBuf};
99
use vite_shared::output;
1010
use 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
};
1720
use 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,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[1]> vp check --no-fmt --no-lint
22
error: No checks enabled
33

4-
`vp check` did not run because both `--no-fmt` and `--no-lint` were set
4+
`vp check` did not run because all checks were disabled by the provided flags

0 commit comments

Comments
 (0)