diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-01-28 06:54:20 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-28 06:54:20 +1100 |
commit | 894ff6bb5881206c61fbd2abd3127e4f3001da95 (patch) | |
tree | ca0f9ffe13e1e9e482d5fc1cbe4808372bd2bd96 | |
parent | 2638aa03a5c3f7f4740ea7bee22127c01eb47a3c (diff) |
fix(cli): early abort before type checking on missing modules (#9285)
Fixes #9275
-rw-r--r-- | cli/module_graph.rs | 48 | ||||
-rw-r--r-- | cli/tests/error_missing_module_named_import.ts | 3 | ||||
-rw-r--r-- | cli/tests/error_missing_module_named_import.ts.out | 2 | ||||
-rw-r--r-- | cli/tests/integration_tests.rs | 6 | ||||
-rw-r--r-- | cli/tsc.rs | 16 |
5 files changed, 70 insertions, 5 deletions
diff --git a/cli/module_graph.rs b/cli/module_graph.rs index a1c408666..e7222289c 100644 --- a/cli/module_graph.rs +++ b/cli/module_graph.rs @@ -791,6 +791,7 @@ impl Graph { /// Type check the module graph, corresponding to the options provided. pub fn check(self, options: CheckOptions) -> Result<ResultInfo, AnyError> { + self.validate()?; let mut config = TsConfig::new(json!({ "allowJs": true, // TODO(@kitsonk) is this really needed? @@ -1692,6 +1693,53 @@ impl Graph { stats, }) } + + /// Validate that the module graph is "valid" in that there are not module + /// slots that have errorred that should be available to be able to statically + /// analyze. In certain situations, we can spin up tsc with an "invalid" + /// graph. + fn validate(&self) -> Result<(), AnyError> { + fn validate_module<F>( + specifier: &ModuleSpecifier, + seen: &mut HashSet<ModuleSpecifier>, + get_module: &F, + ) -> Result<(), AnyError> + where + F: Fn(&ModuleSpecifier) -> ModuleSlot, + { + if seen.contains(specifier) { + return Ok(()); + } + seen.insert(specifier.clone()); + match get_module(specifier) { + ModuleSlot::Err(err) => Err(anyhow!(err.to_string())), + ModuleSlot::Module(module) => { + for (_, dep) in module.dependencies.iter() { + // a dynamic import should be skipped, because while it might not + // be available to statically analyze, it might be available at + // runtime. + if !dep.is_dynamic { + if let Some(code_specifier) = &dep.maybe_code { + validate_module(code_specifier, seen, get_module)?; + } + if let Some(type_specifier) = &dep.maybe_type { + validate_module(type_specifier, seen, get_module)?; + } + } + } + Ok(()) + }, + ModuleSlot::None => Err(custom_error("NotFound", format!("The specifier \"{}\" is unexpectedly not in the module graph.", specifier))), + ModuleSlot::Pending => Err(custom_error("InvalidState", format!("The specifier \"{}\" is in an unexpected state in the module graph.", specifier))), + } + } + + let mut seen = HashSet::new(); + for specifier in &self.roots { + validate_module(specifier, &mut seen, &|s| self.get_module(s).clone())?; + } + Ok(()) + } } impl swc_bundler::Resolve for Graph { diff --git a/cli/tests/error_missing_module_named_import.ts b/cli/tests/error_missing_module_named_import.ts new file mode 100644 index 000000000..9eb5239ff --- /dev/null +++ b/cli/tests/error_missing_module_named_import.ts @@ -0,0 +1,3 @@ +import { a } from "./does_not_exist.js"; + +console.log(a); diff --git a/cli/tests/error_missing_module_named_import.ts.out b/cli/tests/error_missing_module_named_import.ts.out new file mode 100644 index 000000000..4da7c2885 --- /dev/null +++ b/cli/tests/error_missing_module_named_import.ts.out @@ -0,0 +1,2 @@ +error: Cannot resolve module "[WILDCARD]cli/tests/does_not_exist.js" from "[WILDCARD]cli/tests/error_missing_module_named_import.ts". + at [WILDCARD]cli/tests/error_missing_module_named_import.ts:1:0 diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 5830f8f20..1e64f3cdd 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -3004,6 +3004,12 @@ itest!(error_025_tab_indent { exit_code: 1, }); +itest!(error_missing_module_named_import { + args: "run --reload error_missing_module_named_import.ts", + output: "error_missing_module_named_import.ts.out", + exit_code: 1, +}); + itest!(error_no_check { args: "run --reload --no-check error_no_check.ts", output: "error_no_check.ts.out", diff --git a/cli/tsc.rs b/cli/tsc.rs index 601e4c140..26b0c68fb 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -287,10 +287,10 @@ fn load(state: &mut State, args: Value) -> Result<Value, AnyError> { state.maybe_tsbuildinfo.clone() // in certain situations we return a "blank" module to tsc and we need to // handle the request for that module here. - } else if &v.specifier == "deno:///none.d.ts" { + } else if &v.specifier == "deno:///missing_dependency.d.ts" { hash = Some("1".to_string()); - media_type = MediaType::TypeScript; - Some("declare var a: any;\nexport = a;\n".to_string()) + media_type = MediaType::Dts; + Some("declare const __: any;\nexport = __;\n".to_string()) } else if v.specifier.starts_with("asset:///") { let name = v.specifier.replace("asset:///", ""); let maybe_source = get_asset(&name).map(|s| s.to_string()); @@ -383,7 +383,10 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> { // the source file in the graph, so we will return a fake module to // make tsc happy. Err(_) => { - resolved.push(("deno:///none.d.ts".to_string(), ".d.ts".to_string())); + resolved.push(( + "deno:///missing_dependency.d.ts".to_string(), + ".d.ts".to_string(), + )); } } } @@ -816,7 +819,10 @@ mod tests { &mut state, json!({ "base": "https://deno.land/x/a.ts", "specifiers": [ "./bad.ts" ]}), ).expect("should have not errored"); - assert_eq!(actual, json!([["deno:///none.d.ts", ".d.ts"]])); + assert_eq!( + actual, + json!([["deno:///missing_dependency.d.ts", ".d.ts"]]) + ); } #[tokio::test] |