mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
refactor(lsp): factor out workspace walk from resolver update (#22937)
This commit is contained in:
parent
2f7b9660fa
commit
5a716d1d06
5 changed files with 486 additions and 864 deletions
|
@ -2,12 +2,11 @@
|
|||
|
||||
use super::logging::lsp_log;
|
||||
use crate::args::ConfigFile;
|
||||
use crate::cache::FastInsecureHasher;
|
||||
use crate::lsp::logging::lsp_warn;
|
||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||
use crate::util::path::specifier_to_file_path;
|
||||
use deno_ast::MediaType;
|
||||
use deno_config::glob::PathOrPattern;
|
||||
use deno_config::glob::PathOrPatternSet;
|
||||
use deno_config::FmtOptionsConfig;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::serde::de::DeserializeOwned;
|
||||
|
@ -733,12 +732,17 @@ impl ConfigSnapshot {
|
|||
|
||||
/// Determine if the provided specifier is enabled or not.
|
||||
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
specifier_enabled(
|
||||
specifier,
|
||||
self.config_file.as_ref(),
|
||||
&self.settings,
|
||||
&self.workspace_folders,
|
||||
)
|
||||
if let Some(cf) = &self.config_file {
|
||||
if let Ok(files) = cf.to_files_config() {
|
||||
if !files.matches_specifier(specifier) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
.settings
|
||||
.specifier_enabled(specifier)
|
||||
.unwrap_or_else(|| self.config_file.is_some())
|
||||
}
|
||||
|
||||
pub fn specifier_enabled_for_test(
|
||||
|
@ -759,10 +763,55 @@ impl ConfigSnapshot {
|
|||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Settings {
|
||||
pub unscoped: WorkspaceSettings,
|
||||
pub by_workspace_folder: Option<BTreeMap<ModuleSpecifier, WorkspaceSettings>>,
|
||||
pub by_workspace_folder: BTreeMap<ModuleSpecifier, Option<WorkspaceSettings>>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn first_root_uri(&self) -> Option<&ModuleSpecifier> {
|
||||
self.by_workspace_folder.first_key_value().map(|e| e.0)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let Ok(path) = specifier_to_file_path(specifier) else {
|
||||
// Non-file URLs are not disabled by these settings.
|
||||
return Some(true);
|
||||
};
|
||||
let (settings, mut folder_uri) = self.get_for_specifier(specifier);
|
||||
folder_uri = folder_uri.or_else(|| self.first_root_uri());
|
||||
let mut disable_paths = vec![];
|
||||
let mut enable_paths = None;
|
||||
if let Some(folder_uri) = folder_uri {
|
||||
if let Ok(folder_path) = specifier_to_file_path(folder_uri) {
|
||||
disable_paths = settings
|
||||
.disable_paths
|
||||
.iter()
|
||||
.map(|p| folder_path.join(p))
|
||||
.collect::<Vec<_>>();
|
||||
enable_paths = settings.enable_paths.as_ref().map(|enable_paths| {
|
||||
enable_paths
|
||||
.iter()
|
||||
.map(|p| folder_path.join(p))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if disable_paths.iter().any(|p| path.starts_with(p)) {
|
||||
Some(false)
|
||||
} else if let Some(enable_paths) = &enable_paths {
|
||||
for enable_path in enable_paths {
|
||||
if path.starts_with(enable_path) {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
Some(false)
|
||||
} else {
|
||||
settings.enable
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unscoped(&self) -> &WorkspaceSettings {
|
||||
&self.unscoped
|
||||
}
|
||||
|
@ -774,8 +823,14 @@ impl Settings {
|
|||
let Ok(path) = specifier_to_file_path(specifier) else {
|
||||
return (&self.unscoped, None);
|
||||
};
|
||||
if let Some(by_workspace_folder) = &self.by_workspace_folder {
|
||||
for (folder_uri, settings) in by_workspace_folder.iter().rev() {
|
||||
let mut is_first_folder = true;
|
||||
for (folder_uri, settings) in self.by_workspace_folder.iter().rev() {
|
||||
let mut settings = settings.as_ref();
|
||||
if is_first_folder {
|
||||
settings = settings.or(Some(&self.unscoped));
|
||||
}
|
||||
is_first_folder = false;
|
||||
if let Some(settings) = settings {
|
||||
let Ok(folder_path) = specifier_to_file_path(folder_uri) else {
|
||||
continue;
|
||||
};
|
||||
|
@ -786,6 +841,24 @@ impl Settings {
|
|||
}
|
||||
(&self.unscoped, None)
|
||||
}
|
||||
|
||||
pub fn enable_settings_hash(&self) -> u64 {
|
||||
let mut hasher = FastInsecureHasher::default();
|
||||
let unscoped = self.get_unscoped();
|
||||
hasher.write_hashable(unscoped.enable);
|
||||
hasher.write_hashable(&unscoped.enable_paths);
|
||||
hasher.write_hashable(&unscoped.disable_paths);
|
||||
hasher.write_hashable(unscoped.document_preload_limit);
|
||||
for (folder_uri, settings) in &self.by_workspace_folder {
|
||||
hasher.write_hashable(folder_uri);
|
||||
hasher.write_hashable(
|
||||
settings
|
||||
.as_ref()
|
||||
.map(|s| (&s.enable, &s.enable_paths, &s.disable_paths)),
|
||||
);
|
||||
}
|
||||
hasher.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -808,7 +881,7 @@ struct LspConfigFileInfo {
|
|||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub client_capabilities: ClientCapabilities,
|
||||
settings: Settings,
|
||||
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.
|
||||
|
@ -827,29 +900,46 @@ impl Config {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_with_root(root_uri: Url) -> Self {
|
||||
pub fn new_with_roots(root_uris: impl IntoIterator<Item = Url>) -> Self {
|
||||
let mut config = Self::new();
|
||||
let name = root_uri.path_segments().and_then(|s| s.last());
|
||||
let name = name.unwrap_or_default().to_string();
|
||||
config.workspace_folders = vec![(
|
||||
root_uri.clone(),
|
||||
lsp::WorkspaceFolder {
|
||||
uri: root_uri,
|
||||
name,
|
||||
},
|
||||
)];
|
||||
let mut folders = vec![];
|
||||
for root_uri in root_uris {
|
||||
let name = root_uri.path_segments().and_then(|s| s.last());
|
||||
let name = name.unwrap_or_default().to_string();
|
||||
folders.push((
|
||||
root_uri.clone(),
|
||||
lsp::WorkspaceFolder {
|
||||
uri: root_uri,
|
||||
name,
|
||||
},
|
||||
));
|
||||
}
|
||||
config.set_workspace_folders(folders);
|
||||
config
|
||||
}
|
||||
|
||||
pub fn set_workspace_folders(
|
||||
&mut self,
|
||||
folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
|
||||
) {
|
||||
self.settings.by_workspace_folder =
|
||||
folders.iter().map(|(s, _)| (s.clone(), None)).collect();
|
||||
self.workspace_folders = folders;
|
||||
}
|
||||
|
||||
pub fn set_workspace_settings(
|
||||
&mut self,
|
||||
unscoped: WorkspaceSettings,
|
||||
by_workspace_folder: Option<BTreeMap<ModuleSpecifier, WorkspaceSettings>>,
|
||||
folder_settings: Vec<(ModuleSpecifier, WorkspaceSettings)>,
|
||||
) {
|
||||
self.settings = Settings {
|
||||
unscoped,
|
||||
by_workspace_folder,
|
||||
};
|
||||
self.settings.unscoped = unscoped;
|
||||
for (folder_uri, settings) in folder_settings.into_iter() {
|
||||
if let Some(settings_) =
|
||||
self.settings.by_workspace_folder.get_mut(&folder_uri)
|
||||
{
|
||||
*settings_ = Some(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workspace_settings(&self) -> &WorkspaceSettings {
|
||||
|
@ -1006,12 +1096,18 @@ impl Config {
|
|||
}
|
||||
|
||||
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
specifier_enabled(
|
||||
specifier,
|
||||
self.maybe_config_file(),
|
||||
&self.settings,
|
||||
&self.workspace_folders,
|
||||
)
|
||||
let config_file = self.maybe_config_file();
|
||||
if let Some(cf) = config_file {
|
||||
if let Ok(files) = cf.to_files_config() {
|
||||
if !files.matches_specifier(specifier) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
.settings
|
||||
.specifier_enabled(specifier)
|
||||
.unwrap_or_else(|| config_file.is_some())
|
||||
}
|
||||
|
||||
pub fn specifier_enabled_for_test(
|
||||
|
@ -1025,71 +1121,7 @@ impl Config {
|
|||
}
|
||||
}
|
||||
}
|
||||
if !self.specifier_enabled(specifier) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn get_enabled_paths(&self) -> PathOrPatternSet {
|
||||
let mut paths = vec![];
|
||||
for (workspace_uri, _) in &self.workspace_folders {
|
||||
let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else {
|
||||
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
|
||||
continue;
|
||||
};
|
||||
let settings = self.workspace_settings_for_specifier(workspace_uri);
|
||||
if let Some(enable_paths) = &settings.enable_paths {
|
||||
for path in enable_paths {
|
||||
match PathOrPattern::from_relative(&workspace_path, path) {
|
||||
Ok(path_or_pattern) => paths.push(path_or_pattern),
|
||||
Err(err) => {
|
||||
lsp_log!("Invalid enable path '{}': {:#}", path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
paths.push(PathOrPattern::Path(workspace_path));
|
||||
}
|
||||
}
|
||||
paths.sort();
|
||||
paths.dedup();
|
||||
PathOrPatternSet::new(paths)
|
||||
}
|
||||
|
||||
pub fn get_disabled_paths(&self) -> PathOrPatternSet {
|
||||
let mut path_or_patterns = vec![];
|
||||
if let Some(cf) = self.maybe_config_file() {
|
||||
if let Ok(files) = cf.to_files_config() {
|
||||
for path in files.exclude.into_path_or_patterns() {
|
||||
path_or_patterns.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (workspace_uri, _) in &self.workspace_folders {
|
||||
let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else {
|
||||
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
|
||||
continue;
|
||||
};
|
||||
let settings = self.workspace_settings_for_specifier(workspace_uri);
|
||||
let is_enabled = settings
|
||||
.enable_paths
|
||||
.as_ref()
|
||||
.map(|p| !p.is_empty())
|
||||
.unwrap_or_else(|| {
|
||||
settings.enable.unwrap_or_else(|| self.has_config_file())
|
||||
});
|
||||
if is_enabled {
|
||||
for path in &settings.disable_paths {
|
||||
path_or_patterns.push(PathOrPattern::Path(workspace_path.join(path)));
|
||||
}
|
||||
} else {
|
||||
path_or_patterns.push(PathOrPattern::Path(workspace_path));
|
||||
}
|
||||
}
|
||||
path_or_patterns.sort();
|
||||
path_or_patterns.dedup();
|
||||
PathOrPatternSet::new(path_or_patterns)
|
||||
self.specifier_enabled(specifier)
|
||||
}
|
||||
|
||||
pub fn log_file(&self) -> bool {
|
||||
|
@ -1154,57 +1186,6 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
fn specifier_enabled(
|
||||
specifier: &Url,
|
||||
config_file: Option<&ConfigFile>,
|
||||
settings: &Settings,
|
||||
workspace_folders: &[(Url, lsp::WorkspaceFolder)],
|
||||
) -> bool {
|
||||
if let Some(cf) = config_file {
|
||||
if let Ok(files) = cf.to_files_config() {
|
||||
if !files.matches_specifier(specifier) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
let Ok(path) = specifier_to_file_path(specifier) else {
|
||||
// Non-file URLs are not disabled by these settings.
|
||||
return true;
|
||||
};
|
||||
let (settings, mut folder_uri) = settings.get_for_specifier(specifier);
|
||||
folder_uri = folder_uri.or_else(|| workspace_folders.first().map(|f| &f.0));
|
||||
let mut disable_paths = vec![];
|
||||
let mut enable_paths = None;
|
||||
if let Some(folder_uri) = folder_uri {
|
||||
if let Ok(folder_path) = specifier_to_file_path(folder_uri) {
|
||||
disable_paths = settings
|
||||
.disable_paths
|
||||
.iter()
|
||||
.map(|p| folder_path.join(p))
|
||||
.collect::<Vec<_>>();
|
||||
enable_paths = settings.enable_paths.as_ref().map(|enable_paths| {
|
||||
enable_paths
|
||||
.iter()
|
||||
.map(|p| folder_path.join(p))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(enable_paths) = &enable_paths {
|
||||
for enable_path in enable_paths {
|
||||
if path.starts_with(enable_path)
|
||||
&& !disable_paths.iter().any(|p| path.starts_with(p))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
settings.enable.unwrap_or_else(|| config_file.is_some())
|
||||
&& !disable_paths.iter().any(|p| path.starts_with(p))
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> {
|
||||
let lockfile_path = match config_file.resolve_lockfile_path() {
|
||||
Ok(Some(value)) => value,
|
||||
|
@ -1265,7 +1246,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_config_specifier_enabled() {
|
||||
let root_uri = resolve_url("file:///").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri);
|
||||
let mut config = Config::new_with_roots(vec![root_uri]);
|
||||
let specifier = resolve_url("file:///a.ts").unwrap();
|
||||
assert!(!config.specifier_enabled(&specifier));
|
||||
config.set_workspace_settings(
|
||||
|
@ -1273,7 +1254,7 @@ mod tests {
|
|||
"enable": true
|
||||
}))
|
||||
.unwrap(),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
assert!(config.specifier_enabled(&specifier));
|
||||
}
|
||||
|
@ -1281,7 +1262,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_config_snapshot_specifier_enabled() {
|
||||
let root_uri = resolve_url("file:///").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri);
|
||||
let mut config = Config::new_with_roots(vec![root_uri]);
|
||||
let specifier = resolve_url("file:///a.ts").unwrap();
|
||||
assert!(!config.specifier_enabled(&specifier));
|
||||
config.set_workspace_settings(
|
||||
|
@ -1289,7 +1270,7 @@ mod tests {
|
|||
"enable": true
|
||||
}))
|
||||
.unwrap(),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
let config_snapshot = config.snapshot();
|
||||
assert!(config_snapshot.specifier_enabled(&specifier));
|
||||
|
@ -1298,14 +1279,14 @@ mod tests {
|
|||
#[test]
|
||||
fn test_config_specifier_enabled_path() {
|
||||
let root_uri = resolve_url("file:///project/").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri);
|
||||
let mut config = Config::new_with_roots(vec![root_uri]);
|
||||
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 workspace_settings =
|
||||
serde_json::from_str(r#"{ "enablePaths": ["worker"] }"#).unwrap();
|
||||
config.set_workspace_settings(workspace_settings, None);
|
||||
config.set_workspace_settings(workspace_settings, vec![]);
|
||||
assert!(config.specifier_enabled(&specifier_a));
|
||||
assert!(!config.specifier_enabled(&specifier_b));
|
||||
let config_snapshot = config.snapshot();
|
||||
|
@ -1316,7 +1297,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_config_specifier_disabled_path() {
|
||||
let root_uri = resolve_url("file:///root/").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri.clone());
|
||||
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
|
||||
config.settings.unscoped.enable = Some(true);
|
||||
config.settings.unscoped.enable_paths =
|
||||
Some(vec!["mod1.ts".to_string(), "mod2.ts".to_string()]);
|
||||
|
@ -1330,8 +1311,10 @@ mod tests {
|
|||
#[test]
|
||||
fn test_set_workspace_settings_defaults() {
|
||||
let mut config = Config::new();
|
||||
config
|
||||
.set_workspace_settings(serde_json::from_value(json!({})).unwrap(), None);
|
||||
config.set_workspace_settings(
|
||||
serde_json::from_value(json!({})).unwrap(),
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(
|
||||
config.workspace_settings().clone(),
|
||||
WorkspaceSettings {
|
||||
|
@ -1465,7 +1448,7 @@ mod tests {
|
|||
let mut config = Config::new();
|
||||
config.set_workspace_settings(
|
||||
serde_json::from_value(json!({ "cache": "" })).unwrap(),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(
|
||||
config.workspace_settings().clone(),
|
||||
|
@ -1478,7 +1461,7 @@ mod tests {
|
|||
let mut config = Config::new();
|
||||
config.set_workspace_settings(
|
||||
serde_json::from_value(json!({ "import_map": "" })).unwrap(),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(
|
||||
config.workspace_settings().clone(),
|
||||
|
@ -1491,7 +1474,7 @@ mod tests {
|
|||
let mut config = Config::new();
|
||||
config.set_workspace_settings(
|
||||
serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(
|
||||
config.workspace_settings().clone(),
|
||||
|
@ -1504,7 +1487,7 @@ mod tests {
|
|||
let mut config = Config::new();
|
||||
config.set_workspace_settings(
|
||||
serde_json::from_value(json!({ "config": "" })).unwrap(),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(
|
||||
config.workspace_settings().clone(),
|
||||
|
@ -1512,83 +1495,10 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_get_enabled_paths() {
|
||||
let mut config = Config::new();
|
||||
config.workspace_folders = vec![
|
||||
(
|
||||
Url::parse("file:///root1/").unwrap(),
|
||||
lsp::WorkspaceFolder {
|
||||
uri: Url::parse("file:///root1/").unwrap(),
|
||||
name: "1".to_string(),
|
||||
},
|
||||
),
|
||||
(
|
||||
Url::parse("file:///root2/").unwrap(),
|
||||
lsp::WorkspaceFolder {
|
||||
uri: Url::parse("file:///root2/").unwrap(),
|
||||
name: "2".to_string(),
|
||||
},
|
||||
),
|
||||
(
|
||||
Url::parse("file:///root3/").unwrap(),
|
||||
lsp::WorkspaceFolder {
|
||||
uri: Url::parse("file:///root3/").unwrap(),
|
||||
name: "3".to_string(),
|
||||
},
|
||||
),
|
||||
];
|
||||
config.set_workspace_settings(
|
||||
Default::default(),
|
||||
Some(
|
||||
vec![
|
||||
(
|
||||
Url::parse("file:///root1/").unwrap(),
|
||||
WorkspaceSettings {
|
||||
enable_paths: Some(vec![
|
||||
"sub_dir".to_string(),
|
||||
"sub_dir/other".to_string(),
|
||||
"test.ts".to_string(),
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
Url::parse("file:///root2/").unwrap(),
|
||||
WorkspaceSettings {
|
||||
enable_paths: Some(vec!["other.ts".to_string()]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
Url::parse("file:///root3/").unwrap(),
|
||||
WorkspaceSettings {
|
||||
enable: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
config.get_enabled_paths(),
|
||||
PathOrPatternSet::new(vec![
|
||||
PathOrPattern::Path(PathBuf::from("/root1/sub_dir")),
|
||||
PathOrPattern::Path(PathBuf::from("/root1/sub_dir/other")),
|
||||
PathOrPattern::Path(PathBuf::from("/root1/test.ts")),
|
||||
PathOrPattern::Path(PathBuf::from("/root2/other.ts")),
|
||||
PathOrPattern::Path(PathBuf::from("/root3/")),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_enable_via_config_file_detection() {
|
||||
let root_uri = resolve_url("file:///root/").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri.clone());
|
||||
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
|
||||
config.settings.unscoped.enable = None;
|
||||
assert!(!config.specifier_enabled(&root_uri));
|
||||
|
||||
|
@ -1602,7 +1512,7 @@ mod tests {
|
|||
#[test]
|
||||
fn config_specifier_enabled_matches_by_path_component() {
|
||||
let root_uri = resolve_url("file:///root/").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri.clone());
|
||||
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
|
||||
config.settings.unscoped.enable_paths = Some(vec!["mo".to_string()]);
|
||||
assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap()));
|
||||
}
|
||||
|
@ -1610,7 +1520,7 @@ mod tests {
|
|||
#[test]
|
||||
fn config_specifier_enabled_for_test() {
|
||||
let root_uri = resolve_url("file:///root/").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri.clone());
|
||||
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
|
||||
config.settings.unscoped.enable = Some(true);
|
||||
|
||||
config.settings.unscoped.enable_paths =
|
||||
|
@ -1693,7 +1603,7 @@ mod tests {
|
|||
#[test]
|
||||
fn config_snapshot_specifier_enabled_for_test() {
|
||||
let root_uri = resolve_url("file:///root/").unwrap();
|
||||
let mut config = Config::new_with_root(root_uri.clone());
|
||||
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
|
||||
config.settings.unscoped.enable = Some(true);
|
||||
config.set_config_file(
|
||||
ConfigFile::new(
|
||||
|
|
|
@ -3,19 +3,15 @@
|
|||
use super::cache::calculate_fs_version;
|
||||
use super::cache::calculate_fs_version_at_path;
|
||||
use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY;
|
||||
use super::config::Config;
|
||||
use super::language_server::StateNpmSnapshot;
|
||||
use super::text::LineIndex;
|
||||
use super::tsc;
|
||||
use super::tsc::AssetDocument;
|
||||
|
||||
use crate::args::package_json;
|
||||
use crate::args::package_json::PackageJsonDeps;
|
||||
use crate::args::ConfigFile;
|
||||
use crate::args::JsxImportSourceConfig;
|
||||
use crate::cache::FastInsecureHasher;
|
||||
use crate::cache::HttpCache;
|
||||
use crate::jsr::JsrCacheResolver;
|
||||
use crate::lsp::logging::lsp_warn;
|
||||
use crate::npm::CliNpmResolver;
|
||||
use crate::resolver::CliGraphResolver;
|
||||
use crate::resolver::CliGraphResolverOptions;
|
||||
|
@ -28,9 +24,6 @@ use crate::util::path::specifier_to_file_path;
|
|||
use deno_ast::MediaType;
|
||||
use deno_ast::ParsedSource;
|
||||
use deno_ast::SourceTextInfo;
|
||||
use deno_config::glob::FilePatterns;
|
||||
use deno_config::glob::PathOrPattern;
|
||||
use deno_config::glob::PathOrPatternSet;
|
||||
use deno_core::error::custom_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::future;
|
||||
|
@ -53,15 +46,12 @@ use indexmap::IndexMap;
|
|||
use once_cell::sync::Lazy;
|
||||
use package_json::PackageJsonDepsProvider;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs;
|
||||
use std::fs::ReadDir;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tower_lsp::lsp_types as lsp;
|
||||
|
@ -828,14 +818,12 @@ impl FileSystemDocuments {
|
|||
}
|
||||
|
||||
pub struct UpdateDocumentConfigOptions<'a> {
|
||||
pub file_patterns: FilePatterns,
|
||||
pub document_preload_limit: usize,
|
||||
pub config: &'a Config,
|
||||
pub maybe_import_map: Option<Arc<import_map::ImportMap>>,
|
||||
pub maybe_config_file: Option<&'a ConfigFile>,
|
||||
pub maybe_package_json: Option<&'a PackageJson>,
|
||||
pub maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||
pub node_resolver: Option<Arc<CliNodeResolver>>,
|
||||
pub npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
||||
pub workspace_files: &'a BTreeSet<ModuleSpecifier>,
|
||||
}
|
||||
|
||||
/// Specify the documents to include on a `documents.documents(...)` call.
|
||||
|
@ -863,9 +851,6 @@ pub struct Documents {
|
|||
open_docs: HashMap<ModuleSpecifier, Document>,
|
||||
/// Documents stored on the file system.
|
||||
file_system_docs: Arc<Mutex<FileSystemDocuments>>,
|
||||
/// Hash of the config used for resolution. When the hash changes we update
|
||||
/// dependencies.
|
||||
resolver_config_hash: u64,
|
||||
/// Any imports to the context supplied by configuration files. This is like
|
||||
/// the imports into the a module graph in CLI.
|
||||
imports: Arc<IndexMap<ModuleSpecifier, GraphImport>>,
|
||||
|
@ -893,7 +878,6 @@ impl Documents {
|
|||
dependents_map: Default::default(),
|
||||
open_docs: HashMap::default(),
|
||||
file_system_docs: Default::default(),
|
||||
resolver_config_hash: 0,
|
||||
imports: Default::default(),
|
||||
resolver: Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
||||
node_resolver: None,
|
||||
|
@ -1326,84 +1310,13 @@ impl Documents {
|
|||
}
|
||||
|
||||
pub fn update_config(&mut self, options: UpdateDocumentConfigOptions) {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn calculate_resolver_config_hash(
|
||||
file_patterns: &FilePatterns,
|
||||
document_preload_limit: usize,
|
||||
maybe_import_map: Option<&import_map::ImportMap>,
|
||||
maybe_jsx_config: Option<&JsxImportSourceConfig>,
|
||||
maybe_vendor_dir: Option<bool>,
|
||||
maybe_package_json_deps: Option<&PackageJsonDeps>,
|
||||
maybe_unstable_flags: Option<&Vec<String>>,
|
||||
) -> u64 {
|
||||
fn get_pattern_set_vec(set: &PathOrPatternSet) -> Vec<Cow<'_, str>> {
|
||||
let mut paths = set
|
||||
.inner()
|
||||
.iter()
|
||||
.map(|p| match p {
|
||||
PathOrPattern::Path(p) => p.to_string_lossy(),
|
||||
PathOrPattern::NegatedPath(p) => {
|
||||
Cow::Owned(format!("!{}", p.to_string_lossy()))
|
||||
}
|
||||
PathOrPattern::RemoteUrl(p) => Cow::Borrowed(p.as_str()),
|
||||
PathOrPattern::Pattern(p) => p.as_str(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// ensure these are sorted so the hashing is deterministic
|
||||
paths.sort_unstable();
|
||||
paths
|
||||
}
|
||||
|
||||
let mut hasher = FastInsecureHasher::default();
|
||||
hasher.write_hashable(document_preload_limit);
|
||||
hasher.write_hashable(
|
||||
&file_patterns.include.as_ref().map(get_pattern_set_vec),
|
||||
);
|
||||
hasher.write_hashable(&get_pattern_set_vec(&file_patterns.exclude));
|
||||
if let Some(import_map) = maybe_import_map {
|
||||
hasher.write_str(&import_map.to_json());
|
||||
hasher.write_str(import_map.base_url().as_str());
|
||||
}
|
||||
hasher.write_hashable(maybe_vendor_dir);
|
||||
hasher.write_hashable(maybe_jsx_config);
|
||||
hasher.write_hashable(maybe_unstable_flags);
|
||||
if let Some(package_json_deps) = &maybe_package_json_deps {
|
||||
// We need to ensure the hashing is deterministic so explicitly type
|
||||
// this in order to catch if the type of package_json_deps ever changes
|
||||
// from a deterministic IndexMap to something else.
|
||||
let package_json_deps: &IndexMap<_, _> = *package_json_deps;
|
||||
for (key, value) in package_json_deps {
|
||||
hasher.write_hashable(key);
|
||||
match value {
|
||||
Ok(value) => {
|
||||
hasher.write_hashable(value);
|
||||
}
|
||||
Err(err) => {
|
||||
hasher.write_str(&err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
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 = options
|
||||
.maybe_config_file
|
||||
let maybe_jsx_config = maybe_config_file
|
||||
.and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten());
|
||||
let new_resolver_config_hash = calculate_resolver_config_hash(
|
||||
&options.file_patterns,
|
||||
options.document_preload_limit,
|
||||
options.maybe_import_map.as_deref(),
|
||||
maybe_jsx_config.as_ref(),
|
||||
options.maybe_config_file.and_then(|c| c.json.vendor),
|
||||
maybe_package_json_deps.as_ref(),
|
||||
options.maybe_config_file.map(|c| &c.json.unstable),
|
||||
);
|
||||
let deps_provider =
|
||||
Arc::new(PackageJsonDepsProvider::new(maybe_package_json_deps));
|
||||
self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
||||
|
@ -1412,12 +1325,10 @@ impl Documents {
|
|||
package_json_deps_provider: deps_provider,
|
||||
maybe_jsx_import_source_config: maybe_jsx_config,
|
||||
maybe_import_map: options.maybe_import_map,
|
||||
maybe_vendor_dir: options
|
||||
.maybe_config_file
|
||||
maybe_vendor_dir: maybe_config_file
|
||||
.and_then(|c| c.vendor_dir_path())
|
||||
.as_ref(),
|
||||
bare_node_builtins_enabled: options
|
||||
.maybe_config_file
|
||||
bare_node_builtins_enabled: maybe_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
|
||||
|
@ -1427,13 +1338,15 @@ impl Documents {
|
|||
}));
|
||||
self.jsr_resolver = Arc::new(JsrCacheResolver::new(
|
||||
self.cache.clone(),
|
||||
options.maybe_lockfile,
|
||||
options.config.maybe_lockfile().cloned(),
|
||||
));
|
||||
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)) =
|
||||
options.maybe_config_file.map(|cf| cf.to_maybe_imports())
|
||||
maybe_config_file.map(|cf| cf.to_maybe_imports())
|
||||
{
|
||||
imports
|
||||
.into_iter()
|
||||
|
@ -1441,8 +1354,8 @@ impl Documents {
|
|||
let graph_import = GraphImport::new(
|
||||
&referrer,
|
||||
imports,
|
||||
Some(self.get_resolver()),
|
||||
Some(self.get_npm_resolver()),
|
||||
Some(resolver),
|
||||
Some(npm_resolver),
|
||||
);
|
||||
(referrer, graph_import)
|
||||
})
|
||||
|
@ -1451,121 +1364,56 @@ impl Documents {
|
|||
IndexMap::new()
|
||||
},
|
||||
);
|
||||
self.unstable_sloppy_imports = options
|
||||
.maybe_config_file
|
||||
self.unstable_sloppy_imports = maybe_config_file
|
||||
.map(|c| c.has_unstable("sloppy-imports"))
|
||||
.unwrap_or(false);
|
||||
|
||||
// only refresh the dependencies if the underlying configuration has changed
|
||||
if self.resolver_config_hash != new_resolver_config_hash {
|
||||
self.refresh_dependencies(
|
||||
options.file_patterns,
|
||||
options.document_preload_limit,
|
||||
);
|
||||
self.resolver_config_hash = new_resolver_config_hash;
|
||||
|
||||
self.increment_project_version();
|
||||
self.dirty = true;
|
||||
self.calculate_dependents_if_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_dependencies(
|
||||
&mut self,
|
||||
file_patterns: FilePatterns,
|
||||
document_preload_limit: usize,
|
||||
) {
|
||||
let resolver = self.resolver.as_graph_resolver();
|
||||
let npm_resolver = self.resolver.as_graph_npm_resolver();
|
||||
for doc in self.open_docs.values_mut() {
|
||||
if let Some(new_doc) = doc.maybe_with_new_resolver(resolver, npm_resolver)
|
||||
{
|
||||
*doc = new_doc;
|
||||
{
|
||||
let mut fs_docs = self.file_system_docs.lock();
|
||||
// Clean up non-existent documents.
|
||||
fs_docs.docs.retain(|specifier, _| {
|
||||
let Ok(path) = specifier_to_file_path(specifier) else {
|
||||
// Remove non-file schemed docs (deps). They may not be dependencies
|
||||
// anymore after updating resolvers.
|
||||
return false;
|
||||
};
|
||||
if !options.config.specifier_enabled(specifier) {
|
||||
return false;
|
||||
}
|
||||
path.is_file()
|
||||
});
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
if let Some(new_doc) =
|
||||
doc.maybe_with_new_resolver(resolver, npm_resolver)
|
||||
{
|
||||
*doc = new_doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the file system documents
|
||||
let mut fs_docs = self.file_system_docs.lock();
|
||||
if document_preload_limit > 0 {
|
||||
let mut not_found_docs =
|
||||
fs_docs.docs.keys().cloned().collect::<HashSet<_>>();
|
||||
let open_docs = &mut self.open_docs;
|
||||
|
||||
log::debug!("Preloading documents from enabled urls...");
|
||||
let mut finder =
|
||||
PreloadDocumentFinder::new(PreloadDocumentFinderOptions {
|
||||
file_patterns,
|
||||
limit: document_preload_limit,
|
||||
});
|
||||
for specifier in finder.by_ref() {
|
||||
// mark this document as having been found
|
||||
not_found_docs.remove(&specifier);
|
||||
|
||||
if !open_docs.contains_key(&specifier)
|
||||
&& !fs_docs.docs.contains_key(&specifier)
|
||||
self.open_docs = open_docs;
|
||||
for specifier in options.workspace_files {
|
||||
if !options.config.specifier_enabled(specifier) {
|
||||
continue;
|
||||
}
|
||||
if !self.open_docs.contains_key(specifier)
|
||||
&& !fs_docs.docs.contains_key(specifier)
|
||||
{
|
||||
fs_docs.refresh_document(
|
||||
&self.cache,
|
||||
resolver,
|
||||
&specifier,
|
||||
specifier,
|
||||
npm_resolver,
|
||||
);
|
||||
} else {
|
||||
// update the existing entry to have the new resolver
|
||||
if let Some(doc) = fs_docs.docs.get_mut(&specifier) {
|
||||
if let Some(new_doc) =
|
||||
doc.maybe_with_new_resolver(resolver, npm_resolver)
|
||||
{
|
||||
*doc = new_doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if finder.hit_limit() {
|
||||
lsp_warn!(
|
||||
concat!(
|
||||
"Hit the language server document preload limit of {} file system entries. ",
|
||||
"You may want to use the \"deno.enablePaths\" configuration setting to only have Deno ",
|
||||
"partially enable a workspace or increase the limit via \"deno.documentPreloadLimit\". ",
|
||||
"In cases where Deno ends up using too much memory, you may want to lower the limit."
|
||||
),
|
||||
document_preload_limit,
|
||||
);
|
||||
|
||||
// since we hit the limit, just update everything to use the new resolver
|
||||
for uri in not_found_docs {
|
||||
if let Some(doc) = fs_docs.docs.get_mut(&uri) {
|
||||
if let Some(new_doc) =
|
||||
doc.maybe_with_new_resolver(resolver, npm_resolver)
|
||||
{
|
||||
*doc = new_doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clean up and remove any documents that weren't found
|
||||
for uri in not_found_docs {
|
||||
fs_docs.docs.remove(&uri);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This log statement is used in the tests to ensure preloading doesn't
|
||||
// happen, which is not useful in the repl and could be very expensive
|
||||
// if the repl is launched from a directory with a lot of descendants.
|
||||
log::debug!("Skipping document preload.");
|
||||
|
||||
// just update to use the new resolver
|
||||
for doc in fs_docs.docs.values_mut() {
|
||||
if let Some(new_doc) =
|
||||
doc.maybe_with_new_resolver(resolver, npm_resolver)
|
||||
{
|
||||
*doc = new_doc;
|
||||
}
|
||||
}
|
||||
fs_docs.dirty = true;
|
||||
}
|
||||
|
||||
fs_docs.dirty = true;
|
||||
self.dirty = true;
|
||||
self.calculate_dependents_if_dirty();
|
||||
}
|
||||
|
||||
/// Iterate through the documents, building a map where the key is a unique
|
||||
|
@ -1884,230 +1732,13 @@ fn analyze_module(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PendingEntry {
|
||||
/// File specified as a root url.
|
||||
SpecifiedRootFile(PathBuf),
|
||||
/// Directory that is queued to read.
|
||||
Dir(PathBuf, Rc<FilePatterns>),
|
||||
/// The current directory being read.
|
||||
ReadDir(Box<ReadDir>, Rc<FilePatterns>),
|
||||
}
|
||||
|
||||
struct PreloadDocumentFinderOptions {
|
||||
file_patterns: FilePatterns,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
/// Iterator that finds documents that can be preloaded into
|
||||
/// the LSP on startup.
|
||||
struct PreloadDocumentFinder {
|
||||
limit: usize,
|
||||
entry_count: usize,
|
||||
pending_entries: VecDeque<PendingEntry>,
|
||||
root_dir_entries: Vec<PendingEntry>,
|
||||
visited_paths: HashSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl PreloadDocumentFinder {
|
||||
pub fn new(options: PreloadDocumentFinderOptions) -> Self {
|
||||
fn is_allowed_root_dir(dir_path: &Path) -> bool {
|
||||
if dir_path.parent().is_none() {
|
||||
// never search the root directory of a drive
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
let mut finder = PreloadDocumentFinder {
|
||||
limit: options.limit,
|
||||
entry_count: 0,
|
||||
pending_entries: Default::default(),
|
||||
root_dir_entries: Default::default(),
|
||||
visited_paths: Default::default(),
|
||||
};
|
||||
|
||||
let file_patterns_by_base = options.file_patterns.split_by_base();
|
||||
|
||||
// initialize the finder with the initial paths
|
||||
for file_patterns in file_patterns_by_base {
|
||||
let path = &file_patterns.base;
|
||||
if path.is_dir() {
|
||||
if is_allowed_root_dir(path) {
|
||||
finder
|
||||
.root_dir_entries
|
||||
.push(PendingEntry::Dir(path.clone(), Rc::new(file_patterns)));
|
||||
}
|
||||
} else {
|
||||
finder
|
||||
.pending_entries
|
||||
.push_back(PendingEntry::SpecifiedRootFile(path.clone()));
|
||||
}
|
||||
}
|
||||
finder
|
||||
}
|
||||
|
||||
pub fn hit_limit(&self) -> bool {
|
||||
self.entry_count >= self.limit
|
||||
}
|
||||
|
||||
fn get_valid_specifier(path: &Path) -> Option<ModuleSpecifier> {
|
||||
fn is_allowed_media_type(media_type: MediaType) -> bool {
|
||||
match media_type {
|
||||
MediaType::JavaScript
|
||||
| MediaType::Jsx
|
||||
| MediaType::Mjs
|
||||
| MediaType::Cjs
|
||||
| MediaType::TypeScript
|
||||
| MediaType::Mts
|
||||
| MediaType::Cts
|
||||
| MediaType::Dts
|
||||
| MediaType::Dmts
|
||||
| MediaType::Dcts
|
||||
| MediaType::Tsx => true,
|
||||
MediaType::Json // ignore because json never depends on other files
|
||||
| MediaType::Wasm
|
||||
| MediaType::SourceMap
|
||||
| MediaType::TsBuildInfo
|
||||
| MediaType::Unknown => false,
|
||||
}
|
||||
}
|
||||
|
||||
let media_type = MediaType::from_path(path);
|
||||
if is_allowed_media_type(media_type) {
|
||||
if let Ok(specifier) = ModuleSpecifier::from_file_path(path) {
|
||||
return Some(specifier);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PreloadDocumentFinder {
|
||||
type Item = ModuleSpecifier;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
fn is_discoverable_dir(dir_path: &Path) -> bool {
|
||||
if let Some(dir_name) = dir_path.file_name() {
|
||||
let dir_name = dir_name.to_string_lossy().to_lowercase();
|
||||
// We ignore these directories by default because there is a
|
||||
// high likelihood they aren't relevant. Someone can opt-into
|
||||
// them by specifying one of them as an enabled path.
|
||||
if matches!(dir_name.as_str(), "node_modules" | ".git") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore cargo target directories for anyone using Deno with Rust
|
||||
if dir_name == "target"
|
||||
&& dir_path
|
||||
.parent()
|
||||
.map(|p| p.join("Cargo.toml").exists())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_discoverable_file(file_path: &Path) -> bool {
|
||||
// Don't auto-discover minified files as they are likely to be very large
|
||||
// and likely not to have dependencies on code outside them that would
|
||||
// be useful in the LSP
|
||||
if let Some(file_name) = file_path.file_name() {
|
||||
let file_name = file_name.to_string_lossy().to_lowercase();
|
||||
!file_name.as_str().contains(".min.")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// This first drains all the pending entries then adds the root dir entries
|
||||
// one at a time to the pending entries before draining them. This is because
|
||||
// we're traversing based on directory depth, so we want to search deeper
|
||||
// directories first
|
||||
while !self.pending_entries.is_empty() || !self.root_dir_entries.is_empty()
|
||||
{
|
||||
while let Some(entry) = self.pending_entries.pop_front() {
|
||||
match entry {
|
||||
PendingEntry::SpecifiedRootFile(file) => {
|
||||
// since it was a file that was specified as a root url, only
|
||||
// verify that it's valid
|
||||
if let Some(specifier) = Self::get_valid_specifier(&file) {
|
||||
return Some(specifier);
|
||||
}
|
||||
}
|
||||
PendingEntry::Dir(dir_path, file_patterns) => {
|
||||
if self.visited_paths.insert(dir_path.clone()) {
|
||||
if let Ok(read_dir) = fs::read_dir(&dir_path) {
|
||||
self.pending_entries.push_back(PendingEntry::ReadDir(
|
||||
Box::new(read_dir),
|
||||
file_patterns,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
PendingEntry::ReadDir(mut entries, file_patterns) => {
|
||||
while let Some(entry) = entries.next() {
|
||||
self.entry_count += 1;
|
||||
|
||||
if self.hit_limit() {
|
||||
self.pending_entries.clear(); // stop searching
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
if let Ok(file_type) = entry.file_type() {
|
||||
let is_dir = file_type.is_dir();
|
||||
let path_kind = match is_dir {
|
||||
true => deno_config::glob::PathKind::Directory,
|
||||
false => deno_config::glob::PathKind::File,
|
||||
};
|
||||
if file_patterns.matches_path(&path, path_kind) {
|
||||
if is_dir && is_discoverable_dir(&path) {
|
||||
self.pending_entries.push_back(PendingEntry::Dir(
|
||||
path.to_path_buf(),
|
||||
file_patterns.clone(),
|
||||
));
|
||||
} else if file_type.is_file() && is_discoverable_file(&path)
|
||||
{
|
||||
if let Some(specifier) = Self::get_valid_specifier(&path)
|
||||
{
|
||||
// restore the next entries for next time
|
||||
self.pending_entries.push_front(PendingEntry::ReadDir(
|
||||
entries,
|
||||
file_patterns.clone(),
|
||||
));
|
||||
return Some(specifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(entry) = self.root_dir_entries.pop() {
|
||||
self.pending_entries.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cache::GlobalHttpCache;
|
||||
use crate::cache::RealDenoCacheEnv;
|
||||
|
||||
use super::*;
|
||||
use deno_core::serde_json;
|
||||
use import_map::ImportMap;
|
||||
use pretty_assertions::assert_eq;
|
||||
use test_util::PathRef;
|
||||
|
@ -2231,6 +1862,20 @@ console.log(b, "hello deno");
|
|||
let file3_specifier = ModuleSpecifier::from_file_path(&file3_path).unwrap();
|
||||
fs::write(&file3_path, "").unwrap();
|
||||
|
||||
let mut config =
|
||||
Config::new_with_roots(vec![ModuleSpecifier::from_directory_path(
|
||||
&documents_path,
|
||||
)
|
||||
.unwrap()]);
|
||||
let workspace_settings =
|
||||
serde_json::from_str(r#"{ "enable": true }"#).unwrap();
|
||||
config.set_workspace_settings(workspace_settings, vec![]);
|
||||
let workspace_files =
|
||||
[&file1_specifier, &file2_specifier, &file3_specifier]
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
// set the initial import map and point to file 2
|
||||
{
|
||||
let mut import_map = ImportMap::new(
|
||||
|
@ -2243,16 +1888,12 @@ console.log(b, "hello deno");
|
|||
.unwrap();
|
||||
|
||||
documents.update_config(UpdateDocumentConfigOptions {
|
||||
file_patterns: FilePatterns::new_with_base(
|
||||
documents_path.to_path_buf(),
|
||||
),
|
||||
document_preload_limit: 1_000,
|
||||
config: &config,
|
||||
maybe_import_map: Some(Arc::new(import_map)),
|
||||
maybe_config_file: None,
|
||||
maybe_package_json: None,
|
||||
maybe_lockfile: None,
|
||||
node_resolver: None,
|
||||
npm_resolver: None,
|
||||
workspace_files: &workspace_files,
|
||||
});
|
||||
|
||||
// open the document
|
||||
|
@ -2287,16 +1928,12 @@ console.log(b, "hello deno");
|
|||
.unwrap();
|
||||
|
||||
documents.update_config(UpdateDocumentConfigOptions {
|
||||
file_patterns: FilePatterns::new_with_base(
|
||||
documents_path.to_path_buf(),
|
||||
),
|
||||
document_preload_limit: 1_000,
|
||||
config: &config,
|
||||
maybe_import_map: Some(Arc::new(import_map)),
|
||||
maybe_config_file: None,
|
||||
maybe_package_json: None,
|
||||
maybe_lockfile: None,
|
||||
node_resolver: None,
|
||||
npm_resolver: None,
|
||||
workspace_files: &workspace_files,
|
||||
});
|
||||
|
||||
// check the document's dependencies
|
||||
|
@ -2313,163 +1950,4 @@ console.log(b, "hello deno");
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_pre_load_document_finder() {
|
||||
let temp_dir = TempDir::new();
|
||||
temp_dir.create_dir_all("root1/node_modules/");
|
||||
temp_dir.write("root1/node_modules/mod.ts", ""); // no, node_modules
|
||||
|
||||
temp_dir.create_dir_all("root1/sub_dir");
|
||||
temp_dir.create_dir_all("root1/target");
|
||||
temp_dir.create_dir_all("root1/node_modules");
|
||||
temp_dir.create_dir_all("root1/.git");
|
||||
temp_dir.create_dir_all("root1/file.ts"); // no, directory
|
||||
temp_dir.write("root1/mod1.ts", ""); // yes
|
||||
temp_dir.write("root1/mod2.js", ""); // yes
|
||||
temp_dir.write("root1/mod3.tsx", ""); // yes
|
||||
temp_dir.write("root1/mod4.d.ts", ""); // yes
|
||||
temp_dir.write("root1/mod5.jsx", ""); // yes
|
||||
temp_dir.write("root1/mod6.mjs", ""); // yes
|
||||
temp_dir.write("root1/mod7.mts", ""); // yes
|
||||
temp_dir.write("root1/mod8.d.mts", ""); // yes
|
||||
temp_dir.write("root1/other.json", ""); // no, json
|
||||
temp_dir.write("root1/other.txt", ""); // no, text file
|
||||
temp_dir.write("root1/other.wasm", ""); // no, don't load wasm
|
||||
temp_dir.write("root1/Cargo.toml", ""); // no
|
||||
temp_dir.write("root1/sub_dir/mod.ts", ""); // yes
|
||||
temp_dir.write("root1/sub_dir/data.min.ts", ""); // no, minified file
|
||||
temp_dir.write("root1/.git/main.ts", ""); // no, .git folder
|
||||
temp_dir.write("root1/node_modules/main.ts", ""); // no, because it's in a node_modules folder
|
||||
temp_dir.write("root1/target/main.ts", ""); // no, because there is a Cargo.toml in the root directory
|
||||
|
||||
temp_dir.create_dir_all("root2/folder");
|
||||
temp_dir.create_dir_all("root2/sub_folder");
|
||||
temp_dir.write("root2/file1.ts", ""); // yes, provided
|
||||
temp_dir.write("root2/file2.ts", ""); // no, not provided
|
||||
temp_dir.write("root2/main.min.ts", ""); // yes, provided
|
||||
temp_dir.write("root2/folder/main.ts", ""); // yes, provided
|
||||
temp_dir.write("root2/sub_folder/a.js", ""); // no, not provided
|
||||
temp_dir.write("root2/sub_folder/b.ts", ""); // no, not provided
|
||||
temp_dir.write("root2/sub_folder/c.js", ""); // no, not provided
|
||||
|
||||
temp_dir.create_dir_all("root3/");
|
||||
temp_dir.write("root3/mod.ts", ""); // no, not provided
|
||||
|
||||
let mut urls = PreloadDocumentFinder::new(PreloadDocumentFinderOptions {
|
||||
file_patterns: FilePatterns {
|
||||
base: temp_dir.path().to_path_buf(),
|
||||
include: Some(
|
||||
PathOrPatternSet::from_include_relative_path_or_patterns(
|
||||
temp_dir.path().as_path(),
|
||||
&[
|
||||
"root1".to_string(),
|
||||
"root2/file1.ts".to_string(),
|
||||
"root2/main.min.ts".to_string(),
|
||||
"root2/folder".to_string(),
|
||||
],
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
exclude: Default::default(),
|
||||
},
|
||||
limit: 1_000,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Ideally we would test for order here, which should be BFS, but
|
||||
// different file systems have different directory iteration
|
||||
// so we sort the results
|
||||
urls.sort();
|
||||
|
||||
assert_eq!(
|
||||
urls,
|
||||
vec![
|
||||
temp_dir.uri().join("root1/mod1.ts").unwrap(),
|
||||
temp_dir.uri().join("root1/mod2.js").unwrap(),
|
||||
temp_dir.uri().join("root1/mod3.tsx").unwrap(),
|
||||
temp_dir.uri().join("root1/mod4.d.ts").unwrap(),
|
||||
temp_dir.uri().join("root1/mod5.jsx").unwrap(),
|
||||
temp_dir.uri().join("root1/mod6.mjs").unwrap(),
|
||||
temp_dir.uri().join("root1/mod7.mts").unwrap(),
|
||||
temp_dir.uri().join("root1/mod8.d.mts").unwrap(),
|
||||
temp_dir.uri().join("root1/sub_dir/mod.ts").unwrap(),
|
||||
temp_dir.uri().join("root2/file1.ts").unwrap(),
|
||||
temp_dir.uri().join("root2/folder/main.ts").unwrap(),
|
||||
temp_dir.uri().join("root2/main.min.ts").unwrap(),
|
||||
]
|
||||
);
|
||||
|
||||
// now try iterating with a low limit
|
||||
let urls = PreloadDocumentFinder::new(PreloadDocumentFinderOptions {
|
||||
file_patterns: FilePatterns {
|
||||
base: temp_dir.path().to_path_buf(),
|
||||
include: Default::default(),
|
||||
exclude: Default::default(),
|
||||
},
|
||||
limit: 10, // entries and not results
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// since different file system have different iteration
|
||||
// order, the number here may vary, so just assert it's below
|
||||
// a certain amount
|
||||
assert!(urls.len() < 5, "Actual length: {}", urls.len());
|
||||
|
||||
// now try with certain directories and files disabled
|
||||
let mut urls = PreloadDocumentFinder::new(PreloadDocumentFinderOptions {
|
||||
file_patterns: FilePatterns {
|
||||
base: temp_dir.path().to_path_buf(),
|
||||
include: Default::default(),
|
||||
exclude: PathOrPatternSet::from_exclude_relative_path_or_patterns(
|
||||
temp_dir.path().as_path(),
|
||||
&[
|
||||
"root1".to_string(),
|
||||
"root2/file1.ts".to_string(),
|
||||
"**/*.js".to_string(), // ignore js files
|
||||
],
|
||||
)
|
||||
.unwrap(),
|
||||
},
|
||||
limit: 1_000,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
urls.sort();
|
||||
assert_eq!(
|
||||
urls,
|
||||
vec![
|
||||
temp_dir.uri().join("root2/file2.ts").unwrap(),
|
||||
temp_dir.uri().join("root2/folder/main.ts").unwrap(),
|
||||
temp_dir.uri().join("root2/sub_folder/b.ts").unwrap(), // won't have the javascript files
|
||||
temp_dir.uri().join("root3/mod.ts").unwrap(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_pre_load_document_finder_disallowed_dirs() {
|
||||
if cfg!(windows) {
|
||||
let paths = PreloadDocumentFinder::new(PreloadDocumentFinderOptions {
|
||||
file_patterns: FilePatterns {
|
||||
base: PathBuf::from("C:\\"),
|
||||
include: Default::default(),
|
||||
exclude: Default::default(),
|
||||
},
|
||||
limit: 1_000,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(paths, vec![]);
|
||||
} else {
|
||||
let paths = PreloadDocumentFinder::new(PreloadDocumentFinderOptions {
|
||||
file_patterns: FilePatterns {
|
||||
base: PathBuf::from("/"),
|
||||
include: Default::default(),
|
||||
exclude: Default::default(),
|
||||
},
|
||||
limit: 1_000,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(paths, vec![]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use base64::Engine;
|
||||
use deno_ast::MediaType;
|
||||
use deno_config::glob::FilePatterns;
|
||||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
|
@ -28,9 +27,10 @@ use indexmap::IndexSet;
|
|||
use log::error;
|
||||
use serde::Deserialize;
|
||||
use serde_json::from_value;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::env;
|
||||
use std::fmt::Write as _;
|
||||
use std::path::Path;
|
||||
|
@ -274,6 +274,10 @@ pub struct Inner {
|
|||
pub ts_server: Arc<TsServer>,
|
||||
/// A map of specifiers and URLs used to translate over the LSP.
|
||||
pub url_map: urls::LspUrlMap,
|
||||
workspace_files: BTreeSet<ModuleSpecifier>,
|
||||
/// Set to `self.config.settings.enable_settings_hash()` after
|
||||
/// refreshing `self.workspace_files`.
|
||||
workspace_files_hash: u64,
|
||||
}
|
||||
|
||||
impl LanguageServer {
|
||||
|
@ -486,14 +490,12 @@ impl LanguageServer {
|
|||
}
|
||||
let mut configs = configs.into_iter();
|
||||
let unscoped = configs.next().unwrap();
|
||||
let mut by_workspace_folder = BTreeMap::new();
|
||||
let mut folder_settings = Vec::with_capacity(folders.len());
|
||||
for (folder_uri, _) in &folders {
|
||||
by_workspace_folder
|
||||
.insert(folder_uri.clone(), configs.next().unwrap());
|
||||
folder_settings.push((folder_uri.clone(), configs.next().unwrap()));
|
||||
}
|
||||
let mut ls = self.0.write().await;
|
||||
ls.config
|
||||
.set_workspace_settings(unscoped, Some(by_workspace_folder));
|
||||
ls.config.set_workspace_settings(unscoped, folder_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -574,6 +576,8 @@ impl Inner {
|
|||
ts_fixable_diagnostics: Default::default(),
|
||||
ts_server,
|
||||
url_map: Default::default(),
|
||||
workspace_files: Default::default(),
|
||||
workspace_files_hash: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1226,11 +1230,12 @@ impl Inner {
|
|||
if let Some(options) = params.initialization_options {
|
||||
self.config.set_workspace_settings(
|
||||
WorkspaceSettings::from_initialization_options(options),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
let mut workspace_folders = vec![];
|
||||
if let Some(folders) = params.workspace_folders {
|
||||
self.config.workspace_folders = folders
|
||||
workspace_folders = folders
|
||||
.into_iter()
|
||||
.map(|folder| {
|
||||
(
|
||||
|
@ -1243,15 +1248,10 @@ impl Inner {
|
|||
// rootUri is deprecated by the LSP spec. If it's specified, merge it into
|
||||
// workspace_folders.
|
||||
if let Some(root_uri) = params.root_uri {
|
||||
if !self
|
||||
.config
|
||||
.workspace_folders
|
||||
.iter()
|
||||
.any(|(_, f)| f.uri == root_uri)
|
||||
{
|
||||
if !workspace_folders.iter().any(|(_, f)| f.uri == root_uri) {
|
||||
let name = root_uri.path_segments().and_then(|s| s.last());
|
||||
let name = name.unwrap_or_default().to_string();
|
||||
self.config.workspace_folders.insert(
|
||||
workspace_folders.insert(
|
||||
0,
|
||||
(
|
||||
self.url_map.normalize_url(&root_uri, LspUrlKind::Folder),
|
||||
|
@ -1263,6 +1263,7 @@ impl Inner {
|
|||
);
|
||||
}
|
||||
}
|
||||
self.config.set_workspace_folders(workspace_folders);
|
||||
self.config.update_capabilities(¶ms.capabilities);
|
||||
}
|
||||
|
||||
|
@ -1319,23 +1320,144 @@ impl Inner {
|
|||
})
|
||||
}
|
||||
|
||||
fn walk_workspace(config: &Config) -> (BTreeSet<ModuleSpecifier>, bool) {
|
||||
let mut workspace_files = Default::default();
|
||||
let document_preload_limit =
|
||||
config.workspace_settings().document_preload_limit;
|
||||
let mut pending = VecDeque::new();
|
||||
let mut entry_count = 0;
|
||||
let mut roots = config
|
||||
.workspace_folders
|
||||
.iter()
|
||||
.filter_map(|p| specifier_to_file_path(&p.0).ok())
|
||||
.collect::<Vec<_>>();
|
||||
roots.sort();
|
||||
for i in 0..roots.len() {
|
||||
if i == 0 || !roots[i].starts_with(&roots[i - 1]) {
|
||||
if let Ok(read_dir) = std::fs::read_dir(&roots[i]) {
|
||||
pending.push_back((roots[i].clone(), read_dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some((parent_path, read_dir)) = pending.pop_front() {
|
||||
for entry in read_dir {
|
||||
let Ok(entry) = entry else {
|
||||
continue;
|
||||
};
|
||||
if entry_count >= document_preload_limit {
|
||||
return (workspace_files, true);
|
||||
}
|
||||
entry_count += 1;
|
||||
let path = parent_path.join(entry.path());
|
||||
let Ok(specifier) = ModuleSpecifier::from_file_path(&path) else {
|
||||
continue;
|
||||
};
|
||||
// TODO(nayeemrmn): Don't walk folders that are `None` here and aren't
|
||||
// in a `deno.json` scope.
|
||||
if config.settings.specifier_enabled(&specifier) == Some(false) {
|
||||
continue;
|
||||
}
|
||||
let Ok(file_type) = entry.file_type() else {
|
||||
continue;
|
||||
};
|
||||
let Some(file_name) = path.file_name() else {
|
||||
continue;
|
||||
};
|
||||
if file_type.is_dir() {
|
||||
let dir_name = file_name.to_string_lossy().to_lowercase();
|
||||
// We ignore these directories by default because there is a
|
||||
// high likelihood they aren't relevant. Someone can opt-into
|
||||
// them by specifying one of them as an enabled path.
|
||||
if matches!(dir_name.as_str(), "node_modules" | ".git") {
|
||||
continue;
|
||||
}
|
||||
// ignore cargo target directories for anyone using Deno with Rust
|
||||
if dir_name == "target"
|
||||
&& path
|
||||
.parent()
|
||||
.map(|p| p.join("Cargo.toml").exists())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Ok(read_dir) = std::fs::read_dir(&path) {
|
||||
pending.push_back((path, read_dir));
|
||||
}
|
||||
} else if file_type.is_file()
|
||||
|| file_type.is_symlink()
|
||||
&& std::fs::metadata(&path)
|
||||
.ok()
|
||||
.map(|m| m.is_file())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if file_name.to_string_lossy().contains(".min.") {
|
||||
continue;
|
||||
}
|
||||
let media_type = MediaType::from_specifier(&specifier);
|
||||
match media_type {
|
||||
MediaType::JavaScript
|
||||
| MediaType::Jsx
|
||||
| MediaType::Mjs
|
||||
| MediaType::Cjs
|
||||
| MediaType::TypeScript
|
||||
| MediaType::Mts
|
||||
| MediaType::Cts
|
||||
| MediaType::Dts
|
||||
| MediaType::Dmts
|
||||
| MediaType::Dcts
|
||||
| MediaType::Json
|
||||
| MediaType::Tsx => {}
|
||||
MediaType::Wasm
|
||||
| MediaType::SourceMap
|
||||
| MediaType::TsBuildInfo
|
||||
| MediaType::Unknown => {
|
||||
if path.extension().and_then(|s| s.to_str()) != Some("jsonc") {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
workspace_files.insert(specifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
(workspace_files, false)
|
||||
}
|
||||
|
||||
fn refresh_workspace_files(&mut self) {
|
||||
let enable_settings_hash = self.config.settings.enable_settings_hash();
|
||||
if self.workspace_files_hash == enable_settings_hash {
|
||||
return;
|
||||
}
|
||||
let (workspace_files, hit_limit) = Self::walk_workspace(&self.config);
|
||||
if hit_limit {
|
||||
let document_preload_limit =
|
||||
self.config.workspace_settings().document_preload_limit;
|
||||
if document_preload_limit == 0 {
|
||||
log::debug!("Skipped document preload.");
|
||||
} else {
|
||||
lsp_warn!(
|
||||
concat!(
|
||||
"Hit the language server document preload limit of {} file system entries. ",
|
||||
"You may want to use the \"deno.enablePaths\" configuration setting to only have Deno ",
|
||||
"partially enable a workspace or increase the limit via \"deno.documentPreloadLimit\". ",
|
||||
"In cases where Deno ends up using too much memory, you may want to lower the limit."
|
||||
),
|
||||
document_preload_limit,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.workspace_files = workspace_files;
|
||||
self.workspace_files_hash = enable_settings_hash;
|
||||
}
|
||||
|
||||
async fn refresh_documents_config(&mut self) {
|
||||
self.documents.update_config(UpdateDocumentConfigOptions {
|
||||
file_patterns: FilePatterns {
|
||||
base: self.initial_cwd.clone(),
|
||||
include: Some(self.config.get_enabled_paths()),
|
||||
exclude: self.config.get_disabled_paths(),
|
||||
},
|
||||
document_preload_limit: self
|
||||
.config
|
||||
.workspace_settings()
|
||||
.document_preload_limit,
|
||||
config: &self.config,
|
||||
maybe_import_map: self.maybe_import_map.clone(),
|
||||
maybe_config_file: self.config.maybe_config_file(),
|
||||
maybe_package_json: self.maybe_package_json.as_ref(),
|
||||
maybe_lockfile: self.config.maybe_lockfile().cloned(),
|
||||
node_resolver: self.npm.node_resolver.clone(),
|
||||
npm_resolver: self.npm.resolver.clone(),
|
||||
workspace_files: &self.workspace_files,
|
||||
});
|
||||
|
||||
// refresh the npm specifiers because it might have discovered
|
||||
|
@ -1464,7 +1586,7 @@ impl Inner {
|
|||
WorkspaceSettings::from_raw_settings(deno, javascript, typescript)
|
||||
});
|
||||
if let Some(settings) = config {
|
||||
self.config.set_workspace_settings(settings, None);
|
||||
self.config.set_workspace_settings(settings, vec![]);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1495,6 +1617,7 @@ impl Inner {
|
|||
}
|
||||
|
||||
self.recreate_npm_services_if_necessary().await;
|
||||
self.refresh_workspace_files();
|
||||
self.refresh_documents_config().await;
|
||||
|
||||
self.diagnostics_server.invalidate_all();
|
||||
|
@ -1693,6 +1816,7 @@ impl Inner {
|
|||
|
||||
if touched {
|
||||
self.recreate_npm_services_if_necessary().await;
|
||||
self.refresh_workspace_files();
|
||||
self.refresh_documents_config().await;
|
||||
self.diagnostics_server.invalidate_all();
|
||||
self.ts_server.restart(self.snapshot()).await;
|
||||
|
@ -1725,8 +1849,7 @@ impl Inner {
|
|||
}
|
||||
workspace_folders.push((specifier.clone(), folder.clone()));
|
||||
}
|
||||
|
||||
self.config.workspace_folders = workspace_folders;
|
||||
self.config.set_workspace_folders(workspace_folders);
|
||||
}
|
||||
|
||||
async fn document_symbol(
|
||||
|
@ -3385,6 +3508,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
|||
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();
|
||||
|
@ -3518,6 +3642,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
|||
self.refresh_configuration().await;
|
||||
{
|
||||
let mut ls = self.0.write().await;
|
||||
ls.refresh_workspace_files();
|
||||
ls.refresh_documents_config().await;
|
||||
ls.diagnostics_server.invalidate_all();
|
||||
ls.send_diagnostics_update();
|
||||
|
@ -3973,3 +4098,112 @@ impl Inner {
|
|||
Ok(contents)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use test_util::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_walk_workspace() {
|
||||
let temp_dir = TempDir::new();
|
||||
temp_dir.create_dir_all("root1/node_modules/");
|
||||
temp_dir.write("root1/node_modules/mod.ts", ""); // no, node_modules
|
||||
|
||||
temp_dir.create_dir_all("root1/sub_dir");
|
||||
temp_dir.create_dir_all("root1/target");
|
||||
temp_dir.create_dir_all("root1/node_modules");
|
||||
temp_dir.create_dir_all("root1/.git");
|
||||
temp_dir.create_dir_all("root1/file.ts"); // no, directory
|
||||
temp_dir.write("root1/mod0.ts", ""); // yes
|
||||
temp_dir.write("root1/mod1.js", ""); // yes
|
||||
temp_dir.write("root1/mod2.tsx", ""); // yes
|
||||
temp_dir.write("root1/mod3.d.ts", ""); // yes
|
||||
temp_dir.write("root1/mod4.jsx", ""); // yes
|
||||
temp_dir.write("root1/mod5.mjs", ""); // yes
|
||||
temp_dir.write("root1/mod6.mts", ""); // yes
|
||||
temp_dir.write("root1/mod7.d.mts", ""); // yes
|
||||
temp_dir.write("root1/mod8.json", ""); // yes
|
||||
temp_dir.write("root1/mod9.jsonc", ""); // yes
|
||||
temp_dir.write("root1/other.txt", ""); // no, text file
|
||||
temp_dir.write("root1/other.wasm", ""); // no, don't load wasm
|
||||
temp_dir.write("root1/Cargo.toml", ""); // no
|
||||
temp_dir.write("root1/sub_dir/mod.ts", ""); // yes
|
||||
temp_dir.write("root1/sub_dir/data.min.ts", ""); // no, minified file
|
||||
temp_dir.write("root1/.git/main.ts", ""); // no, .git folder
|
||||
temp_dir.write("root1/node_modules/main.ts", ""); // no, because it's in a node_modules folder
|
||||
temp_dir.write("root1/target/main.ts", ""); // no, because there is a Cargo.toml in the root directory
|
||||
|
||||
temp_dir.create_dir_all("root2/folder");
|
||||
temp_dir.create_dir_all("root2/sub_folder");
|
||||
temp_dir.write("root2/file1.ts", ""); // yes, enabled
|
||||
temp_dir.write("root2/file2.ts", ""); // no, not enabled
|
||||
temp_dir.write("root2/folder/main.ts", ""); // yes, enabled
|
||||
temp_dir.write("root2/folder/other.ts", ""); // no, disabled
|
||||
temp_dir.write("root2/sub_folder/a.js", ""); // no, not enabled
|
||||
temp_dir.write("root2/sub_folder/b.ts", ""); // no, not enabled
|
||||
temp_dir.write("root2/sub_folder/c.js", ""); // no, not enabled
|
||||
|
||||
temp_dir.create_dir_all("root3/");
|
||||
temp_dir.write("root3/mod.ts", ""); // no, not enabled
|
||||
|
||||
let mut config = Config::new_with_roots(vec![
|
||||
temp_dir.uri().join("root1/").unwrap(),
|
||||
temp_dir.uri().join("root2/").unwrap(),
|
||||
temp_dir.uri().join("root3/").unwrap(),
|
||||
]);
|
||||
config.set_workspace_settings(
|
||||
Default::default(),
|
||||
vec![
|
||||
(
|
||||
temp_dir.uri().join("root1/").unwrap(),
|
||||
WorkspaceSettings {
|
||||
enable: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
temp_dir.uri().join("root2/").unwrap(),
|
||||
WorkspaceSettings {
|
||||
enable: Some(true),
|
||||
enable_paths: Some(vec![
|
||||
"file1.ts".to_string(),
|
||||
"folder".to_string(),
|
||||
]),
|
||||
disable_paths: vec!["folder/other.ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
temp_dir.uri().join("root3/").unwrap(),
|
||||
WorkspaceSettings {
|
||||
enable: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
let (workspace_files, hit_limit) = Inner::walk_workspace(&config);
|
||||
assert!(!hit_limit);
|
||||
assert_eq!(
|
||||
json!(workspace_files),
|
||||
json!([
|
||||
temp_dir.uri().join("root1/mod0.ts").unwrap(),
|
||||
temp_dir.uri().join("root1/mod1.js").unwrap(),
|
||||
temp_dir.uri().join("root1/mod2.tsx").unwrap(),
|
||||
temp_dir.uri().join("root1/mod3.d.ts").unwrap(),
|
||||
temp_dir.uri().join("root1/mod4.jsx").unwrap(),
|
||||
temp_dir.uri().join("root1/mod5.mjs").unwrap(),
|
||||
temp_dir.uri().join("root1/mod6.mts").unwrap(),
|
||||
temp_dir.uri().join("root1/mod7.d.mts").unwrap(),
|
||||
temp_dir.uri().join("root1/mod8.json").unwrap(),
|
||||
temp_dir.uri().join("root1/mod9.jsonc").unwrap(),
|
||||
temp_dir.uri().join("root1/sub_dir/mod.ts").unwrap(),
|
||||
temp_dir.uri().join("root2/file1.ts").unwrap(),
|
||||
temp_dir.uri().join("root2/folder/main.ts").unwrap(),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5469,7 +5469,7 @@ mod tests {
|
|||
.variable_types
|
||||
.suppress_when_type_matches_name = true;
|
||||
let mut config = config::Config::new();
|
||||
config.set_workspace_settings(settings, None);
|
||||
config.set_workspace_settings(settings, vec![]);
|
||||
let user_preferences = UserPreferences::from_config_for_specifier(
|
||||
&config,
|
||||
&Default::default(),
|
||||
|
|
|
@ -1085,7 +1085,7 @@ fn closed_file_pre_load_does_not_occur() {
|
|||
.new_command()
|
||||
.args_vec(["repl", "-A", "--log-level=debug"])
|
||||
.with_pty(|console| {
|
||||
assert_contains!(console.all_output(), "Skipping document preload.",);
|
||||
assert_contains!(console.all_output(), "Skipped document preload.",);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue