diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2022-10-21 11:20:18 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-21 15:20:18 +0000 |
commit | bcfe279fba865763c87f9cd8d5a2d0b2cbf451be (patch) | |
tree | 68e4d1bc52e261df50279f9ecea14795d1c46f6c /cli/tsc.rs | |
parent | 0e1a71fec6fff5fe62d7e6b2bfffb7ab877d7b71 (diff) |
feat(unstable/npm): initial type checking of npm specifiers (#16332)
Diffstat (limited to 'cli/tsc.rs')
-rw-r--r-- | cli/tsc.rs | 132 |
1 files changed, 112 insertions, 20 deletions
diff --git a/cli/tsc.rs b/cli/tsc.rs index e9800d9d2..90ae7334c 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -4,6 +4,12 @@ use crate::args::TsConfig; use crate::diagnostics::Diagnostics; use crate::graph_util::GraphData; use crate::graph_util::ModuleEntry; +use crate::node; +use crate::node::node_resolve_npm_reference; +use crate::node::NodeResolution; +use crate::node::NodeResolutionMode; +use crate::npm::NpmPackageReference; +use crate::npm::NpmPackageResolver; use deno_ast::MediaType; use deno_core::anyhow::anyhow; @@ -28,6 +34,7 @@ use deno_core::RuntimeOptions; use deno_core::Snapshot; use deno_graph::Resolved; use once_cell::sync::Lazy; +use std::borrow::Cow; use std::collections::HashMap; use std::fmt; use std::path::PathBuf; @@ -171,7 +178,7 @@ fn get_maybe_hash( } /// Hash the URL so it can be sent to `tsc` in a supportable way -fn hash_url(specifier: &ModuleSpecifier, media_type: &MediaType) -> String { +fn hash_url(specifier: &ModuleSpecifier, media_type: MediaType) -> String { let hash = crate::checksum::gen(&[specifier.path().as_bytes()]); format!( "{}:///{}{}", @@ -187,7 +194,7 @@ fn hash_url(specifier: &ModuleSpecifier, media_type: &MediaType) -> String { /// think a `.js` version exists, when it doesn't. fn maybe_remap_specifier( specifier: &ModuleSpecifier, - media_type: &MediaType, + media_type: MediaType, ) -> Option<String> { let path = if specifier.scheme() == "file" { if let Ok(path) = specifier.to_file_path() { @@ -279,6 +286,7 @@ pub struct Request { pub graph_data: Arc<RwLock<GraphData>>, pub hash_data: Vec<Vec<u8>>, pub maybe_config_specifier: Option<ModuleSpecifier>, + pub maybe_npm_resolver: Option<NpmPackageResolver>, pub maybe_tsbuildinfo: Option<String>, /// A vector of strings that represent the root/entry point modules for the /// program. @@ -295,13 +303,14 @@ pub struct Response { pub stats: Stats, } -#[derive(Debug)] +#[derive(Debug, Default)] struct State { hash_data: Vec<Vec<u8>>, graph_data: Arc<RwLock<GraphData>>, maybe_config_specifier: Option<ModuleSpecifier>, maybe_tsbuildinfo: Option<String>, maybe_response: Option<RespondArgs>, + maybe_npm_resolver: Option<NpmPackageResolver>, remapped_specifiers: HashMap<String, ModuleSpecifier>, root_map: HashMap<String, ModuleSpecifier>, } @@ -311,6 +320,7 @@ impl State { graph_data: Arc<RwLock<GraphData>>, hash_data: Vec<Vec<u8>>, maybe_config_specifier: Option<ModuleSpecifier>, + maybe_npm_resolver: Option<NpmPackageResolver>, maybe_tsbuildinfo: Option<String>, root_map: HashMap<String, ModuleSpecifier>, remapped_specifiers: HashMap<String, ModuleSpecifier>, @@ -319,6 +329,7 @@ impl State { hash_data, graph_data, maybe_config_specifier, + maybe_npm_resolver, maybe_tsbuildinfo, maybe_response: None, remapped_specifiers, @@ -417,7 +428,7 @@ struct LoadArgs { specifier: String, } -pub fn as_ts_script_kind(media_type: &MediaType) -> i32 { +pub fn as_ts_script_kind(media_type: MediaType) -> i32 { match media_type { MediaType::JavaScript => 1, MediaType::Jsx => 2, @@ -431,7 +442,10 @@ pub fn as_ts_script_kind(media_type: &MediaType) -> i32 { MediaType::Dcts => 3, MediaType::Tsx => 4, MediaType::Json => 6, - _ => 0, + MediaType::SourceMap + | MediaType::TsBuildInfo + | MediaType::Wasm + | MediaType::Unknown => 0, } } @@ -446,19 +460,19 @@ fn op_load(state: &mut OpState, args: Value) -> Result<Value, AnyError> { let mut media_type = MediaType::Unknown; let graph_data = state.graph_data.read(); let data = if &v.specifier == "deno:///.tsbuildinfo" { - state.maybe_tsbuildinfo.as_deref() + state.maybe_tsbuildinfo.as_deref().map(Cow::Borrowed) // in certain situations we return a "blank" module to tsc and we need to // handle the request for that module here. } else if &v.specifier == "deno:///missing_dependency.d.ts" { hash = Some("1".to_string()); media_type = MediaType::Dts; - Some("declare const __: any;\nexport = __;\n") + Some(Cow::Borrowed("declare const __: any;\nexport = __;\n")) } else if v.specifier.starts_with("asset:///") { let name = v.specifier.replace("asset:///", ""); let maybe_source = get_asset(&name); hash = get_maybe_hash(maybe_source, &state.hash_data); media_type = MediaType::from(&v.specifier); - maybe_source + maybe_source.map(Cow::Borrowed) } else { let specifier = if let Some(remapped_specifier) = state.remapped_specifiers.get(&v.specifier) @@ -477,18 +491,31 @@ fn op_load(state: &mut OpState, args: Value) -> Result<Value, AnyError> { graph_data.get(&graph_data.follow_redirect(&specifier)) { media_type = *mt; - Some(code as &str) + Some(Cow::Borrowed(code as &str)) + } else if state + .maybe_npm_resolver + .as_ref() + .map(|resolver| resolver.in_npm_package(&specifier)) + .unwrap_or(false) + { + media_type = MediaType::from(&specifier); + let file_path = specifier.to_file_path().unwrap(); + let code = std::fs::read_to_string(&file_path) + .with_context(|| format!("Unable to load {}", file_path.display()))?; + Some(Cow::Owned(code)) } else { media_type = MediaType::Unknown; None }; - hash = get_maybe_hash(maybe_source, &state.hash_data); + hash = get_maybe_hash(maybe_source.as_deref(), &state.hash_data); maybe_source }; - Ok( - json!({ "data": data, "version": hash, "scriptKind": as_ts_script_kind(&media_type) }), - ) + Ok(json!({ + "data": data, + "version": hash, + "scriptKind": as_ts_script_kind(media_type), + })) } #[derive(Debug, Deserialize, Serialize)] @@ -550,17 +577,51 @@ fn op_resolve( let types = graph_data.follow_redirect(specifier); match graph_data.get(&types) { Some(ModuleEntry::Module { media_type, .. }) => { - Some((types, media_type)) + Some((types, *media_type)) } _ => None, } } - _ => Some((specifier, media_type)), + _ => Some((specifier, *media_type)), }, - _ => None, + _ => { + // handle npm:<package> urls + if let Ok(npm_ref) = + NpmPackageReference::from_specifier(&specifier) + { + if let Some(npm_resolver) = &state.maybe_npm_resolver { + Some(resolve_npm_package_reference_types( + &npm_ref, + npm_resolver, + )?) + } else { + None + } + } else { + None + } + } } } - _ => None, + _ => { + state.maybe_npm_resolver.as_ref().and_then(|npm_resolver| { + if npm_resolver.in_npm_package(&referrer) { + // we're in an npm package, so use node resolution + Some(NodeResolution::into_specifier_and_media_type( + node::node_resolve( + specifier, + &referrer, + node::NodeResolutionMode::Types, + npm_resolver, + ) + .ok() + .flatten(), + )) + } else { + None + } + }) + } }; let result = match maybe_result { Some((specifier, media_type)) => { @@ -599,6 +660,33 @@ fn op_resolve( Ok(resolved) } +pub fn resolve_npm_package_reference_types( + npm_ref: &NpmPackageReference, + npm_resolver: &NpmPackageResolver, +) -> Result<(ModuleSpecifier, MediaType), AnyError> { + let maybe_resolution = node_resolve_npm_reference( + npm_ref, + NodeResolutionMode::Types, + npm_resolver, + )?; + Ok(NodeResolution::into_specifier_and_media_type( + maybe_resolution, + )) +} + +#[op] +fn op_is_node_file(state: &mut OpState, path: String) -> bool { + let state = state.borrow::<State>(); + match ModuleSpecifier::parse(&path) { + Ok(specifier) => state + .maybe_npm_resolver + .as_ref() + .map(|r| r.in_npm_package(&specifier)) + .unwrap_or(false), + Err(_) => false, + } +} + #[derive(Debug, Deserialize, Eq, PartialEq)] struct RespondArgs { pub diagnostics: Diagnostics, @@ -629,13 +717,13 @@ pub fn exec(request: Request) -> Result<Response, AnyError> { .iter() .map(|(s, mt)| match s.scheme() { "data" | "blob" => { - let specifier_str = hash_url(s, mt); + let specifier_str = hash_url(s, *mt); remapped_specifiers.insert(specifier_str.clone(), s.clone()); specifier_str } _ => { let ext_media_type = get_tsc_media_type(s); - if mt != &ext_media_type { + if *mt != ext_media_type { let new_specifier = format!("{}{}", s, mt.as_ts_extension()); root_map.insert(new_specifier.clone(), s.clone()); new_specifier @@ -653,6 +741,7 @@ pub fn exec(request: Request) -> Result<Response, AnyError> { op_create_hash::decl(), op_emit::decl(), op_exists::decl(), + op_is_node_file::decl(), op_load::decl(), op_resolve::decl(), op_respond::decl(), @@ -662,6 +751,7 @@ pub fn exec(request: Request) -> Result<Response, AnyError> { request.graph_data.clone(), request.hash_data.clone(), request.maybe_config_specifier.clone(), + request.maybe_npm_resolver.clone(), request.maybe_tsbuildinfo.clone(), root_map.clone(), remapped_specifiers.clone(), @@ -771,6 +861,7 @@ mod tests { Arc::new(RwLock::new((&graph).into())), hash_data, None, + None, maybe_tsbuildinfo, HashMap::new(), HashMap::new(), @@ -820,6 +911,7 @@ mod tests { graph_data: Arc::new(RwLock::new((&graph).into())), hash_data, maybe_config_specifier: None, + maybe_npm_resolver: None, maybe_tsbuildinfo: None, root_names: vec![(specifier.clone(), MediaType::TypeScript)], }; @@ -865,7 +957,7 @@ mod tests { "data:application/javascript,console.log(\"Hello%20Deno\");", ) .unwrap(); - assert_eq!(hash_url(&specifier, &MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js"); + assert_eq!(hash_url(&specifier, MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js"); } #[test] |