mirror of
https://github.com/denoland/deno.git
synced 2024-12-27 01:29:14 -05:00
963 lines
29 KiB
Rust
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(&[
|
|
&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(&[&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,
|
|
},
|
|
},
|
|
]
|
|
);
|
|
}
|