feat(lsp): ability to configure document pre-load limit (#19097)

Adds a `deno.preloadLimit` option (ex. `"deno.preloadLimit": 2000`)
which specifies how many file entries to traverse on the file system
when the lsp loads or its configuration changes.

Closes #18955
This commit is contained in:
David Sherret 2023-05-11 17:17:14 -04:00 committed by GitHub
parent c926bc0deb
commit 28a72d5488
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 239 additions and 168 deletions

View file

@ -26,13 +26,6 @@ pub enum TestingNotification {
Progress(testing_lsp_custom::TestRunProgressParams), Progress(testing_lsp_custom::TestRunProgressParams),
} }
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub enum LspClientKind {
#[default]
CodeEditor,
Repl,
}
#[derive(Clone)] #[derive(Clone)]
pub struct Client(Arc<dyn ClientTrait>); pub struct Client(Arc<dyn ClientTrait>);
@ -51,10 +44,6 @@ impl Client {
Self(Arc::new(ReplClient)) Self(Arc::new(ReplClient))
} }
pub fn kind(&self) -> LspClientKind {
self.0.kind()
}
/// Gets additional methods that should only be called outside /// Gets additional methods that should only be called outside
/// the LSP's lock to prevent deadlocking scenarios. /// the LSP's lock to prevent deadlocking scenarios.
pub fn when_outside_lsp_lock(&self) -> OutsideLockClient { pub fn when_outside_lsp_lock(&self) -> OutsideLockClient {
@ -160,7 +149,6 @@ impl OutsideLockClient {
#[async_trait] #[async_trait]
trait ClientTrait: Send + Sync { trait ClientTrait: Send + Sync {
fn kind(&self) -> LspClientKind;
async fn publish_diagnostics( async fn publish_diagnostics(
&self, &self,
uri: lsp::Url, uri: lsp::Url,
@ -189,10 +177,6 @@ struct TowerClient(tower_lsp::Client);
#[async_trait] #[async_trait]
impl ClientTrait for TowerClient { impl ClientTrait for TowerClient {
fn kind(&self) -> LspClientKind {
LspClientKind::CodeEditor
}
async fn publish_diagnostics( async fn publish_diagnostics(
&self, &self,
uri: lsp::Url, uri: lsp::Url,
@ -312,10 +296,6 @@ struct ReplClient;
#[async_trait] #[async_trait]
impl ClientTrait for ReplClient { impl ClientTrait for ReplClient {
fn kind(&self) -> LspClientKind {
LspClientKind::Repl
}
async fn publish_diagnostics( async fn publish_diagnostics(
&self, &self,
_uri: lsp::Url, _uri: lsp::Url,

View file

@ -391,7 +391,7 @@ pub async fn collect(
code_lenses.extend( code_lenses.extend(
collect_tsc( collect_tsc(
specifier, specifier,
&config.get_workspace_settings(), config.workspace_settings(),
line_index, line_index,
navigation_tree, navigation_tree,
) )

View file

@ -519,7 +519,7 @@ mod tests {
source_fixtures: &[(&str, &str)], source_fixtures: &[(&str, &str)],
location: &Path, location: &Path,
) -> Documents { ) -> Documents {
let mut documents = Documents::new(location, Default::default()); let mut documents = Documents::new(location);
for (specifier, source, version, language_id) in fixtures { for (specifier, source, version, language_id) in fixtures {
let specifier = let specifier =
resolve_url(specifier).expect("failed to create specifier"); resolve_url(specifier).expect("failed to create specifier");

View file

@ -265,6 +265,10 @@ fn default_to_true() -> bool {
true true
} }
fn default_document_preload_limit() -> usize {
1000
}
fn empty_string_none<'de, D: serde::Deserializer<'de>>( fn empty_string_none<'de, D: serde::Deserializer<'de>>(
d: D, d: D,
) -> Result<Option<String>, D::Error> { ) -> Result<Option<String>, D::Error> {
@ -318,6 +322,10 @@ pub struct WorkspaceSettings {
#[serde(default = "default_to_true")] #[serde(default = "default_to_true")]
pub lint: bool, pub lint: bool,
/// Limits the number of files that can be preloaded by the language server.
#[serde(default = "default_document_preload_limit")]
pub document_preload_limit: usize,
/// A flag that indicates if Dene should validate code against the unstable /// A flag that indicates if Dene should validate code against the unstable
/// APIs for the workspace. /// APIs for the workspace.
#[serde(default)] #[serde(default)]
@ -354,6 +362,7 @@ impl Default for WorkspaceSettings {
inlay_hints: Default::default(), inlay_hints: Default::default(),
internal_debug: false, internal_debug: false,
lint: true, lint: true,
document_preload_limit: default_document_preload_limit(),
suggest: Default::default(), suggest: Default::default(),
testing: Default::default(), testing: Default::default(),
tls_certificate: None, tls_certificate: None,
@ -439,8 +448,8 @@ impl Config {
} }
} }
pub fn get_workspace_settings(&self) -> WorkspaceSettings { pub fn workspace_settings(&self) -> &WorkspaceSettings {
self.settings.workspace.clone() &self.settings.workspace
} }
/// Set the workspace settings directly, which occurs during initialization /// Set the workspace settings directly, which occurs during initialization
@ -714,7 +723,7 @@ mod tests {
.set_workspace_settings(json!({})) .set_workspace_settings(json!({}))
.expect("could not update"); .expect("could not update");
assert_eq!( assert_eq!(
config.get_workspace_settings(), config.workspace_settings().clone(),
WorkspaceSettings { WorkspaceSettings {
enable: false, enable: false,
enable_paths: Vec::new(), enable_paths: Vec::new(),
@ -750,6 +759,7 @@ mod tests {
}, },
internal_debug: false, internal_debug: false,
lint: true, lint: true,
document_preload_limit: 1_000,
suggest: CompletionSettings { suggest: CompletionSettings {
complete_function_calls: false, complete_function_calls: false,
names: true, names: true,
@ -778,7 +788,7 @@ mod tests {
.set_workspace_settings(json!({ "cache": "" })) .set_workspace_settings(json!({ "cache": "" }))
.expect("could not update"); .expect("could not update");
assert_eq!( assert_eq!(
config.get_workspace_settings(), config.workspace_settings().clone(),
WorkspaceSettings::default() WorkspaceSettings::default()
); );
} }
@ -790,7 +800,7 @@ mod tests {
.set_workspace_settings(json!({ "import_map": "" })) .set_workspace_settings(json!({ "import_map": "" }))
.expect("could not update"); .expect("could not update");
assert_eq!( assert_eq!(
config.get_workspace_settings(), config.workspace_settings().clone(),
WorkspaceSettings::default() WorkspaceSettings::default()
); );
} }
@ -802,7 +812,7 @@ mod tests {
.set_workspace_settings(json!({ "tls_certificate": "" })) .set_workspace_settings(json!({ "tls_certificate": "" }))
.expect("could not update"); .expect("could not update");
assert_eq!( assert_eq!(
config.get_workspace_settings(), config.workspace_settings().clone(),
WorkspaceSettings::default() WorkspaceSettings::default()
); );
} }
@ -814,7 +824,7 @@ mod tests {
.set_workspace_settings(json!({ "config": "" })) .set_workspace_settings(json!({ "config": "" }))
.expect("could not update"); .expect("could not update");
assert_eq!( assert_eq!(
config.get_workspace_settings(), config.workspace_settings().clone(),
WorkspaceSettings::default() WorkspaceSettings::default()
); );
} }

View file

@ -1096,7 +1096,7 @@ mod tests {
location: &Path, location: &Path,
maybe_import_map: Option<(&str, &str)>, maybe_import_map: Option<(&str, &str)>,
) -> StateSnapshot { ) -> StateSnapshot {
let mut documents = Documents::new(location, Default::default()); let mut documents = Documents::new(location);
for (specifier, source, version, language_id) in fixtures { for (specifier, source, version, language_id) in fixtures {
let specifier = let specifier =
resolve_url(specifier).expect("failed to create specifier"); resolve_url(specifier).expect("failed to create specifier");

View file

@ -1,7 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use super::cache::calculate_fs_version; use super::cache::calculate_fs_version;
use super::client::LspClientKind;
use super::text::LineIndex; use super::text::LineIndex;
use super::tsc; use super::tsc;
use super::tsc::AssetDocument; use super::tsc::AssetDocument;
@ -793,6 +792,16 @@ fn get_document_path(
} }
} }
pub struct UpdateDocumentConfigOptions<'a> {
pub enabled_urls: Vec<Url>,
pub document_preload_limit: usize,
pub maybe_import_map: Option<Arc<import_map::ImportMap>>,
pub maybe_config_file: Option<&'a ConfigFile>,
pub maybe_package_json: Option<&'a PackageJson>,
pub npm_registry_api: Arc<CliNpmRegistryApi>,
pub npm_resolution: Arc<NpmResolution>,
}
/// Specify the documents to include on a `documents.documents(...)` call. /// Specify the documents to include on a `documents.documents(...)` call.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum DocumentsFilter { pub enum DocumentsFilter {
@ -818,8 +827,6 @@ pub struct Documents {
open_docs: HashMap<ModuleSpecifier, Document>, open_docs: HashMap<ModuleSpecifier, Document>,
/// Documents stored on the file system. /// Documents stored on the file system.
file_system_docs: Arc<Mutex<FileSystemDocuments>>, file_system_docs: Arc<Mutex<FileSystemDocuments>>,
/// Kind of the client that is using the documents.
lsp_client_kind: LspClientKind,
/// Hash of the config used for resolution. When the hash changes we update /// Hash of the config used for resolution. When the hash changes we update
/// dependencies. /// dependencies.
resolver_config_hash: u64, resolver_config_hash: u64,
@ -839,14 +846,13 @@ pub struct Documents {
} }
impl Documents { impl Documents {
pub fn new(location: &Path, lsp_client_kind: LspClientKind) -> Self { pub fn new(location: &Path) -> Self {
Self { Self {
cache: HttpCache::new(location), cache: HttpCache::new(location),
dirty: true, dirty: true,
dependents_map: Default::default(), dependents_map: Default::default(),
open_docs: HashMap::default(), open_docs: HashMap::default(),
file_system_docs: Default::default(), file_system_docs: Default::default(),
lsp_client_kind,
resolver_config_hash: 0, resolver_config_hash: 0,
imports: Default::default(), imports: Default::default(),
resolver: Default::default(), resolver: Default::default(),
@ -1161,15 +1167,7 @@ impl Documents {
Ok(()) Ok(())
} }
pub fn update_config( pub fn update_config(&mut self, options: UpdateDocumentConfigOptions) {
&mut self,
enabled_urls: Vec<Url>,
maybe_import_map: Option<Arc<import_map::ImportMap>>,
maybe_config_file: Option<&ConfigFile>,
maybe_package_json: Option<&PackageJson>,
npm_registry_api: Arc<CliNpmRegistryApi>,
npm_resolution: Arc<NpmResolution>,
) {
fn calculate_resolver_config_hash( fn calculate_resolver_config_hash(
enabled_urls: &[Url], enabled_urls: &[Url],
maybe_import_map: Option<&import_map::ImportMap>, maybe_import_map: Option<&import_map::ImportMap>,
@ -1208,14 +1206,16 @@ impl Documents {
hasher.finish() hasher.finish()
} }
let maybe_package_json_deps = maybe_package_json.map(|package_json| { let maybe_package_json_deps =
package_json::get_local_package_json_version_reqs(package_json) options.maybe_package_json.map(|package_json| {
}); package_json::get_local_package_json_version_reqs(package_json)
let maybe_jsx_config = });
maybe_config_file.and_then(|cf| cf.to_maybe_jsx_import_source_config()); let maybe_jsx_config = options
.maybe_config_file
.and_then(|cf| cf.to_maybe_jsx_import_source_config());
let new_resolver_config_hash = calculate_resolver_config_hash( let new_resolver_config_hash = calculate_resolver_config_hash(
&enabled_urls, &options.enabled_urls,
maybe_import_map.as_deref(), options.maybe_import_map.as_deref(),
maybe_jsx_config.as_ref(), maybe_jsx_config.as_ref(),
maybe_package_json_deps.as_ref(), maybe_package_json_deps.as_ref(),
); );
@ -1223,21 +1223,21 @@ impl Documents {
Arc::new(PackageJsonDepsProvider::new(maybe_package_json_deps)); Arc::new(PackageJsonDepsProvider::new(maybe_package_json_deps));
let deps_installer = Arc::new(PackageJsonDepsInstaller::new( let deps_installer = Arc::new(PackageJsonDepsInstaller::new(
deps_provider.clone(), deps_provider.clone(),
npm_registry_api.clone(), options.npm_registry_api.clone(),
npm_resolution.clone(), options.npm_resolution.clone(),
)); ));
self.resolver = Arc::new(CliGraphResolver::new( self.resolver = Arc::new(CliGraphResolver::new(
maybe_jsx_config, maybe_jsx_config,
maybe_import_map, options.maybe_import_map,
false, false,
npm_registry_api, options.npm_registry_api,
npm_resolution, options.npm_resolution,
deps_provider, deps_provider,
deps_installer, deps_installer,
)); ));
self.imports = Arc::new( self.imports = Arc::new(
if let Some(Ok(imports)) = if let Some(Ok(imports)) =
maybe_config_file.map(|cf| cf.to_maybe_imports()) options.maybe_config_file.map(|cf| cf.to_maybe_imports())
{ {
imports imports
.into_iter() .into_iter()
@ -1257,14 +1257,21 @@ impl Documents {
// only refresh the dependencies if the underlying configuration has changed // only refresh the dependencies if the underlying configuration has changed
if self.resolver_config_hash != new_resolver_config_hash { if self.resolver_config_hash != new_resolver_config_hash {
self.refresh_dependencies(enabled_urls); self.refresh_dependencies(
options.enabled_urls,
options.document_preload_limit,
);
self.resolver_config_hash = new_resolver_config_hash; self.resolver_config_hash = new_resolver_config_hash;
} }
self.dirty = true; self.dirty = true;
} }
fn refresh_dependencies(&mut self, enabled_urls: Vec<Url>) { fn refresh_dependencies(
&mut self,
enabled_urls: Vec<Url>,
document_preload_limit: usize,
) {
let resolver = self.resolver.as_graph_resolver(); let resolver = self.resolver.as_graph_resolver();
for doc in self.open_docs.values_mut() { for doc in self.open_docs.values_mut() {
if let Some(new_doc) = doc.maybe_with_new_resolver(resolver) { if let Some(new_doc) = doc.maybe_with_new_resolver(resolver) {
@ -1274,51 +1281,73 @@ impl Documents {
// update the file system documents // update the file system documents
let mut fs_docs = self.file_system_docs.lock(); let mut fs_docs = self.file_system_docs.lock();
match self.lsp_client_kind { if document_preload_limit > 0 {
LspClientKind::CodeEditor => { let mut not_found_docs =
let mut not_found_docs = fs_docs.docs.keys().cloned().collect::<HashSet<_>>();
fs_docs.docs.keys().cloned().collect::<HashSet<_>>(); let open_docs = &mut self.open_docs;
let open_docs = &mut self.open_docs;
log::debug!("Preloading documents from enabled urls..."); log::debug!("Preloading documents from enabled urls...");
for specifier in PreloadDocumentFinder::from_enabled_urls(&enabled_urls) let mut finder = PreloadDocumentFinder::from_enabled_urls_with_limit(
&enabled_urls,
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)
{ {
// mark this document as having been found fs_docs.refresh_document(&self.cache, resolver, &specifier);
not_found_docs.remove(&specifier); } else {
// update the existing entry to have the new resolver
if !open_docs.contains_key(&specifier) if let Some(doc) = fs_docs.docs.get_mut(&specifier) {
&& !fs_docs.docs.contains_key(&specifier) if let Some(new_doc) = doc.maybe_with_new_resolver(resolver) {
{ *doc = new_doc;
fs_docs.refresh_document(&self.cache, resolver, &specifier);
} 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) {
*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) {
*doc = new_doc;
}
}
}
} else {
// clean up and remove any documents that weren't found // clean up and remove any documents that weren't found
for uri in not_found_docs { for uri in not_found_docs {
fs_docs.docs.remove(&uri); fs_docs.docs.remove(&uri);
} }
} }
LspClientKind::Repl => { } else {
// This log statement is used in the tests to ensure preloading doesn't // 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 // 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. // if the repl is launched from a directory with a lot of descendants.
log::debug!("Skipping document preload for repl."); log::debug!("Skipping document preload.");
// for the repl, just update to use the new resolver // just update to use the new resolver
for doc in fs_docs.docs.values_mut() { for doc in fs_docs.docs.values_mut() {
if let Some(new_doc) = doc.maybe_with_new_resolver(resolver) { if let Some(new_doc) = doc.maybe_with_new_resolver(resolver) {
*doc = new_doc; *doc = new_doc;
}
} }
} }
} }
fs_docs.dirty = true; fs_docs.dirty = true;
} }
@ -1558,19 +1587,15 @@ enum PendingEntry {
/// Iterator that finds documents that can be preloaded into /// Iterator that finds documents that can be preloaded into
/// the LSP on startup. /// the LSP on startup.
struct PreloadDocumentFinder { struct PreloadDocumentFinder {
limit: u16, limit: usize,
entry_count: u16, entry_count: usize,
pending_entries: VecDeque<PendingEntry>, pending_entries: VecDeque<PendingEntry>,
} }
impl PreloadDocumentFinder { impl PreloadDocumentFinder {
pub fn from_enabled_urls(enabled_urls: &Vec<Url>) -> Self {
Self::from_enabled_urls_with_limit(enabled_urls, 1_000)
}
pub fn from_enabled_urls_with_limit( pub fn from_enabled_urls_with_limit(
enabled_urls: &Vec<Url>, enabled_urls: &Vec<Url>,
limit: u16, limit: usize,
) -> Self { ) -> Self {
fn is_allowed_root_dir(dir_path: &Path) -> bool { fn is_allowed_root_dir(dir_path: &Path) -> bool {
if dir_path.parent().is_none() { if dir_path.parent().is_none() {
@ -1605,6 +1630,10 @@ impl PreloadDocumentFinder {
finder finder
} }
pub fn hit_limit(&self) -> bool {
self.entry_count >= self.limit
}
fn get_valid_specifier(path: &Path) -> Option<ModuleSpecifier> { fn get_valid_specifier(path: &Path) -> Option<ModuleSpecifier> {
fn is_allowed_media_type(media_type: MediaType) -> bool { fn is_allowed_media_type(media_type: MediaType) -> bool {
match media_type { match media_type {
@ -1699,15 +1728,7 @@ impl Iterator for PreloadDocumentFinder {
while let Some(entry) = entries.next() { while let Some(entry) = entries.next() {
self.entry_count += 1; self.entry_count += 1;
if self.entry_count >= self.limit { if self.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."
),
self.limit,
);
self.pending_entries.clear(); // stop searching self.pending_entries.clear(); // stop searching
return None; return None;
} }
@ -1769,7 +1790,7 @@ mod tests {
fn setup(temp_dir: &TempDir) -> (Documents, PathBuf) { fn setup(temp_dir: &TempDir) -> (Documents, PathBuf) {
let location = temp_dir.path().join("deps"); let location = temp_dir.path().join("deps");
let documents = Documents::new(&location, Default::default()); let documents = Documents::new(&location);
(documents, location) (documents, location)
} }
@ -1899,14 +1920,15 @@ console.log(b, "hello deno");
.append("test".to_string(), "./file2.ts".to_string()) .append("test".to_string(), "./file2.ts".to_string())
.unwrap(); .unwrap();
documents.update_config( documents.update_config(UpdateDocumentConfigOptions {
vec![], enabled_urls: vec![],
Some(Arc::new(import_map)), document_preload_limit: 1_000,
None, maybe_import_map: Some(Arc::new(import_map)),
None, maybe_config_file: None,
npm_registry_api.clone(), maybe_package_json: None,
npm_resolution.clone(), npm_registry_api: npm_registry_api.clone(),
); npm_resolution: npm_resolution.clone(),
});
// open the document // open the document
let document = documents.open( let document = documents.open(
@ -1939,14 +1961,15 @@ console.log(b, "hello deno");
.append("test".to_string(), "./file3.ts".to_string()) .append("test".to_string(), "./file3.ts".to_string())
.unwrap(); .unwrap();
documents.update_config( documents.update_config(UpdateDocumentConfigOptions {
vec![], enabled_urls: vec![],
Some(Arc::new(import_map)), document_preload_limit: 1_000,
None, maybe_import_map: Some(Arc::new(import_map)),
None, maybe_config_file: None,
maybe_package_json: None,
npm_registry_api, npm_registry_api,
npm_resolution, npm_resolution,
); });
// check the document's dependencies // check the document's dependencies
let document = documents.get(&file1_specifier).unwrap(); let document = documents.get(&file1_specifier).unwrap();
@ -2001,12 +2024,15 @@ console.log(b, "hello deno");
temp_dir.create_dir_all("root3/"); temp_dir.create_dir_all("root3/");
temp_dir.write("root3/mod.ts", ""); // no, not provided temp_dir.write("root3/mod.ts", ""); // no, not provided
let mut urls = PreloadDocumentFinder::from_enabled_urls(&vec![ let mut urls = PreloadDocumentFinder::from_enabled_urls_with_limit(
temp_dir.uri().join("root1/").unwrap(), &vec![
temp_dir.uri().join("root2/file1.ts").unwrap(), temp_dir.uri().join("root1/").unwrap(),
temp_dir.uri().join("root2/main.min.ts").unwrap(), temp_dir.uri().join("root2/file1.ts").unwrap(),
temp_dir.uri().join("root2/folder/").unwrap(), temp_dir.uri().join("root2/main.min.ts").unwrap(),
]) temp_dir.uri().join("root2/folder/").unwrap(),
],
1_000,
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Ideally we would test for order here, which should be BFS, but // Ideally we would test for order here, which should be BFS, but
@ -2048,18 +2074,18 @@ console.log(b, "hello deno");
#[test] #[test]
pub fn test_pre_load_document_finder_disallowed_dirs() { pub fn test_pre_load_document_finder_disallowed_dirs() {
if cfg!(windows) { if cfg!(windows) {
let paths = PreloadDocumentFinder::from_enabled_urls(&vec![Url::parse( let paths = PreloadDocumentFinder::from_enabled_urls_with_limit(
"file:///c:/", &vec![Url::parse("file:///c:/").unwrap()],
1_000,
) )
.unwrap()])
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(paths, vec![]); assert_eq!(paths, vec![]);
} else { } else {
let paths = let paths = PreloadDocumentFinder::from_enabled_urls_with_limit(
PreloadDocumentFinder::from_enabled_urls(&vec![ &vec![Url::parse("file:///").unwrap()],
Url::parse("file:///").unwrap() 1_000,
]) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(paths, vec![]); assert_eq!(paths, vec![]);
} }
} }

View file

@ -49,6 +49,7 @@ use super::documents::Document;
use super::documents::Documents; use super::documents::Documents;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
use super::documents::LanguageId; use super::documents::LanguageId;
use super::documents::UpdateDocumentConfigOptions;
use super::logging::lsp_log; use super::logging::lsp_log;
use super::logging::lsp_warn; use super::logging::lsp_warn;
use super::lsp_custom; use super::lsp_custom;
@ -285,7 +286,7 @@ impl LanguageServer {
if let Some(testing_server) = &inner.maybe_testing_server { if let Some(testing_server) = &inner.maybe_testing_server {
match params.map(serde_json::from_value) { match params.map(serde_json::from_value) {
Some(Ok(params)) => testing_server Some(Ok(params)) => testing_server
.run_request(params, inner.config.get_workspace_settings()), .run_request(params, inner.config.workspace_settings().clone()),
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())), Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
None => Err(LspError::invalid_params("Missing parameters")), None => Err(LspError::invalid_params("Missing parameters")),
} }
@ -489,7 +490,7 @@ impl Inner {
let module_registries = let module_registries =
ModuleRegistry::new(&module_registries_location, http_client.clone()); ModuleRegistry::new(&module_registries_location, http_client.clone());
let location = dir.deps_folder_path(); let location = dir.deps_folder_path();
let documents = Documents::new(&location, client.kind()); let documents = Documents::new(&location);
let deps_http_cache = HttpCache::new(&location); let deps_http_cache = HttpCache::new(&location);
let cache_metadata = cache::CacheMetadata::new(deps_http_cache.clone()); let cache_metadata = cache::CacheMetadata::new(deps_http_cache.clone());
let performance = Arc::new(Performance::default()); let performance = Arc::new(Performance::default());
@ -602,9 +603,9 @@ impl Inner {
} }
fn get_config_file(&self) -> Result<Option<ConfigFile>, AnyError> { fn get_config_file(&self) -> Result<Option<ConfigFile>, AnyError> {
let workspace_settings = self.config.get_workspace_settings(); let workspace_settings = self.config.workspace_settings();
let maybe_config = workspace_settings.config; let maybe_config = &workspace_settings.config;
if let Some(config_str) = &maybe_config { if let Some(config_str) = maybe_config {
if !config_str.is_empty() { if !config_str.is_empty() {
lsp_log!("Setting Deno configuration from: \"{}\"", config_str); lsp_log!("Setting Deno configuration from: \"{}\"", config_str);
let config_url = if let Ok(url) = Url::from_file_path(config_str) { let config_url = if let Ok(url) = Url::from_file_path(config_str) {
@ -744,8 +745,8 @@ impl Inner {
pub fn update_cache(&mut self) -> Result<(), AnyError> { pub fn update_cache(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_cache", None::<()>); let mark = self.performance.mark("update_cache", None::<()>);
self.performance.measure(mark); self.performance.measure(mark);
let maybe_cache = self.config.get_workspace_settings().cache; let maybe_cache = &self.config.workspace_settings().cache;
let maybe_cache_path = if let Some(cache_str) = &maybe_cache { let maybe_cache_path = if let Some(cache_str) = maybe_cache {
lsp_log!("Setting cache path from: \"{}\"", cache_str); lsp_log!("Setting cache path from: \"{}\"", cache_str);
let cache_url = if let Ok(url) = Url::from_file_path(cache_str) { let cache_url = if let Ok(url) = Url::from_file_path(cache_str) {
Ok(url) Ok(url)
@ -785,7 +786,7 @@ impl Inner {
.clone() .clone()
.or_else(|| env::var("DENO_DIR").map(String::into).ok()); .or_else(|| env::var("DENO_DIR").map(String::into).ok());
let dir = DenoDir::new(maybe_custom_root)?; let dir = DenoDir::new(maybe_custom_root)?;
let workspace_settings = self.config.get_workspace_settings(); let workspace_settings = self.config.workspace_settings();
let maybe_root_path = self let maybe_root_path = self
.config .config
.root_uri .root_uri
@ -793,15 +794,17 @@ impl Inner {
.and_then(|uri| specifier_to_file_path(uri).ok()); .and_then(|uri| specifier_to_file_path(uri).ok());
let root_cert_store = get_root_cert_store( let root_cert_store = get_root_cert_store(
maybe_root_path, maybe_root_path,
workspace_settings.certificate_stores, workspace_settings.certificate_stores.clone(),
workspace_settings.tls_certificate.map(CaData::File), workspace_settings.tls_certificate.clone().map(CaData::File),
)?; )?;
let root_cert_store_provider = let root_cert_store_provider =
Arc::new(LspRootCertStoreProvider(root_cert_store)); Arc::new(LspRootCertStoreProvider(root_cert_store));
let module_registries_location = dir.registries_folder_path(); let module_registries_location = dir.registries_folder_path();
self.http_client = Arc::new(HttpClient::new( self.http_client = Arc::new(HttpClient::new(
Some(root_cert_store_provider), Some(root_cert_store_provider),
workspace_settings.unsafely_ignore_certificate_errors, workspace_settings
.unsafely_ignore_certificate_errors
.clone(),
)); ));
self.module_registries = ModuleRegistry::new( self.module_registries = ModuleRegistry::new(
&module_registries_location, &module_registries_location,
@ -883,8 +886,9 @@ impl Inner {
Ok( Ok(
if let Some(import_map_str) = self if let Some(import_map_str) = self
.config .config
.get_workspace_settings() .workspace_settings()
.import_map .import_map
.clone()
.and_then(|s| if s.is_empty() { None } else { Some(s) }) .and_then(|s| if s.is_empty() { None } else { Some(s) })
{ {
lsp_log!( lsp_log!(
@ -957,14 +961,14 @@ impl Inner {
} }
pub fn update_debug_flag(&self) { pub fn update_debug_flag(&self) {
let internal_debug = self.config.get_workspace_settings().internal_debug; let internal_debug = self.config.workspace_settings().internal_debug;
super::logging::set_lsp_debug_flag(internal_debug) super::logging::set_lsp_debug_flag(internal_debug)
} }
async fn update_registries(&mut self) -> Result<(), AnyError> { async fn update_registries(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_registries", None::<()>); let mark = self.performance.mark("update_registries", None::<()>);
self.recreate_http_client_and_dependents(self.maybe_cache_path.clone())?; self.recreate_http_client_and_dependents(self.maybe_cache_path.clone())?;
let workspace_settings = self.config.get_workspace_settings(); let workspace_settings = self.config.workspace_settings();
for (registry, enabled) in workspace_settings.suggest.imports.hosts.iter() { for (registry, enabled) in workspace_settings.suggest.imports.hosts.iter() {
if *enabled { if *enabled {
lsp_log!("Enabling import suggestions for: {}", registry); lsp_log!("Enabling import suggestions for: {}", registry);
@ -1037,7 +1041,7 @@ impl Inner {
"useUnknownInCatchVariables": false, "useUnknownInCatchVariables": false,
})); }));
let config = &self.config; let config = &self.config;
let workspace_settings = config.get_workspace_settings(); let workspace_settings = config.workspace_settings();
if workspace_settings.unstable { if workspace_settings.unstable {
let unstable_libs = json!({ let unstable_libs = json!({
"lib": ["deno.ns", "deno.window", "deno.unstable"] "lib": ["deno.ns", "deno.window", "deno.unstable"]
@ -1169,14 +1173,18 @@ impl Inner {
} }
fn refresh_documents_config(&mut self) { fn refresh_documents_config(&mut self) {
self.documents.update_config( self.documents.update_config(UpdateDocumentConfigOptions {
self.config.enabled_urls(), enabled_urls: self.config.enabled_urls(),
self.maybe_import_map.clone(), document_preload_limit: self
self.maybe_config_file.as_ref(), .config
self.maybe_package_json.as_ref(), .workspace_settings()
self.npm_api.clone(), .document_preload_limit,
self.npm_resolution.clone(), maybe_import_map: self.maybe_import_map.clone(),
); maybe_config_file: self.maybe_config_file.as_ref(),
maybe_package_json: self.maybe_package_json.as_ref(),
npm_registry_api: self.npm_api.clone(),
npm_resolution: self.npm_resolution.clone(),
});
} }
async fn shutdown(&self) -> LspResult<()> { async fn shutdown(&self) -> LspResult<()> {
@ -1871,7 +1879,7 @@ impl Inner {
.normalize_url(&params.text_document.uri, LspUrlKind::File); .normalize_url(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
|| !(self.config.get_workspace_settings().enabled_code_lens() || !(self.config.workspace_settings().enabled_code_lens()
|| self.config.specifier_code_lens_test(&specifier)) || self.config.specifier_code_lens_test(&specifier))
{ {
return Ok(None); return Ok(None);
@ -2171,7 +2179,7 @@ impl Inner {
), ),
include_automatic_optional_chain_completions: Some(true), include_automatic_optional_chain_completions: Some(true),
include_completions_for_import_statements: Some( include_completions_for_import_statements: Some(
self.config.get_workspace_settings().suggest.auto_imports, self.config.workspace_settings().suggest.auto_imports,
), ),
include_completions_for_module_exports: Some(true), include_completions_for_module_exports: Some(true),
include_completions_with_object_literal_method_snippets: Some( include_completions_with_object_literal_method_snippets: Some(
@ -2205,7 +2213,7 @@ impl Inner {
if let Some(completions) = maybe_completion_info { if let Some(completions) = maybe_completion_info {
let results = completions.as_completion_response( let results = completions.as_completion_response(
line_index, line_index,
&self.config.get_workspace_settings().suggest, &self.config.workspace_settings().suggest,
&specifier, &specifier,
position, position,
); );
@ -3315,7 +3323,7 @@ impl Inner {
let specifier = self let specifier = self
.url_map .url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File); .normalize_url(&params.text_document.uri, LspUrlKind::File);
let workspace_settings = self.config.get_workspace_settings(); let workspace_settings = self.config.workspace_settings();
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
|| !workspace_settings.enabled_inlay_hints() || !workspace_settings.enabled_inlay_hints()
@ -3334,7 +3342,7 @@ impl Inner {
let req = tsc::RequestMethod::ProvideInlayHints(( let req = tsc::RequestMethod::ProvideInlayHints((
specifier, specifier,
range, range,
(&workspace_settings).into(), workspace_settings.into(),
)); ));
let maybe_inlay_hints: Option<Vec<tsc::InlayHint>> = self let maybe_inlay_hints: Option<Vec<tsc::InlayHint>> = self
.ts_server .ts_server
@ -3388,7 +3396,7 @@ impl Inner {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
documents_specifiers.sort(); documents_specifiers.sort();
let measures = self.performance.to_vec(); let measures = self.performance.to_vec();
let workspace_settings = self.config.get_workspace_settings(); let workspace_settings = self.config.workspace_settings();
write!( write!(
contents, contents,

View file

@ -294,6 +294,7 @@ pub fn get_repl_workspace_settings() -> WorkspaceSettings {
inlay_hints: Default::default(), inlay_hints: Default::default(),
internal_debug: false, internal_debug: false,
lint: false, lint: false,
document_preload_limit: 0, // don't pre-load any modules as it's expensive and not useful for the repl
tls_certificate: None, tls_certificate: None,
unsafely_ignore_certificate_errors: None, unsafely_ignore_certificate_errors: None,
unstable: false, unstable: false,

View file

@ -3525,7 +3525,7 @@ mod tests {
fixtures: &[(&str, &str, i32, LanguageId)], fixtures: &[(&str, &str, i32, LanguageId)],
location: &Path, location: &Path,
) -> StateSnapshot { ) -> StateSnapshot {
let mut documents = Documents::new(location, Default::default()); let mut documents = Documents::new(location);
for (specifier, source, version, language_id) in fixtures { for (specifier, source, version, language_id) in fixtures {
let specifier = let specifier =
resolve_url(specifier).expect("failed to create specifier"); resolve_url(specifier).expect("failed to create specifier");

View file

@ -7418,6 +7418,49 @@ fn lsp_closed_file_find_references() {
client.shutdown(); client.shutdown();
} }
#[test]
fn lsp_closed_file_find_references_low_document_pre_load() {
let context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = context.temp_dir();
temp_dir.create_dir_all("sub_dir");
temp_dir.write("./other_file.ts", "export const b = 5;");
temp_dir.write("./sub_dir/mod.ts", "export const a = 5;");
temp_dir.write(
"./sub_dir/mod.test.ts",
"import { a } from './mod.ts'; console.log(a);",
);
let temp_dir_url = temp_dir.uri();
let mut client = context.new_lsp_command().build();
client.initialize(|builder| {
builder.set_preload_limit(1);
});
client.did_open(json!({
"textDocument": {
"uri": temp_dir_url.join("sub_dir/mod.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": r#"export const a = 5;"#
}
}));
let res = client.write_request(
"textDocument/references",
json!({
"textDocument": {
"uri": temp_dir_url.join("sub_dir/mod.ts").unwrap(),
},
"position": { "line": 0, "character": 13 },
"context": {
"includeDeclaration": false
}
}),
);
// won't have results because the document won't be pre-loaded
assert_eq!(res, json!([]));
client.shutdown();
}
#[test] #[test]
fn lsp_data_urls_with_jsx_compiler_option() { fn lsp_data_urls_with_jsx_compiler_option() {
let context = TestContextBuilder::new().use_temp_cwd().build(); let context = TestContextBuilder::new().use_temp_cwd().build();

View file

@ -1014,9 +1014,6 @@ fn closed_file_pre_load_does_not_occur() {
.new_command() .new_command()
.args_vec(["repl", "-A", "--log-level=debug"]) .args_vec(["repl", "-A", "--log-level=debug"])
.with_pty(|console| { .with_pty(|console| {
assert_contains!( assert_contains!(console.all_output(), "Skipping document preload.",);
console.all_output(),
"Skipping document preload for repl.",
);
}); });
} }

View file

@ -378,6 +378,12 @@ impl InitializeParamsBuilder {
self self
} }
pub fn set_preload_limit(&mut self, arg: usize) -> &mut Self {
let options = self.initialization_options_mut();
options.insert("documentPreloadLimit".to_string(), arg.into());
self
}
pub fn set_tls_certificate(&mut self, value: impl AsRef<str>) -> &mut Self { pub fn set_tls_certificate(&mut self, value: impl AsRef<str>) -> &mut Self {
let options = self.initialization_options_mut(); let options = self.initialization_options_mut();
options.insert( options.insert(