summaryrefslogtreecommitdiff
path: root/cli/tsc.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2022-10-21 11:20:18 -0400
committerGitHub <noreply@github.com>2022-10-21 15:20:18 +0000
commitbcfe279fba865763c87f9cd8d5a2d0b2cbf451be (patch)
tree68e4d1bc52e261df50279f9ecea14795d1c46f6c /cli/tsc.rs
parent0e1a71fec6fff5fe62d7e6b2bfffb7ab877d7b71 (diff)
feat(unstable/npm): initial type checking of npm specifiers (#16332)
Diffstat (limited to 'cli/tsc.rs')
-rw-r--r--cli/tsc.rs132
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]