// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use crate::checksum; use crate::doc::Location; use crate::file_fetcher::map_file_extension; use crate::file_fetcher::SourceFile; use crate::file_fetcher::SourceFileFetcher; use crate::import_map::ImportMap; use crate::msg::MediaType; use crate::op_error::OpError; use crate::permissions::Permissions; use crate::tsc::pre_process_file; use crate::tsc::ImportDesc; use crate::tsc::TsReferenceDesc; use crate::tsc::TsReferenceKind; use crate::tsc::AVAILABLE_LIBS; use crate::version; use deno_core::ErrBox; use deno_core::ModuleSpecifier; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; use futures::Future; use futures::FutureExt; use serde::Serialize; use serde::Serializer; use std::collections::HashMap; use std::collections::HashSet; use std::path::PathBuf; use std::pin::Pin; // TODO(bartlomieju): it'd be great if this function returned // more structured data and possibly format the same as TS diagnostics. /// Decorate error with location of import that caused the error. fn err_with_location(e: ErrBox, maybe_location: Option<&Location>) -> ErrBox { if let Some(location) = maybe_location { let location_str = format!( "\nImported from \"{}:{}\"", location.filename, location.line ); let err_str = e.to_string(); OpError::other(format!("{}{}", err_str, location_str)).into() } else { e } } /// Disallow http:// imports from modules loaded over https:// fn validate_no_downgrade( module_specifier: &ModuleSpecifier, maybe_referrer: Option<&ModuleSpecifier>, maybe_location: Option<&Location>, ) -> Result<(), ErrBox> { if let Some(referrer) = maybe_referrer.as_ref() { if let "https" = referrer.as_url().scheme() { if let "http" = module_specifier.as_url().scheme() { let e = OpError::permission_denied( "Modules loaded over https:// are not allowed to import modules over http://".to_string() ); return Err(err_with_location(e.into(), maybe_location)); }; }; }; Ok(()) } /// Verify that remote file doesn't try to statically import local file. fn validate_no_file_from_remote( module_specifier: &ModuleSpecifier, maybe_referrer: Option<&ModuleSpecifier>, maybe_location: Option<&Location>, ) -> Result<(), ErrBox> { if let Some(referrer) = maybe_referrer.as_ref() { let referrer_url = referrer.as_url(); match referrer_url.scheme() { "http" | "https" => { let specifier_url = module_specifier.as_url(); match specifier_url.scheme() { "http" | "https" => {} _ => { let e = OpError::permission_denied( "Remote modules are not allowed to statically import local modules. Use dynamic import instead.".to_string() ); return Err(err_with_location(e.into(), maybe_location)); } } } _ => {} } } Ok(()) } // TODO(bartlomieju): handle imports/references in ambient contexts/TS modules // https://github.com/denoland/deno/issues/6133 fn resolve_imports_and_references( referrer: ModuleSpecifier, maybe_import_map: Option<&ImportMap>, import_descs: Vec, ref_descs: Vec, ) -> Result<(Vec, Vec), ErrBox> { let mut imports = vec![]; let mut references = vec![]; for import_desc in import_descs { let maybe_resolved = if let Some(import_map) = maybe_import_map.as_ref() { import_map.resolve(&import_desc.specifier, &referrer.to_string())? } else { None }; let resolved_specifier = if let Some(resolved) = maybe_resolved { resolved } else { ModuleSpecifier::resolve_import( &import_desc.specifier, &referrer.to_string(), )? }; let resolved_type_directive = if let Some(types_specifier) = import_desc.deno_types.as_ref() { Some(ModuleSpecifier::resolve_import( &types_specifier, &referrer.to_string(), )?) } else { None }; let import_descriptor = ImportDescriptor { specifier: import_desc.specifier.to_string(), resolved_specifier, type_directive: import_desc.deno_types.clone(), resolved_type_directive, location: import_desc.location, }; imports.push(import_descriptor); } for ref_desc in ref_descs { if AVAILABLE_LIBS.contains(&ref_desc.specifier.as_str()) { continue; } let resolved_specifier = ModuleSpecifier::resolve_import( &ref_desc.specifier, &referrer.to_string(), )?; let reference_descriptor = ReferenceDescriptor { specifier: ref_desc.specifier.to_string(), resolved_specifier, kind: ref_desc.kind, location: ref_desc.location, }; references.push(reference_descriptor); } Ok((imports, references)) } fn serialize_module_specifier( spec: &ModuleSpecifier, s: S, ) -> Result where S: Serializer, { s.serialize_str(&spec.to_string()) } fn serialize_option_module_specifier( maybe_spec: &Option, s: S, ) -> Result where S: Serializer, { if let Some(spec) = maybe_spec { serialize_module_specifier(spec, s) } else { s.serialize_none() } } const SUPPORTED_MEDIA_TYPES: [MediaType; 4] = [ MediaType::JavaScript, MediaType::TypeScript, MediaType::JSX, MediaType::TSX, ]; pub type ModuleGraph = HashMap; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ImportDescriptor { pub specifier: String, #[serde(serialize_with = "serialize_module_specifier")] pub resolved_specifier: ModuleSpecifier, // These two fields are for support of @deno-types directive // directly prepending import statement pub type_directive: Option, #[serde(serialize_with = "serialize_option_module_specifier")] pub resolved_type_directive: Option, #[serde(skip)] pub location: Location, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ReferenceDescriptor { pub specifier: String, #[serde(serialize_with = "serialize_module_specifier")] pub resolved_specifier: ModuleSpecifier, #[serde(skip)] pub kind: TsReferenceKind, #[serde(skip)] pub location: Location, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ModuleGraphFile { pub specifier: String, pub url: String, pub redirect: Option, pub filename: String, pub version_hash: String, pub imports: Vec, pub referenced_files: Vec, pub lib_directives: Vec, pub types_directives: Vec, pub type_headers: Vec, pub media_type: MediaType, pub source_code: String, } type SourceFileFuture = Pin>>>; pub struct ModuleGraphLoader { permissions: Permissions, file_fetcher: SourceFileFetcher, maybe_import_map: Option, pending_downloads: FuturesUnordered, has_downloaded: HashSet, graph: ModuleGraph, is_dyn_import: bool, analyze_dynamic_imports: bool, } impl ModuleGraphLoader { pub fn new( file_fetcher: SourceFileFetcher, maybe_import_map: Option, permissions: Permissions, is_dyn_import: bool, analyze_dynamic_imports: bool, ) -> Self { Self { file_fetcher, permissions, maybe_import_map, pending_downloads: FuturesUnordered::new(), has_downloaded: HashSet::new(), graph: ModuleGraph::new(), is_dyn_import, analyze_dynamic_imports, } } /// This method is used to add specified module and all of its /// dependencies to the graph. /// /// It resolves when all dependent modules have been fetched and analyzed. /// /// This method can be called multiple times. pub async fn add_to_graph( &mut self, specifier: &ModuleSpecifier, maybe_referrer: Option, ) -> Result<(), ErrBox> { self.download_module(specifier.clone(), maybe_referrer, None)?; loop { let (specifier, source_file) = self.pending_downloads.next().await.unwrap()?; self.visit_module(&specifier, source_file)?; if self.pending_downloads.is_empty() { break; } } Ok(()) } /// This method is used to create a graph from in-memory files stored in /// a hash map. Useful for creating module graph for code received from /// the runtime. pub fn build_local_graph( &mut self, _root_name: &str, source_map: &HashMap, ) -> Result<(), ErrBox> { for (spec, source_code) in source_map.iter() { self.visit_memory_module(spec.to_string(), source_code.to_string())?; } Ok(()) } /// Consumes the loader and returns created graph. pub fn get_graph(self) -> ModuleGraph { self.graph } fn visit_memory_module( &mut self, specifier: String, source_code: String, ) -> Result<(), ErrBox> { let mut referenced_files = vec![]; let mut lib_directives = vec![]; let mut types_directives = vec![]; // FIXME(bartlomieju): // The resolveModules op only handles fully qualified URLs for referrer. // However we will have cases where referrer is "/foo.ts". We add this dummy // prefix "memory://" in order to use resolution logic. let module_specifier = if let Ok(spec) = ModuleSpecifier::resolve_url(&specifier) { spec } else { ModuleSpecifier::resolve_url(&format!("memory://{}", specifier))? }; let (raw_imports, raw_references) = pre_process_file( &module_specifier.to_string(), map_file_extension(&PathBuf::from(&specifier)), &source_code, self.analyze_dynamic_imports, )?; let (imports, references) = resolve_imports_and_references( module_specifier.clone(), self.maybe_import_map.as_ref(), raw_imports, raw_references, )?; for ref_descriptor in references { match ref_descriptor.kind { TsReferenceKind::Lib => { lib_directives.push(ref_descriptor); } TsReferenceKind::Types => { types_directives.push(ref_descriptor); } TsReferenceKind::Path => { referenced_files.push(ref_descriptor); } } } self.graph.insert( module_specifier.to_string(), ModuleGraphFile { specifier: specifier.to_string(), url: specifier.to_string(), redirect: None, version_hash: "".to_string(), media_type: map_file_extension(&PathBuf::from(specifier.clone())), filename: specifier, source_code, imports, referenced_files, lib_directives, types_directives, type_headers: vec![], }, ); Ok(()) } // TODO(bartlomieju): decorate errors with import location in the source code // https://github.com/denoland/deno/issues/5080 fn download_module( &mut self, module_specifier: ModuleSpecifier, maybe_referrer: Option, maybe_location: Option, ) -> Result<(), ErrBox> { if self.has_downloaded.contains(&module_specifier) { return Ok(()); } validate_no_downgrade( &module_specifier, maybe_referrer.as_ref(), maybe_location.as_ref(), )?; if !self.is_dyn_import { validate_no_file_from_remote( &module_specifier, maybe_referrer.as_ref(), maybe_location.as_ref(), )?; } self.has_downloaded.insert(module_specifier.clone()); let spec = module_specifier; let file_fetcher = self.file_fetcher.clone(); let perms = self.permissions.clone(); let load_future = async move { let spec_ = spec.clone(); let source_file = file_fetcher .fetch_source_file(&spec_, maybe_referrer, perms) .await .map_err(|e| err_with_location(e, maybe_location.as_ref()))?; Ok((spec_.clone(), source_file)) } .boxed_local(); self.pending_downloads.push(load_future); Ok(()) } fn visit_module( &mut self, module_specifier: &ModuleSpecifier, source_file: SourceFile, ) -> Result<(), ErrBox> { let mut imports = vec![]; let mut referenced_files = vec![]; let mut lib_directives = vec![]; let mut types_directives = vec![]; let mut type_headers = vec![]; // IMPORTANT: source_file.url might be different than requested // module_specifier because of HTTP redirects. In such // situation we add an "empty" ModuleGraphFile with 'redirect' // field set that will be later used in TS worker when building // map of available source file. It will perform substitution // for proper URL point to redirect target. if module_specifier.as_url() != &source_file.url { // TODO(bartlomieju): refactor, this is a band-aid self.graph.insert( module_specifier.to_string(), ModuleGraphFile { specifier: module_specifier.to_string(), url: module_specifier.to_string(), redirect: Some(source_file.url.to_string()), filename: source_file.filename.to_str().unwrap().to_string(), version_hash: checksum::gen(&[ &source_file.source_code.as_bytes(), version::DENO.as_bytes(), ]), media_type: source_file.media_type, source_code: "".to_string(), imports: vec![], referenced_files: vec![], lib_directives: vec![], types_directives: vec![], type_headers: vec![], }, ); } let module_specifier = ModuleSpecifier::from(source_file.url.clone()); let version_hash = checksum::gen(&[ &source_file.source_code.as_bytes(), version::DENO.as_bytes(), ]); let source_code = source_file.source_code.to_string()?; if SUPPORTED_MEDIA_TYPES.contains(&source_file.media_type) { if let Some(types_specifier) = source_file.types_header { let type_header = ReferenceDescriptor { specifier: types_specifier.to_string(), resolved_specifier: ModuleSpecifier::resolve_import( &types_specifier, &module_specifier.to_string(), )?, kind: TsReferenceKind::Types, // TODO(bartlomieju): location is not needed in here and constructing // location by hand is bad location: Location { filename: module_specifier.to_string(), line: 0, col: 0, }, }; self.download_module( type_header.resolved_specifier.clone(), Some(module_specifier.clone()), None, )?; type_headers.push(type_header); } let (raw_imports, raw_refs) = pre_process_file( &module_specifier.to_string(), source_file.media_type, &source_code, self.analyze_dynamic_imports, )?; let (imports_, references) = resolve_imports_and_references( module_specifier.clone(), self.maybe_import_map.as_ref(), raw_imports, raw_refs, )?; for import_descriptor in imports_ { self.download_module( import_descriptor.resolved_specifier.clone(), Some(module_specifier.clone()), Some(import_descriptor.location.clone()), )?; if let Some(type_dir_url) = import_descriptor.resolved_type_directive.as_ref() { self.download_module( type_dir_url.clone(), Some(module_specifier.clone()), Some(import_descriptor.location.clone()), )?; } imports.push(import_descriptor); } for ref_descriptor in references { self.download_module( ref_descriptor.resolved_specifier.clone(), Some(module_specifier.clone()), Some(ref_descriptor.location.clone()), )?; match ref_descriptor.kind { TsReferenceKind::Lib => { lib_directives.push(ref_descriptor); } TsReferenceKind::Types => { types_directives.push(ref_descriptor); } TsReferenceKind::Path => { referenced_files.push(ref_descriptor); } } } } self.graph.insert( module_specifier.to_string(), ModuleGraphFile { specifier: module_specifier.to_string(), url: module_specifier.to_string(), redirect: None, version_hash, filename: source_file.filename.to_str().unwrap().to_string(), media_type: source_file.media_type, source_code, imports, referenced_files, lib_directives, types_directives, type_headers, }, ); Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::GlobalState; async fn build_graph( module_specifier: &ModuleSpecifier, ) -> Result { let global_state = GlobalState::new(Default::default()).unwrap(); let mut graph_loader = ModuleGraphLoader::new( global_state.file_fetcher.clone(), None, Permissions::allow_all(), false, false, ); graph_loader.add_to_graph(&module_specifier, None).await?; Ok(graph_loader.get_graph()) } // TODO(bartlomieju): this test is flaky, because it's using 019_media_types // file, reenable once Python server is replaced with Rust one. #[ignore] #[tokio::test] async fn source_graph_fetch() { let http_server_guard = test_util::http_server(); let module_specifier = ModuleSpecifier::resolve_url_or_path( "http://localhost:4545/cli/tests/019_media_types.ts", ) .unwrap(); let graph = build_graph(&module_specifier) .await .expect("Failed to build graph"); let a = graph .get("http://localhost:4545/cli/tests/019_media_types.ts") .unwrap(); assert!(graph.contains_key( "http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js" )); assert!(graph.contains_key( "http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts" )); assert!(graph.contains_key("http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts")); assert!(graph.contains_key( "http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts" )); assert!(graph.contains_key("http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js")); assert!(graph.contains_key( "http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js" )); assert!(graph.contains_key( "http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js" )); assert!(graph.contains_key( "http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts" )); assert_eq!( serde_json::to_value(&a.imports).unwrap(), json!([ { "specifier": "http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts", "typeDirective": null, "resolvedTypeDirective": null, }, { "specifier": "http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts", "typeDirective": null, "resolvedTypeDirective": null, }, { "specifier": "http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts", "typeDirective": null, "resolvedTypeDirective": null, }, { "specifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts", "typeDirective": null, "resolvedTypeDirective": null, }, { "specifier": "http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js", "typeDirective": null, "resolvedTypeDirective": null, }, { "specifier": "http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js", "typeDirective": null, "resolvedTypeDirective": null, }, { "specifier": "http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js", "typeDirective": null, "resolvedTypeDirective": null, }, { "specifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js", "typeDirective": null, "resolvedTypeDirective": null, }, ]) ); drop(http_server_guard); } #[tokio::test] async fn source_graph_type_references() { let http_server_guard = test_util::http_server(); let module_specifier = ModuleSpecifier::resolve_url_or_path( "http://localhost:4545/cli/tests/type_definitions.ts", ) .unwrap(); let graph = build_graph(&module_specifier) .await .expect("Failed to build graph"); eprintln!("json {:#?}", serde_json::to_value(&graph).unwrap()); let a = graph .get("http://localhost:4545/cli/tests/type_definitions.ts") .unwrap(); assert_eq!( serde_json::to_value(&a.imports).unwrap(), json!([ { "specifier": "./type_definitions/foo.js", "resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/foo.js", "typeDirective": "./type_definitions/foo.d.ts", "resolvedTypeDirective": "http://localhost:4545/cli/tests/type_definitions/foo.d.ts" }, { "specifier": "./type_definitions/fizz.js", "resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/fizz.js", "typeDirective": "./type_definitions/fizz.d.ts", "resolvedTypeDirective": "http://localhost:4545/cli/tests/type_definitions/fizz.d.ts" }, { "specifier": "./type_definitions/qat.ts", "resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/qat.ts", "typeDirective": null, "resolvedTypeDirective": null, }, ]) ); assert!(graph .contains_key("http://localhost:4545/cli/tests/type_definitions/foo.js")); assert!(graph.contains_key( "http://localhost:4545/cli/tests/type_definitions/foo.d.ts" )); assert!(graph.contains_key( "http://localhost:4545/cli/tests/type_definitions/fizz.js" )); assert!(graph.contains_key( "http://localhost:4545/cli/tests/type_definitions/fizz.d.ts" )); assert!(graph .contains_key("http://localhost:4545/cli/tests/type_definitions/qat.ts")); drop(http_server_guard); } #[tokio::test] async fn source_graph_type_references2() { let http_server_guard = test_util::http_server(); let module_specifier = ModuleSpecifier::resolve_url_or_path( "http://localhost:4545/cli/tests/type_directives_02.ts", ) .unwrap(); let graph = build_graph(&module_specifier) .await .expect("Failed to build graph"); eprintln!("{:#?}", serde_json::to_value(&graph).unwrap()); let a = graph .get("http://localhost:4545/cli/tests/type_directives_02.ts") .unwrap(); assert_eq!( serde_json::to_value(&a.imports).unwrap(), json!([ { "specifier": "./subdir/type_reference.js", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/type_reference.js", "typeDirective": null, "resolvedTypeDirective": null, } ]) ); assert!(graph.contains_key( "http://localhost:4545/cli/tests/subdir/type_reference.d.ts" )); let b = graph .get("http://localhost:4545/cli/tests/subdir/type_reference.js") .unwrap(); assert_eq!( serde_json::to_value(&b.types_directives).unwrap(), json!([ { "specifier": "./type_reference.d.ts", "resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/type_reference.d.ts", } ]) ); drop(http_server_guard); } #[tokio::test] async fn source_graph_type_references3() { let http_server_guard = test_util::http_server(); let module_specifier = ModuleSpecifier::resolve_url_or_path( "http://localhost:4545/cli/tests/type_directives_01.ts", ) .unwrap(); let graph = build_graph(&module_specifier) .await .expect("Failed to build graph"); let ts = graph .get("http://localhost:4545/cli/tests/type_directives_01.ts") .unwrap(); assert_eq!( serde_json::to_value(&ts.imports).unwrap(), json!([ { "specifier": "http://127.0.0.1:4545/xTypeScriptTypes.js", "resolvedSpecifier": "http://127.0.0.1:4545/xTypeScriptTypes.js", "typeDirective": null, "resolvedTypeDirective": null, } ]) ); let headers = graph .get("http://127.0.0.1:4545/xTypeScriptTypes.js") .unwrap(); assert_eq!( serde_json::to_value(&headers.type_headers).unwrap(), json!([ { "specifier": "./xTypeScriptTypes.d.ts", "resolvedSpecifier": "http://127.0.0.1:4545/xTypeScriptTypes.d.ts" } ]) ); drop(http_server_guard); } #[tokio::test] async fn source_graph_different_langs() { let http_server_guard = test_util::http_server(); // ModuleGraphLoader was mistakenly parsing this file as TSX // https://github.com/denoland/deno/issues/5867 let module_specifier = ModuleSpecifier::resolve_url_or_path( "http://localhost:4545/cli/tests/ts_with_generic.ts", ) .unwrap(); build_graph(&module_specifier) .await .expect("Failed to build graph"); drop(http_server_guard); } } // TODO(bartlomieju): use baseline tests from TSC to ensure // compatibility #[test] fn test_pre_process_file() { let source = r#" // This comment is placed to make sure that directives are parsed // even when they start on non-first line /// /// /// // @deno-types="./type_definitions/foo.d.ts" import { foo } from "./type_definitions/foo.js"; // @deno-types="./type_definitions/fizz.d.ts" import "./type_definitions/fizz.js"; /// import * as qat from "./type_definitions/qat.ts"; console.log(foo); console.log(fizz); console.log(qat.qat); "#; let (imports, references) = pre_process_file("some/file.ts", MediaType::TypeScript, source, true) .expect("Failed to parse"); assert_eq!( imports, vec![ ImportDesc { specifier: "./type_definitions/foo.js".to_string(), deno_types: Some("./type_definitions/foo.d.ts".to_string()), location: Location { filename: "some/file.ts".to_string(), line: 9, col: 0, }, }, ImportDesc { specifier: "./type_definitions/fizz.js".to_string(), deno_types: Some("./type_definitions/fizz.d.ts".to_string()), location: Location { filename: "some/file.ts".to_string(), line: 11, col: 0, }, }, ImportDesc { specifier: "./type_definitions/qat.ts".to_string(), deno_types: None, location: Location { filename: "some/file.ts".to_string(), line: 15, col: 0, }, }, ] ); // According to TS docs (https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) // directives that are not at the top of the file are ignored, so only // 3 references should be captured instead of 4. assert_eq!( references, vec![ TsReferenceDesc { specifier: "dom".to_string(), kind: TsReferenceKind::Lib, location: Location { filename: "some/file.ts".to_string(), line: 5, col: 0, }, }, TsReferenceDesc { specifier: "./type_reference.d.ts".to_string(), kind: TsReferenceKind::Types, location: Location { filename: "some/file.ts".to_string(), line: 6, col: 0, }, }, TsReferenceDesc { specifier: "./type_reference/dep.ts".to_string(), kind: TsReferenceKind::Path, location: Location { filename: "some/file.ts".to_string(), line: 7, col: 0, }, }, ] ); }