Skip to content

Commit 67b4248

Browse files
authored
feat(cli): add --watch flag to deno check (#34224)
Adds `--watch` support to `deno check`, so that the type checker re-runs automatically whenever a file in the dependency graph changes. This has been a long-standing user request — the existing workaround is to wrap `deno check` in an external file-watching tool, which is less precise and doesn't reuse anything across runs. The implementation follows the same shape as `run --watch`: when the flag is present, the check pass is run inside `file_watcher::watch_func`, and the factory is built via `from_flags_for_watcher` so the graph loader's `FileWatcherReporter` automatically registers every loaded module with the watcher. No bespoke path collection is needed beyond what the module graph already exposes. Closes #14858
1 parent f1a7e4e commit 67b4248

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

cli/args/flags.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ pub struct CheckFlags {
202202
pub doc: bool,
203203
pub doc_only: bool,
204204
pub check_js: bool,
205+
pub watch: Option<WatchFlags>,
205206
}
206207

207208
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -764,6 +765,9 @@ impl DenoSubcommand {
764765
Self::Bench(BenchFlags {
765766
watch: Some(flags), ..
766767
})
768+
| Self::Check(CheckFlags {
769+
watch: Some(flags), ..
770+
})
767771
| Self::Lint(LintFlags {
768772
watch: Some(flags), ..
769773
})
@@ -2896,6 +2900,9 @@ Unless --reload is specified, this command will not re-download already cached d
28962900
.arg(allow_import_arg())
28972901
.arg(deny_import_arg())
28982902
.arg(v8_flags_arg())
2903+
.arg(watch_arg(false))
2904+
.arg(watch_exclude_arg())
2905+
.arg(no_clear_screen_arg())
28992906
}
29002907
)
29012908
}
@@ -6792,6 +6799,7 @@ fn check_parse(
67926799
doc: matches.get_flag("doc"),
67936800
doc_only: matches.get_flag("doc-only"),
67946801
check_js: matches.get_flag("check-js"),
6802+
watch: watch_arg_parse(matches)?,
67956803
});
67966804
flags.code_cache_enabled = !matches.get_flag("no-code-cache");
67976805
allow_and_deny_import_parse(flags, matches)?;
@@ -10228,6 +10236,7 @@ mod tests {
1022810236
doc: false,
1022910237
doc_only: false,
1023010238
check_js: false,
10239+
watch: None,
1023110240
}),
1023210241
type_check_mode: TypeCheckMode::Local,
1023310242
code_cache_enabled: true,
@@ -10244,6 +10253,7 @@ mod tests {
1024410253
doc: false,
1024510254
doc_only: false,
1024610255
check_js: false,
10256+
watch: None,
1024710257
}),
1024810258
type_check_mode: TypeCheckMode::Local,
1024910259
code_cache_enabled: true,
@@ -10260,6 +10270,7 @@ mod tests {
1026010270
doc: true,
1026110271
doc_only: false,
1026210272
check_js: false,
10273+
watch: None,
1026310274
}),
1026410275
type_check_mode: TypeCheckMode::Local,
1026510276
code_cache_enabled: true,
@@ -10276,6 +10287,7 @@ mod tests {
1027610287
doc: false,
1027710288
doc_only: true,
1027810289
check_js: false,
10290+
watch: None,
1027910291
}),
1028010292
type_check_mode: TypeCheckMode::Local,
1028110293
code_cache_enabled: true,
@@ -10306,6 +10318,7 @@ mod tests {
1030610318
doc: false,
1030710319
doc_only: false,
1030810320
check_js: false,
10321+
watch: None,
1030910322
}),
1031010323
type_check_mode: TypeCheckMode::All,
1031110324
code_cache_enabled: true,
@@ -10335,6 +10348,7 @@ mod tests {
1033510348
doc: false,
1033610349
doc_only: false,
1033710350
check_js: true,
10351+
watch: None,
1033810352
}),
1033910353
type_check_mode: TypeCheckMode::Local,
1034010354
code_cache_enabled: true,
@@ -16127,6 +16141,7 @@ Usage: deno repl [OPTIONS] [-- [ARGS]...]\n"
1612716141
doc: false,
1612816142
doc_only: false,
1612916143
check_js: false,
16144+
watch: None,
1613016145
}),
1613116146
type_check_mode: TypeCheckMode::Local,
1613216147
code_cache_enabled: true,

cli/tools/check.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,40 @@ use crate::factory::CliFactory;
1111
use crate::graph_container::CheckSpecifiersOptions;
1212
use crate::graph_container::CollectSpecifiersOptions;
1313
use crate::util::extract;
14+
use crate::util::file_watcher;
1415

1516
pub async fn check(
1617
flags: Arc<Flags>,
1718
check_flags: CheckFlags,
1819
) -> Result<(), AnyError> {
19-
let factory = CliFactory::from_flags(flags);
20+
if let Some(watch_flags) = &check_flags.watch {
21+
let no_clear_screen = watch_flags.no_clear_screen;
22+
file_watcher::watch_func(
23+
flags,
24+
file_watcher::PrintConfig::new("Check", !no_clear_screen),
25+
move |flags, watcher_communicator, changed_paths| {
26+
let check_flags = check_flags.clone();
27+
watcher_communicator.show_path_changed(changed_paths);
28+
Ok(async move {
29+
let factory = CliFactory::from_flags_for_watcher(
30+
flags,
31+
watcher_communicator.clone(),
32+
);
33+
check_with_factory(&factory, check_flags).await
34+
})
35+
},
36+
)
37+
.await
38+
} else {
39+
let factory = CliFactory::from_flags(flags);
40+
check_with_factory(&factory, check_flags).await
41+
}
42+
}
2043

44+
async fn check_with_factory(
45+
factory: &CliFactory,
46+
check_flags: CheckFlags,
47+
) -> Result<(), AnyError> {
2148
let main_graph_container = factory.main_module_graph_container().await?;
2249

2350
let specifiers = main_graph_container.collect_specifiers(

tests/integration/watcher_tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,35 @@ async fn fmt_check_all_files_on_each_change_test() {
491491
check_alive_then_kill(child);
492492
}
493493

494+
#[test(flaky)]
495+
async fn check_watch_test() {
496+
let t = TempDir::new();
497+
let file_to_check = t.path().join("main.ts");
498+
file_to_check.write("const x: number = \"hello\";\n");
499+
500+
let mut child = util::deno_cmd()
501+
.current_dir(t.path())
502+
.arg("check")
503+
.arg(&file_to_check)
504+
.arg("--watch")
505+
.piped_output()
506+
.spawn()
507+
.unwrap();
508+
let (_stdout_lines, mut stderr_lines) = child_lines(&mut child);
509+
510+
let next_line = next_line(&mut stderr_lines).await.unwrap();
511+
assert_contains!(&next_line, "Check started");
512+
assert_contains!(wait_contains("TS2322", &mut stderr_lines).await, "TS2322");
513+
wait_contains("Check failed.", &mut stderr_lines).await;
514+
515+
// Fix the type error.
516+
file_to_check.write("const x: number = 42;\nconsole.log(x);\n");
517+
518+
wait_contains("Check finished.", &mut stderr_lines).await;
519+
520+
check_alive_then_kill(child);
521+
}
522+
494523
#[test(flaky)]
495524
async fn run_watch_no_dynamic() {
496525
let t = TempDir::new();

0 commit comments

Comments
 (0)