Skip to main content
Module

x/deno/cli/lsp/config.rs

A modern runtime for JavaScript and TypeScript.
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use super::client::Client;use super::logging::lsp_log;use crate::fs_util;use deno_core::error::AnyError;use deno_core::serde::Deserialize;use deno_core::serde::Serialize;use deno_core::serde_json;use deno_core::serde_json::Value;use deno_core::ModuleSpecifier;use std::collections::BTreeMap;use std::collections::HashMap;use std::sync::Arc;use tower_lsp::lsp_types as lsp;
pub const SETTINGS_SECTION: &str = "deno";
#[derive(Debug, Clone, Default)]pub struct ClientCapabilities { pub code_action_disabled_support: bool, pub line_folding_only: bool, pub status_notification: bool, /// The client provides the `experimental.testingApi` capability, which is /// built around VSCode's testing API. It indicates that the server should /// send notifications about tests discovered in modules. pub testing_api: bool, pub workspace_configuration: bool, pub workspace_did_change_watched_files: bool,}
fn is_true() -> bool { true}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]#[serde(rename_all = "camelCase")]pub struct CodeLensSettings { /// Flag for providing implementation code lenses. #[serde(default)] pub implementations: bool, /// Flag for providing reference code lenses. #[serde(default)] pub references: bool, /// Flag for providing reference code lens on all functions. For this to have /// an impact, the `references` flag needs to be `true`. #[serde(default)] pub references_all_functions: bool, /// Flag for providing test code lens on `Deno.test` statements. There is /// also the `test_args` setting, but this is not used by the server. #[serde(default = "is_true")] pub test: bool,}
impl Default for CodeLensSettings { fn default() -> Self { Self { implementations: false, references: false, references_all_functions: false, test: true, } }}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]#[serde(rename_all = "camelCase")]pub struct CodeLensSpecifierSettings { /// Flag for providing test code lens on `Deno.test` statements. There is /// also the `test_args` setting, but this is not used by the server. #[serde(default = "is_true")] pub test: bool,}
impl Default for CodeLensSpecifierSettings { fn default() -> Self { Self { test: true } }}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]#[serde(rename_all = "camelCase")]pub struct CompletionSettings { #[serde(default)] pub complete_function_calls: bool, #[serde(default = "is_true")] pub names: bool, #[serde(default = "is_true")] pub paths: bool, #[serde(default = "is_true")] pub auto_imports: bool, #[serde(default)] pub imports: ImportCompletionSettings,}
impl Default for CompletionSettings { fn default() -> Self { Self { complete_function_calls: false, names: true, paths: true, auto_imports: true, imports: ImportCompletionSettings::default(), } }}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]#[serde(rename_all = "camelCase")]pub struct ImportCompletionSettings { /// A flag that indicates if non-explicitly set origins should be checked for /// supporting import suggestions. #[serde(default = "is_true")] pub auto_discover: bool, /// A map of origins which have had explicitly set if import suggestions are /// enabled. #[serde(default)] pub hosts: HashMap<String, bool>,}
impl Default for ImportCompletionSettings { fn default() -> Self { Self { auto_discover: true, hosts: HashMap::default(), } }}
/// Deno language server specific settings that can be applied uniquely to a/// specifier.#[derive(Debug, Default, Clone, Deserialize)]#[serde(rename_all = "camelCase")]pub struct SpecifierSettings { /// A flag that indicates if Deno is enabled for this specifier or not. pub enable: bool, /// A list of paths, using the workspace folder as a base that should be Deno /// enabled. #[serde(default)] pub enable_paths: Vec<String>, /// Code lens specific settings for the resource. #[serde(default)] pub code_lens: CodeLensSpecifierSettings,}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]#[serde(rename_all = "camelCase")]pub struct TestingSettings { /// A vector of arguments which should be used when running the tests for /// a workspace. #[serde(default)] pub args: Vec<String>, /// Enable or disable the testing API if the client is capable of supporting /// the testing API. #[serde(default = "is_true")] pub enable: bool,}
impl Default for TestingSettings { fn default() -> Self { Self { args: vec!["--allow-all".to_string(), "--no-check".to_string()], enable: true, } }}
fn default_to_true() -> bool { true}
/// Deno language server specific settings that are applied to a workspace.#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]#[serde(rename_all = "camelCase")]pub struct WorkspaceSettings { /// A flag that indicates if Deno is enabled for the workspace. #[serde(default)] pub enable: bool,
/// A list of paths, using the root_uri as a base that should be Deno enabled. #[serde(default)] pub enable_paths: Vec<String>,
/// An option that points to a path string of the path to utilise as the /// cache/DENO_DIR for the language server. pub cache: Option<String>,
/// Override the default stores used to validate certificates. This overrides /// the environment variable `DENO_TLS_CA_STORE` if present. pub certificate_stores: Option<Vec<String>>,
/// An option that points to a path string of the config file to apply to /// code within the workspace. pub config: Option<String>,
/// An option that points to a path string of the import map to apply to the /// code within the workspace. pub import_map: Option<String>,
/// Code lens specific settings for the workspace. #[serde(default)] pub code_lens: CodeLensSettings,
/// A flag that indicates if internal debug logging should be made available. #[serde(default)] pub internal_debug: bool,
/// A flag that indicates if linting is enabled for the workspace. #[serde(default = "default_to_true")] pub lint: bool,
/// A flag that indicates if Dene should validate code against the unstable /// APIs for the workspace. #[serde(default)] pub suggest: CompletionSettings,
/// Testing settings for the workspace. #[serde(default)] pub testing: TestingSettings,
/// An option which sets the cert file to use when attempting to fetch remote /// resources. This overrides `DENO_CERT` if present. pub tls_certificate: Option<String>,
/// An option, if set, will unsafely ignore certificate errors when fetching /// remote resources. #[serde(default)] pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
#[serde(default)] pub unstable: bool,}
impl WorkspaceSettings { /// Determine if any code lenses are enabled at all. This allows short /// circuiting when there are no code lenses enabled. pub fn enabled_code_lens(&self) -> bool { self.code_lens.implementations || self.code_lens.references }}
#[derive(Debug, Clone, Default)]pub struct ConfigSnapshot { pub client_capabilities: ClientCapabilities, pub enabled_paths: HashMap<String, Vec<String>>, pub settings: Settings,}
impl ConfigSnapshot { /// Determine if the provided specifier is enabled or not. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { if !self.enabled_paths.is_empty() { let specifier_str = specifier.to_string(); for (workspace, enabled_paths) in self.enabled_paths.iter() { if specifier_str.starts_with(workspace) { return enabled_paths .iter() .any(|path| specifier_str.starts_with(path)); } } } if let Some((_, SpecifierSettings { enable, .. })) = self.settings.specifiers.get(specifier) { *enable } else { self.settings.workspace.enable } }}
#[derive(Debug, Clone)]pub struct SpecifierWithClientUri { pub specifier: ModuleSpecifier, pub client_uri: ModuleSpecifier,}
#[derive(Debug, Default, Clone)]pub struct Settings { pub specifiers: BTreeMap<ModuleSpecifier, (ModuleSpecifier, SpecifierSettings)>, pub workspace: WorkspaceSettings,}
#[derive(Debug)]pub struct Config { pub client_capabilities: ClientCapabilities, enabled_paths: HashMap<String, Vec<String>>, pub root_uri: Option<ModuleSpecifier>, settings: Settings, pub workspace_folders: Option<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>,}
impl Config { pub fn new() -> Self { Self { client_capabilities: ClientCapabilities::default(), enabled_paths: Default::default(), /// Root provided by the initialization parameters. root_uri: None, settings: Default::default(), workspace_folders: None, } }
pub fn get_workspace_settings(&self) -> WorkspaceSettings { self.settings.workspace.clone() }
/// Set the workspace settings directly, which occurs during initialization /// and when the client does not support workspace configuration requests pub fn set_workspace_settings( &mut self, value: Value, ) -> Result<(), AnyError> { let workspace_settings = serde_json::from_value(value)?; self.settings.workspace = workspace_settings; Ok(()) }
pub fn snapshot(&self) -> Arc<ConfigSnapshot> { Arc::new(ConfigSnapshot { client_capabilities: self.client_capabilities.clone(), enabled_paths: self.enabled_paths.clone(), settings: self.settings.clone(), }) }
pub fn has_specifier_settings(&self, specifier: &ModuleSpecifier) -> bool { self.settings.specifiers.contains_key(specifier) }
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { if !self.enabled_paths.is_empty() { let specifier_str = specifier.to_string(); for (workspace, enabled_paths) in self.enabled_paths.iter() { if specifier_str.starts_with(workspace) { return enabled_paths .iter() .any(|path| specifier_str.starts_with(path)); } } } self .settings .specifiers .get(specifier) .map(|(_, s)| s.enable) .unwrap_or_else(|| self.settings.workspace.enable) }
pub fn specifier_code_lens_test(&self, specifier: &ModuleSpecifier) -> bool { let value = self .settings .specifiers .get(specifier) .map(|(_, s)| s.code_lens.test) .unwrap_or_else(|| self.settings.workspace.code_lens.test); value }
pub fn update_capabilities( &mut self, capabilities: &lsp::ClientCapabilities, ) { if let Some(experimental) = &capabilities.experimental { self.client_capabilities.status_notification = experimental .get("statusNotification") .and_then(|it| it.as_bool()) == Some(true); self.client_capabilities.testing_api = experimental.get("testingApi").and_then(|it| it.as_bool()) == Some(true); }
if let Some(workspace) = &capabilities.workspace { self.client_capabilities.workspace_configuration = workspace.configuration.unwrap_or(false); self.client_capabilities.workspace_did_change_watched_files = workspace .did_change_watched_files .and_then(|it| it.dynamic_registration) .unwrap_or(false); }
if let Some(text_document) = &capabilities.text_document { self.client_capabilities.line_folding_only = text_document .folding_range .as_ref() .and_then(|it| it.line_folding_only) .unwrap_or(false); self.client_capabilities.code_action_disabled_support = text_document .code_action .as_ref() .and_then(|it| it.disabled_support) .unwrap_or(false); } }
/// Given the configured workspaces or root URI and the their settings, /// update and resolve any paths that should be enabled pub async fn update_enabled_paths(&mut self, client: Client) -> bool { if let Some(workspace_folders) = self.workspace_folders.clone() { let mut touched = false; for (workspace, folder) in workspace_folders { if let Ok(settings) = client.specifier_configuration(&folder.uri).await { if self.update_enabled_paths_entry(&workspace, settings.enable_paths) { touched = true; } } } touched } else if let Some(root_uri) = self.root_uri.clone() { self.update_enabled_paths_entry( &root_uri, self.settings.workspace.enable_paths.clone(), ) } else { false } }
/// Update a specific entry in the enabled paths for a given workspace. fn update_enabled_paths_entry( &mut self, workspace: &ModuleSpecifier, enabled_paths: Vec<String>, ) -> bool { let workspace = fs_util::ensure_directory_specifier(workspace.clone()); let key = workspace.to_string(); let mut touched = false; if !enabled_paths.is_empty() { if let Ok(workspace_path) = fs_util::specifier_to_file_path(&workspace) { let mut paths = Vec::new(); for path in &enabled_paths { let fs_path = workspace_path.join(path); match ModuleSpecifier::from_file_path(fs_path) { Ok(path_uri) => { paths.push(path_uri.to_string()); } Err(_) => { lsp_log!("Unable to resolve a file path for `deno.enablePath` from \"{}\" for workspace \"{}\".", path, workspace); } } } if !paths.is_empty() { touched = true; self.enabled_paths.insert(key, paths); } } } else { touched = true; self.enabled_paths.remove(&key); } touched }
pub fn get_specifiers_with_client_uris(&self) -> Vec<SpecifierWithClientUri> { self .settings .specifiers .iter() .map(|(s, (u, _))| SpecifierWithClientUri { specifier: s.clone(), client_uri: u.clone(), }) .collect() }
pub fn set_specifier_settings( &mut self, specifier: ModuleSpecifier, client_uri: ModuleSpecifier, settings: SpecifierSettings, ) { self .settings .specifiers .insert(specifier, (client_uri, settings)); }}
#[cfg(test)]mod tests { use super::*; use deno_core::resolve_url; use deno_core::serde_json::json;
#[test] fn test_config_specifier_enabled() { let mut config = Config::new(); let specifier = resolve_url("file:///a.ts").unwrap(); assert!(!config.specifier_enabled(&specifier)); config .set_workspace_settings(json!({ "enable": true })) .expect("could not update"); assert!(config.specifier_enabled(&specifier)); }
#[test] fn test_config_snapshot_specifier_enabled() { let mut config = Config::new(); let specifier = resolve_url("file:///a.ts").unwrap(); assert!(!config.specifier_enabled(&specifier)); config .set_workspace_settings(json!({ "enable": true })) .expect("could not update"); let config_snapshot = config.snapshot(); assert!(config_snapshot.specifier_enabled(&specifier)); }
#[test] fn test_config_specifier_enabled_path() { let mut config = Config::new(); let specifier_a = resolve_url("file:///project/worker/a.ts").unwrap(); let specifier_b = resolve_url("file:///project/other/b.ts").unwrap(); assert!(!config.specifier_enabled(&specifier_a)); assert!(!config.specifier_enabled(&specifier_b)); let mut enabled_paths = HashMap::new(); enabled_paths.insert( "file:///project/".to_string(), vec!["file:///project/worker/".to_string()], ); config.enabled_paths = enabled_paths; assert!(config.specifier_enabled(&specifier_a)); assert!(!config.specifier_enabled(&specifier_b)); let config_snapshot = config.snapshot(); assert!(config_snapshot.specifier_enabled(&specifier_a)); assert!(!config_snapshot.specifier_enabled(&specifier_b)); }
#[test] fn test_set_workspace_settings_defaults() { let mut config = Config::new(); config .set_workspace_settings(json!({})) .expect("could not update"); assert_eq!( config.get_workspace_settings(), WorkspaceSettings { enable: false, enable_paths: Vec::new(), cache: None, certificate_stores: None, config: None, import_map: None, code_lens: CodeLensSettings { implementations: false, references: false, references_all_functions: false, test: true, }, internal_debug: false, lint: true, suggest: CompletionSettings { complete_function_calls: false, names: true, paths: true, auto_imports: true, imports: ImportCompletionSettings { auto_discover: true, hosts: HashMap::new(), } }, testing: TestingSettings { args: vec!["--allow-all".to_string(), "--no-check".to_string()], enable: true }, tls_certificate: None, unsafely_ignore_certificate_errors: None, unstable: false, } ); }}