diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs index b9e8c7463efb34..0671dc500ac96d 100644 --- a/cli/diagnostics.rs +++ b/cli/diagnostics.rs @@ -367,6 +367,16 @@ impl Diagnostics { })); } + /// Return a set of diagnostics where only the values where the predicate + /// returns `true` are included. + pub fn filter

(&self, predicate: P) -> Self + where + P: FnMut(&Diagnostic) -> bool, + { + let diagnostics = self.0.clone().into_iter().filter(predicate).collect(); + Self(diagnostics) + } + pub fn is_empty(&self) -> bool { self.0.is_empty() } diff --git a/cli/emit.rs b/cli/emit.rs index 86cb8f57c5c558..c6fbfd9a0a79cf 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -12,6 +12,7 @@ use crate::config_file::ConfigFile; use crate::config_file::IgnoredCompilerOptions; use crate::config_file::TsConfig; use crate::diagnostics::Diagnostics; +use crate::flags; use crate::tsc; use crate::version; @@ -293,6 +294,9 @@ fn is_emittable(media_type: &MediaType, include_js: bool) -> bool { /// Options for performing a check of a module graph. Note that the decision to /// emit or not is determined by the `ts_config` settings. pub(crate) struct CheckOptions { + /// The check flag from the option which can effect the filtering of + /// diagnostics in the emit result. + pub check: flags::CheckFlag, /// Set the debug flag on the TypeScript type checker. pub debug: bool, /// If true, any files emitted will be cached, even if there are diagnostics @@ -357,9 +361,21 @@ pub(crate) fn check_and_maybe_emit( root_names, })?; + let diagnostics = if options.check == flags::CheckFlag::Local { + response.diagnostics.filter(|d| { + if let Some(file_name) = &d.file_name { + !file_name.starts_with("http") + } else { + true + } + }) + } else { + response.diagnostics + }; + // sometimes we want to emit when there are diagnostics, and sometimes we // don't. tsc will always return an emit if there are diagnostics - if (response.diagnostics.is_empty() || options.emit_with_diagnostics) + if (diagnostics.is_empty() || options.emit_with_diagnostics) && !response.emitted_files.is_empty() { if let Some(info) = &response.maybe_tsbuildinfo { @@ -414,7 +430,7 @@ pub(crate) fn check_and_maybe_emit( } Ok(CheckEmitResult { - diagnostics: response.diagnostics, + diagnostics, stats: response.stats, }) } diff --git a/cli/flags.rs b/cli/flags.rs index 70db0345a381e8..31180a47ee81f8 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -188,6 +188,24 @@ impl Default for DenoSubcommand { } } +#[derive(Debug, Clone, PartialEq)] +pub enum CheckFlag { + /// Type check all modules. The default value. + All, + /// Skip type checking of all modules. Represents `--no-check` on the command + /// line. + None, + /// Only type check local modules. Represents `--no-check=remote` on the + /// command line. + Local, +} + +impl Default for CheckFlag { + fn default() -> Self { + Self::All + } +} + #[derive(Clone, Debug, PartialEq, Default)] pub struct Flags { /// Vector of CLI arguments - these are user script arguments, all Deno @@ -209,6 +227,7 @@ pub struct Flags { /// the language server is configured with an explicit cache option. pub cache_path: Option, pub cached_only: bool, + pub check: CheckFlag, pub config_path: Option, pub coverage_dir: Option, pub enable_testing_features: bool, @@ -220,7 +239,6 @@ pub struct Flags { pub lock_write: bool, pub lock: Option, pub log_level: Option, - pub no_check: bool, pub no_remote: bool, /// If true, a list of Node built-in modules will be injected into /// the import map. @@ -1643,8 +1661,17 @@ Only local files from entry point module graph are watched.", fn no_check_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name("no-check") + .takes_value(true) + .require_equals(true) + .min_values(0) + .value_name("NO_CHECK_TYPE") .long("no-check") .help("Skip type checking modules") + .long_help( + "Skip type checking of modules. +If the value of '--no-check=remote' is supplied, diagnostic errors from remote +modules will be ignored.", + ) } fn script_arg<'a, 'b>() -> Arg<'a, 'b> { @@ -2334,8 +2361,16 @@ fn compat_arg_parse(flags: &mut Flags, matches: &ArgMatches) { } fn no_check_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { - if matches.is_present("no-check") { - flags.no_check = true; + if let Some(cache_type) = matches.value_of("no-check") { + match cache_type { + "remote" => flags.check = CheckFlag::Local, + _ => debug!( + "invalid value for 'no-check' of '{}' using default", + cache_type + ), + } + } else if matches.is_present("no-check") { + flags.check = CheckFlag::None; } } @@ -3131,7 +3166,7 @@ mod tests { import_map_path: Some("import_map.json".to_string()), no_remote: true, config_path: Some("tsconfig.json".to_string()), - no_check: true, + check: CheckFlag::None, reload: true, lock: Some(PathBuf::from("lock.json")), lock_write: true, @@ -3216,7 +3251,7 @@ mod tests { import_map_path: Some("import_map.json".to_string()), no_remote: true, config_path: Some("tsconfig.json".to_string()), - no_check: true, + check: CheckFlag::None, reload: true, lock: Some(PathBuf::from("lock.json")), lock_write: true, @@ -3483,7 +3518,7 @@ mod tests { source_file: "script.ts".to_string(), out_file: None, }), - no_check: true, + check: CheckFlag::None, ..Flags::default() } ); @@ -3682,7 +3717,7 @@ mod tests { import_map_path: Some("import_map.json".to_string()), no_remote: true, config_path: Some("tsconfig.json".to_string()), - no_check: true, + check: CheckFlag::None, reload: true, lock: Some(PathBuf::from("lock.json")), lock_write: true, @@ -3833,7 +3868,23 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), }), - no_check: true, + check: CheckFlag::None, + ..Flags::default() + } + ); + } + + #[test] + fn no_check_remote() { + let r = + flags_from_vec(svec!["deno", "run", "--no-check=remote", "script.ts"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run(RunFlags { + script: "script.ts".to_string(), + }), + check: CheckFlag::Local, ..Flags::default() } ); @@ -4406,7 +4457,7 @@ mod tests { import_map_path: Some("import_map.json".to_string()), no_remote: true, config_path: Some("tsconfig.json".to_string()), - no_check: true, + check: CheckFlag::None, reload: true, lock: Some(PathBuf::from("lock.json")), lock_write: true, diff --git a/cli/main.rs b/cli/main.rs index 3d8a80cee2b2fe..8878fb57a1373c 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -40,6 +40,7 @@ use crate::file_fetcher::File; use crate::file_watcher::ResolutionResult; use crate::flags::BundleFlags; use crate::flags::CacheFlags; +use crate::flags::CheckFlag; use crate::flags::CompileFlags; use crate::flags::CompletionsFlags; use crate::flags::CoverageFlags; @@ -706,7 +707,7 @@ async fn create_graph_and_maybe_check( // locker. emit::lock(graph.as_ref()); - if !ps.flags.no_check { + if ps.flags.check != CheckFlag::None { graph.valid_types_only().map_err(emit::GraphError::from)?; let lib = if ps.flags.unstable { emit::TypeLib::UnstableDenoWindow @@ -731,6 +732,7 @@ async fn create_graph_and_maybe_check( graph.clone(), &mut cache, emit::CheckOptions { + check: ps.flags.check.clone(), debug, emit_with_diagnostics: false, maybe_config_specifier, @@ -759,7 +761,7 @@ fn bundle_module_graph( ps.maybe_config_file.as_ref(), None, )?; - if flags.no_check { + if flags.check == CheckFlag::None { if let Some(ignored_options) = maybe_ignored_options { eprintln!("{}", ignored_options); } diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index 0c3b48cd9d335c..d79a5fe31fd6ba 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -5,6 +5,7 @@ use crate::config_file::IgnoredCompilerOptions; use crate::diagnostics::Diagnostics; use crate::emit; use crate::errors::get_error_class_name; +use crate::flags; use crate::proc_state::ProcState; use crate::resolver::ImportMapResolver; use crate::resolver::JsxResolver; @@ -248,6 +249,7 @@ async fn op_emit( graph.clone(), cache.as_mut_cacher(), emit::CheckOptions { + check: flags::CheckFlag::All, debug, emit_with_diagnostics: true, maybe_config_specifier: None, @@ -268,6 +270,7 @@ async fn op_emit( graph.clone(), cache.as_mut_cacher(), emit::CheckOptions { + check: flags::CheckFlag::All, debug, emit_with_diagnostics: true, maybe_config_specifier: None, diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 97f24bc9085ac4..b62cdc464a1731 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -404,7 +404,7 @@ impl ProcState { }; if !reload_on_watch { let graph_data = self.graph_data.lock(); - if self.flags.no_check + if self.flags.check == flags::CheckFlag::None || roots.iter().all(|root| { graph_data .checked_libs_map @@ -452,7 +452,7 @@ impl ProcState { graph_data.modules.keys().cloned().collect() }; - let config_type = if self.flags.no_check { + let config_type = if self.flags.check == flags::CheckFlag::None { emit::ConfigType::Emit } else { emit::ConfigType::Check { @@ -483,7 +483,7 @@ impl ProcState { if let Some(ignored_options) = maybe_ignored_options { log::warn!("{}", ignored_options); } - let emit_result = if self.flags.no_check { + let emit_result = if self.flags.check == flags::CheckFlag::None { let options = emit::EmitOptions { ts_config, reload_exclusions, @@ -502,6 +502,7 @@ impl ProcState { .as_ref() .map(|cf| cf.specifier.clone()); let options = emit::CheckOptions { + check: self.flags.check.clone(), debug: self.flags.log_level == Some(log::Level::Debug), emit_with_diagnostics: false, maybe_config_specifier, @@ -599,7 +600,7 @@ impl ProcState { graph_data.check_if_prepared(&roots).unwrap()?; type_check_result?; - if !self.flags.no_check { + if self.flags.check != flags::CheckFlag::None { for specifier in specifiers.keys() { let checked_libs = graph_data .checked_libs_map diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 47041e499b89f9..33ca787d926904 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -920,6 +920,19 @@ itest!(no_check_decorators { output: "no_check_decorators.ts.out", }); +itest!(check_remote { + args: "run --quiet --reload no_check_remote.ts", + output: "no_check_remote.ts.disabled.out", + exit_code: 1, + http_server: true, +}); + +itest!(no_check_remote { + args: "run --quiet --reload --no-check=remote no_check_remote.ts", + output: "no_check_remote.ts.enabled.out", + http_server: true, +}); + itest!(runtime_decorators { args: "run --quiet --reload --no-check runtime_decorators.ts", output: "runtime_decorators.ts.out", diff --git a/cli/tests/testdata/no_check_remote.ts b/cli/tests/testdata/no_check_remote.ts new file mode 100644 index 00000000000000..2ae8c2692cc20c --- /dev/null +++ b/cli/tests/testdata/no_check_remote.ts @@ -0,0 +1,3 @@ +import * as a from "http://localhost:4545/subdir/type_error.ts"; + +console.log(a.a); diff --git a/cli/tests/testdata/no_check_remote.ts.disabled.out b/cli/tests/testdata/no_check_remote.ts.disabled.out new file mode 100644 index 00000000000000..3442646348e6ec --- /dev/null +++ b/cli/tests/testdata/no_check_remote.ts.disabled.out @@ -0,0 +1,4 @@ +error: TS2322 [ERROR]: Type '12' is not assignable to type '"a"'. +export const a: "a" = 12; + ^ + at http://localhost:4545/subdir/type_error.ts:1:14 diff --git a/cli/tests/testdata/no_check_remote.ts.enabled.out b/cli/tests/testdata/no_check_remote.ts.enabled.out new file mode 100644 index 00000000000000..48082f72f087ce --- /dev/null +++ b/cli/tests/testdata/no_check_remote.ts.enabled.out @@ -0,0 +1 @@ +12 diff --git a/cli/tests/testdata/subdir/type_error.ts b/cli/tests/testdata/subdir/type_error.ts new file mode 100644 index 00000000000000..cc3c1d29d82612 --- /dev/null +++ b/cli/tests/testdata/subdir/type_error.ts @@ -0,0 +1 @@ +export const a: "a" = 12; diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index 18d2d20e740c8b..a426fea0f213ae 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -1,4 +1,6 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::flags::CheckFlag; use crate::flags::Flags; use crate::fs_util::canonicalize_path; use deno_core::error::generic_error; @@ -267,8 +269,12 @@ pub fn install( } } - if flags.no_check { - executable_args.push("--no-check".to_string()); + // we should avoid a default branch here to ensure we continue to cover any + // changes to this flag. + match flags.check { + CheckFlag::All => (), + CheckFlag::None => executable_args.push("--no-check".to_string()), + CheckFlag::Local => executable_args.push("--no-check=remote".to_string()), } if flags.unstable { @@ -687,7 +693,7 @@ mod tests { Flags { allow_net: Some(vec![]), allow_read: Some(vec![]), - no_check: true, + check: CheckFlag::None, log_level: Some(Level::Error), ..Flags::default() }, diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index 5dd6f23ad22aee..d8ec14dc605562 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -1,6 +1,7 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use crate::deno_dir::DenoDir; +use crate::flags::CheckFlag; use crate::flags::DenoSubcommand; use crate::flags::Flags; use crate::flags::RunFlags; @@ -226,7 +227,7 @@ pub fn compile_to_runtime_flags( lock_write: false, lock: None, log_level: flags.log_level, - no_check: false, + check: CheckFlag::All, compat: flags.compat, unsafely_ignore_certificate_errors: flags .unsafely_ignore_certificate_errors,