1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-28 18:19:08 -05:00
denoland-deno/cli/module_graph.rs
Bartek Iwańczuk 3cbd1075c7
Incremental compilation for TypeScript (#6428)
This commit adds incremental compilation capabilities to internal TS compiler.

Instead of using "ts.createProgram()" API for compilation step (during deno 
startup), "ts.createIncrementalProgram()" API is used instead.

Thanks to TS' ".tsbuildinfo" file that already stores all necessary metadata
for compilation I was able to remove our own invention that is ".graph" file. 
".tsbuildinfo" file is stored alongside compiled source and is used to 
cache-bust outdated dependencies, facilitated by the "version" field. 
The value for "version" field is computed in Rust during loading of module 
graph and is basically a hash of the file contents.

Please keep in mind that incremental compilation is only used for initial 
compilation (or dynamic imports compilation) - bundling and runtime compiler 
APIs haven't been changed at all.

Due to problems with source map I changed compilation settings to inline 
source map (inlineSourceMap instead of sourceMap).
2020-06-24 16:59:12 +02:00

963 lines
29 KiB
Rust

// 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<ImportDesc>,
ref_descs: Vec<TsReferenceDesc>,
) -> Result<(Vec<ImportDescriptor>, Vec<ReferenceDescriptor>), 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<S>(
spec: &ModuleSpecifier,
s: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(&spec.to_string())
}
fn serialize_option_module_specifier<S>(
maybe_spec: &Option<ModuleSpecifier>,
s: S,
) -> Result<S::Ok, S::Error>
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<String, ModuleGraphFile>;
#[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<String>,
#[serde(serialize_with = "serialize_option_module_specifier")]
pub resolved_type_directive: Option<ModuleSpecifier>,
#[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<String>,
pub filename: String,
pub version_hash: String,
pub imports: Vec<ImportDescriptor>,
pub referenced_files: Vec<ReferenceDescriptor>,
pub lib_directives: Vec<ReferenceDescriptor>,
pub types_directives: Vec<ReferenceDescriptor>,
pub type_headers: Vec<ReferenceDescriptor>,
pub media_type: MediaType,
pub source_code: String,
}
type SourceFileFuture =
Pin<Box<dyn Future<Output = Result<(ModuleSpecifier, SourceFile), ErrBox>>>>;
pub struct ModuleGraphLoader {
permissions: Permissions,
file_fetcher: SourceFileFetcher,
maybe_import_map: Option<ImportMap>,
pending_downloads: FuturesUnordered<SourceFileFuture>,
has_downloaded: HashSet<ModuleSpecifier>,
graph: ModuleGraph,
is_dyn_import: bool,
analyze_dynamic_imports: bool,
}
impl ModuleGraphLoader {
pub fn new(
file_fetcher: SourceFileFetcher,
maybe_import_map: Option<ImportMap>,
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<ModuleSpecifier>,
) -> 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<String, String>,
) -> 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<ModuleSpecifier>,
maybe_location: Option<Location>,
) -> 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(vec![
&source_file.source_code,
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(vec![&source_file.source_code, version::DENO.as_bytes()]);
let source_code = String::from_utf8(source_file.source_code)?;
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<ModuleGraph, ErrBox> {
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
/// <reference lib="dom" />
/// <reference types="./type_reference.d.ts" />
/// <reference path="./type_reference/dep.ts" />
// @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";
/// <reference path="./type_reference/dep2.ts" />
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,
},
},
]
);
}