diff options
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/cache.rs | 2 | ||||
-rw-r--r-- | cli/lsp/diagnostics.rs | 29 | ||||
-rw-r--r-- | cli/lsp/documents.rs | 69 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 58 |
4 files changed, 118 insertions, 40 deletions
diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs index 47f6e44ba..23469a583 100644 --- a/cli/lsp/cache.rs +++ b/cli/lsp/cache.rs @@ -68,7 +68,7 @@ impl CacheMetadata { &self, specifier: &ModuleSpecifier, ) -> Option<Arc<HashMap<MetadataKey, String>>> { - if specifier.scheme() == "file" || specifier.scheme() == "npm" { + if matches!(specifier.scheme(), "file" | "npm" | "node") { return None; } let version = self diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index b4be63a55..4faff2fae 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -13,6 +13,7 @@ use super::tsc; use super::tsc::TsServer; use crate::args::LintOptions; +use crate::node; use crate::npm::NpmPackageReference; use crate::tools::lint::get_configured_rules; @@ -614,6 +615,8 @@ pub enum DenoDiagnostic { }, /// An error occurred when resolving the specifier string. ResolutionError(deno_graph::ResolutionError), + /// Invalid `node:` specifier. + InvalidNodeSpecifier(ModuleSpecifier), } impl DenoDiagnostic { @@ -641,6 +644,7 @@ impl DenoDiagnostic { }, ResolutionError::ResolverError { .. } => "resolver-error", }, + Self::InvalidNodeSpecifier(_) => "resolver-error", } } @@ -791,6 +795,7 @@ impl DenoDiagnostic { Self::NoLocal(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier), None), Self::Redirect { from, to} => (lsp::DiagnosticSeverity::INFORMATION, format!("The import of \"{}\" was redirected to \"{}\".", from, to), Some(json!({ "specifier": from, "redirect": to }))), Self::ResolutionError(err) => (lsp::DiagnosticSeverity::ERROR, err.to_string(), None), + Self::InvalidNodeSpecifier(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unknown Node built-in module: {}", specifier.path()), None), }; lsp::Diagnostic { range: *range, @@ -872,6 +877,30 @@ fn diagnose_resolved( ); } } + } else if let Some(module_name) = specifier.as_str().strip_prefix("node:") + { + if node::resolve_builtin_node_module(module_name).is_err() { + diagnostics.push( + DenoDiagnostic::InvalidNodeSpecifier(specifier.clone()) + .to_lsp_diagnostic(&range), + ); + } else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver { + // check that a @types/node package exists in the resolver + let types_node_ref = + NpmPackageReference::from_str("npm:@types/node").unwrap(); + if npm_resolver + .resolve_package_folder_from_deno_module(&types_node_ref.req) + .is_err() + { + diagnostics.push( + DenoDiagnostic::NoCacheNpm( + types_node_ref, + ModuleSpecifier::parse("npm:@types/node").unwrap(), + ) + .to_lsp_diagnostic(&range), + ); + } + } } else { // When the document is not available, it means that it cannot be found // in the cache or locally on the disk, so we want to issue a diagnostic diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index bc1e4a808..b99b64bfe 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -770,6 +770,9 @@ pub struct Documents { maybe_resolver: Option<CliResolver>, /// The npm package requirements. npm_reqs: Arc<HashSet<NpmPackageReq>>, + /// Gets if any document had a node: specifier such that a @types/node package + /// should be injected. + has_injected_types_node_package: bool, /// Resolves a specifier to its final redirected to specifier. specifier_resolver: Arc<SpecifierResolver>, } @@ -785,6 +788,7 @@ impl Documents { imports: Default::default(), maybe_resolver: None, npm_reqs: Default::default(), + has_injected_types_node_package: false, specifier_resolver: Arc::new(SpecifierResolver::new(location)), } } @@ -925,6 +929,12 @@ impl Documents { (*self.npm_reqs).clone() } + /// Returns if a @types/node package was injected into the npm + /// resolver based on the state of the documents. + pub fn has_injected_types_node_package(&self) -> bool { + self.has_injected_types_node_package + } + /// Return a document for the specifier. pub fn get(&self, original_specifier: &ModuleSpecifier) -> Option<Document> { let specifier = self.specifier_resolver.resolve(original_specifier)?; @@ -985,11 +995,15 @@ impl Documents { /// tsc when type checking. pub fn resolve( &self, - specifiers: &[String], - referrer: &ModuleSpecifier, + specifiers: Vec<String>, + referrer_doc: &AssetOrDocument, maybe_npm_resolver: Option<&NpmPackageResolver>, - ) -> Option<Vec<Option<(ModuleSpecifier, MediaType)>>> { - let dependencies = self.get(referrer)?.0.dependencies.clone(); + ) -> Vec<Option<(ModuleSpecifier, MediaType)>> { + let referrer = referrer_doc.specifier(); + let dependencies = match referrer_doc { + AssetOrDocument::Asset(_) => None, + AssetOrDocument::Document(doc) => Some(doc.0.dependencies.clone()), + }; let mut results = Vec::new(); for specifier in specifiers { if let Some(npm_resolver) = maybe_npm_resolver { @@ -997,7 +1011,7 @@ impl Documents { // we're in an npm package, so use node resolution results.push(Some(NodeResolution::into_specifier_and_media_type( node::node_resolve( - specifier, + &specifier, referrer, NodeResolutionMode::Types, npm_resolver, @@ -1009,15 +1023,28 @@ impl Documents { continue; } } - // handle npm:<package> urls + if let Some(module_name) = specifier.strip_prefix("node:") { + if crate::node::resolve_builtin_node_module(module_name).is_ok() { + // 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 + results.push(Some(( + ModuleSpecifier::parse(&specifier).unwrap(), + MediaType::Dts, + ))); + continue; + } + } if specifier.starts_with("asset:") { - if let Ok(specifier) = ModuleSpecifier::parse(specifier) { + if let Ok(specifier) = ModuleSpecifier::parse(&specifier) { let media_type = MediaType::from(&specifier); results.push(Some((specifier, media_type))); } else { results.push(None); } - } else if let Some(dep) = dependencies.deps.get(specifier) { + } else if let Some(dep) = + dependencies.as_ref().and_then(|d| d.deps.get(&specifier)) + { if let Resolved::Ok { specifier, .. } = &dep.maybe_type { results.push(self.resolve_dependency(specifier, maybe_npm_resolver)); } else if let Resolved::Ok { specifier, .. } = &dep.maybe_code { @@ -1026,12 +1053,12 @@ impl Documents { results.push(None); } } else if let Some(Resolved::Ok { specifier, .. }) = - self.resolve_imports_dependency(specifier) + self.resolve_imports_dependency(&specifier) { // clone here to avoid double borrow of self let specifier = specifier.clone(); results.push(self.resolve_dependency(&specifier, maybe_npm_resolver)); - } else if let Ok(npm_ref) = NpmPackageReference::from_str(specifier) { + } else if let Ok(npm_ref) = NpmPackageReference::from_str(&specifier) { results.push(maybe_npm_resolver.map(|npm_resolver| { NodeResolution::into_specifier_and_media_type( node_resolve_npm_reference( @@ -1048,7 +1075,7 @@ impl Documents { results.push(None); } } - Some(results) + results } /// Update the location of the on disk cache for the document store. @@ -1125,6 +1152,7 @@ impl Documents { analyzed_specifiers: HashSet<ModuleSpecifier>, pending_specifiers: VecDeque<ModuleSpecifier>, npm_reqs: HashSet<NpmPackageReq>, + has_node_builtin_specifier: bool, } impl DocAnalyzer { @@ -1148,7 +1176,11 @@ impl Documents { fn analyze_doc(&mut self, specifier: &ModuleSpecifier, doc: &Document) { self.analyzed_specifiers.insert(specifier.clone()); - for dependency in doc.dependencies().values() { + for (name, dependency) in doc.dependencies() { + if !self.has_node_builtin_specifier && name.starts_with("node:") { + self.has_node_builtin_specifier = true; + } + if let Some(dep) = dependency.get_code() { self.add(dep, specifier); } @@ -1185,8 +1217,19 @@ impl Documents { } } + let mut npm_reqs = doc_analyzer.npm_reqs; + // Ensure a @types/node package exists when any module uses a node: specifier. + // Unlike on the command line, here we just add @types/node to the npm package + // requirements since this won't end up in the lockfile. + self.has_injected_types_node_package = doc_analyzer + .has_node_builtin_specifier + && !npm_reqs.iter().any(|r| r.name == "@types/node"); + if self.has_injected_types_node_package { + npm_reqs.insert(NpmPackageReq::from_str("@types/node").unwrap()); + } + self.dependents_map = Arc::new(doc_analyzer.dependents_map); - self.npm_reqs = Arc::new(doc_analyzer.npm_reqs); + self.npm_reqs = Arc::new(npm_reqs); self.dirty = false; file_system_docs.dirty = false; } diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index ced8fb699..3619f529c 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -216,7 +216,7 @@ fn new_assets_map() -> Arc<Mutex<AssetsMap>> { let asset = AssetDocument::new(specifier.clone(), v); (specifier, asset) }) - .collect(); + .collect::<AssetsMap>(); Arc::new(Mutex::new(assets)) } @@ -2728,28 +2728,29 @@ fn op_resolve( let state = state.borrow_mut::<State>(); let mark = state.performance.mark("op_resolve", Some(&args)); let referrer = state.normalize_specifier(&args.base)?; - - let result = if let Some(resolved) = state.state_snapshot.documents.resolve( - &args.specifiers, - &referrer, - state.state_snapshot.maybe_npm_resolver.as_ref(), - ) { - Ok( - resolved - .into_iter() - .map(|o| { - o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string())) - }) - .collect(), - ) - } else { - Err(custom_error( + let result = match state.get_asset_or_document(&referrer) { + Some(referrer_doc) => { + let resolved = state.state_snapshot.documents.resolve( + args.specifiers, + &referrer_doc, + state.state_snapshot.maybe_npm_resolver.as_ref(), + ); + Ok( + resolved + .into_iter() + .map(|o| { + o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string())) + }) + .collect(), + ) + } + None => Err(custom_error( "NotFound", format!( "Error resolving. Referring specifier \"{}\" was not found.", args.base ), - )) + )), }; state.performance.measure(mark); @@ -2764,15 +2765,20 @@ fn op_respond(state: &mut OpState, args: Response) -> bool { } #[op] -fn op_script_names(state: &mut OpState) -> Vec<ModuleSpecifier> { +fn op_script_names(state: &mut OpState) -> Vec<String> { let state = state.borrow_mut::<State>(); - state - .state_snapshot - .documents - .documents(true, true) - .into_iter() - .map(|d| d.specifier().clone()) - .collect() + let documents = &state.state_snapshot.documents; + let open_docs = documents.documents(true, true); + + let mut result = Vec::with_capacity(open_docs.len() + 1); + + if documents.has_injected_types_node_package() { + // ensure this is first so it resolves the node types first + result.push("asset:///node_types.d.ts".to_string()); + } + + result.extend(open_docs.into_iter().map(|d| d.specifier().to_string())); + result } #[derive(Debug, Deserialize, Serialize)] |