diff options
Diffstat (limited to 'cli/lsp/config.rs')
-rw-r--r-- | cli/lsp/config.rs | 347 |
1 files changed, 173 insertions, 174 deletions
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index e80851429..1a707c44c 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -731,7 +731,7 @@ pub struct ConfigSnapshot { pub client_capabilities: ClientCapabilities, pub settings: Settings, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, - pub tree: Arc<ConfigTree>, + pub tree: ConfigTree, } impl ConfigSnapshot { @@ -745,7 +745,7 @@ impl ConfigSnapshot { /// Determine if the provided specifier is enabled or not. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { let config_file = self.tree.config_file_for_specifier(specifier); - if let Some(cf) = &config_file { + if let Some(cf) = config_file { if let Ok(files) = cf.to_files_config() { if !files.matches_specifier(specifier) { return false; @@ -781,10 +781,6 @@ pub struct Settings { } impl Settings { - pub fn first_root_uri(&self) -> Option<&ModuleSpecifier> { - self.first_folder.as_ref() - } - /// Returns `None` if the value should be deferred to the presence of a /// `deno.json` file. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> Option<bool> { @@ -793,7 +789,7 @@ impl Settings { return Some(true); }; let (settings, mut folder_uri) = self.get_for_specifier(specifier); - folder_uri = folder_uri.or_else(|| self.first_root_uri()); + folder_uri = folder_uri.or(self.first_folder.as_ref()); let mut disable_paths = vec![]; let mut enable_paths = None; if let Some(folder_uri) = folder_uri { @@ -879,7 +875,7 @@ pub struct Config { pub client_capabilities: ClientCapabilities, pub settings: Settings, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, - pub tree: Arc<ConfigTree>, + pub tree: ConfigTree, } impl Config { @@ -997,7 +993,7 @@ impl Config { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { let config_file = self.tree.config_file_for_specifier(specifier); - if let Some(cf) = &config_file { + if let Some(cf) = config_file { if let Ok(files) = cf.to_files_config() { if !files.matches_specifier(specifier) { return false; @@ -1086,23 +1082,51 @@ 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, Serialize)] +pub struct LspTsConfig { + #[serde(flatten)] + inner: TsConfig, +} + +impl Default for LspTsConfig { + fn default() -> Self { + Self { + inner: 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, + })), + } + } +} + +impl LspTsConfig { + pub fn new(config_file: Option<&ConfigFile>) -> Self { + let mut ts_config = Self::default(); + if let Some(config_file) = config_file { + match config_file.to_compiler_options() { + Ok((value, maybe_ignored_options)) => { + ts_config.inner.merge(&value); + if let Some(ignored_options) = maybe_ignored_options { + lsp_warn!("{}", ignored_options); + } + } + Err(err) => lsp_warn!("{}", err), + } + } + ts_config + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -1120,7 +1144,7 @@ pub struct ConfigData { pub fmt_options: Arc<FmtOptions>, pub lint_options: Arc<LintOptions>, pub lint_rules: Arc<ConfiguredRules>, - pub ts_config: Arc<TsConfig>, + pub ts_config: Arc<LspTsConfig>, pub node_modules_dir: Option<PathBuf>, pub vendor_dir: Option<PathBuf>, pub lockfile: Option<Arc<Mutex<Lockfile>>>, @@ -1242,18 +1266,7 @@ impl ConfigData { .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 ts_config = LspTsConfig::new(config_file.as_ref()); 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()); @@ -1425,206 +1438,191 @@ impl ConfigData { } } -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct ConfigTree { - root: Mutex<Option<(ModuleSpecifier, Arc<ConfigData>)>>, + first_folder: Option<ModuleSpecifier>, + scopes: Arc<BTreeMap<ModuleSpecifier, ConfigData>>, } impl ConfigTree { - pub fn root_data(&self) -> Option<Arc<ConfigData>> { - self.root.lock().as_ref().map(|(_, d)| d.clone()) + pub fn root_data(&self) -> Option<&ConfigData> { + self.first_folder.as_ref().and_then(|s| self.scopes.get(s)) } - pub fn root_config_file(&self) -> Option<Arc<ConfigFile>> { + pub fn root_ts_config(&self) -> Arc<LspTsConfig> { self - .root - .lock() - .as_ref() - .and_then(|(_, d)| d.config_file.clone()) + .root_data() + .map(|d| d.ts_config.clone()) + .unwrap_or_default() } - pub fn root_ts_config(&self) -> Arc<TsConfig> { - 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<&PathBuf> { + self.root_data().and_then(|d| d.vendor_dir.as_ref()) } - pub fn root_vendor_dir(&self) -> Option<PathBuf> { - self - .root - .lock() - .as_ref() - .and_then(|(_, d)| d.vendor_dir.clone()) + pub fn root_lockfile(&self) -> Option<&Arc<Mutex<Lockfile>>> { + self.root_data().and_then(|d| d.lockfile.as_ref()) } - pub fn root_lockfile(&self) -> Option<Arc<Mutex<Lockfile>>> { - self - .root - .lock() - .as_ref() - .and_then(|(_, d)| d.lockfile.clone()) + pub fn root_import_map(&self) -> Option<&Arc<ImportMap>> { + self.root_data().and_then(|d| d.import_map.as_ref()) } pub fn scope_for_specifier( &self, - _specifier: &ModuleSpecifier, - ) -> Option<ModuleSpecifier> { - self.root.lock().as_ref().map(|r| r.0.clone()) + specifier: &ModuleSpecifier, + ) -> Option<&ModuleSpecifier> { + self + .scopes + .keys() + .rfind(|s| specifier.as_str().starts_with(s.as_str())) + .or(self.first_folder.as_ref()) } pub fn data_for_specifier( &self, - _specifier: &ModuleSpecifier, - ) -> Option<Arc<ConfigData>> { - self.root_data() + specifier: &ModuleSpecifier, + ) -> Option<&ConfigData> { + self + .scope_for_specifier(specifier) + .and_then(|s| self.scopes.get(s)) } - pub fn data_by_scope(&self) -> BTreeMap<ModuleSpecifier, Arc<ConfigData>> { - self.root.lock().iter().cloned().collect() + pub fn data_by_scope(&self) -> &Arc<BTreeMap<ModuleSpecifier, ConfigData>> { + &self.scopes } pub fn config_file_for_specifier( &self, - _specifier: &ModuleSpecifier, - ) -> Option<Arc<ConfigFile>> { - self.root_config_file() - } - - pub fn has_config_file_for_specifier( - &self, - _specifier: &ModuleSpecifier, - ) -> bool { + specifier: &ModuleSpecifier, + ) -> Option<&Arc<ConfigFile>> { self - .root - .lock() - .as_ref() - .map(|(_, d)| d.config_file.is_some()) - .unwrap_or(false) + .data_for_specifier(specifier) + .and_then(|d| d.config_file.as_ref()) } - pub fn config_files(&self) -> Vec<Arc<ConfigFile>> { - self.root_config_file().into_iter().collect() + pub fn config_files(&self) -> Vec<&Arc<ConfigFile>> { + self + .scopes + .iter() + .filter_map(|(_, d)| d.config_file.as_ref()) + .collect() } - pub fn package_jsons(&self) -> Vec<Arc<PackageJson>> { + pub fn package_jsons(&self) -> Vec<&Arc<PackageJson>> { self - .root - .lock() - .as_ref() - .and_then(|(_, d)| d.package_json.clone()) - .into_iter() + .scopes + .iter() + .filter_map(|(_, d)| d.package_json.as_ref()) .collect() } pub fn fmt_options_for_specifier( &self, - _specifier: &ModuleSpecifier, + specifier: &ModuleSpecifier, ) -> Arc<FmtOptions> { self - .root - .lock() - .as_ref() - .map(|(_, d)| d.fmt_options.clone()) + .data_for_specifier(specifier) + .map(|d| d.fmt_options.clone()) .unwrap_or_default() } - pub fn lockfile_for_specifier( + /// Returns (scope_uri, type). + pub fn watched_file_type( &self, - _specifier: &ModuleSpecifier, - ) -> Option<Arc<Mutex<Lockfile>>> { - self.root_lockfile() + specifier: &ModuleSpecifier, + ) -> Option<(&ModuleSpecifier, ConfigWatchedFileType)> { + for (scope_uri, data) in self.scopes.iter() { + if let Some(typ) = data.watched_files.get(specifier) { + return Some((scope_uri, *typ)); + } + } + None } - pub fn import_map_for_specifier( - &self, - _specifier: &ModuleSpecifier, - ) -> Option<Arc<ImportMap>> { + 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() - .and_then(|(_, d)| d.import_map.clone()) + .scopes + .values() + .any(|data| data.watched_files.contains_key(specifier)) } pub async fn refresh( - &self, + &mut self, settings: &Settings, - root_uri: &ModuleSpecifier, workspace_files: &BTreeSet<ModuleSpecifier>, 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, + let mut scopes = BTreeMap::new(); + for (folder_uri, ws_settings) in &settings.by_workspace_folder { + let mut ws_settings = ws_settings.as_ref(); + if Some(folder_uri) == settings.first_folder.as_ref() { + ws_settings = ws_settings.or(Some(&settings.unscoped)); + } + if let Some(ws_settings) = ws_settings { + if let Some(config_path) = &ws_settings.config { + if let Ok(config_uri) = folder_uri.join(config_path) { + scopes.insert( + folder_uri.clone(), + ConfigData::load( + Some(&config_uri), + folder_uri, + settings, + Some(file_fetcher), + ) + .await, + ); + } + } + } + } + + for specifier in workspace_files { + if specifier.path().ends_with("/deno.json") + || specifier.path().ends_with("/deno.jsonc") + { + if let Ok(scope) = specifier.join(".") { + let entry = scopes.entry(scope.clone()); + #[allow(clippy::map_entry)] + if matches!(entry, std::collections::btree_map::Entry::Vacant(_)) { + let data = ConfigData::load( + Some(specifier), + &scope, settings, Some(file_fetcher), ) - .await, - ), - )); + .await; + entry.or_insert(data); + } + } } - } 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)); + for folder_uri in settings.by_workspace_folder.keys() { + if !scopes + .keys() + .any(|s| folder_uri.as_str().starts_with(s.as_str())) + { + scopes.insert( + folder_uri.clone(), + ConfigData::load(None, folder_uri, settings, Some(file_fetcher)) + .await, + ); } } - 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)) + self.first_folder = settings.first_folder.clone(); + self.scopes = Arc::new(scopes); } #[cfg(test)] - pub async fn inject_config_file(&self, config_file: ConfigFile) { + pub async fn inject_config_file(&mut self, config_file: ConfigFile) { let scope = config_file.specifier.join(".").unwrap(); let data = ConfigData::load_inner( Some(config_file), @@ -1633,7 +1631,8 @@ impl ConfigTree { None, ) .await; - *self.root.lock() = Some((scope, Arc::new(data))); + self.first_folder = Some(scope.clone()); + self.scopes = Arc::new([(scope, data)].into_iter().collect()); } } |