diff --git a/cli/args/mod.rs b/cli/args/mod.rs index cb4473ca26..de889c6540 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -260,6 +260,12 @@ pub struct FmtOptions { pub files: FilePatterns, } +impl Default for FmtOptions { + fn default() -> Self { + Self::new_with_base(PathBuf::from("/")) + } +} + impl FmtOptions { pub fn new_with_base(base: PathBuf) -> Self { Self { @@ -394,6 +400,12 @@ pub struct LintOptions { pub fix: bool, } +impl Default for LintOptions { + fn default() -> Self { + Self::new_with_base(PathBuf::from("/")) + } +} + impl LintOptions { pub fn new_with_base(base: PathBuf) -> Self { Self { diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 54073edaf3..652697a9e3 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -43,6 +43,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::collections::HashSet; use std::path::Path; +use std::sync::Arc; use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types::Position; use tower_lsp::lsp_types::Range; @@ -216,7 +217,7 @@ fn code_as_string(code: &Option) -> String { /// Rewrites imports in quick fixes and code changes to be Deno specific. pub struct TsResponseImportMapper<'a> { documents: &'a Documents, - maybe_import_map: Option<&'a ImportMap>, + maybe_import_map: Option>, node_resolver: Option<&'a CliNodeResolver>, npm_resolver: Option<&'a dyn CliNpmResolver>, } @@ -224,7 +225,7 @@ pub struct TsResponseImportMapper<'a> { impl<'a> TsResponseImportMapper<'a> { pub fn new( documents: &'a Documents, - maybe_import_map: Option<&'a ImportMap>, + maybe_import_map: Option>, node_resolver: Option<&'a CliNodeResolver>, npm_resolver: Option<&'a dyn CliNpmResolver>, ) -> Self { @@ -269,7 +270,7 @@ impl<'a> TsResponseImportMapper<'a> { let sub_path = (export != ".").then_some(export); let mut req = None; req = req.or_else(|| { - let import_map = self.maybe_import_map?; + let import_map = self.maybe_import_map.as_ref()?; for entry in import_map.entries_for_referrer(referrer) { let Some(value) = entry.raw_value else { continue; @@ -296,7 +297,7 @@ impl<'a> TsResponseImportMapper<'a> { JsrPackageNvReference::new(nv_ref).to_string() }; let specifier = ModuleSpecifier::parse(&spec_str).ok()?; - if let Some(import_map) = self.maybe_import_map { + if let Some(import_map) = &self.maybe_import_map { if let Some(result) = import_map.lookup(&specifier, referrer) { return Some(result); } @@ -315,7 +316,7 @@ impl<'a> TsResponseImportMapper<'a> { // check if any pkg reqs match what is found in an import map if !pkg_reqs.is_empty() { let sub_path = self.resolve_package_path(specifier); - if let Some(import_map) = self.maybe_import_map { + if let Some(import_map) = &self.maybe_import_map { let pkg_reqs = pkg_reqs.iter().collect::>(); let mut matches = Vec::new(); for entry in import_map.entries_for_referrer(referrer) { @@ -357,7 +358,7 @@ impl<'a> TsResponseImportMapper<'a> { } // check if the import map has this specifier - if let Some(import_map) = self.maybe_import_map { + if let Some(import_map) = &self.maybe_import_map { if let Some(result) = import_map.lookup(specifier, referrer) { return Some(result); } @@ -942,7 +943,7 @@ impl CodeActionCollection { let action = fix_ts_import_action( specifier, action, - &language_server.get_ts_response_import_mapper(), + &language_server.get_ts_response_import_mapper(specifier), )?; let edit = ts_changes_to_edit(&action.changes, language_server)?; let code_action = lsp::CodeAction { diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index a87c49108d..5fa2a00a6e 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -2,22 +2,34 @@ use super::logging::lsp_log; use crate::args::ConfigFile; +use crate::args::FmtOptions; +use crate::args::LintOptions; use crate::cache::FastInsecureHasher; +use crate::file_fetcher::FileFetcher; use crate::lsp::logging::lsp_warn; +use crate::tools::lint::get_configured_rules; +use crate::tools::lint::ConfiguredRules; use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::path::specifier_to_file_path; use deno_ast::MediaType; use deno_config::FmtOptionsConfig; +use deno_config::TsConfig; +use deno_core::anyhow::anyhow; use deno_core::parking_lot::Mutex; use deno_core::serde::de::DeserializeOwned; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; use deno_core::serde_json; +use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; use deno_lockfile::Lockfile; +use deno_runtime::deno_node::PackageJson; +use deno_runtime::permissions::PermissionsContainer; +use import_map::ImportMap; use lsp::Url; use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -717,9 +729,9 @@ impl WorkspaceSettings { #[derive(Debug, Clone, Default)] pub struct ConfigSnapshot { pub client_capabilities: ClientCapabilities, - pub config_file: Option, pub settings: Settings, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, + pub tree: Arc, } impl ConfigSnapshot { @@ -732,7 +744,8 @@ impl ConfigSnapshot { /// Determine if the provided specifier is enabled or not. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { - if let Some(cf) = &self.config_file { + let config_file = self.tree.config_file_for_specifier(specifier); + if let Some(cf) = &config_file { if let Ok(files) = cf.to_files_config() { if !files.matches_specifier(specifier) { return false; @@ -742,14 +755,14 @@ impl ConfigSnapshot { self .settings .specifier_enabled(specifier) - .unwrap_or_else(|| self.config_file.is_some()) + .unwrap_or_else(|| config_file.is_some()) } pub fn specifier_enabled_for_test( &self, specifier: &ModuleSpecifier, ) -> bool { - if let Some(cf) = &self.config_file { + if let Some(cf) = self.tree.config_file_for_specifier(specifier) { if let Some(options) = cf.to_test_config().ok().flatten() { if !options.files.matches_specifier(specifier) { return false; @@ -861,47 +874,18 @@ impl Settings { } } -#[derive(Debug)] -struct WithCanonicalizedSpecifier { - /// Stored canonicalized specifier, which is used for file watcher events. - canonicalized_specifier: ModuleSpecifier, - file: T, -} - -/// Contains the config file and dependent information. -#[derive(Debug)] -struct LspConfigFileInfo { - config_file: WithCanonicalizedSpecifier, - /// An optional deno.lock file, which is resolved relative to the config file. - maybe_lockfile: Option>>>, - /// The canonicalized node_modules directory, which is found relative to the config file. - maybe_node_modules_dir: Option, -} - -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Config { pub client_capabilities: ClientCapabilities, pub settings: Settings, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, - /// An optional configuration file which has been specified in the client - /// options along with some data that is computed after the config file is set. - maybe_config_file_info: Option, + pub tree: Arc, } impl Config { - pub fn new() -> Self { - Self { - client_capabilities: ClientCapabilities::default(), - // Root provided by the initialization parameters. - settings: Default::default(), - workspace_folders: vec![], - maybe_config_file_info: None, - } - } - #[cfg(test)] pub fn new_with_roots(root_uris: impl IntoIterator) -> Self { - let mut config = Self::new(); + let mut config = Self::default(); let mut folders = vec![]; for root_uri in root_uris { let name = root_uri.path_segments().and_then(|s| s.last()); @@ -1001,103 +985,18 @@ impl Config { self.workspace_folders.first().map(|p| &p.0) } - pub fn maybe_node_modules_dir_path(&self) -> Option<&PathBuf> { - self - .maybe_config_file_info - .as_ref() - .and_then(|p| p.maybe_node_modules_dir.as_ref()) - } - - pub fn maybe_vendor_dir_path(&self) -> Option { - self.maybe_config_file().and_then(|c| c.vendor_dir_path()) - } - - pub fn maybe_config_file(&self) -> Option<&ConfigFile> { - self - .maybe_config_file_info - .as_ref() - .map(|c| &c.config_file.file) - } - - /// Canonicalized specifier of the config file, which should only be used for - /// file watcher events. Otherwise, prefer using the non-canonicalized path - /// as the rest of the CLI does for config files. - pub fn maybe_config_file_canonicalized_specifier( - &self, - ) -> Option<&ModuleSpecifier> { - self - .maybe_config_file_info - .as_ref() - .map(|c| &c.config_file.canonicalized_specifier) - } - - pub fn maybe_lockfile(&self) -> Option<&Arc>> { - self - .maybe_config_file_info - .as_ref() - .and_then(|c| c.maybe_lockfile.as_ref().map(|l| &l.file)) - } - - /// Canonicalized specifier of the lockfile, which should only be used for - /// file watcher events. Otherwise, prefer using the non-canonicalized path - /// as the rest of the CLI does for config files. - pub fn maybe_lockfile_canonicalized_specifier( - &self, - ) -> Option<&ModuleSpecifier> { - self.maybe_config_file_info.as_ref().and_then(|c| { - c.maybe_lockfile - .as_ref() - .map(|l| &l.canonicalized_specifier) - }) - } - - pub fn clear_config_file(&mut self) { - self.maybe_config_file_info = None; - } - - pub fn has_config_file(&self) -> bool { - self.maybe_config_file_info.is_some() - } - - pub fn set_config_file(&mut self, config_file: ConfigFile) { - self.maybe_config_file_info = Some(LspConfigFileInfo { - maybe_lockfile: resolve_lockfile_from_config(&config_file).map( - |lockfile| { - let path = canonicalize_path_maybe_not_exists(&lockfile.filename) - .unwrap_or_else(|_| lockfile.filename.clone()); - WithCanonicalizedSpecifier { - canonicalized_specifier: ModuleSpecifier::from_file_path(path) - .unwrap(), - file: Arc::new(Mutex::new(lockfile)), - } - }, - ), - maybe_node_modules_dir: resolve_node_modules_dir(&config_file), - config_file: WithCanonicalizedSpecifier { - canonicalized_specifier: config_file - .specifier - .to_file_path() - .ok() - .and_then(|p| canonicalize_path_maybe_not_exists(&p).ok()) - .and_then(|p| ModuleSpecifier::from_file_path(p).ok()) - .unwrap_or_else(|| config_file.specifier.clone()), - file: config_file, - }, - }); - } - pub fn snapshot(&self) -> Arc { Arc::new(ConfigSnapshot { client_capabilities: self.client_capabilities.clone(), - config_file: self.maybe_config_file().cloned(), settings: self.settings.clone(), workspace_folders: self.workspace_folders.clone(), + tree: self.tree.clone(), }) } pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { - let config_file = self.maybe_config_file(); - if let Some(cf) = config_file { + let config_file = self.tree.config_file_for_specifier(specifier); + if let Some(cf) = &config_file { if let Ok(files) = cf.to_files_config() { if !files.matches_specifier(specifier) { return false; @@ -1114,7 +1013,7 @@ impl Config { &self, specifier: &ModuleSpecifier, ) -> bool { - if let Some(cf) = self.maybe_config_file() { + if let Some(cf) = self.tree.config_file_for_specifier(specifier) { if let Some(options) = cf.to_test_config().ok().flatten() { if !options.files.matches_specifier(specifier) { return false; @@ -1186,6 +1085,551 @@ impl Config { } } +pub fn default_ts_config() -> TsConfig { + TsConfig::new(json!({ + "allowJs": true, + "esModuleInterop": true, + "experimentalDecorators": false, + "isolatedModules": true, + "jsx": "react", + "lib": ["deno.ns", "deno.window", "deno.unstable"], + "module": "esnext", + "moduleDetection": "force", + "noEmit": true, + "resolveJsonModule": true, + "strict": true, + "target": "esnext", + "useDefineForClassFields": true, + "useUnknownInCatchVariables": false, + })) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConfigWatchedFileType { + DenoJson, + Lockfile, + PackageJson, + ImportMap, +} + +/// Contains the config file and dependent information. +#[derive(Debug, Clone)] +pub struct ConfigData { + pub config_file: Option>, + pub fmt_options: Arc, + pub lint_options: Arc, + pub lint_rules: Arc, + pub ts_config: Arc, + pub node_modules_dir: Option, + pub vendor_dir: Option, + pub lockfile: Option>>, + pub package_json: Option>, + pub import_map: Option>, + watched_files: HashMap, +} + +impl ConfigData { + async fn load( + config_file_specifier: Option<&ModuleSpecifier>, + scope: &ModuleSpecifier, + settings: &Settings, + file_fetcher: Option<&FileFetcher>, + ) -> Self { + if let Some(specifier) = config_file_specifier { + match ConfigFile::from_specifier(specifier.clone()) { + Ok(config_file) => { + lsp_log!( + " Resolved Deno configuration file: \"{}\"", + config_file.specifier.as_str() + ); + Self::load_inner(Some(config_file), scope, settings, file_fetcher) + .await + } + Err(err) => { + lsp_warn!( + " Couldn't read Deno configuration file \"{}\": {}", + specifier.as_str(), + err + ); + let mut data = + Self::load_inner(None, scope, settings, file_fetcher).await; + data + .watched_files + .insert(specifier.clone(), ConfigWatchedFileType::DenoJson); + let canonicalized_specifier = specifier + .to_file_path() + .ok() + .and_then(|p| canonicalize_path_maybe_not_exists(&p).ok()) + .and_then(|p| ModuleSpecifier::from_file_path(p).ok()); + if let Some(specifier) = canonicalized_specifier { + data + .watched_files + .insert(specifier, ConfigWatchedFileType::DenoJson); + } + data + } + } + } else { + Self::load_inner(None, scope, settings, file_fetcher).await + } + } + + async fn load_inner( + config_file: Option, + scope: &ModuleSpecifier, + settings: &Settings, + file_fetcher: Option<&FileFetcher>, + ) -> Self { + let (settings, workspace_folder) = settings.get_for_specifier(scope); + let mut watched_files = HashMap::with_capacity(6); + if let Some(config_file) = &config_file { + watched_files + .entry(config_file.specifier.clone()) + .or_insert(ConfigWatchedFileType::DenoJson); + } + let config_file_canonicalized_specifier = config_file + .as_ref() + .and_then(|c| c.specifier.to_file_path().ok()) + .and_then(|p| canonicalize_path_maybe_not_exists(&p).ok()) + .and_then(|p| ModuleSpecifier::from_file_path(p).ok()); + if let Some(specifier) = config_file_canonicalized_specifier { + watched_files + .entry(specifier) + .or_insert(ConfigWatchedFileType::DenoJson); + } + + // Resolve some config file fields ahead of time + let fmt_options = config_file + .as_ref() + .and_then(|config_file| { + config_file + .to_fmt_config() + .and_then(|o| { + let base_path = config_file + .specifier + .to_file_path() + .map_err(|_| anyhow!("Invalid base path."))?; + FmtOptions::resolve(o, None, &base_path) + }) + .inspect_err(|err| { + lsp_warn!(" Couldn't read formatter configuration: {}", err) + }) + .ok() + }) + .unwrap_or_default(); + let lint_options = config_file + .as_ref() + .and_then(|config_file| { + config_file + .to_lint_config() + .and_then(|o| { + let base_path = config_file + .specifier + .to_file_path() + .map_err(|_| anyhow!("Invalid base path."))?; + LintOptions::resolve(o, None, &base_path) + }) + .inspect_err(|err| { + lsp_warn!(" Couldn't read lint configuration: {}", err) + }) + .ok() + }) + .unwrap_or_default(); + let lint_rules = + get_configured_rules(lint_options.rules.clone(), config_file.as_ref()); + let mut ts_config = default_ts_config(); + if let Some(config_file) = &config_file { + match config_file.to_compiler_options() { + Ok((value, maybe_ignored_options)) => { + ts_config.merge(&value); + if let Some(ignored_options) = maybe_ignored_options { + lsp_warn!("{}", ignored_options); + } + } + Err(err) => lsp_warn!("{}", err), + } + } + let node_modules_dir = + config_file.as_ref().and_then(resolve_node_modules_dir); + let vendor_dir = config_file.as_ref().and_then(|c| c.vendor_dir_path()); + + // Load lockfile + let lockfile = config_file.as_ref().and_then(resolve_lockfile_from_config); + if let Some(lockfile) = &lockfile { + if let Ok(specifier) = ModuleSpecifier::from_file_path(&lockfile.filename) + { + watched_files + .entry(specifier) + .or_insert(ConfigWatchedFileType::Lockfile); + } + } + let lockfile_canonicalized_specifier = lockfile + .as_ref() + .and_then(|lockfile| { + canonicalize_path_maybe_not_exists(&lockfile.filename).ok() + }) + .and_then(|p| ModuleSpecifier::from_file_path(p).ok()); + if let Some(specifier) = lockfile_canonicalized_specifier { + watched_files + .entry(specifier) + .or_insert(ConfigWatchedFileType::Lockfile); + } + + // Load package.json + let mut package_json = None; + if let Ok(path) = specifier_to_file_path(scope) { + let path = path.join("package.json"); + if let Ok(specifier) = ModuleSpecifier::from_file_path(&path) { + watched_files + .entry(specifier) + .or_insert(ConfigWatchedFileType::PackageJson); + } + let package_json_canonicalized_specifier = + canonicalize_path_maybe_not_exists(&path) + .ok() + .and_then(|p| ModuleSpecifier::from_file_path(p).ok()); + if let Some(specifier) = package_json_canonicalized_specifier { + watched_files + .entry(specifier) + .or_insert(ConfigWatchedFileType::PackageJson); + } + if let Ok(source) = std::fs::read_to_string(&path) { + match PackageJson::load_from_string(path.clone(), source) { + Ok(result) => { + lsp_log!(" Resolved package.json: \"{}\"", path.display()); + package_json = Some(result); + } + Err(err) => { + lsp_warn!( + " Couldn't read package.json \"{}\": {}", + path.display(), + err + ); + } + } + } + } + + // Load import map + let mut import_map = None; + let mut import_map_value = None; + let mut import_map_specifier = None; + if let Some(config_file) = &config_file { + if config_file.is_an_import_map() { + import_map_value = Some(config_file.to_import_map_value_from_imports()); + import_map_specifier = Some(config_file.specifier.clone()); + } else if let Ok(Some(specifier)) = config_file.to_import_map_specifier() + { + import_map_specifier = Some(specifier); + } + } else if let Some(import_map_str) = &settings.import_map { + if let Ok(specifier) = Url::parse(import_map_str) { + import_map_specifier = Some(specifier); + } else if let Some(folder_uri) = workspace_folder { + if let Ok(specifier) = folder_uri.join(import_map_str) { + import_map_specifier = Some(specifier); + } + } + } + if let Some(specifier) = &import_map_specifier { + if let Ok(path) = specifier_to_file_path(specifier) { + watched_files + .entry(specifier.clone()) + .or_insert(ConfigWatchedFileType::ImportMap); + let import_map_canonicalized_specifier = + canonicalize_path_maybe_not_exists(&path) + .ok() + .and_then(|p| ModuleSpecifier::from_file_path(p).ok()); + if let Some(specifier) = import_map_canonicalized_specifier { + watched_files + .entry(specifier) + .or_insert(ConfigWatchedFileType::ImportMap); + } + } + if import_map_value.is_none() { + if let Some(file_fetcher) = file_fetcher { + let fetch_result = file_fetcher + .fetch(specifier, PermissionsContainer::allow_all()) + .await; + let value_result = fetch_result.and_then(|f| { + serde_json::from_slice::(&f.source).map_err(|e| e.into()) + }); + match value_result { + Ok(value) => { + import_map_value = Some(value); + } + Err(err) => { + lsp_warn!( + " Couldn't read import map \"{}\": {}", + specifier.as_str(), + err + ); + } + } + } + } + } + if let (Some(value), Some(specifier)) = + (import_map_value, import_map_specifier) + { + match import_map::parse_from_value(specifier.clone(), value) { + Ok(result) => { + if config_file.as_ref().map(|c| &c.specifier) == Some(&specifier) { + lsp_log!(" Resolved import map from configuration file"); + } else { + lsp_log!(" Resolved import map: \"{}\"", specifier.as_str()); + } + if !result.diagnostics.is_empty() { + lsp_warn!( + " Import map diagnostics:\n{}", + result + .diagnostics + .iter() + .map(|d| format!(" - {d}")) + .collect::>() + .join("\n") + ); + } + import_map = Some(result.import_map); + } + Err(err) => { + lsp_warn!( + "Couldn't read import map \"{}\": {}", + specifier.as_str(), + err + ); + } + } + } + + ConfigData { + config_file: config_file.map(Arc::new), + fmt_options: Arc::new(fmt_options), + lint_options: Arc::new(lint_options), + lint_rules: Arc::new(lint_rules), + ts_config: Arc::new(ts_config), + node_modules_dir, + vendor_dir, + lockfile: lockfile.map(Mutex::new).map(Arc::new), + package_json: package_json.map(Arc::new), + import_map: import_map.map(Arc::new), + watched_files, + } + } +} + +#[derive(Debug, Default)] +pub struct ConfigTree { + root: Mutex)>>, +} + +impl ConfigTree { + pub fn root_data(&self) -> Option> { + self.root.lock().as_ref().map(|(_, d)| d.clone()) + } + + pub fn root_config_file(&self) -> Option> { + self + .root + .lock() + .as_ref() + .and_then(|(_, d)| d.config_file.clone()) + } + + pub fn root_ts_config(&self) -> Arc { + self + .root + .lock() + .as_ref() + .map(|(_, d)| d.ts_config.clone()) + .unwrap_or_else(|| Arc::new(default_ts_config())) + } + + pub fn root_vendor_dir(&self) -> Option { + self + .root + .lock() + .as_ref() + .and_then(|(_, d)| d.vendor_dir.clone()) + } + + pub fn root_lockfile(&self) -> Option>> { + self + .root + .lock() + .as_ref() + .and_then(|(_, d)| d.lockfile.clone()) + } + + pub fn scope_for_specifier( + &self, + _specifier: &ModuleSpecifier, + ) -> Option { + self.root.lock().as_ref().map(|r| r.0.clone()) + } + + pub fn data_for_specifier( + &self, + _specifier: &ModuleSpecifier, + ) -> Option> { + self.root_data() + } + + pub fn data_by_scope(&self) -> BTreeMap> { + self.root.lock().iter().cloned().collect() + } + + pub fn config_file_for_specifier( + &self, + _specifier: &ModuleSpecifier, + ) -> Option> { + self.root_config_file() + } + + pub fn has_config_file_for_specifier( + &self, + _specifier: &ModuleSpecifier, + ) -> bool { + self + .root + .lock() + .as_ref() + .map(|(_, d)| d.config_file.is_some()) + .unwrap_or(false) + } + + pub fn config_files(&self) -> Vec> { + self.root_config_file().into_iter().collect() + } + + pub fn package_jsons(&self) -> Vec> { + self + .root + .lock() + .as_ref() + .and_then(|(_, d)| d.package_json.clone()) + .into_iter() + .collect() + } + + pub fn fmt_options_for_specifier( + &self, + _specifier: &ModuleSpecifier, + ) -> Arc { + self + .root + .lock() + .as_ref() + .map(|(_, d)| d.fmt_options.clone()) + .unwrap_or_default() + } + + pub fn lockfile_for_specifier( + &self, + _specifier: &ModuleSpecifier, + ) -> Option>> { + self.root_lockfile() + } + + pub fn import_map_for_specifier( + &self, + _specifier: &ModuleSpecifier, + ) -> Option> { + self + .root + .lock() + .as_ref() + .and_then(|(_, d)| d.import_map.clone()) + } + + pub async fn refresh( + &self, + settings: &Settings, + root_uri: &ModuleSpecifier, + workspace_files: &BTreeSet, + file_fetcher: &FileFetcher, + ) { + lsp_log!("Refreshing configuration tree..."); + let mut root = None; + if let Some(config_path) = &settings.unscoped.config { + if let Ok(config_uri) = root_uri.join(config_path) { + root = Some(( + root_uri.clone(), + Arc::new( + ConfigData::load( + Some(&config_uri), + root_uri, + settings, + Some(file_fetcher), + ) + .await, + ), + )); + } + } else { + let get_uri_if_exists = |name| { + let uri = root_uri.join(name).ok(); + uri.filter(|s| workspace_files.contains(s)) + }; + let config_uri = get_uri_if_exists("deno.jsonc") + .or_else(|| get_uri_if_exists("deno.json")); + root = Some(( + root_uri.clone(), + Arc::new( + ConfigData::load( + config_uri.as_ref(), + root_uri, + settings, + Some(file_fetcher), + ) + .await, + ), + )); + } + *self.root.lock() = root; + } + + /// Returns (scope_uri, type). + pub fn watched_file_type( + &self, + specifier: &ModuleSpecifier, + ) -> Option<(ModuleSpecifier, ConfigWatchedFileType)> { + if let Some((scope_uri, data)) = &*self.root.lock() { + if let Some(typ) = data.watched_files.get(specifier) { + return Some((scope_uri.clone(), *typ)); + } + } + None + } + + pub fn is_watched_file(&self, specifier: &ModuleSpecifier) -> bool { + if specifier.path().ends_with("/deno.json") + || specifier.path().ends_with("/deno.jsonc") + || specifier.path().ends_with("/package.json") + { + return true; + } + self + .root + .lock() + .as_ref() + .is_some_and(|(_, d)| d.watched_files.contains_key(specifier)) + } + + #[cfg(test)] + pub async fn inject_config_file(&self, config_file: ConfigFile) { + let scope = config_file.specifier.join(".").unwrap(); + let data = ConfigData::load_inner( + Some(config_file), + &scope, + &Default::default(), + None, + ) + .await; + *self.root.lock() = Some((scope, Arc::new(data))); + } +} + fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option { let lockfile_path = match config_file.resolve_lockfile_path() { Ok(Some(value)) => value, @@ -1224,7 +1668,7 @@ fn resolve_lockfile_from_path(lockfile_path: PathBuf) -> Option { match Lockfile::new(lockfile_path, false) { Ok(value) => { if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) { - lsp_log!(" Resolved lock file: \"{}\"", specifier); + lsp_log!(" Resolved lockfile: \"{}\"", specifier); } Some(value) } @@ -1310,7 +1754,7 @@ mod tests { #[test] fn test_set_workspace_settings_defaults() { - let mut config = Config::new(); + let mut config = Config::default(); config.set_workspace_settings( serde_json::from_value(json!({})).unwrap(), vec![], @@ -1445,7 +1889,7 @@ mod tests { #[test] fn test_empty_cache() { - let mut config = Config::new(); + let mut config = Config::default(); config.set_workspace_settings( serde_json::from_value(json!({ "cache": "" })).unwrap(), vec![], @@ -1458,7 +1902,7 @@ mod tests { #[test] fn test_empty_import_map() { - let mut config = Config::new(); + let mut config = Config::default(); config.set_workspace_settings( serde_json::from_value(json!({ "import_map": "" })).unwrap(), vec![], @@ -1471,7 +1915,7 @@ mod tests { #[test] fn test_empty_tls_certificate() { - let mut config = Config::new(); + let mut config = Config::default(); config.set_workspace_settings( serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(), vec![], @@ -1484,7 +1928,7 @@ mod tests { #[test] fn test_empty_config() { - let mut config = Config::new(); + let mut config = Config::default(); config.set_workspace_settings( serde_json::from_value(json!({ "config": "" })).unwrap(), vec![], @@ -1495,16 +1939,19 @@ mod tests { ); } - #[test] - fn config_enable_via_config_file_detection() { + #[tokio::test] + async fn config_enable_via_config_file_detection() { let root_uri = resolve_url("file:///root/").unwrap(); let mut config = Config::new_with_roots(vec![root_uri.clone()]); config.settings.unscoped.enable = None; assert!(!config.specifier_enabled(&root_uri)); - config.set_config_file( - ConfigFile::new("{}", root_uri.join("deno.json").unwrap()).unwrap(), - ); + config + .tree + .inject_config_file( + ConfigFile::new("{}", root_uri.join("deno.json").unwrap()).unwrap(), + ) + .await; assert!(config.specifier_enabled(&root_uri)); } @@ -1517,8 +1964,8 @@ mod tests { assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap())); } - #[test] - fn config_specifier_enabled_for_test() { + #[tokio::test] + async fn config_specifier_enabled_for_test() { let root_uri = resolve_url("file:///root/").unwrap(); let mut config = Config::new_with_roots(vec![root_uri.clone()]); config.settings.unscoped.enable = Some(true); @@ -1537,19 +1984,22 @@ mod tests { ); config.settings.unscoped.enable_paths = None; - config.set_config_file( - ConfigFile::new( - &json!({ - "exclude": ["mod2.ts"], - "test": { - "exclude": ["mod3.ts"], - }, - }) - .to_string(), - root_uri.join("deno.json").unwrap(), + config + .tree + .inject_config_file( + ConfigFile::new( + &json!({ + "exclude": ["mod2.ts"], + "test": { + "exclude": ["mod3.ts"], + }, + }) + .to_string(), + root_uri.join("deno.json").unwrap(), + ) + .unwrap(), ) - .unwrap(), - ); + .await; assert!( config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) ); @@ -1560,38 +2010,38 @@ mod tests { !config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap()) ); - config.set_config_file( - ConfigFile::new( - &json!({ - "test": { - "include": ["mod1.ts"], - }, - }) - .to_string(), - root_uri.join("deno.json").unwrap(), + config + .tree + .inject_config_file( + ConfigFile::new( + &json!({ + "test": { + "include": ["mod1.ts"], + }, + }) + .to_string(), + root_uri.join("deno.json").unwrap(), + ) + .unwrap(), ) - .unwrap(), - ); - assert!( - config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) - ); - assert!( - !config.specifier_enabled_for_test(&root_uri.join("mod2.ts").unwrap()) - ); + .await; - config.set_config_file( - ConfigFile::new( - &json!({ - "test": { - "exclude": ["mod2.ts"], - "include": ["mod2.ts"], - }, - }) - .to_string(), - root_uri.join("deno.json").unwrap(), + config + .tree + .inject_config_file( + ConfigFile::new( + &json!({ + "test": { + "exclude": ["mod2.ts"], + "include": ["mod2.ts"], + }, + }) + .to_string(), + root_uri.join("deno.json").unwrap(), + ) + .unwrap(), ) - .unwrap(), - ); + .await; assert!( !config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) ); @@ -1600,24 +2050,27 @@ mod tests { ); } - #[test] - fn config_snapshot_specifier_enabled_for_test() { + #[tokio::test] + async fn config_snapshot_specifier_enabled_for_test() { let root_uri = resolve_url("file:///root/").unwrap(); let mut config = Config::new_with_roots(vec![root_uri.clone()]); config.settings.unscoped.enable = Some(true); - config.set_config_file( - ConfigFile::new( - &json!({ - "exclude": ["mod2.ts"], - "test": { - "exclude": ["mod3.ts"], - }, - }) - .to_string(), - root_uri.join("deno.json").unwrap(), + config + .tree + .inject_config_file( + ConfigFile::new( + &json!({ + "exclude": ["mod2.ts"], + "test": { + "exclude": ["mod3.ts"], + }, + }) + .to_string(), + root_uri.join("deno.json").unwrap(), + ) + .unwrap(), ) - .unwrap(), - ); + .await; let config_snapshot = config.snapshot(); assert!(config_snapshot .specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())); diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 7b969b8abb..e3b922ba82 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -21,7 +21,6 @@ use crate::graph_util::enhanced_resolution_error_message; use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams; use crate::resolver::SloppyImportsResolution; use crate::resolver::SloppyImportsResolver; -use crate::tools::lint::get_configured_rules; use deno_ast::MediaType; use deno_core::anyhow::anyhow; @@ -46,6 +45,7 @@ use deno_runtime::tokio_util::create_basic_runtime; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; +use import_map::ImportMap; use log::error; use std::collections::HashMap; use std::collections::HashSet; @@ -62,10 +62,10 @@ use tower_lsp::lsp_types as lsp; pub struct DiagnosticServerUpdateMessage { pub snapshot: Arc, pub config: Arc, - pub lint_options: LintOptions, pub url_map: LspUrlMap, } +#[derive(Debug)] struct DiagnosticRecord { pub specifier: ModuleSpecifier, pub versioned: VersionedDiagnostics, @@ -461,7 +461,6 @@ impl DiagnosticsServer { DiagnosticServerUpdateMessage { snapshot, config, - lint_options, url_map, }, batch_index, @@ -612,14 +611,7 @@ impl DiagnosticsServer { let mark = performance.mark("lsp.update_diagnostics_lint"); let diagnostics = spawn_blocking({ let token = token.clone(); - move || { - generate_lint_diagnostics( - &snapshot, - &config, - &lint_options, - token, - ) - } + move || generate_lint_diagnostics(&snapshot, &config, token) }) .await .unwrap(); @@ -791,17 +783,12 @@ fn ts_json_to_diagnostics( fn generate_lint_diagnostics( snapshot: &language_server::StateSnapshot, config: &ConfigSnapshot, - lint_options: &LintOptions, token: CancellationToken, ) -> DiagnosticVec { let documents = snapshot .documents .documents(DocumentsFilter::OpenDiagnosable); - let lint_rules = get_configured_rules( - lint_options.rules.clone(), - config.config_file.as_ref(), - ) - .rules; + let config_data_by_scope = config.tree.data_by_scope(); let mut diagnostics_vec = Vec::new(); for document in documents { let settings = @@ -820,14 +807,20 @@ fn generate_lint_diagnostics( } } let version = document.maybe_lsp_version(); + let (lint_options, lint_rules) = config + .tree + .scope_for_specifier(document.specifier()) + .and_then(|s| config_data_by_scope.get(&s)) + .map(|d| (d.lint_options.clone(), d.lint_rules.clone())) + .unwrap_or_default(); diagnostics_vec.push(DiagnosticRecord { specifier: document.specifier().clone(), versioned: VersionedDiagnostics { version, diagnostics: generate_document_lint_diagnostics( config, - lint_options, - lint_rules.clone(), + &lint_options, + lint_rules.rules.clone(), &document, ), }, @@ -1304,6 +1297,7 @@ fn diagnose_resolution( resolution: &Resolution, is_dynamic: bool, maybe_assert_type: Option<&str>, + import_map: Option<&ImportMap>, ) -> Vec { fn check_redirect_diagnostic( specifier: &ModuleSpecifier, @@ -1392,7 +1386,7 @@ fn diagnose_resolution( .push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone())); } else if module_name == dependency_key { let mut is_mapped = false; - if let Some(import_map) = &snapshot.maybe_import_map { + if let Some(import_map) = import_map { if let Resolution::Ok(resolved) = &resolution { if import_map.resolve(module_name, &resolved.specifier).is_ok() { is_mapped = true; @@ -1455,7 +1449,8 @@ fn diagnose_dependency( } } - if let Some(import_map) = &snapshot.maybe_import_map { + let import_map = snapshot.config.tree.import_map_for_specifier(referrer); + if let Some(import_map) = &import_map { if let Resolution::Ok(resolved) = &dependency.maybe_code { if let Some(to) = import_map.lookup(&resolved.specifier, referrer) { if dependency_key != to { @@ -1504,6 +1499,7 @@ fn diagnose_dependency( }, dependency.is_dynamic, dependency.maybe_attribute_type.as_deref(), + import_map.as_deref(), ) .iter() .flat_map(|diag| { @@ -1526,6 +1522,7 @@ fn diagnose_dependency( &dependency.maybe_type, dependency.is_dynamic, dependency.maybe_attribute_type.as_deref(), + import_map.as_deref(), ) .iter() .map(|diag| diag.to_lsp_diagnostic(&range)), @@ -1580,20 +1577,21 @@ mod tests { use super::*; use crate::cache::GlobalHttpCache; use crate::cache::RealDenoCacheEnv; + use crate::lsp::config::Config; use crate::lsp::config::ConfigSnapshot; use crate::lsp::config::Settings; use crate::lsp::config::WorkspaceSettings; use crate::lsp::documents::Documents; use crate::lsp::documents::LanguageId; use crate::lsp::language_server::StateSnapshot; - use deno_config::glob::FilePatterns; + use deno_config::ConfigFile; use pretty_assertions::assert_eq; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use test_util::TempDir; - fn mock_state_snapshot( + async fn mock_state_snapshot( fixtures: &[(&str, &str, i32, LanguageId)], location: &Path, maybe_import_map: Option<(&str, &str)>, @@ -1613,22 +1611,19 @@ mod tests { (*source).into(), ); } - let maybe_import_map = maybe_import_map.map(|(base, json_string)| { - let base_url = ModuleSpecifier::parse(base).unwrap(); - let result = import_map::parse_from_json(&base_url, json_string).unwrap(); - if !result.diagnostics.is_empty() { - panic!("unexpected import map diagnostics"); - } - Arc::new(result.import_map) - }); + let config = Config::new_with_roots([resolve_url("file:///").unwrap()]); + if let Some((base_url, json_string)) = maybe_import_map { + let base_url = resolve_url(base_url).unwrap(); + let config_file = ConfigFile::new(json_string, base_url).unwrap(); + config.tree.inject_config_file(config_file).await; + } StateSnapshot { documents, - maybe_import_map, assets: Default::default(), cache_metadata: cache::CacheMetadata::new(Arc::new( GlobalHttpCache::new(location.to_path_buf(), RealDenoCacheEnv), )), - config: Default::default(), + config: config.snapshot(), npm: None, } } @@ -1655,14 +1650,14 @@ mod tests { } } - fn setup( + async fn setup( temp_dir: &TempDir, sources: &[(&str, &str, i32, LanguageId)], maybe_import_map: Option<(&str, &str)>, ) -> (StateSnapshot, PathBuf) { let location = temp_dir.path().join("deps").to_path_buf(); let state_snapshot = - mock_state_snapshot(sources, &location, maybe_import_map); + mock_state_snapshot(sources, &location, maybe_import_map).await; (state_snapshot, location) } @@ -1681,18 +1676,14 @@ let c: number = "a"; LanguageId::TypeScript, )], None, - ); + ) + .await; let snapshot = Arc::new(snapshot); let cache = Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv)); - let ts_server = TsServer::new(Default::default(), cache); + let ts_server = + TsServer::new(Default::default(), cache, Default::default()); ts_server.start(None); - let lint_options = LintOptions { - rules: Default::default(), - files: FilePatterns::new_with_base(temp_dir.path().to_path_buf()), - reporter_kind: Default::default(), - fix: false, - }; // test enabled { @@ -1700,7 +1691,6 @@ let c: number = "a"; let diagnostics = generate_lint_diagnostics( &snapshot, &enabled_config, - &lint_options, Default::default(), ); assert_eq!(get_diagnostics_for_single(diagnostics).len(), 6); @@ -1712,7 +1702,7 @@ let c: number = "a"; ) .await .unwrap(); - assert_eq!(get_diagnostics_for_single(diagnostics).len(), 5); + assert_eq!(get_diagnostics_for_single(diagnostics).len(), 4); let diagnostics = generate_deno_diagnostics( &snapshot, &enabled_config, @@ -1732,7 +1722,6 @@ let c: number = "a"; let diagnostics = generate_lint_diagnostics( &snapshot, &disabled_config, - &lint_options, Default::default(), ); assert_eq!(get_diagnostics_for_single(diagnostics).len(), 0); @@ -1793,7 +1782,8 @@ let c: number = "a"; } }"#, )), - ); + ) + .await; let config = mock_config(); let token = CancellationToken::new(); let actual = generate_deno_diagnostics(&snapshot, &config, token); @@ -1919,7 +1909,8 @@ let c: number = "a"; LanguageId::TypeScript, )], None, - ); + ) + .await; let config = mock_config(); let token = CancellationToken::new(); let actual = generate_deno_diagnostics(&snapshot, &config, token); diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 9aa862dbc9..7d1d89b8d6 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -37,7 +37,6 @@ use deno_lockfile::Lockfile; use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; -use deno_runtime::deno_node::PackageJson; use deno_runtime::permissions::PermissionsContainer; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; @@ -817,15 +816,6 @@ impl FileSystemDocuments { } } -pub struct UpdateDocumentConfigOptions<'a> { - pub config: &'a Config, - pub maybe_import_map: Option>, - pub maybe_package_json: Option<&'a PackageJson>, - pub node_resolver: Option>, - pub npm_resolver: Option>, - pub workspace_files: &'a BTreeSet, -} - /// Specify the documents to include on a `documents.documents(...)` call. #[derive(Debug, Clone, Copy)] pub enum DocumentsFilter { @@ -1309,26 +1299,34 @@ impl Documents { Arc::new(JsrCacheResolver::new(self.cache.clone(), lockfile)); } - pub fn update_config(&mut self, options: UpdateDocumentConfigOptions) { - let maybe_config_file = options.config.maybe_config_file(); - let maybe_package_json_deps = - options.maybe_package_json.map(|package_json| { - package_json::get_local_package_json_version_reqs(package_json) - }); - let maybe_jsx_config = maybe_config_file - .and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()); - let deps_provider = - Arc::new(PackageJsonDepsProvider::new(maybe_package_json_deps)); + pub fn update_config( + &mut self, + config: &Config, + node_resolver: Option>, + npm_resolver: Option>, + workspace_files: &BTreeSet, + ) { + let config_data = config.tree.root_data(); + let config_file = + config_data.as_ref().and_then(|d| d.config_file.as_deref()); self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions { - node_resolver: options.node_resolver, - npm_resolver: options.npm_resolver, - package_json_deps_provider: deps_provider, - maybe_jsx_import_source_config: maybe_jsx_config, - maybe_import_map: options.maybe_import_map, - maybe_vendor_dir: maybe_config_file - .and_then(|c| c.vendor_dir_path()) - .as_ref(), - bare_node_builtins_enabled: maybe_config_file + node_resolver, + npm_resolver, + package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new( + config_data + .as_ref() + .and_then(|d| d.package_json.as_ref()) + .map(|package_json| { + package_json::get_local_package_json_version_reqs(package_json) + }), + )), + maybe_jsx_import_source_config: config_file + .and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()), + maybe_import_map: config_data.as_ref().and_then(|d| d.import_map.clone()), + maybe_vendor_dir: config_data + .as_ref() + .and_then(|d| d.vendor_dir.as_ref()), + bare_node_builtins_enabled: config_file .map(|config| config.has_unstable("bare-node-builtins")) .unwrap_or(false), // Don't set this for the LSP because instead we'll use the OpenDocumentsLoader @@ -1338,16 +1336,14 @@ impl Documents { })); self.jsr_resolver = Arc::new(JsrCacheResolver::new( self.cache.clone(), - options.config.maybe_lockfile().cloned(), + config.tree.root_lockfile(), )); self.redirect_resolver = Arc::new(RedirectResolver::new(self.cache.clone())); let resolver = self.resolver.as_graph_resolver(); let npm_resolver = self.resolver.as_graph_npm_resolver(); self.imports = Arc::new( - if let Some(Ok(imports)) = - maybe_config_file.map(|cf| cf.to_maybe_imports()) - { + if let Some(Ok(imports)) = config_file.map(|cf| cf.to_maybe_imports()) { imports .into_iter() .map(|(referrer, imports)| { @@ -1364,7 +1360,7 @@ impl Documents { IndexMap::new() }, ); - self.unstable_sloppy_imports = maybe_config_file + self.unstable_sloppy_imports = config_file .map(|c| c.has_unstable("sloppy-imports")) .unwrap_or(false); { @@ -1376,7 +1372,7 @@ impl Documents { // anymore after updating resolvers. return false; }; - if !options.config.specifier_enabled(specifier) { + if !config.specifier_enabled(specifier) { return false; } path.is_file() @@ -1384,7 +1380,7 @@ impl Documents { let mut open_docs = std::mem::take(&mut self.open_docs); for docs in [&mut open_docs, &mut fs_docs.docs] { for doc in docs.values_mut() { - if !options.config.specifier_enabled(doc.specifier()) { + if !config.specifier_enabled(doc.specifier()) { continue; } if let Some(new_doc) = @@ -1395,8 +1391,8 @@ impl Documents { } } self.open_docs = open_docs; - for specifier in options.workspace_files { - if !options.config.specifier_enabled(specifier) { + for specifier in workspace_files { + if !config.specifier_enabled(specifier) { continue; } if !self.open_docs.contains_key(specifier) @@ -1738,8 +1734,9 @@ mod tests { use crate::cache::RealDenoCacheEnv; use super::*; + use deno_config::ConfigFile; use deno_core::serde_json; - use import_map::ImportMap; + use deno_core::serde_json::json; use pretty_assertions::assert_eq; use test_util::PathRef; use test_util::TempDir; @@ -1842,8 +1839,8 @@ console.log(b, "hello deno"); assert_eq!(documents.documents(DocumentsFilter::All).len(), 1); } - #[test] - fn test_documents_refresh_dependencies_config_change() { + #[tokio::test] + async fn test_documents_refresh_dependencies_config_change() { // it should never happen that a user of this API causes this to happen, // but we'll guard against it anyway let temp_dir = TempDir::new(); @@ -1878,23 +1875,23 @@ console.log(b, "hello deno"); // set the initial import map and point to file 2 { - let mut import_map = ImportMap::new( - ModuleSpecifier::from_file_path(documents_path.join("import_map.json")) + config + .tree + .inject_config_file( + ConfigFile::new( + &json!({ + "imports": { + "test": "./file2.ts", + }, + }) + .to_string(), + config.root_uri().unwrap().join("deno.json").unwrap(), + ) .unwrap(), - ); - import_map - .imports_mut() - .append("test".to_string(), "./file2.ts".to_string()) - .unwrap(); + ) + .await; - documents.update_config(UpdateDocumentConfigOptions { - config: &config, - maybe_import_map: Some(Arc::new(import_map)), - maybe_package_json: None, - node_resolver: None, - npm_resolver: None, - workspace_files: &workspace_files, - }); + documents.update_config(&config, None, None, &workspace_files); // open the document let document = documents.open( @@ -1918,23 +1915,23 @@ console.log(b, "hello deno"); // now point at file 3 { - let mut import_map = ImportMap::new( - ModuleSpecifier::from_file_path(documents_path.join("import_map.json")) + config + .tree + .inject_config_file( + ConfigFile::new( + &json!({ + "imports": { + "test": "./file3.ts", + }, + }) + .to_string(), + config.root_uri().unwrap().join("deno.json").unwrap(), + ) .unwrap(), - ); - import_map - .imports_mut() - .append("test".to_string(), "./file3.ts".to_string()) - .unwrap(); + ) + .await; - documents.update_config(UpdateDocumentConfigOptions { - config: &config, - maybe_import_map: Some(Arc::new(import_map)), - maybe_package_json: None, - node_resolver: None, - npm_resolver: None, - workspace_files: &workspace_files, - }); + documents.update_config(&config, None, None, &workspace_files); // check the document's dependencies let document = documents.get(&file1_specifier).unwrap(); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index de5f7e3576..fafd9fe4ca 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -18,11 +18,9 @@ use deno_lockfile::Lockfile; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs; use deno_runtime::deno_node::NodeResolver; -use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_semver::jsr::JsrPackageReqReference; -use import_map::ImportMap; use indexmap::IndexSet; use log::error; use serde::Deserialize; @@ -71,7 +69,6 @@ use super::documents::Document; use super::documents::Documents; use super::documents::DocumentsFilter; use super::documents::LanguageId; -use super::documents::UpdateDocumentConfigOptions; use super::jsr::CliJsrSearchApi; use super::logging::lsp_log; use super::logging::lsp_warn; @@ -92,16 +89,11 @@ use super::tsc::GetCompletionDetailsArgs; use super::tsc::TsServer; use super::urls; use crate::args::get_root_cert_store; -use crate::args::package_json; -use crate::args::resolve_import_map; use crate::args::CaData; use crate::args::CacheSetting; use crate::args::CliOptions; use crate::args::ConfigFile; use crate::args::Flags; -use crate::args::FmtOptions; -use crate::args::LintOptions; -use crate::args::TsConfig; use crate::cache::DenoDir; use crate::cache::FastInsecureHasher; use crate::cache::GlobalHttpCache; @@ -111,6 +103,7 @@ use crate::factory::CliFactory; use crate::file_fetcher::FileFetcher; use crate::graph_util; use crate::http_util::HttpClient; +use crate::lsp::config::ConfigWatchedFileType; use crate::lsp::logging::init_log_file; use crate::lsp::tsc::file_text_changes_to_workspace_edit; use crate::lsp::urls::LspUrlKind; @@ -157,10 +150,15 @@ struct LspNpmConfigHash(u64); impl LspNpmConfigHash { pub fn from_inner(inner: &Inner) -> Self { + let config_data = inner.config.tree.root_data(); + let node_modules_dir = config_data + .as_ref() + .and_then(|d| d.node_modules_dir.as_ref()); + let lockfile = config_data.as_ref().and_then(|d| d.lockfile.as_ref()); let mut hasher = FastInsecureHasher::new(); - hasher.write_hashable(inner.config.maybe_node_modules_dir_path()); + hasher.write_hashable(node_modules_dir); hasher.write_hashable(&inner.maybe_global_cache_path); - if let Some(lockfile) = inner.config.maybe_lockfile() { + if let Some(lockfile) = lockfile { hasher.write_hashable(&*lockfile.lock()); } Self(hasher.finish()) @@ -183,7 +181,6 @@ pub struct StateSnapshot { pub cache_metadata: cache::CacheMetadata, pub config: Arc, pub documents: Documents, - pub maybe_import_map: Option>, pub npm: Option, } @@ -254,14 +251,6 @@ pub struct Inner { /// An optional path to the DENO_DIR which has been specified in the client /// options. maybe_global_cache_path: Option, - /// An optional import map which is used to resolve modules. - maybe_import_map: Option>, - /// An optional package.json configuration file. - maybe_package_json: Option, - /// Configuration for formatter which has been taken from specified config file. - fmt_options: FmtOptions, - /// An optional configuration for linter which has been taken from specified config file. - lint_options: LintOptions, /// A lazily create "server" for handling test run requests. maybe_testing_server: Option, /// Services used for dealing with npm related functionality. @@ -344,7 +333,11 @@ impl LanguageServer { // do as much as possible in a read, then do a write outside let maybe_prepare_cache_result = { let inner = self.0.read().await; // ensure dropped - match inner.prepare_cache(specifiers, referrer, force_global_cache) { + match inner.prepare_cache( + specifiers, + referrer.clone(), + force_global_cache, + ) { Ok(maybe_cache_result) => maybe_cache_result, Err(err) => { lsp_warn!("Error preparing caching: {:#}", err); @@ -376,7 +369,7 @@ impl LanguageServer { } { let mut inner = self.0.write().await; - let lockfile = inner.config.maybe_lockfile().cloned(); + let lockfile = inner.config.tree.lockfile_for_specifier(&referrer); inner.documents.refresh_jsr_resolver(lockfile); inner.refresh_npm_specifiers().await; } @@ -530,9 +523,12 @@ impl Inner { let documents = Documents::new(deps_http_cache.clone()); let cache_metadata = cache::CacheMetadata::new(deps_http_cache.clone()); let performance = Arc::new(Performance::default()); - let ts_server = - Arc::new(TsServer::new(performance.clone(), deps_http_cache.clone())); - let config = Config::new(); + let config = Config::default(); + let ts_server = Arc::new(TsServer::new( + performance.clone(), + deps_http_cache.clone(), + config.tree.clone(), + )); let diagnostics_state = Arc::new(DiagnosticsState::default()); let diagnostics_server = DiagnosticsServer::new( client.clone(), @@ -558,11 +554,7 @@ impl Inner { initial_cwd: initial_cwd.clone(), jsr_search_api, maybe_global_cache_path: None, - maybe_import_map: None, - maybe_package_json: None, - fmt_options: FmtOptions::new_with_base(initial_cwd.clone()), task_queue: Default::default(), - lint_options: LintOptions::new_with_base(initial_cwd), maybe_testing_server: None, module_registries, module_registries_location, @@ -645,76 +637,6 @@ impl Inner { Ok(navigation_tree) } - fn get_config_file(&self) -> Result, AnyError> { - let workspace_settings = self.config.workspace_settings(); - let maybe_config = &workspace_settings.config; - if let Some(config_str) = maybe_config { - if !config_str.is_empty() { - lsp_log!("Setting Deno 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) = self.config.root_uri() { - root_uri.join(config_str).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 - )) - }?; - lsp_log!(" Resolved configuration file: \"{}\"", config_url); - - let config_file = ConfigFile::from_specifier(config_url)?; - return Ok(Some(config_file)); - } - } - - // Auto-discover config - - // It is possible that root_uri is not set, for example when having a single - // file open and not a workspace. In those situations we can't - // automatically discover the configuration - if let Some(root_uri) = self.config.root_uri() { - let root_path = specifier_to_file_path(root_uri)?; - let mut checked = std::collections::HashSet::new(); - let maybe_config = - ConfigFile::discover_from(&root_path, &mut checked, None)?; - Ok(maybe_config.map(|c| { - lsp_log!(" Auto-resolved configuration file: \"{}\"", c.specifier); - c - })) - } else { - Ok(None) - } - } - - fn get_package_json( - &self, - maybe_config_file: Option<&ConfigFile>, - ) -> Result, AnyError> { - if crate::args::has_flag_env_var("DENO_NO_PACKAGE_JSON") { - return Ok(None); - } - - // It is possible that root_uri is not set, for example when having a single - // file open and not a workspace. In those situations we can't - // automatically discover the configuration - if let Some(root_uri) = self.config.root_uri() { - let root_path = specifier_to_file_path(root_uri)?; - let maybe_package_json = package_json::discover_from( - &root_path, - maybe_config_file.and_then(|f| f.specifier.to_file_path().ok()), - )?; - Ok(maybe_package_json.map(|c| { - lsp_log!(" Auto-resolved package.json: \"{}\"", c.specifier()); - c - })) - } else { - Ok(None) - } - } - fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool { if specifier.scheme() == "asset" { matches!( @@ -740,23 +662,6 @@ impl Inner { } } - fn merge_user_tsconfig( - &self, - tsconfig: &mut TsConfig, - ) -> Result<(), AnyError> { - if let Some(config_file) = self.config.maybe_config_file() { - let (value, maybe_ignored_options) = config_file.to_compiler_options()?; - tsconfig.merge(&value); - if let Some(ignored_options) = maybe_ignored_options { - // TODO(@kitsonk) turn these into diagnostics that can be sent to the - // client - lsp_warn!("{}", ignored_options); - } - } - - Ok(()) - } - pub fn snapshot(&self) -> Arc { let maybe_state_npm_snapshot = self .npm @@ -785,7 +690,6 @@ impl Inner { cache_metadata: self.cache_metadata.clone(), config: self.config.snapshot(), documents: self.documents.clone(), - maybe_import_map: self.maybe_import_map.clone(), npm: maybe_state_npm_snapshot, }) } @@ -876,7 +780,7 @@ impl Inner { self.jsr_search_api = CliJsrSearchApi::new(deps_file_fetcher.clone()); self.npm.search_api = CliNpmSearchApi::new(deps_file_fetcher); let maybe_local_cache = - self.config.maybe_vendor_dir_path().map(|local_path| { + self.config.tree.root_vendor_dir().map(|local_path| { Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone())) }); let cache: Arc = maybe_local_cache @@ -903,14 +807,16 @@ impl Inner { if config_hash == self.npm.config_hash { return; // no need to do anything } - + let config_data = self.config.tree.root_data(); let npm_resolver = create_npm_resolver( &deno_dir, &self.initial_cwd, &self.http_client, - self.config.maybe_config_file(), - self.config.maybe_lockfile(), - self.config.maybe_node_modules_dir_path().cloned(), + config_data.as_ref().and_then(|d| d.config_file.as_deref()), + config_data.as_ref().and_then(|d| d.lockfile.as_ref()), + config_data + .as_ref() + .and_then(|d| d.node_modules_dir.clone()), ) .await; let node_resolver = Arc::new(NodeResolver::new( @@ -929,41 +835,6 @@ impl Inner { self.npm.config_hash = config_hash; } - pub async fn update_import_map(&mut self) -> Result<(), AnyError> { - let mark = self.performance.mark("lsp.update_import_map"); - - let maybe_import_map_url = self.resolve_import_map_specifier()?; - let maybe_import_map = self - .fetch_import_map( - maybe_import_map_url.as_ref(), - CacheSetting::RespectHeaders, - ) - .await?; - if let Some(import_map) = maybe_import_map { - if import_map.base_url().scheme() != "data" { - lsp_log!(" Resolved import map: \"{}\"", import_map.base_url()); - } - self.maybe_import_map = Some(Arc::new(import_map)); - } else { - self.maybe_import_map = None; - } - self.performance.measure(mark); - Ok(()) - } - - async fn fetch_import_map( - &self, - import_map_url: Option<&ModuleSpecifier>, - cache_setting: CacheSetting, - ) -> Result, AnyError> { - resolve_import_map( - import_map_url, - self.config.maybe_config_file(), - &self.create_file_fetcher(cache_setting), - ) - .await - } - fn create_file_fetcher(&self, cache_setting: CacheSetting) -> FileFetcher { let mut file_fetcher = FileFetcher::new( self.deps_http_cache.clone(), @@ -979,10 +850,13 @@ impl Inner { fn resolve_import_map_specifier( &self, + referrer: &ModuleSpecifier, ) -> Result, AnyError> { let Some(import_map_str) = self .config - .workspace_settings() + .settings + .get_for_specifier(referrer) + .0 .import_map .clone() .and_then(|s| if s.is_empty() { None } else { Some(s) }) @@ -993,7 +867,9 @@ impl Inner { "Using import map from workspace settings: \"{}\"", import_map_str ); - if let Some(config_file) = self.config.maybe_config_file() { + if let Some(config_file) = + self.config.tree.config_file_for_specifier(referrer) + { if let Some(import_map_path) = &config_file.json.import_map { lsp_log!("Warning: Import map \"{}\" configured in \"{}\" being ignored due to an import map being explicitly configured in workspace settings.", import_map_path, config_file.specifier); } @@ -1036,103 +912,6 @@ impl Inner { self.performance.measure(mark); Ok(()) } - - fn update_config_file(&mut self) -> Result<(), AnyError> { - self.config.clear_config_file(); - self.fmt_options = FmtOptions::new_with_base(self.initial_cwd.clone()); - self.lint_options = LintOptions::new_with_base(self.initial_cwd.clone()); - if let Some(config_file) = self.get_config_file()? { - let lint_options = config_file - .to_lint_config() - .and_then(|maybe_lint_config| { - LintOptions::resolve(maybe_lint_config, None, &self.initial_cwd) - }) - .map_err(|err| { - anyhow!("Unable to update lint configuration: {:?}", err) - })?; - let fmt_options = config_file - .to_fmt_config() - .and_then(|maybe_fmt_config| { - FmtOptions::resolve(maybe_fmt_config, None, &self.initial_cwd) - }) - .map_err(|err| { - anyhow!("Unable to update formatter configuration: {:?}", err) - })?; - - self.config.set_config_file(config_file); - self.lint_options = lint_options; - self.fmt_options = fmt_options; - self.recreate_http_client_and_dependents()?; - if let Some(config_file) = self.config.maybe_config_file() { - if let Ok((compiler_options, _)) = config_file.to_compiler_options() { - if let Some(compiler_options_obj) = compiler_options.as_object() { - if let Some(jsx_import_source) = - compiler_options_obj.get("jsxImportSource") - { - if let Some(jsx_import_source) = jsx_import_source.as_str() { - let specifiers = vec![Url::parse(&format!( - "data:application/typescript;base64,{}", - base64::engine::general_purpose::STANDARD.encode(format!( - "import '{jsx_import_source}/jsx-runtime';" - )) - )) - .unwrap()]; - let referrer = config_file.specifier.clone(); - self.task_queue.queue_task(Box::new(|ls: LanguageServer| { - spawn(async move { - if let Err(err) = - ls.cache(specifiers, referrer, false).await - { - lsp_warn!("{:#}", err); - } - }); - })); - } - } - } - } - } - } - - Ok(()) - } - - /// Updates the package.json. Always ensure this is done after updating - /// the configuration file as the resolution of this depends on that. - fn update_package_json(&mut self) -> Result<(), AnyError> { - self.maybe_package_json = None; - self.maybe_package_json = - self.get_package_json(self.config.maybe_config_file())?; - Ok(()) - } - - async fn update_tsconfig(&mut self) -> Result<(), AnyError> { - let mark = self.performance.mark("lsp.update_tsconfig"); - let mut tsconfig = TsConfig::new(json!({ - "allowJs": true, - "esModuleInterop": true, - "experimentalDecorators": false, - "isolatedModules": true, - "jsx": "react", - "lib": ["deno.ns", "deno.window", "deno.unstable"], - "module": "esnext", - "moduleDetection": "force", - "noEmit": true, - "resolveJsonModule": true, - "strict": true, - "target": "esnext", - "useDefineForClassFields": true, - // TODO(@kitsonk) remove for Deno 1.15 - "useUnknownInCatchVariables": false, - })); - if let Err(err) = self.merge_user_tsconfig(&mut tsconfig) { - lsp_warn!("Error merging tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - let _ok = self.ts_server.configure(self.snapshot(), tsconfig).await?; - self.performance.measure(mark); - Ok(()) - } } async fn create_npm_resolver( @@ -1272,23 +1051,12 @@ impl Inner { .start(self.config.internal_inspect().to_address()); self.update_debug_flag(); - // Check to see if we need to change the cache path + self.refresh_workspace_files(); + self.refresh_config_tree().await; if let Err(err) = self.update_cache() { lsp_warn!("Error updating cache: {:#}", err); self.client.show_message(MessageType::WARNING, err); } - if let Err(err) = self.update_config_file() { - lsp_warn!("Error updating config file: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_package_json() { - lsp_warn!("Error updating package.json: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } if capabilities.code_action_provider.is_some() { let fixable_diagnostics = self @@ -1298,11 +1066,6 @@ impl Inner { self.ts_fixable_diagnostics = fixable_diagnostics; } - // Check to see if we need to setup the import map - if let Err(err) = self.update_import_map().await { - lsp_warn!("Error updating import map: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } // Check to see if we need to setup any module registries if let Err(err) = self.update_registries().await { lsp_warn!("Error updating registries: {:#}", err); @@ -1450,15 +1213,58 @@ impl Inner { self.workspace_files_hash = enable_settings_hash; } + async fn refresh_config_tree(&mut self) { + let file_fetcher = self.create_file_fetcher(CacheSetting::RespectHeaders); + if let Some(root_uri) = self.config.root_uri() { + self + .config + .tree + .refresh( + &self.config.settings, + root_uri, + &self.workspace_files, + &file_fetcher, + ) + .await; + for config_file in self.config.tree.config_files() { + if let Ok((compiler_options, _)) = config_file.to_compiler_options() { + if let Some(compiler_options_obj) = compiler_options.as_object() { + if let Some(jsx_import_source) = + compiler_options_obj.get("jsxImportSource") + { + if let Some(jsx_import_source) = jsx_import_source.as_str() { + let specifiers = vec![Url::parse(&format!( + "data:application/typescript;base64,{}", + base64::engine::general_purpose::STANDARD.encode(format!( + "import '{jsx_import_source}/jsx-runtime';" + )) + )) + .unwrap()]; + let referrer = config_file.specifier.clone(); + self.task_queue.queue_task(Box::new(|ls: LanguageServer| { + spawn(async move { + if let Err(err) = + ls.cache(specifiers, referrer, false).await + { + lsp_warn!("{:#}", err); + } + }); + })); + } + } + } + } + } + } + } + async fn refresh_documents_config(&mut self) { - self.documents.update_config(UpdateDocumentConfigOptions { - config: &self.config, - maybe_import_map: self.maybe_import_map.clone(), - maybe_package_json: self.maybe_package_json.as_ref(), - node_resolver: self.npm.node_resolver.clone(), - npm_resolver: self.npm.resolver.clone(), - workspace_files: &self.workspace_files, - }); + self.documents.update_config( + &self.config, + self.npm.node_resolver.clone(), + self.npm.resolver.clone(), + &self.workspace_files, + ); // refresh the npm specifiers because it might have discovered // a @types/node package and now's a good time to do that anyway @@ -1591,6 +1397,8 @@ impl Inner { }; self.update_debug_flag(); + self.refresh_workspace_files(); + self.refresh_config_tree().await; if let Err(err) = self.update_cache() { lsp_warn!("Error updating cache: {:#}", err); self.client.show_message(MessageType::WARNING, err); @@ -1599,27 +1407,8 @@ impl Inner { lsp_warn!("Error updating registries: {:#}", err); self.client.show_message(MessageType::WARNING, err); } - if let Err(err) = self.update_config_file() { - lsp_warn!("Error updating config file: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_package_json() { - lsp_warn!("Error updating package.json: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_import_map().await { - lsp_warn!("Error updating import map: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - self.recreate_npm_services_if_necessary().await; - self.refresh_workspace_files(); self.refresh_documents_config().await; - self.diagnostics_server.invalidate_all(); self.send_diagnostics_update(); self.send_testing_update(); @@ -1629,194 +1418,73 @@ impl Inner { &mut self, params: DidChangeWatchedFilesParams, ) { - fn has_lockfile_content_changed(lockfile: &Lockfile) -> bool { - match Lockfile::new(lockfile.filename.clone(), false) { - Ok(new_lockfile) => { - // only update if the lockfile has changed - FastInsecureHasher::hash(lockfile) - != FastInsecureHasher::hash(new_lockfile) - } - Err(err) => { - lsp_warn!("Error loading lockfile: {:#}", err); - false - } - } - } - - fn has_config_changed(config: &Config, changes: &IndexSet) -> bool { - // Check the canonicalized specifier here because file watcher - // changes will be for the canonicalized path in vscode, but also check the - // non-canonicalized specifier in order to please the tests and handle - // a client that might send that instead. - if config - .maybe_config_file_canonicalized_specifier() - .map(|s| changes.contains(s)) - .unwrap_or(false) - { - return true; - } - match config.maybe_config_file() { - Some(file) => { - if changes.contains(&file.specifier) { - return true; - } - } - None => { - // check for auto-discovery - if changes.iter().any(|url| { - url.path().ends_with("/deno.json") - || url.path().ends_with("/deno.jsonc") - }) { - return true; - } - } - } - - // if the lockfile has changed, reload the config as well - if let Some(lockfile) = config.maybe_lockfile() { - let lockfile_matches = config - .maybe_lockfile_canonicalized_specifier() - .map(|s| changes.contains(s)) - .or_else(|| { - ModuleSpecifier::from_file_path(&lockfile.lock().filename) - .ok() - .map(|s| changes.contains(&s)) - }) - .unwrap_or(false); - lockfile_matches && has_lockfile_content_changed(&lockfile.lock()) - } else { - // check for auto-discovery - changes.iter().any(|url| url.path().ends_with("/deno.lock")) - } - } - let mark = self .performance .mark_with_args("lsp.did_change_watched_files", ¶ms); - let mut touched = false; - let changes: IndexSet = params + + let changes = params .changes + .into_iter() + .map(|e| (self.url_map.normalize_url(&e.uri, LspUrlKind::File), e)) + .collect::>(); + if changes .iter() - .map(|f| self.url_map.normalize_url(&f.uri, LspUrlKind::File)) - .collect(); - - let mut config_changes = IndexSet::with_capacity(changes.len()); - - // if the current deno.json has changed, we need to reload it - if has_config_changed(&self.config, &changes) { - // Check the 'current' config specifier from both before and after it's - // updated. Check canonicalized and uncanonicalized variants for each. - // If any are included in `changes`, send our custom notification for - // `deno.json` changes: `deno/didChangeDenoConfigurationNotification`. - let mut files_to_check = IndexSet::with_capacity(4); - // Collect previous config specifiers. - if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) { - files_to_check.insert(url.clone()); - } - if let Some(url) = self.config.maybe_config_file_canonicalized_specifier() - { - files_to_check.insert(url.clone()); - } - // Update config. - if let Err(err) = self.update_config_file() { - lsp_warn!("Error updating config file: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - // Collect new config specifiers. - if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) { - files_to_check.insert(url.clone()); - } - if let Some(url) = self.config.maybe_config_file_canonicalized_specifier() - { - files_to_check.insert(url.clone()); - } - if let Some(root_uri) = self.config.root_uri() { - config_changes.extend( - params - .changes - .iter() - .filter(|e| files_to_check.contains(&e.uri)) - .map(|e| lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), - file_uri: e.uri.clone(), - typ: - lsp_custom::DenoConfigurationChangeType::from_file_change_type( - e.typ, - ), - configuration_type: lsp_custom::DenoConfigurationType::DenoJson, - }), - ); - } - if let Err(err) = self.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - touched = true; - } - - let has_package_json_changed = changes - .iter() - .any(|e| e.as_str().ends_with("/package.json")); - - if has_package_json_changed { - let mut files_to_check = IndexSet::with_capacity(2); - if let Some(package_json) = &self.maybe_package_json { - files_to_check.insert(package_json.specifier()); - } - if let Err(err) = self.update_package_json() { - lsp_warn!("Error updating package.json: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Some(package_json) = &self.maybe_package_json { - files_to_check.insert(package_json.specifier()); - } - if let Some(root_uri) = self.config.root_uri() { - config_changes.extend( - params - .changes - .iter() - .filter(|e| files_to_check.contains(&e.uri)) - .map(|e| lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), - file_uri: e.uri.clone(), - typ: - lsp_custom::DenoConfigurationChangeType::from_file_change_type( - e.typ, - ), - configuration_type: - lsp_custom::DenoConfigurationType::PackageJson, - }), - ); - } - touched = true; - } - - if !config_changes.is_empty() { - self.client.send_did_change_deno_configuration_notification( - lsp_custom::DidChangeDenoConfigurationNotificationParams { - changes: config_changes.into_iter().collect(), - }, - ); - } - - // if the current import map, or config file has changed, we need to - // reload the import map - let import_map_changed = self - .maybe_import_map - .as_ref() - .map(|import_map| changes.contains(import_map.base_url())) - .unwrap_or(false); - if touched || import_map_changed { - if let Err(err) = self.update_import_map().await { - lsp_warn!("Error updating import map: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - touched = true; - } - - if touched { - self.recreate_npm_services_if_necessary().await; + .any(|(s, _)| self.config.tree.is_watched_file(s)) + { + let mut deno_config_changes = IndexSet::with_capacity(changes.len()); + deno_config_changes.extend(changes.iter().filter_map(|(s, e)| { + self.config.tree.watched_file_type(s).and_then(|t| { + let configuration_type = match t.1 { + ConfigWatchedFileType::DenoJson => { + lsp_custom::DenoConfigurationType::DenoJson + } + ConfigWatchedFileType::PackageJson => { + lsp_custom::DenoConfigurationType::PackageJson + } + _ => return None, + }; + Some(lsp_custom::DenoConfigurationChangeEvent { + scope_uri: t.0, + file_uri: e.uri.clone(), + typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type( + e.typ, + ), + configuration_type, + }) + }) + })); + self.workspace_files_hash = 0; self.refresh_workspace_files(); + self.refresh_config_tree().await; + deno_config_changes.extend(changes.iter().filter_map(|(s, e)| { + self.config.tree.watched_file_type(s).and_then(|t| { + let configuration_type = match t.1 { + ConfigWatchedFileType::DenoJson => { + lsp_custom::DenoConfigurationType::DenoJson + } + ConfigWatchedFileType::PackageJson => { + lsp_custom::DenoConfigurationType::PackageJson + } + _ => return None, + }; + Some(lsp_custom::DenoConfigurationChangeEvent { + scope_uri: t.0, + file_uri: e.uri.clone(), + typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type( + e.typ, + ), + configuration_type, + }) + }) + })); + if !deno_config_changes.is_empty() { + self.client.send_did_change_deno_configuration_notification( + lsp_custom::DidChangeDenoConfigurationNotificationParams { + changes: deno_config_changes.into_iter().collect(), + }, + ); + } + self.recreate_npm_services_if_necessary().await; self.refresh_documents_config().await; self.diagnostics_server.invalidate_all(); self.ts_server.restart(self.snapshot()).await; @@ -1902,7 +1570,13 @@ impl Inner { .url_map .normalize_url(¶ms.text_document.uri, LspUrlKind::File); // skip formatting any files ignored by the config file - if !self.fmt_options.files.matches_specifier(&specifier) { + if !self + .config + .tree + .fmt_options_for_specifier(&specifier) + .files + .matches_specifier(&specifier) + { return Ok(None); } let document = match self.documents.get(&specifier) { @@ -1925,7 +1599,12 @@ impl Inner { // spawn a blocking task to allow doing other work while this is occurring let text_edits = deno_core::unsync::spawn_blocking({ - let fmt_options = self.fmt_options.options.clone(); + let fmt_options = self + .config + .tree + .fmt_options_for_specifier(&specifier) + .options + .clone(); let document = document.clone(); move || { let format_result = match document.maybe_parsed_source() { @@ -2152,10 +1831,14 @@ impl Inner { line_index.offset_tsc(diagnostic.range.start)? ..line_index.offset_tsc(diagnostic.range.end)?, codes, - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&specifier) + .options) + .into(), tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, ), ) @@ -2246,7 +1929,6 @@ impl Inner { ..line_index.offset_tsc(params.range.end)?, Some(tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, )), only, @@ -2305,10 +1987,14 @@ impl Inner { .get_combined_code_fix( self.snapshot(), &code_action_data, - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&code_action_data.specifier) + .options) + .into(), tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &code_action_data.specifier, ), ) @@ -2322,7 +2008,7 @@ impl Inner { fix_ts_import_changes( &code_action_data.specifier, &combined_code_actions.changes, - &self.get_ts_response_import_mapper(), + &self.get_ts_response_import_mapper(&code_action_data.specifier), ) .map_err(|err| { error!("Unable to remap changes: {:#}", err); @@ -2351,14 +2037,18 @@ impl Inner { .get_edits_for_refactor( self.snapshot(), action_data.specifier.clone(), - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&action_data.specifier) + .options) + .into(), line_index.offset_tsc(action_data.range.start)? ..line_index.offset_tsc(action_data.range.end)?, action_data.refactor_name, action_data.action_name, Some(tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &action_data.specifier, )), ) @@ -2374,10 +2064,13 @@ impl Inner { Ok(result) } - pub fn get_ts_response_import_mapper(&self) -> TsResponseImportMapper { + pub fn get_ts_response_import_mapper( + &self, + referrer: &ModuleSpecifier, + ) -> TsResponseImportMapper { TsResponseImportMapper::new( &self.documents, - self.maybe_import_map.as_deref(), + self.config.tree.import_map_for_specifier(referrer), self.npm.node_resolver.as_deref(), self.npm.resolver.as_deref(), ) @@ -2690,7 +2383,7 @@ impl Inner { &self.jsr_search_api, &self.npm.search_api, &self.documents, - self.maybe_import_map.clone(), + self.config.tree.import_map_for_specifier(&specifier), ) .await; } @@ -2716,13 +2409,17 @@ impl Inner { tsc::GetCompletionsAtPositionOptions { user_preferences: tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, ), trigger_character, trigger_kind, }, - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&specifier) + .options) + .into(), ) .await; @@ -2769,11 +2466,17 @@ impl Inner { .get_completion_details( self.snapshot(), GetCompletionDetailsArgs { - format_code_settings: Some((&self.fmt_options.options).into()), + format_code_settings: Some( + (&self + .config + .tree + .fmt_options_for_specifier(specifier) + .options) + .into(), + ), preferences: Some( tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, specifier, ), ), @@ -3289,6 +2992,12 @@ impl Inner { if options.enabled == UpdateImportsOnFileMoveEnabled::Never { continue; } + let format_code_settings = (&self + .config + .tree + .fmt_options_for_specifier(&old_specifier) + .options) + .into(); changes.extend( self .ts_server @@ -3299,7 +3008,7 @@ impl Inner { &resolve_url(&rename.new_uri).unwrap(), LspUrlKind::File, ), - (&self.fmt_options.options).into(), + format_code_settings, tsc::UserPreferences { allow_text_changes_in_new_files: Some(true), ..Default::default() @@ -3350,7 +3059,6 @@ impl Inner { let snapshot = DiagnosticServerUpdateMessage { snapshot: self.snapshot(), config: self.config.snapshot(), - lint_options: self.lint_options.clone(), url_map: self.url_map.clone(), }; if let Err(err) = self.diagnostics_server.update(snapshot) { @@ -3459,31 +3167,31 @@ impl tower_lsp::LanguageServer for LanguageServer { ls.maybe_testing_server = Some(test_server); } - if let Some(root_uri) = ls.config.root_uri() { - let mut config_events = vec![]; - if let Some(config_file) = ls.config.maybe_config_file() { + let mut config_events = vec![]; + for (scope_uri, config_data) in ls.config.tree.data_by_scope() { + if let Some(config_file) = &config_data.config_file { config_events.push(lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), + scope_uri: scope_uri.clone(), file_uri: config_file.specifier.clone(), typ: lsp_custom::DenoConfigurationChangeType::Added, configuration_type: lsp_custom::DenoConfigurationType::DenoJson, }); } - if let Some(package_json) = &ls.maybe_package_json { + if let Some(package_json) = &config_data.package_json { config_events.push(lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), + scope_uri: scope_uri.clone(), file_uri: package_json.specifier(), typ: lsp_custom::DenoConfigurationChangeType::Added, configuration_type: lsp_custom::DenoConfigurationType::PackageJson, }); } - if !config_events.is_empty() { - ls.client.send_did_change_deno_configuration_notification( - lsp_custom::DidChangeDenoConfigurationNotificationParams { - changes: config_events, - }, - ); - } + } + if !config_events.is_empty() { + ls.client.send_did_change_deno_configuration_notification( + lsp_custom::DidChangeDenoConfigurationNotificationParams { + changes: config_events, + }, + ); } (ls.client.clone(), ls.http_client.clone()) @@ -3504,11 +3212,6 @@ impl tower_lsp::LanguageServer for LanguageServer { { let mut ls = self.0.write().await; init_log_file(ls.config.log_file()); - if let Err(err) = ls.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - ls.client.show_message(MessageType::WARNING, err); - } - ls.refresh_workspace_files(); ls.refresh_documents_config().await; ls.diagnostics_server.invalidate_all(); ls.send_diagnostics_update(); @@ -3643,6 +3346,7 @@ impl tower_lsp::LanguageServer for LanguageServer { { let mut ls = self.0.write().await; ls.refresh_workspace_files(); + ls.refresh_config_tree().await; ls.refresh_documents_config().await; ls.diagnostics_server.invalidate_all(); ls.send_diagnostics_update(); @@ -3845,10 +3549,11 @@ impl Inner { let mark = self .performance .mark_with_args("lsp.cache", (&specifiers, &referrer)); + let config_data = self.config.tree.data_for_specifier(&referrer); let roots = if !specifiers.is_empty() { specifiers } else { - vec![referrer] + vec![referrer.clone()] }; let workspace_settings = self.config.workspace_settings(); let mut cli_options = CliOptions::new( @@ -3860,21 +3565,28 @@ impl Inner { .unsafely_ignore_certificate_errors .clone(), node_modules_dir: Some( - self.config.maybe_node_modules_dir_path().is_some(), + config_data + .as_ref() + .and_then(|d| d.node_modules_dir.as_ref()) + .is_some(), ), // bit of a hack to force the lsp to cache the @types/node package type_check_mode: crate::args::TypeCheckMode::Local, ..Default::default() }, self.initial_cwd.clone(), - self.config.maybe_config_file().cloned(), - self.config.maybe_lockfile().cloned(), - self.maybe_package_json.clone(), + config_data + .as_ref() + .and_then(|d| d.config_file.as_deref().cloned()), + config_data.as_ref().and_then(|d| d.lockfile.clone()), + config_data + .as_ref() + .and_then(|d| d.package_json.as_deref().cloned()), force_global_cache, )?; // don't use the specifier in self.maybe_import_map because it's not // necessarily an import map specifier (could be a deno.json) - if let Some(import_map) = self.resolve_import_map_specifier()? { + if let Some(import_map) = self.resolve_import_map_specifier(&referrer)? { cli_options.set_import_map_specifier(Some(import_map)); } @@ -3907,7 +3619,7 @@ impl Inner { fn task_definitions(&self) -> LspResult> { let mut result = vec![]; - if let Some(config_file) = self.config.maybe_config_file() { + for config_file in self.config.tree.config_files() { if let Some(tasks) = json!(&config_file.json.tasks).as_object() { for (name, value) in tasks { let Some(command) = value.as_str() else { @@ -3921,7 +3633,7 @@ impl Inner { } }; } - if let Some(package_json) = &self.maybe_package_json { + for package_json in self.config.tree.package_jsons() { if let Some(scripts) = &package_json.scripts { for (name, command) in scripts { result.push(TaskDefinition { @@ -3968,7 +3680,6 @@ impl Inner { text_span, tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, ), ) diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index e88aba07be..1d7dac4e9a 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -3,6 +3,7 @@ use super::analysis::CodeActionData; use super::code_lens; use super::config; +use super::config::ConfigTree; use super::documents::AssetOrDocument; use super::documents::DocumentsFilter; use super::language_server; @@ -22,7 +23,6 @@ use super::urls::INVALID_SPECIFIER; use crate::args::jsr_url; use crate::args::FmtOptionsConfig; -use crate::args::TsConfig; use crate::cache::HttpCache; use crate::lsp::cache::CacheMetadata; use crate::lsp::documents::Documents; @@ -221,6 +221,7 @@ pub struct TsServer { sender: mpsc::UnboundedSender, receiver: Mutex>>, specifier_map: Arc, + config_tree: Arc, inspector_server: Mutex>>, } @@ -238,7 +239,11 @@ impl std::fmt::Debug for TsServer { } impl TsServer { - pub fn new(performance: Arc, cache: Arc) -> Self { + pub fn new( + performance: Arc, + cache: Arc, + config_tree: Arc, + ) -> Self { let (tx, request_rx) = mpsc::unbounded_channel::(); Self { performance, @@ -246,6 +251,7 @@ impl TsServer { sender: tx, receiver: Mutex::new(Some(request_rx)), specifier_map: Arc::new(TscSpecifierMap::new()), + config_tree, inspector_server: Mutex::new(None), } } @@ -268,6 +274,7 @@ impl TsServer { let performance = self.performance.clone(); let cache = self.cache.clone(); let specifier_map = self.specifier_map.clone(); + let config_tree = self.config_tree.clone(); let _join_handle = thread::spawn(move || { run_tsc_thread( receiver, @@ -275,6 +282,7 @@ impl TsServer { cache.clone(), specifier_map.clone(), maybe_inspector_server, + config_tree, ) }); } @@ -343,18 +351,6 @@ impl TsServer { self.request(snapshot, req).await } - pub async fn configure( - &self, - snapshot: Arc, - tsconfig: TsConfig, - ) -> Result { - let req = TscRequest { - method: "$configure", - args: json!([tsconfig]), - }; - self.request(snapshot, req).await - } - pub async fn get_supported_code_fixes( &self, snapshot: Arc, @@ -3482,7 +3478,7 @@ impl CompletionEntry { { if let Ok(import_specifier) = resolve_url(&import_data.file_name) { if let Some(new_module_specifier) = language_server - .get_ts_response_import_mapper() + .get_ts_response_import_mapper(specifier) .check_specifier(&import_specifier, specifier) .or_else(|| relative_specifier(specifier, &import_specifier)) { @@ -3849,6 +3845,7 @@ struct State { response: Option, state_snapshot: Arc, specifier_map: Arc, + config_tree: Arc, token: CancellationToken, } @@ -3856,6 +3853,7 @@ impl State { fn new( state_snapshot: Arc, specifier_map: Arc, + config_tree: Arc, performance: Arc, ) -> Self { Self { @@ -3864,6 +3862,7 @@ impl State { response: None, state_snapshot, specifier_map, + config_tree, token: Default::default(), } } @@ -4077,10 +4076,20 @@ fn op_script_version( Ok(r) } +#[op2] +#[serde] +fn op_ts_config(state: &mut OpState) -> serde_json::Value { + let state = state.borrow_mut::(); + let mark = state.performance.mark("tsc.op.op_ts_config"); + let r = json!(state.config_tree.root_ts_config()); + state.performance.measure(mark); + r +} + #[op2] #[string] fn op_project_version(state: &mut OpState) -> String { - let state = state.borrow_mut::(); + let state: &mut State = state.borrow_mut::(); let mark = state.performance.mark("tsc.op.op_project_version"); let r = state.state_snapshot.documents.project_version(); state.performance.measure(mark); @@ -4093,13 +4102,19 @@ fn run_tsc_thread( cache: Arc, specifier_map: Arc, maybe_inspector_server: Option>, + config_tree: Arc, ) { let has_inspector_server = maybe_inspector_server.is_some(); // Create and setup a JsRuntime based on a snapshot. It is expected that the // supplied snapshot is an isolate that contains the TypeScript language // server. let mut tsc_runtime = JsRuntime::new(RuntimeOptions { - extensions: vec![deno_tsc::init_ops(performance, cache, specifier_map)], + extensions: vec![deno_tsc::init_ops( + performance, + cache, + specifier_map, + config_tree, + )], startup_snapshot: Some(tsc::compiler_snapshot()), inspector: maybe_inspector_server.is_some(), ..Default::default() @@ -4166,12 +4181,14 @@ deno_core::extension!(deno_tsc, op_respond, op_script_names, op_script_version, + op_ts_config, op_project_version, ], options = { performance: Arc, cache: Arc, specifier_map: Arc, + config_tree: Arc, }, state = |state, options| { state.put(State::new( @@ -4180,10 +4197,10 @@ deno_core::extension!(deno_tsc, cache_metadata: CacheMetadata::new(options.cache.clone()), config: Default::default(), documents: Documents::new(options.cache.clone()), - maybe_import_map: None, npm: None, }), options.specifier_map, + options.config_tree, options.performance, )); }, @@ -4344,9 +4361,10 @@ pub struct UserPreferences { impl UserPreferences { pub fn from_config_for_specifier( config: &config::Config, - fmt_config: &FmtOptionsConfig, specifier: &ModuleSpecifier, ) -> Self { + let fmt_options = config.tree.fmt_options_for_specifier(specifier); + let fmt_config = &fmt_options.options; let base_preferences = Self { allow_incomplete_completions: Some(true), allow_text_changes_in_new_files: Some(specifier.scheme() == "file"), @@ -4450,7 +4468,8 @@ impl UserPreferences { language_settings.preferences.use_aliases_for_renames, ), // Only use workspace settings for quote style if there's no `deno.json`. - quote_preference: if config.has_config_file() { + quote_preference: if config.tree.has_config_file_for_specifier(specifier) + { base_preferences.quote_preference } else { Some(language_settings.preferences.quote_style) @@ -4630,7 +4649,6 @@ mod tests { assets: Default::default(), cache_metadata: CacheMetadata::new(cache), config: Default::default(), - maybe_import_map: None, npm: None, } } @@ -4645,13 +4663,21 @@ mod tests { Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv)); let snapshot = Arc::new(mock_state_snapshot(sources, &location)); let performance = Arc::new(Performance::default()); - let ts_server = TsServer::new(performance, cache.clone()); + let config_tree = Arc::new(ConfigTree::default()); + config_tree + .inject_config_file( + deno_config::ConfigFile::new( + &json!({ + "compilerOptions": config, + }) + .to_string(), + resolve_url("file:///deno.json").unwrap(), + ) + .unwrap(), + ) + .await; + let ts_server = TsServer::new(performance, cache.clone(), config_tree); ts_server.start(None); - let ts_config = TsConfig::new(config); - assert!(ts_server - .configure(snapshot.clone(), ts_config,) - .await - .unwrap()); (ts_server, snapshot, cache) } @@ -4670,43 +4696,6 @@ mod tests { assert_eq!(actual, r"test [`a link`](http://deno.land/x/mod.ts) test"); } - #[tokio::test] - async fn test_project_configure() { - let temp_dir = TempDir::new(); - setup( - &temp_dir, - json!({ - "target": "esnext", - "module": "esnext", - "noEmit": true, - }), - &[], - ) - .await; - } - - #[tokio::test] - async fn test_project_reconfigure() { - let temp_dir = TempDir::new(); - let (ts_server, snapshot, _) = setup( - &temp_dir, - json!({ - "target": "esnext", - "module": "esnext", - "noEmit": true, - }), - &[], - ) - .await; - let ts_config = TsConfig::new(json!({ - "target": "esnext", - "module": "esnext", - "noEmit": true, - "lib": ["deno.ns", "deno.worker"] - })); - assert!(ts_server.configure(snapshot, ts_config).await.unwrap()); - } - #[tokio::test] async fn test_get_diagnostics() { let temp_dir = TempDir::new(); @@ -4716,6 +4705,7 @@ mod tests { "target": "esnext", "module": "esnext", "noEmit": true, + "lib": [], }), &[( "file:///a.ts", @@ -5468,11 +5458,10 @@ mod tests { .inlay_hints .variable_types .suppress_when_type_matches_name = true; - let mut config = config::Config::new(); + let mut config = config::Config::default(); config.set_workspace_settings(settings, vec![]); let user_preferences = UserPreferences::from_config_for_specifier( &config, - &Default::default(), &ModuleSpecifier::parse("file:///foo.ts").unwrap(), ); assert_eq!( diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index cb282663ca..fec6647843 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -857,6 +857,12 @@ pub struct ConfiguredRules { pub no_slow_types: bool, } +impl Default for ConfiguredRules { + fn default() -> Self { + get_configured_rules(Default::default(), None) + } +} + impl ConfiguredRules { fn incremental_cache_state(&self) -> Vec<&str> { // use a hash of the rule names in order to bust the cache diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index 32c3bf0351..e6123d25ed 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -503,9 +503,6 @@ delete Object.prototype.__proto__; } } - /** @type {ts.CompilerOptions} */ - let compilationSettings = {}; - /** @type {ts.LanguageService} */ let languageService; @@ -720,7 +717,17 @@ delete Object.prototype.__proto__; if (logDebug) { debug("host.getCompilationSettings()"); } - return compilationSettings; + const tsConfig = normalizeConfig(ops.op_ts_config()); + const { options, errors } = ts + .convertCompilerOptionsFromJson(tsConfig, ""); + Object.assign(options, { + allowNonTsExtensions: true, + allowImportingTsExtensions: true, + }); + if (errors.length > 0 && logDebug) { + debug(ts.formatDiagnostics(errors, host)); + } + return options; }, getScriptFileNames() { if (logDebug) { @@ -1010,21 +1017,6 @@ delete Object.prototype.__proto__; serverRestart(); return respond(id, true); } - case "$configure": { - const config = normalizeConfig(args[0]); - const { options, errors } = ts - .convertCompilerOptionsFromJson(config, ""); - Object.assign(options, { - allowNonTsExtensions: true, - allowImportingTsExtensions: true, - }); - if (errors.length > 0 && logDebug) { - debug(ts.formatDiagnostics(errors, host)); - } - compilationSettings = options; - moduleSpecifierCache.clear(); - return respond(id, true); - } case "$getSupportedCodeFixes": { return respond( id, diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index c5913e07b1..61b1d1bd11 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -145,32 +145,6 @@ fn lsp_tsconfig_types_config_sub_dir() { client.shutdown(); } -#[test] -fn lsp_tsconfig_bad_config_path() { - let context = TestContextBuilder::new().use_temp_cwd().build(); - let mut client = context.new_lsp_command().build(); - client.initialize(|builder| { - builder - .set_config("bad_tsconfig.json") - .set_maybe_root_uri(None); - }); - let (method, maybe_params) = client.read_notification(); - assert_eq!(method, "window/showMessage"); - assert_eq!(maybe_params, Some(lsp::ShowMessageParams { - typ: lsp::MessageType::WARNING, - message: "The path to the configuration file (\"bad_tsconfig.json\") is not resolvable.".to_string() - })); - let diagnostics = client.did_open(json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.args);\n" - } - })); - assert_eq!(diagnostics.all().len(), 0); -} - #[test] fn lsp_triple_slash_types() { let context = TestContextBuilder::new().use_temp_cwd().build(); @@ -223,7 +197,7 @@ fn lsp_import_map() { } })); - assert_eq!(diagnostics.all().len(), 0); + assert_eq!(json!(diagnostics.all()), json!([])); let res = client.write_request( "textDocument/hover", @@ -497,7 +471,7 @@ fn lsp_import_map_embedded_in_config_file_after_initialize() { }] })); - assert_eq!(client.read_diagnostics().all().len(), 0); + assert_eq!(json!(client.read_diagnostics().all()), json!([])); let res = client.write_request( "textDocument/hover", @@ -546,7 +520,7 @@ fn lsp_import_map_config_file_auto_discovered() { }] })); client.wait_until_stderr_line(|line| { - line.contains("Auto-resolved configuration file:") + line.contains(" Resolved Deno configuration file:") }); let uri = temp_dir.uri().join("a.ts").unwrap(); @@ -607,7 +581,7 @@ fn lsp_import_map_config_file_auto_discovered() { }] })); client.wait_until_stderr_line(|line| { - line.contains("Auto-resolved configuration file:") + line.contains(" Resolved Deno configuration file:") }); let res = client.write_request( "textDocument/hover", @@ -675,7 +649,7 @@ fn lsp_import_map_config_file_auto_discovered_symlink() { // this will discover the deno.json in the root let search_line = format!( - "Auto-resolved configuration file: \"{}\"", + " Resolved Deno configuration file: \"{}\"", temp_dir.uri().join("deno.json").unwrap().as_str() ); client.wait_until_stderr_line(|line| line.contains(&search_line)); @@ -8946,10 +8920,7 @@ fn lsp_performance() { "lsp.update_diagnostics_deps", "lsp.update_diagnostics_lint", "lsp.update_diagnostics_ts", - "lsp.update_import_map", "lsp.update_registries", - "lsp.update_tsconfig", - "tsc.host.$configure", "tsc.host.$getAssets", "tsc.host.$getDiagnostics", "tsc.host.$getSupportedCodeFixes", @@ -8959,7 +8930,7 @@ fn lsp_performance() { "tsc.op.op_project_version", "tsc.op.op_script_names", "tsc.op.op_script_version", - "tsc.request.$configure", + "tsc.op.op_ts_config", "tsc.request.$getAssets", "tsc.request.$getSupportedCodeFixes", "tsc.request.getQuickInfoAtPosition",