diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index e7be61cdc6..96bb89d75d 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -81,7 +81,7 @@ pub async fn generate_lint_diagnostics( tokio::task::spawn_blocking(move || { let mut diagnostic_list = Vec::new(); - let file_cache = state_snapshot.file_cache.read().unwrap(); + let file_cache = state_snapshot.file_cache.lock().unwrap(); for (specifier, doc_data) in state_snapshot.doc_data.iter() { let file_id = file_cache.lookup(specifier).unwrap(); let version = doc_data.version; @@ -242,7 +242,7 @@ pub async fn generate_ts_diagnostics( for (specifier, doc_data) in state_snapshot_.doc_data.iter() { let file_id = { // TODO(lucacasonato): this is highly inefficient - let file_cache = state_snapshot_.file_cache.read().unwrap(); + let file_cache = state_snapshot_.file_cache.lock().unwrap(); file_cache.lookup(specifier).unwrap() }; let version = doc_data.version; @@ -266,8 +266,8 @@ pub async fn generate_dependency_diagnostics( tokio::task::spawn_blocking(move || { let mut diagnostics = Vec::new(); - let file_cache = state_snapshot.file_cache.read().unwrap(); - let mut sources = if let Ok(sources) = state_snapshot.sources.write() { + let file_cache = state_snapshot.file_cache.lock().unwrap(); + let mut sources = if let Ok(sources) = state_snapshot.sources.lock() { sources } else { return Err(custom_error("Deadlock", "deadlock locking sources")); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 08ece2b629..09119cb362 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -17,12 +17,13 @@ use std::collections::HashMap; use std::env; use std::path::PathBuf; use std::sync::Arc; -use std::sync::RwLock; +use std::sync::Mutex; use tokio::fs; use crate::deno_dir; use crate::import_map::ImportMap; use crate::media_type::MediaType; +use crate::tsc_config::parse_config; use crate::tsc_config::TsConfig; use super::analysis; @@ -42,24 +43,25 @@ use super::utils; #[derive(Debug, Clone)] pub struct LanguageServer { - assets: Arc>>>, + assets: Arc>>>, client: Client, ts_server: TsServer, - config: Arc>, - doc_data: Arc>>, - file_cache: Arc>, - sources: Arc>, - diagnostics: Arc>, - maybe_import_map: Arc>>, - maybe_import_map_uri: Arc>>, + config: Arc>, + doc_data: Arc>>, + file_cache: Arc>, + sources: Arc>, + diagnostics: Arc>, + maybe_config_uri: Arc>>, + maybe_import_map: Arc>>, + maybe_import_map_uri: Arc>>, } #[derive(Debug, Clone, Default)] pub struct StateSnapshot { - pub assets: Arc>>>, + pub assets: Arc>>>, pub doc_data: HashMap, - pub file_cache: Arc>, - pub sources: Arc>, + pub file_cache: Arc>, + pub sources: Arc>, } impl LanguageServer { @@ -68,7 +70,7 @@ impl LanguageServer { let dir = deno_dir::DenoDir::new(maybe_custom_root) .expect("could not access DENO_DIR"); let location = dir.root.join("deps"); - let sources = Arc::new(RwLock::new(Sources::new(&location))); + let sources = Arc::new(Mutex::new(Sources::new(&location))); LanguageServer { assets: Default::default(), @@ -79,13 +81,14 @@ impl LanguageServer { file_cache: Default::default(), sources, diagnostics: Default::default(), + maybe_config_uri: Default::default(), maybe_import_map: Default::default(), maybe_import_map_uri: Default::default(), } } fn enabled(&self) -> bool { - let config = self.config.read().unwrap(); + let config = self.config.lock().unwrap(); config.settings.enable } @@ -103,12 +106,12 @@ impl LanguageServer { return Err(anyhow!("asset source missing: {}", specifier)); } } else { - let file_cache = self.file_cache.read().unwrap(); + let file_cache = self.file_cache.lock().unwrap(); if let Some(file_id) = file_cache.lookup(&specifier) { let file_text = file_cache.get_contents(file_id)?; text::index_lines(&file_text) } else { - let mut sources = self.sources.write().unwrap(); + let mut sources = self.sources.lock().unwrap(); if let Some(line_index) = sources.get_line_index(&specifier) { line_index } else { @@ -121,20 +124,20 @@ impl LanguageServer { async fn prepare_diagnostics(&self) -> Result<(), AnyError> { let (enabled, lint_enabled) = { - let config = self.config.read().unwrap(); + let config = self.config.lock().unwrap(); (config.settings.enable, config.settings.lint) }; let lint = async { if lint_enabled { - let diagnostic_collection = self.diagnostics.read().unwrap().clone(); + let diagnostic_collection = self.diagnostics.lock().unwrap().clone(); let diagnostics = diagnostics::generate_lint_diagnostics( self.snapshot(), diagnostic_collection, ) .await; { - let mut diagnostics_collection = self.diagnostics.write().unwrap(); + let mut diagnostics_collection = self.diagnostics.lock().unwrap(); for (file_id, version, diagnostics) in diagnostics { diagnostics_collection.set( file_id, @@ -153,7 +156,7 @@ impl LanguageServer { let ts = async { if enabled { let diagnostics = { - let diagnostic_collection = self.diagnostics.read().unwrap().clone(); + let diagnostic_collection = self.diagnostics.lock().unwrap().clone(); match diagnostics::generate_ts_diagnostics( &self.ts_server, &diagnostic_collection, @@ -169,7 +172,7 @@ impl LanguageServer { } }; { - let mut diagnostics_collection = self.diagnostics.write().unwrap(); + let mut diagnostics_collection = self.diagnostics.lock().unwrap(); for (file_id, version, diagnostics) in diagnostics { diagnostics_collection.set( file_id, @@ -187,14 +190,14 @@ impl LanguageServer { let deps = async { if enabled { - let diagnostics_collection = self.diagnostics.read().unwrap().clone(); + let diagnostics_collection = self.diagnostics.lock().unwrap().clone(); let diagnostics = diagnostics::generate_dependency_diagnostics( self.snapshot(), diagnostics_collection, ) .await?; { - let mut diagnostics_collection = self.diagnostics.write().unwrap(); + let mut diagnostics_collection = self.diagnostics.lock().unwrap(); for (file_id, version, diagnostics) in diagnostics { diagnostics_collection.set( file_id, @@ -220,12 +223,12 @@ impl LanguageServer { async fn publish_diagnostics(&self) -> Result<(), AnyError> { let (maybe_changes, diagnostics_collection) = { - let mut diagnostics_collection = self.diagnostics.write().unwrap(); + let mut diagnostics_collection = self.diagnostics.lock().unwrap(); let maybe_changes = diagnostics_collection.take_changes(); (maybe_changes, diagnostics_collection.clone()) }; if let Some(diagnostic_changes) = maybe_changes { - let settings = self.config.read().unwrap().settings.clone(); + let settings = self.config.lock().unwrap().settings.clone(); for file_id in diagnostic_changes { // TODO(@kitsonk) not totally happy with the way we collect and store // different types of diagnostics and offer them up to the client, we @@ -253,12 +256,12 @@ impl LanguageServer { ); } let specifier = { - let file_cache = self.file_cache.read().unwrap(); + let file_cache = self.file_cache.lock().unwrap(); file_cache.get_specifier(file_id).clone() }; let uri = specifier.as_url().clone(); let version = if let Some(doc_data) = - self.doc_data.read().unwrap().get(&specifier) + self.doc_data.lock().unwrap().get(&specifier) { doc_data.version } else { @@ -277,7 +280,7 @@ impl LanguageServer { pub fn snapshot(&self) -> StateSnapshot { StateSnapshot { assets: self.assets.clone(), - doc_data: self.doc_data.read().unwrap().clone(), + doc_data: self.doc_data.lock().unwrap().clone(), file_cache: self.file_cache.clone(), sources: self.sources.clone(), } @@ -285,11 +288,11 @@ impl LanguageServer { pub async fn update_import_map(&self) -> Result<(), AnyError> { let (maybe_import_map, maybe_root_uri) = { - let config = self.config.read().unwrap(); + let config = self.config.lock().unwrap(); (config.settings.import_map.clone(), config.root_uri.clone()) }; if let Some(import_map_str) = &maybe_import_map { - info!("update import map"); + info!("Updating import map from: \"{}\"", import_map_str); let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str) { Ok(url) @@ -320,10 +323,10 @@ impl LanguageServer { })?; let import_map = ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?; - *self.maybe_import_map_uri.write().unwrap() = Some(import_map_url); - *self.maybe_import_map.write().unwrap() = Some(import_map); + *self.maybe_import_map_uri.lock().unwrap() = Some(import_map_url); + *self.maybe_import_map.lock().unwrap() = Some(import_map); } else { - *self.maybe_import_map.write().unwrap() = None; + *self.maybe_import_map.lock().unwrap() = None; } Ok(()) } @@ -339,14 +342,56 @@ impl LanguageServer { "strict": true, "target": "esnext", })); - { - let config = self.config.read().unwrap(); + let (maybe_config, maybe_root_uri) = { + let config = self.config.lock().unwrap(); if config.settings.unstable { let unstable_libs = json!({ "lib": ["deno.ns", "deno.window", "deno.unstable"] }); tsconfig.merge(&unstable_libs); } + (config.settings.config.clone(), config.root_uri.clone()) + }; + if let Some(config_str) = &maybe_config { + info!("Updating TypeScript configuration from: \"{}\"", config_str); + let config_url = if let Ok(url) = Url::from_file_path(config_str) { + Ok(url) + } else if let Some(root_uri) = &maybe_root_uri { + let root_path = root_uri + .to_file_path() + .map_err(|_| anyhow!("Bad root_uri: {}", root_uri))?; + let config_path = root_path.join(config_str); + Url::from_file_path(config_path).map_err(|_| { + anyhow!("Bad file path for configuration file: \"{}\"", config_str) + }) + } else { + Err(anyhow!( + "The path to the configuration file (\"{}\") is not resolvable.", + config_str + )) + }?; + let config_path = config_url + .to_file_path() + .map_err(|_| anyhow!("Bad file path."))?; + let config_text = + fs::read_to_string(config_path.clone()) + .await + .map_err(|err| { + anyhow!( + "Failed to load the configuration file at: {}. [{}]", + config_url, + err + ) + })?; + let (value, maybe_ignored_options) = + parse_config(&config_text, &config_path)?; + tsconfig.merge(&value); + *self.maybe_config_uri.lock().unwrap() = Some(config_url); + if let Some(ignored_options) = maybe_ignored_options { + // TODO(@kitsonk) turn these into diagnostics that can be sent to the + // client + warn!("{}", ignored_options); + } } self .ts_server @@ -388,7 +433,7 @@ impl lspower::LanguageServer for LanguageServer { } { - let mut config = self.config.write().unwrap(); + let mut config = self.config.lock().unwrap(); config.root_uri = params.root_uri; if let Some(value) = params.initialization_options { config.update(value)?; @@ -451,10 +496,10 @@ impl lspower::LanguageServer for LanguageServer { return; } let specifier = utils::normalize_url(params.text_document.uri); - let maybe_import_map = self.maybe_import_map.read().unwrap().clone(); + let maybe_import_map = self.maybe_import_map.lock().unwrap().clone(); if self .doc_data - .write() + .lock() .unwrap() .insert( specifier.clone(), @@ -472,7 +517,7 @@ impl lspower::LanguageServer for LanguageServer { self .file_cache - .write() + .lock() .unwrap() .set_contents(specifier, Some(params.text_document.text.into_bytes())); // TODO(@lucacasonato): error handling @@ -482,15 +527,15 @@ impl lspower::LanguageServer for LanguageServer { async fn did_change(&self, params: DidChangeTextDocumentParams) { let specifier = utils::normalize_url(params.text_document.uri); let mut content = { - let file_cache = self.file_cache.read().unwrap(); + let file_cache = self.file_cache.lock().unwrap(); let file_id = file_cache.lookup(&specifier).unwrap(); file_cache.get_contents(file_id).unwrap() }; apply_content_changes(&mut content, params.content_changes); { - let mut doc_data = self.doc_data.write().unwrap(); + let mut doc_data = self.doc_data.lock().unwrap(); let doc_data = doc_data.get_mut(&specifier).unwrap(); - let maybe_import_map = self.maybe_import_map.read().unwrap(); + let maybe_import_map = self.maybe_import_map.lock().unwrap(); doc_data.update( params.text_document.version, &content, @@ -500,7 +545,7 @@ impl lspower::LanguageServer for LanguageServer { self .file_cache - .write() + .lock() .unwrap() .set_contents(specifier, Some(content.into_bytes())); @@ -516,7 +561,7 @@ impl lspower::LanguageServer for LanguageServer { return; } let specifier = utils::normalize_url(params.text_document.uri); - if self.doc_data.write().unwrap().remove(&specifier).is_none() { + if self.doc_data.lock().unwrap().remove(&specifier).is_none() { error!("orphaned document: {}", specifier); } // TODO(@kitsonk) should we do garbage collection on the diagnostics? @@ -544,7 +589,7 @@ impl lspower::LanguageServer for LanguageServer { match res { Err(err) => error!("failed to fetch the extension settings {:?}", err), Ok(Some(config)) => { - if let Err(err) = self.config.write().unwrap().update(config) { + if let Err(err) = self.config.lock().unwrap().update(config) { error!("failed to update settings: {}", err); } if let Err(err) = self.update_import_map().await { @@ -570,7 +615,7 @@ impl lspower::LanguageServer for LanguageServer { ) { // if the current import map has changed, we need to reload it let maybe_import_map_uri = - self.maybe_import_map_uri.read().unwrap().clone(); + self.maybe_import_map_uri.lock().unwrap().clone(); if let Some(import_map_uri) = maybe_import_map_uri { if params.changes.iter().any(|fe| import_map_uri == fe.uri) { if let Err(err) = self.update_import_map().await { @@ -581,6 +626,18 @@ impl lspower::LanguageServer for LanguageServer { } } } + // if the current tsconfig has changed, we need to reload it + let maybe_config_uri = self.maybe_config_uri.lock().unwrap().clone(); + if let Some(config_uri) = maybe_config_uri { + if params.changes.iter().any(|fe| config_uri == fe.uri) { + if let Err(err) = self.update_tsconfig().await { + self + .client + .show_message(MessageType::Warning, err.to_string()) + .await; + } + } + } } async fn formatting( @@ -589,7 +646,7 @@ impl lspower::LanguageServer for LanguageServer { ) -> LspResult>> { let specifier = utils::normalize_url(params.text_document.uri.clone()); let file_text = { - let file_cache = self.file_cache.read().unwrap(); + let file_cache = self.file_cache.lock().unwrap(); let file_id = file_cache.lookup(&specifier).unwrap(); // TODO(lucacasonato): handle error properly file_cache.get_contents(file_id).unwrap() @@ -934,7 +991,7 @@ pub struct VirtualTextDocumentParams { impl LanguageServer { async fn cache(&self, params: CacheParams) -> LspResult { let specifier = utils::normalize_url(params.text_document.uri); - let maybe_import_map = self.maybe_import_map.read().unwrap().clone(); + let maybe_import_map = self.maybe_import_map.lock().unwrap().clone(); sources::cache(specifier.clone(), maybe_import_map) .await .map_err(|err| { @@ -942,9 +999,9 @@ impl LanguageServer { LspError::internal_error() })?; { - let file_cache = self.file_cache.read().unwrap(); + let file_cache = self.file_cache.lock().unwrap(); if let Some(file_id) = file_cache.lookup(&specifier) { - let mut diagnostics_collection = self.diagnostics.write().unwrap(); + let mut diagnostics_collection = self.diagnostics.lock().unwrap(); diagnostics_collection.invalidate(&file_id); } } @@ -962,7 +1019,7 @@ impl LanguageServer { let specifier = utils::normalize_url(params.text_document.uri); let url = specifier.as_url(); let contents = if url.as_str() == "deno:/status.md" { - let file_cache = self.file_cache.read().unwrap(); + let file_cache = self.file_cache.lock().unwrap(); Some(format!( r#"# Deno Language Server Status @@ -987,7 +1044,7 @@ impl LanguageServer { } } _ => { - let mut sources = self.sources.write().unwrap(); + let mut sources = self.sources.lock().unwrap(); if let Some(text) = sources.get_text(&specifier) { Some(text) } else { diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 9de7ddcace..d40e18bbda 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -94,7 +94,7 @@ pub async fn get_asset( Ok(Some(asset_text.to_string())) } else { { - let assets = state_snapshot.assets.read().unwrap(); + let assets = state_snapshot.assets.lock().unwrap(); if let Some(asset) = assets.get(specifier) { return Ok(asset.clone()); } @@ -107,7 +107,7 @@ pub async fn get_asset( ) .await?, )?; - let mut assets = state_snapshot.assets.write().unwrap(); + let mut assets = state_snapshot.assets.lock().unwrap(); assets.insert(specifier.clone(), asset.clone()); Ok(asset) } @@ -767,7 +767,7 @@ fn cache_snapshot( { let s = ModuleSpecifier::resolve_url(&specifier)?; let content = { - let file_cache = state.state_snapshot.file_cache.read().unwrap(); + let file_cache = state.state_snapshot.file_cache.lock().unwrap(); let file_id = file_cache.lookup(&s).unwrap(); file_cache.get_contents(file_id)? }; @@ -863,7 +863,7 @@ fn get_length(state: &mut State, args: Value) -> Result { .unwrap(); Ok(json!(content.chars().count())) } else { - let mut sources = state.state_snapshot.sources.write().unwrap(); + let mut sources = state.state_snapshot.sources.lock().unwrap(); Ok(json!(sources.get_length(&specifier).unwrap())) } } @@ -888,7 +888,7 @@ fn get_text(state: &mut State, args: Value) -> Result { .unwrap() .clone() } else { - let mut sources = state.state_snapshot.sources.write().unwrap(); + let mut sources = state.state_snapshot.sources.lock().unwrap(); sources.get_text(&specifier).unwrap() }; Ok(json!(text::slice(&content, v.start..v.end))) @@ -898,7 +898,7 @@ fn resolve(state: &mut State, args: Value) -> Result { let v: ResolveArgs = serde_json::from_value(args)?; let mut resolved = Vec::>::new(); let referrer = ModuleSpecifier::resolve_url(&v.base)?; - let mut sources = if let Ok(sources) = state.state_snapshot.sources.write() { + let mut sources = if let Ok(sources) = state.state_snapshot.sources.lock() { sources } else { return Err(custom_error("Deadlock", "deadlock locking sources")); @@ -999,7 +999,7 @@ fn script_version(state: &mut State, args: Value) -> Result { return Ok(json!(version.to_string())); } } else { - let mut sources = state.state_snapshot.sources.write().unwrap(); + let mut sources = state.state_snapshot.sources.lock().unwrap(); if let Some(version) = sources.get_script_version(&specifier) { return Ok(json!(version)); } @@ -1256,7 +1256,7 @@ mod tests { use crate::lsp::language_server::DocumentData; use std::collections::HashMap; use std::sync::Arc; - use std::sync::RwLock; + use std::sync::Mutex; fn mock_state_snapshot(sources: Vec<(&str, &str, i32)>) -> StateSnapshot { let mut doc_data = HashMap::new(); @@ -1270,7 +1270,7 @@ mod tests { ); file_cache.set_contents(specifier, Some(content.as_bytes().to_vec())); } - let file_cache = Arc::new(RwLock::new(file_cache)); + let file_cache = Arc::new(Mutex::new(file_cache)); StateSnapshot { assets: Default::default(), doc_data,