diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 362b029e9a..3e5460a1d3 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1112,19 +1112,29 @@ impl Default for LspTsConfig { } impl LspTsConfig { - pub fn new(config_file: Option<&ConfigFile>) -> Self { + pub fn new( + config_file: Option<&ConfigFile>, + import_map: Option<&ImportMap>, + ) -> Self { let mut ts_config = Self::default(); - if let Some(config_file) = config_file { - match config_file.to_compiler_options() { - Ok((value, maybe_ignored_options)) => { - ts_config.inner.merge(&value); - if let Some(ignored_options) = maybe_ignored_options { - lsp_warn!("{}", ignored_options); - } - } - Err(err) => lsp_warn!("{}", err), - } + match ts_config.inner.merge_tsconfig_from_config_file(config_file) { + Ok(Some(ignored_options)) => lsp_warn!("{}", ignored_options), + Err(err) => lsp_warn!("{}", err), + _ => {} } + let mut maybe_map_jsx_import_source = || { + let import_map = import_map?; + let referrer = &config_file?.specifier; + let compiler_options = ts_config.inner.0.as_object_mut()?; + let jsx_import_source = + compiler_options.get("jsxImportSource")?.as_str()?; + let jsx_import_source = + import_map.resolve(jsx_import_source, referrer).ok()?; + compiler_options + .insert("jsxImportSource".to_string(), json!(jsx_import_source)); + Some(()) + }; + maybe_map_jsx_import_source(); ts_config } } @@ -1267,7 +1277,6 @@ impl ConfigData { .unwrap_or_default(); let lint_rules = get_configured_rules(lint_options.rules.clone(), config_file.as_ref()); - let ts_config = LspTsConfig::new(config_file.as_ref()); let vendor_dir = config_file.as_ref().and_then(|c| c.vendor_dir_path()); // Load lockfile @@ -1436,6 +1445,7 @@ impl ConfigData { } } } + let ts_config = LspTsConfig::new(config_file.as_ref(), import_map.as_ref()); ConfigData { config_file: config_file.map(Arc::new), diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index d62744e039..48ca7355aa 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -28,6 +28,7 @@ use crate::lsp::documents::Documents; use crate::lsp::logging::lsp_warn; use crate::tsc; use crate::tsc::ResolveArgs; +use crate::tsc::MISSING_DEPENDENCY_SPECIFIER; use crate::util::path::relative_specifier; use crate::util::path::specifier_to_file_path; use crate::util::path::to_percent_decoded_str; @@ -4008,7 +4009,7 @@ fn op_load<'s>( .mark_with_args("tsc.op.op_load", specifier); let specifier = state.specifier_map.normalize(specifier)?; let maybe_load_response = - if specifier.as_str() == "internal:///missing_dependency.d.ts" { + if specifier.as_str() == MISSING_DEPENDENCY_SPECIFIER { None } else { let asset_or_document = state.get_asset_or_document(&specifier); @@ -4026,11 +4027,11 @@ fn op_load<'s>( } #[op2] -fn op_resolve<'s>( - scope: &'s mut v8::HandleScope, +#[serde] +fn op_resolve( state: &mut OpState, #[serde] args: ResolveArgs, -) -> Result, AnyError> { +) -> Result>, AnyError> { let state = state.borrow_mut::(); let mark = state.performance.mark_with_args("tsc.op.op_resolve", &args); let referrer = state.specifier_map.normalize(&args.base)?; @@ -4043,13 +4044,17 @@ fn op_resolve<'s>( ); resolved .into_iter() - .map(|o| { - o.map(|(s, mt)| { - ( - state.specifier_map.denormalize(&s), - mt.as_ts_extension().to_string(), - ) - }) + // Resolved `node:` specifier means the user doesn't have @types/node, + // resolve to stub. + .map(|o| match o.filter(|(s, _)| s.scheme() != "node") { + Some((s, mt)) => Some(( + state.specifier_map.denormalize(&s), + mt.as_ts_extension().to_string(), + )), + None => Some(( + MISSING_DEPENDENCY_SPECIFIER.to_string(), + MediaType::Dts.as_ts_extension().to_string(), + )), }) .collect() } @@ -4062,9 +4067,8 @@ fn op_resolve<'s>( } }; - let response = serde_v8::to_v8(scope, specifiers)?; state.performance.measure(mark); - Ok(response) + Ok(specifiers) } #[op2] @@ -4750,6 +4754,14 @@ mod tests { (ts_server, snapshot, cache) } + fn setup_op_state(state_snapshot: Arc) -> OpState { + let state = + State::new(state_snapshot, Default::default(), Default::default()); + let mut op_state = OpState::new(None); + op_state.put(state); + op_state + } + #[test] fn test_replace_links() { let actual = replace_links(r"test {@link http://deno.land/x/mod.ts} test"); @@ -5552,4 +5564,36 @@ mod tests { Some(false) ); } + + #[tokio::test] + async fn resolve_unknown_dependency_to_stub_module() { + let temp_dir = TempDir::new(); + let (_, snapshot, _) = setup( + &temp_dir, + json!({ + "target": "esnext", + "module": "esnext", + "lib": ["deno.ns", "deno.window"], + "noEmit": true, + }), + &[("file:///a.ts", "", 1, LanguageId::TypeScript)], + ) + .await; + let mut state = setup_op_state(snapshot); + let resolved = op_resolve::call( + &mut state, + ResolveArgs { + base: "file:///a.ts".to_string(), + specifiers: vec!["./b.ts".to_string()], + }, + ) + .unwrap(); + assert_eq!( + resolved, + vec![Some(( + MISSING_DEPENDENCY_SPECIFIER.to_string(), + MediaType::Dts.as_ts_extension().to_string() + ))] + ); + } } diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index 22e2a36417..c6beddf32a 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -725,10 +725,6 @@ delete Object.prototype.__proto__; if (item) { isCjsCache.add(item); const [resolvedFileName, extension] = item; - if (resolvedFileName.startsWith("node:")) { - // probably means the user doesn't have @types/node, so resolve to undefined - return undefined; - } return { resolvedFileName, extension, diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index 4961d1ed94..6f8035bec1 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -30,7 +30,6 @@ use deno_graph::GraphKind; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::ResolutionResolved; -use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolver; @@ -444,6 +443,9 @@ pub fn as_ts_script_kind(media_type: MediaType) -> i32 { } } +pub const MISSING_DEPENDENCY_SPECIFIER: &str = + "internal:///missing_dependency.d.ts"; + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct LoadResponse { @@ -471,7 +473,7 @@ fn op_load( 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 load_specifier == "internal:///missing_dependency.d.ts" { + } else if load_specifier == MISSING_DEPENDENCY_SPECIFIER { None } else if let Some(name) = load_specifier.strip_prefix("asset:///") { let maybe_source = get_lazily_loaded_asset(name); @@ -575,14 +577,12 @@ fn op_resolve( )? }; for specifier in args.specifiers { - if let Some(module_name) = specifier.strip_prefix("node:") { - if deno_node::is_builtin_node_module(module_name) { - // return itself for node: specifiers because during type checking - // we resolve to the ambient modules in the @types/node package - // rather than deno_std/node - resolved.push((specifier, MediaType::Dts.to_string())); - continue; - } + if specifier.starts_with("node:") { + resolved.push(( + MISSING_DEPENDENCY_SPECIFIER.to_string(), + MediaType::Dts.to_string(), + )); + continue; } if specifier.starts_with("asset:///") { @@ -632,7 +632,7 @@ fn op_resolve( (specifier_str, media_type.as_ts_extension().into()) } None => ( - "internal:///missing_dependency.d.ts".to_string(), + MISSING_DEPENDENCY_SPECIFIER.to_string(), ".d.ts".to_string(), ), }; @@ -1159,7 +1159,7 @@ mod tests { .expect("should have not errored"); assert_eq!( actual, - vec![("internal:///missing_dependency.d.ts".into(), ".d.ts".into())] + vec![(MISSING_DEPENDENCY_SPECIFIER.into(), ".d.ts".into())] ); }