diff --git a/crates/vite_global_cli/src/help.rs b/crates/vite_global_cli/src/help.rs index 55767b0d56..21b3bfbcbd 100644 --- a/crates/vite_global_cli/src/help.rs +++ b/crates/vite_global_cli/src/help.rs @@ -760,6 +760,10 @@ fn delegated_help_doc(command: &str) -> Option { row("--fix", "Auto-fix format and lint issues"), row("--no-fmt", "Skip format check"), row("--no-lint", "Skip lint check"), + row( + "--type-check-only", + "Run only type checking, skipping format and lint rules", + ), row("-h, --help", "Print help"), ], ), diff --git a/packages/cli/binding/src/check/analysis.rs b/packages/cli/binding/src/check/analysis.rs index 2dbaa00577..3e0ed5e8c8 100644 --- a/packages/cli/binding/src/check/analysis.rs +++ b/packages/cli/binding/src/check/analysis.rs @@ -39,6 +39,7 @@ pub(super) struct LintFailure { pub(super) enum LintMessageKind { LintOnly, LintAndTypeCheck, + TypeCheckOnly, } impl LintMessageKind { @@ -56,6 +57,7 @@ impl LintMessageKind { match self { Self::LintOnly => "Found no warnings or lint errors", Self::LintAndTypeCheck => "Found no warnings, lint errors, or type errors", + Self::TypeCheckOnly => "Found no type errors", } } @@ -63,6 +65,7 @@ impl LintMessageKind { match self { Self::LintOnly => "Lint warnings found", Self::LintAndTypeCheck => "Lint or type warnings found", + Self::TypeCheckOnly => "Type warnings found", } } @@ -70,6 +73,7 @@ impl LintMessageKind { match self { Self::LintOnly => "Lint issues found", Self::LintAndTypeCheck => "Lint or type issues found", + Self::TypeCheckOnly => "Type errors found", } } } @@ -252,4 +256,24 @@ mod tests { assert_eq!(kind.warning_heading(), "Lint or type warnings found"); assert_eq!(kind.issue_heading(), "Lint or type issues found"); } + + #[test] + fn lint_message_kind_type_check_only_messages() { + let kind = LintMessageKind::TypeCheckOnly; + + assert_eq!(kind.success_label(), "Found no type errors"); + assert_eq!(kind.warning_heading(), "Type warnings found"); + assert_eq!(kind.issue_heading(), "Type errors found"); + } + + #[test] + fn lint_message_kind_from_lint_config_does_not_return_type_check_only() { + assert_eq!(LintMessageKind::from_lint_config(None), LintMessageKind::LintOnly); + assert_eq!( + LintMessageKind::from_lint_config(Some(&json!({ + "options": { "typeCheck": true } + }))), + LintMessageKind::LintAndTypeCheck + ); + } } diff --git a/packages/cli/binding/src/check/mod.rs b/packages/cli/binding/src/check/mod.rs index d166723e19..48889d4eeb 100644 --- a/packages/cli/binding/src/check/mod.rs +++ b/packages/cli/binding/src/check/mod.rs @@ -22,6 +22,7 @@ pub(crate) async fn execute_check( fix: bool, no_fmt: bool, no_lint: bool, + type_check_only: bool, paths: Vec, envs: &Arc, Arc>>, cwd: &AbsolutePathBuf, @@ -35,13 +36,25 @@ pub(crate) async fn execute_check( return Ok(ExitStatus(1)); } + if type_check_only && no_lint { + output::error("Conflicting flags"); + print_summary_line("`vp check --type-check-only` cannot be used with `--no-lint`"); + return Ok(ExitStatus(1)); + } + + if type_check_only && fix { + output::error("Conflicting flags"); + print_summary_line("`vp check --type-check-only` cannot be used with `--fix`"); + return Ok(ExitStatus(1)); + } + let mut status = ExitStatus::SUCCESS; let has_paths = !paths.is_empty(); let mut fmt_fix_started: Option = None; let mut deferred_lint_pass: Option<(String, String)> = None; let resolved_vite_config = resolver.resolve_universal_vite_config().await?; - if !no_fmt { + if !no_fmt && !type_check_only { let mut args = if fix { vec![] } else { vec!["--check".to_string()] }; if has_paths { args.push("--no-error-on-unmatched-pattern".to_string()); @@ -115,8 +128,11 @@ pub(crate) async fn execute_check( } if !no_lint { - let lint_message_kind = - LintMessageKind::from_lint_config(resolved_vite_config.lint.as_ref()); + let lint_message_kind = if type_check_only { + LintMessageKind::TypeCheckOnly + } else { + LintMessageKind::from_lint_config(resolved_vite_config.lint.as_ref()) + }; let mut args = Vec::new(); if fix { args.push("--fix".to_string()); @@ -127,6 +143,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 type_check_only { + args.push("--type-check-only".to_string()); + } if has_paths { args.extend(paths.iter().cloned()); } diff --git a/packages/cli/binding/src/cli/mod.rs b/packages/cli/binding/src/cli/mod.rs index 5811ef75bc..5d3f431ac6 100644 --- a/packages/cli/binding/src/cli/mod.rs +++ b/packages/cli/binding/src/cli/mod.rs @@ -63,9 +63,17 @@ async fn execute_direct_subcommand( let cwd_arc: Arc = cwd.clone().into(); let status = match subcommand { - SynthesizableSubcommand::Check { fix, no_fmt, no_lint, paths } => { + SynthesizableSubcommand::Check { fix, no_fmt, no_lint, type_check_only, paths } => { return crate::check::execute_check( - &resolver, fix, no_fmt, no_lint, paths, &envs, cwd, &cwd_arc, + &resolver, + fix, + no_fmt, + no_lint, + type_check_only, + paths, + &envs, + cwd, + &cwd_arc, ) .await; } diff --git a/packages/cli/binding/src/cli/types.rs b/packages/cli/binding/src/cli/types.rs index 6e408460b6..6e2f39e3c6 100644 --- a/packages/cli/binding/src/cli/types.rs +++ b/packages/cli/binding/src/cli/types.rs @@ -93,6 +93,9 @@ pub enum SynthesizableSubcommand { /// Skip lint check #[arg(long = "no-lint")] no_lint: bool, + /// Run only type checking, skipping format and lint rules + #[arg(long = "type-check-only")] + type_check_only: bool, /// File paths to check (passed through to fmt and lint) #[arg(trailing_var_arg = true)] paths: Vec, diff --git a/packages/cli/snap-tests-global/command-check-help/snap.txt b/packages/cli/snap-tests-global/command-check-help/snap.txt index acfb446b24..7d00796229 100644 --- a/packages/cli/snap-tests-global/command-check-help/snap.txt +++ b/packages/cli/snap-tests-global/command-check-help/snap.txt @@ -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 + --type-check-only Run only type checking, skipping format and lint rules + -h, --help Print help Examples: vp check @@ -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 + --type-check-only Run only type checking, skipping format and lint rules + -h, --help Print help Examples: vp check @@ -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 + --type-check-only Run only type checking, skipping format and lint rules + -h, --help Print help Examples: vp check diff --git a/packages/cli/snap-tests/check-type-check-only-conflict-fix/package.json b/packages/cli/snap-tests/check-type-check-only-conflict-fix/package.json new file mode 100644 index 0000000000..b578491b56 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-conflict-fix/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-type-check-only-conflict-fix", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-type-check-only-conflict-fix/snap.txt b/packages/cli/snap-tests/check-type-check-only-conflict-fix/snap.txt new file mode 100644 index 0000000000..da3ae65a8e --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-conflict-fix/snap.txt @@ -0,0 +1,4 @@ +[1]> vp check --type-check-only --fix +error: Conflicting flags + +`vp check --type-check-only` cannot be used with `--fix` diff --git a/packages/cli/snap-tests/check-type-check-only-conflict-fix/steps.json b/packages/cli/snap-tests/check-type-check-only-conflict-fix/steps.json new file mode 100644 index 0000000000..0042703c59 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-conflict-fix/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check --type-check-only --fix"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/package.json b/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/package.json new file mode 100644 index 0000000000..ad92eec410 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-type-check-only-conflict-no-lint", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/snap.txt b/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/snap.txt new file mode 100644 index 0000000000..f4276a3d80 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/snap.txt @@ -0,0 +1,4 @@ +[1]> vp check --type-check-only --no-lint +error: Conflicting flags + +`vp check --type-check-only` cannot be used with `--no-lint` diff --git a/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/steps.json b/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/steps.json new file mode 100644 index 0000000000..bedddcd226 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-conflict-no-lint/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check --type-check-only --no-lint"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-fail/package.json b/packages/cli/snap-tests/check-type-check-only-fail/package.json new file mode 100644 index 0000000000..97bb2ffd94 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-fail/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-type-check-only-fail", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-type-check-only-fail/snap.txt b/packages/cli/snap-tests/check-type-check-only-fail/snap.txt new file mode 100644 index 0000000000..0395b63fe9 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-fail/snap.txt @@ -0,0 +1,11 @@ +[1]> vp check --type-check-only +error: Type errors found +× typescript(TS2322): Type 'string' is not assignable to type 'number'. + ╭─[src/index.ts:2:9] + 1 │ export function hello(): number { + 2 │ const value: number = "not a number"; + · ───── + 3 │ return value; + ╰──── + +Found 1 error and 0 warnings in 2 files (ms, threads) diff --git a/packages/cli/snap-tests/check-type-check-only-fail/src/index.ts b/packages/cli/snap-tests/check-type-check-only-fail/src/index.ts new file mode 100644 index 0000000000..7aaf98a69a --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-fail/src/index.ts @@ -0,0 +1,4 @@ +export function hello(): number { + const value: number = "not a number"; + return value; +} diff --git a/packages/cli/snap-tests/check-type-check-only-fail/steps.json b/packages/cli/snap-tests/check-type-check-only-fail/steps.json new file mode 100644 index 0000000000..d01b9736cf --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-fail/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check --type-check-only"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-fail/tsconfig.json b/packages/cli/snap-tests/check-type-check-only-fail/tsconfig.json new file mode 100644 index 0000000000..4bb099e662 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-fail/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "strict": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-fail/vite.config.ts b/packages/cli/snap-tests/check-type-check-only-fail/vite.config.ts new file mode 100644 index 0000000000..ecd9dc9d34 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-fail/vite.config.ts @@ -0,0 +1,8 @@ +export default { + lint: { + options: { + typeAware: true, + typeCheck: true, + }, + }, +}; diff --git a/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/package.json b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/package.json new file mode 100644 index 0000000000..bd5fdc5616 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-type-check-only-no-typecheck-config", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/snap.txt b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/snap.txt new file mode 100644 index 0000000000..0395b63fe9 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/snap.txt @@ -0,0 +1,11 @@ +[1]> vp check --type-check-only +error: Type errors found +× typescript(TS2322): Type 'string' is not assignable to type 'number'. + ╭─[src/index.ts:2:9] + 1 │ export function hello(): number { + 2 │ const value: number = "not a number"; + · ───── + 3 │ return value; + ╰──── + +Found 1 error and 0 warnings in 2 files (ms, threads) diff --git a/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/src/index.ts b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/src/index.ts new file mode 100644 index 0000000000..7aaf98a69a --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/src/index.ts @@ -0,0 +1,4 @@ +export function hello(): number { + const value: number = "not a number"; + return value; +} diff --git a/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/steps.json b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/steps.json new file mode 100644 index 0000000000..d01b9736cf --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check --type-check-only"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/tsconfig.json b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/tsconfig.json new file mode 100644 index 0000000000..4bb099e662 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "strict": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/vite.config.ts b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/vite.config.ts new file mode 100644 index 0000000000..d493bb3b8f --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-no-typecheck-config/vite.config.ts @@ -0,0 +1,8 @@ +export default { + lint: { + options: { + typeAware: true, + typeCheck: false, + }, + }, +}; diff --git a/packages/cli/snap-tests/check-type-check-only-pass/package.json b/packages/cli/snap-tests/check-type-check-only-pass/package.json new file mode 100644 index 0000000000..467c05abdb --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-pass/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-type-check-only-pass", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-type-check-only-pass/snap.txt b/packages/cli/snap-tests/check-type-check-only-pass/snap.txt new file mode 100644 index 0000000000..0a77087aaa --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-pass/snap.txt @@ -0,0 +1,2 @@ +> vp check --type-check-only +pass: Found no type errors in 2 files (ms, threads) diff --git a/packages/cli/snap-tests/check-type-check-only-pass/src/index.ts b/packages/cli/snap-tests/check-type-check-only-pass/src/index.ts new file mode 100644 index 0000000000..346b65c560 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-pass/src/index.ts @@ -0,0 +1,3 @@ +export function hello(): string { + return "hello"; +} diff --git a/packages/cli/snap-tests/check-type-check-only-pass/steps.json b/packages/cli/snap-tests/check-type-check-only-pass/steps.json new file mode 100644 index 0000000000..d01b9736cf --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-pass/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check --type-check-only"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-pass/tsconfig.json b/packages/cli/snap-tests/check-type-check-only-pass/tsconfig.json new file mode 100644 index 0000000000..4bb099e662 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-pass/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "strict": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/cli/snap-tests/check-type-check-only-pass/vite.config.ts b/packages/cli/snap-tests/check-type-check-only-pass/vite.config.ts new file mode 100644 index 0000000000..ecd9dc9d34 --- /dev/null +++ b/packages/cli/snap-tests/check-type-check-only-pass/vite.config.ts @@ -0,0 +1,8 @@ +export default { + lint: { + options: { + typeAware: true, + typeCheck: true, + }, + }, +};