summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-01-28 06:54:20 +1100
committerGitHub <noreply@github.com>2021-01-28 06:54:20 +1100
commit894ff6bb5881206c61fbd2abd3127e4f3001da95 (patch)
treeca0f9ffe13e1e9e482d5fc1cbe4808372bd2bd96
parent2638aa03a5c3f7f4740ea7bee22127c01eb47a3c (diff)
fix(cli): early abort before type checking on missing modules (#9285)
Fixes #9275
-rw-r--r--cli/module_graph.rs48
-rw-r--r--cli/tests/error_missing_module_named_import.ts3
-rw-r--r--cli/tests/error_missing_module_named_import.ts.out2
-rw-r--r--cli/tests/integration_tests.rs6
-rw-r--r--cli/tsc.rs16
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]