diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2024-04-15 17:50:52 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-15 17:50:52 -0400 |
commit | 6f278e5c40d101f0fb8e7b69e28d34b1c766a8fe (patch) | |
tree | 97d3edc0f0b527c3dc7f07ba71d5828cd2c77943 /cli/lsp | |
parent | 7e4ee02e2e37db8adfaf4a05aba3819838904650 (diff) |
fix(lsp): improved cjs tracking (#23374)
Our cjs tracking was a bit broken. It was marking stuff as esm that was
actually cjs leading to type checking errors.
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/documents.rs | 205 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 5 |
2 files changed, 130 insertions, 80 deletions
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 1dd18f599..e7ef048cf 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -12,6 +12,7 @@ use super::tsc::AssetDocument; use crate::args::package_json; use crate::cache::HttpCache; use crate::jsr::JsrCacheResolver; +use crate::lsp::logging::lsp_warn; use crate::npm::CliNpmResolver; use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; @@ -43,7 +44,6 @@ use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; use indexmap::IndexMap; -use once_cell::sync::Lazy; use package_json::PackageJsonDepsProvider; use std::borrow::Cow; use std::collections::BTreeSet; @@ -57,36 +57,6 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use tower_lsp::lsp_types as lsp; -static JS_HEADERS: Lazy<HashMap<String, String>> = Lazy::new(|| { - ([( - "content-type".to_string(), - "application/javascript".to_string(), - )]) - .into_iter() - .collect() -}); - -static JSX_HEADERS: Lazy<HashMap<String, String>> = Lazy::new(|| { - ([("content-type".to_string(), "text/jsx".to_string())]) - .into_iter() - .collect() -}); - -static TS_HEADERS: Lazy<HashMap<String, String>> = Lazy::new(|| { - ([( - "content-type".to_string(), - "application/typescript".to_string(), - )]) - .into_iter() - .collect() -}); - -static TSX_HEADERS: Lazy<HashMap<String, String>> = Lazy::new(|| { - ([("content-type".to_string(), "text/tsx".to_string())]) - .into_iter() - .collect() -}); - pub const DOCUMENT_SCHEMES: [&str; 5] = ["data", "blob", "file", "http", "https"]; @@ -103,18 +73,6 @@ pub enum LanguageId { } impl LanguageId { - pub fn as_media_type(&self) -> MediaType { - match self { - LanguageId::JavaScript => MediaType::JavaScript, - LanguageId::Jsx => MediaType::Jsx, - LanguageId::TypeScript => MediaType::TypeScript, - LanguageId::Tsx => MediaType::Tsx, - LanguageId::Json => MediaType::Json, - LanguageId::JsonC => MediaType::Json, - LanguageId::Markdown | LanguageId::Unknown => MediaType::Unknown, - } - } - pub fn as_extension(&self) -> Option<&'static str> { match self { LanguageId::JavaScript => Some("js"), @@ -128,13 +86,15 @@ impl LanguageId { } } - fn as_headers(&self) -> Option<&HashMap<String, String>> { + pub fn as_content_type(&self) -> Option<&'static str> { match self { - Self::JavaScript => Some(&JS_HEADERS), - Self::Jsx => Some(&JSX_HEADERS), - Self::TypeScript => Some(&TS_HEADERS), - Self::Tsx => Some(&TSX_HEADERS), - _ => None, + LanguageId::JavaScript => Some("application/javascript"), + LanguageId::Jsx => Some("text/jsx"), + LanguageId::TypeScript => Some("application/typescript"), + LanguageId::Tsx => Some("text/tsx"), + LanguageId::Json | LanguageId::JsonC => Some("application/json"), + LanguageId::Markdown => Some("text/markdown"), + LanguageId::Unknown => None, } } @@ -291,6 +251,7 @@ pub struct Document { // so having a mutex to hold it is ok maybe_navigation_tree: Mutex<Option<Arc<tsc::NavigationTree>>>, maybe_parsed_source: Option<ParsedSourceResult>, + media_type: MediaType, specifier: ModuleSpecifier, text_info: SourceTextInfo, } @@ -302,15 +263,23 @@ impl Document { maybe_headers: Option<HashMap<String, String>>, text_info: SourceTextInfo, resolver: &dyn deno_graph::source::Resolver, + maybe_node_resolver: Option<&CliNodeResolver>, npm_resolver: &dyn deno_graph::source::NpmResolver, ) -> Arc<Self> { // we only ever do `Document::new` on disk resources that are supposed to // be diagnosable, unlike `Document::open`, so it is safe to unconditionally // parse the module. + let media_type = resolve_media_type( + &specifier, + maybe_headers.as_ref(), + None, + maybe_node_resolver, + ); let (maybe_parsed_source, maybe_module) = parse_and_analyze_module( &specifier, text_info.clone(), maybe_headers.as_ref(), + media_type, resolver, npm_resolver, ); @@ -328,6 +297,7 @@ impl Document { maybe_navigation_tree: Mutex::new(None), maybe_parsed_source: maybe_parsed_source .filter(|_| specifier.scheme() == "file"), + media_type, text_info, specifier, }) @@ -336,12 +306,27 @@ impl Document { fn maybe_with_new_resolver( &self, resolver: &dyn deno_graph::source::Resolver, + maybe_node_resolver: Option<&CliNodeResolver>, npm_resolver: &dyn deno_graph::source::NpmResolver, ) -> Option<Arc<Self>> { - let parsed_source_result = match &self.maybe_parsed_source { + let mut parsed_source_result = match &self.maybe_parsed_source { Some(parsed_source_result) => parsed_source_result.clone(), None => return None, // nothing to change }; + let media_type = resolve_media_type( + &self.specifier, + self.maybe_headers.as_ref(), + self.maybe_language_id, + maybe_node_resolver, + ); + // reparse if the media type has changed + if let Ok(parsed_source) = &parsed_source_result { + if parsed_source.media_type() != media_type { + parsed_source_result = + parse_source(&self.specifier, self.text_info.clone(), media_type); + } + } + let maybe_module = Some(analyze_module( &self.specifier, &parsed_source_result, @@ -363,27 +348,37 @@ impl Document { maybe_headers: self.maybe_headers.clone(), maybe_language_id: self.maybe_language_id, maybe_lsp_version: self.maybe_lsp_version, + media_type, text_info: self.text_info.clone(), specifier: self.specifier.clone(), })) } + #[allow(clippy::too_many_arguments)] fn open( specifier: ModuleSpecifier, version: i32, language_id: LanguageId, + maybe_headers: Option<HashMap<String, String>>, content: Arc<str>, cache: &Arc<dyn HttpCache>, resolver: &dyn deno_graph::source::Resolver, + maybe_node_resolver: Option<&CliNodeResolver>, npm_resolver: &dyn deno_graph::source::NpmResolver, ) -> Arc<Self> { - let maybe_headers = language_id.as_headers(); let text_info = SourceTextInfo::new(content); + let media_type = resolve_media_type( + &specifier, + None, + Some(language_id), + maybe_node_resolver, + ); let (maybe_parsed_source, maybe_module) = if language_id.is_diagnosable() { parse_and_analyze_module( &specifier, text_info.clone(), - maybe_headers, + maybe_headers.as_ref(), + media_type, resolver, npm_resolver, ) @@ -400,11 +395,12 @@ impl Document { line_index, maybe_language_id: Some(language_id), maybe_lsp_version: Some(version), - maybe_headers: maybe_headers.map(ToOwned::to_owned), + maybe_headers, maybe_module, maybe_navigation_tree: Mutex::new(None), maybe_parsed_source: maybe_parsed_source .filter(|_| specifier.scheme() == "file"), + media_type, text_info, specifier, }) @@ -434,20 +430,18 @@ impl Document { } } let text_info = SourceTextInfo::from_string(content); + let media_type = self.media_type; let (maybe_parsed_source, maybe_module) = if self .maybe_language_id .as_ref() .map(|li| li.is_diagnosable()) .unwrap_or(false) { - let maybe_headers = self - .maybe_language_id - .as_ref() - .and_then(|li| li.as_headers()); parse_and_analyze_module( &self.specifier, text_info.clone(), - maybe_headers, + self.maybe_headers.as_ref(), + media_type, resolver, npm_resolver, ) @@ -477,6 +471,7 @@ impl Document { .filter(|_| self.specifier.scheme() == "file"), maybe_lsp_version: Some(version), maybe_navigation_tree: Mutex::new(None), + media_type, })) } @@ -494,6 +489,7 @@ impl Document { maybe_parsed_source: self.maybe_parsed_source.clone(), maybe_lsp_version: self.maybe_lsp_version, maybe_navigation_tree: Mutex::new(None), + media_type: self.media_type, }) } @@ -554,18 +550,7 @@ impl Document { } pub fn media_type(&self) -> MediaType { - if let Some(Ok(module)) = &self.maybe_module { - return module.media_type; - } - let specifier_media_type = MediaType::from_specifier(&self.specifier); - if specifier_media_type != MediaType::Unknown { - return specifier_media_type; - } - - self - .maybe_language_id - .map(|id| id.as_media_type()) - .unwrap_or(MediaType::Unknown) + self.media_type } pub fn maybe_language_id(&self) -> Option<LanguageId> { @@ -629,6 +614,39 @@ impl Document { } } +fn resolve_media_type( + specifier: &ModuleSpecifier, + maybe_headers: Option<&HashMap<String, String>>, + maybe_language_id: Option<LanguageId>, + maybe_node_resolver: Option<&CliNodeResolver>, +) -> MediaType { + if let Some(node_resolver) = maybe_node_resolver { + if node_resolver.in_npm_package(specifier) { + match node_resolver.url_to_node_resolution(specifier.clone()) { + Ok(resolution) => { + let (_, media_type) = + NodeResolution::into_specifier_and_media_type(Some(resolution)); + return media_type; + } + Err(err) => { + lsp_warn!("Node resolution failed for '{}': {}", specifier, err); + } + } + } + } + + if maybe_headers.is_some() { + return MediaType::from_specifier_and_headers(specifier, maybe_headers); + } + + // LanguageId is a subset of MediaType, so get its content type and + // also use the specifier to inform its media type + MediaType::from_specifier_and_content_type( + specifier, + maybe_language_id.and_then(|id| id.as_content_type()), + ) +} + pub fn to_lsp_range(range: &deno_graph::Range) -> lsp::Range { lsp::Range { start: lsp::Position { @@ -712,6 +730,7 @@ impl FileSystemDocuments { cache: &Arc<dyn HttpCache>, resolver: &dyn deno_graph::source::Resolver, specifier: &ModuleSpecifier, + maybe_node_resolver: Option<&CliNodeResolver>, npm_resolver: &dyn deno_graph::source::NpmResolver, ) -> Option<Arc<Document>> { let fs_version = if specifier.scheme() == "data" { @@ -724,7 +743,13 @@ impl FileSystemDocuments { != fs_version { // attempt to update the file on the file system - self.refresh_document(cache, resolver, specifier, npm_resolver) + self.refresh_document( + cache, + resolver, + specifier, + maybe_node_resolver, + npm_resolver, + ) } else { file_system_doc } @@ -737,6 +762,7 @@ impl FileSystemDocuments { cache: &Arc<dyn HttpCache>, resolver: &dyn deno_graph::source::Resolver, specifier: &ModuleSpecifier, + maybe_node_resolver: Option<&CliNodeResolver>, npm_resolver: &dyn deno_graph::source::NpmResolver, ) -> Option<Arc<Document>> { let doc = if specifier.scheme() == "file" { @@ -751,6 +777,7 @@ impl FileSystemDocuments { None, SourceTextInfo::from_string(content), resolver, + maybe_node_resolver, npm_resolver, ) } else if specifier.scheme() == "data" { @@ -764,6 +791,7 @@ impl FileSystemDocuments { None, SourceTextInfo::from_string(source), resolver, + maybe_node_resolver, npm_resolver, ) } else { @@ -791,6 +819,7 @@ impl FileSystemDocuments { maybe_headers, SourceTextInfo::from_string(content), resolver, + maybe_node_resolver, npm_resolver, ) }; @@ -837,6 +866,8 @@ pub struct Documents { /// Any imports to the context supplied by configuration files. This is like /// the imports into the a module graph in CLI. imports: Arc<IndexMap<ModuleSpecifier, GraphImport>>, + /// Resolver for node_modules. + maybe_node_resolver: Option<Arc<CliNodeResolver>>, /// A resolver that takes into account currently loaded import map and JSX /// settings. resolver: Arc<CliGraphResolver>, @@ -861,6 +892,7 @@ impl Documents { open_docs: HashMap::default(), file_system_docs: Default::default(), imports: Default::default(), + maybe_node_resolver: None, resolver: Arc::new(CliGraphResolver::new(CliGraphResolverOptions { node_resolver: None, npm_resolver: None, @@ -905,9 +937,14 @@ impl Documents { specifier.clone(), version, language_id, + // todo(dsherret): don't we want to pass in the headers from + // the cache for remote modules here in order to get the + // x-typescript-types? + None, content, &self.cache, resolver, + self.maybe_node_resolver.as_deref(), npm_resolver, ); @@ -1102,6 +1139,7 @@ impl Documents { &self.cache, self.get_resolver(), &specifier, + self.maybe_node_resolver.as_deref(), self.get_npm_resolver(), ) } @@ -1281,6 +1319,7 @@ impl Documents { ) { let config_data = config.tree.root_data(); let config_file = config_data.and_then(|d| d.config_file.as_deref()); + self.maybe_node_resolver = node_resolver.clone(); self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions { node_resolver, npm_resolver, @@ -1352,9 +1391,11 @@ impl Documents { if !config.specifier_enabled(doc.specifier()) { continue; } - if let Some(new_doc) = - doc.maybe_with_new_resolver(resolver, npm_resolver) - { + if let Some(new_doc) = doc.maybe_with_new_resolver( + resolver, + self.maybe_node_resolver.as_deref(), + npm_resolver, + ) { *doc = new_doc; } } @@ -1362,9 +1403,11 @@ impl Documents { if !config.specifier_enabled(doc.specifier()) { continue; } - if let Some(new_doc) = - doc.maybe_with_new_resolver(resolver, npm_resolver) - { + if let Some(new_doc) = doc.maybe_with_new_resolver( + resolver, + self.maybe_node_resolver.as_deref(), + npm_resolver, + ) { *doc.value_mut() = new_doc; } } @@ -1385,6 +1428,7 @@ impl Documents { &self.cache, resolver, specifier, + self.maybe_node_resolver.as_deref(), npm_resolver, ); } @@ -1628,10 +1672,11 @@ fn parse_and_analyze_module( specifier: &ModuleSpecifier, text_info: SourceTextInfo, maybe_headers: Option<&HashMap<String, String>>, + media_type: MediaType, resolver: &dyn deno_graph::source::Resolver, npm_resolver: &dyn deno_graph::source::NpmResolver, ) -> (Option<ParsedSourceResult>, Option<ModuleResult>) { - let parsed_source_result = parse_source(specifier, text_info, maybe_headers); + let parsed_source_result = parse_source(specifier, text_info, media_type); let module_result = analyze_module( specifier, &parsed_source_result, @@ -1645,12 +1690,12 @@ fn parse_and_analyze_module( fn parse_source( specifier: &ModuleSpecifier, text_info: SourceTextInfo, - maybe_headers: Option<&HashMap<String, String>>, + media_type: MediaType, ) -> ParsedSourceResult { deno_ast::parse_module(deno_ast::ParseParams { specifier: specifier.clone(), text_info, - media_type: MediaType::from_specifier_and_headers(specifier, maybe_headers), + media_type, capture_tokens: true, scope_analysis: true, maybe_syntax: None, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 2e0726a79..13da932f7 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -3996,6 +3996,7 @@ struct LoadResponse { data: Arc<str>, script_kind: i32, version: Option<String>, + is_cjs: bool, } #[op2] @@ -4018,6 +4019,10 @@ fn op_load<'s>( data: doc.text(), script_kind: crate::tsc::as_ts_script_kind(doc.media_type()), version: state.script_version(&specifier), + is_cjs: matches!( + doc.media_type(), + MediaType::Cjs | MediaType::Cts | MediaType::Dcts + ), }) }; |