mirror of
https://github.com/denoland/deno.git
synced 2025-09-27 20:59:10 +00:00
refactor(cli): migrate runtime compile/bundle to new infrastructure (#8192)
Fixes #8060
This commit is contained in:
parent
3558769d46
commit
fdcc78500c
23 changed files with 852 additions and 2770 deletions
|
|
@ -1,968 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::ast::Location;
|
||||
use crate::checksum;
|
||||
use crate::file_fetcher::SourceFile;
|
||||
use crate::file_fetcher::SourceFileFetcher;
|
||||
use crate::import_map::ImportMap;
|
||||
use crate::media_type::MediaType;
|
||||
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::error::custom_error;
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::stream::FuturesUnordered;
|
||||
use deno_core::futures::stream::StreamExt;
|
||||
use deno_core::futures::Future;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
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: AnyError,
|
||||
maybe_location: Option<&Location>,
|
||||
) -> AnyError {
|
||||
if let Some(location) = maybe_location {
|
||||
let location_str = format!(
|
||||
"\nImported from \"{}:{}\"",
|
||||
location.filename, location.line
|
||||
);
|
||||
let err_str = e.to_string();
|
||||
generic_error(format!("{}{}", err_str, location_str))
|
||||
} 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<(), AnyError> {
|
||||
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 = custom_error("PermissionDenied",
|
||||
"Modules loaded over https:// are not allowed to import modules over http://"
|
||||
);
|
||||
return Err(err_with_location(e, 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<(), AnyError> {
|
||||
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 = custom_error(
|
||||
"PermissionDenied",
|
||||
"Remote modules are not allowed to statically import local \
|
||||
modules. Use dynamic import instead.",
|
||||
);
|
||||
return Err(err_with_location(e, 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>), AnyError> {
|
||||
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(Clone, Debug, Serialize, PartialEq)]
|
||||
#[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), AnyError>>>,
|
||||
>;
|
||||
|
||||
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<(), AnyError> {
|
||||
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<(), AnyError> {
|
||||
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<(), AnyError> {
|
||||
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(),
|
||||
MediaType::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: MediaType::from(&specifier),
|
||||
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<(), AnyError> {
|
||||
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<(), AnyError> {
|
||||
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.clone();
|
||||
|
||||
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::program_state::ProgramState;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
|
||||
async fn build_graph(
|
||||
module_specifier: &ModuleSpecifier,
|
||||
) -> Result<ModuleGraph, AnyError> {
|
||||
let program_state = ProgramState::new(Default::default()).unwrap();
|
||||
let mut graph_loader = ModuleGraphLoader::new(
|
||||
program_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,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[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"));
|
||||
}
|
||||
|
||||
#[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",
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[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"
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
let file_specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("some/file.ts").unwrap();
|
||||
assert_eq!(
|
||||
references,
|
||||
vec![
|
||||
TsReferenceDesc {
|
||||
specifier: "dom".to_string(),
|
||||
kind: TsReferenceKind::Lib,
|
||||
location: Location {
|
||||
filename: file_specifier.to_string(),
|
||||
line: 5,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
TsReferenceDesc {
|
||||
specifier: "./type_reference.d.ts".to_string(),
|
||||
kind: TsReferenceKind::Types,
|
||||
location: Location {
|
||||
filename: file_specifier.to_string(),
|
||||
line: 6,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
TsReferenceDesc {
|
||||
specifier: "./type_reference/dep.ts".to_string(),
|
||||
kind: TsReferenceKind::Path,
|
||||
location: Location {
|
||||
filename: file_specifier.to_string(),
|
||||
line: 7,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue