1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(lsp): respect nested deno.json for fmt and lint config (#23159)

This commit is contained in:
Nayeem Rahman 2024-04-02 23:02:50 +01:00 committed by GitHub
parent 3b9fd1af80
commit 2b1c6e172e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 480 additions and 316 deletions

View file

@ -43,7 +43,6 @@ use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types as lsp;
use tower_lsp::lsp_types::Position; use tower_lsp::lsp_types::Position;
use tower_lsp::lsp_types::Range; use tower_lsp::lsp_types::Range;
@ -217,7 +216,7 @@ fn code_as_string(code: &Option<lsp::NumberOrString>) -> String {
/// Rewrites imports in quick fixes and code changes to be Deno specific. /// Rewrites imports in quick fixes and code changes to be Deno specific.
pub struct TsResponseImportMapper<'a> { pub struct TsResponseImportMapper<'a> {
documents: &'a Documents, documents: &'a Documents,
maybe_import_map: Option<Arc<ImportMap>>, maybe_import_map: Option<&'a ImportMap>,
node_resolver: Option<&'a CliNodeResolver>, node_resolver: Option<&'a CliNodeResolver>,
npm_resolver: Option<&'a dyn CliNpmResolver>, npm_resolver: Option<&'a dyn CliNpmResolver>,
} }
@ -225,7 +224,7 @@ pub struct TsResponseImportMapper<'a> {
impl<'a> TsResponseImportMapper<'a> { impl<'a> TsResponseImportMapper<'a> {
pub fn new( pub fn new(
documents: &'a Documents, documents: &'a Documents,
maybe_import_map: Option<Arc<ImportMap>>, maybe_import_map: Option<&'a ImportMap>,
node_resolver: Option<&'a CliNodeResolver>, node_resolver: Option<&'a CliNodeResolver>,
npm_resolver: Option<&'a dyn CliNpmResolver>, npm_resolver: Option<&'a dyn CliNpmResolver>,
) -> Self { ) -> Self {
@ -270,7 +269,7 @@ impl<'a> TsResponseImportMapper<'a> {
let sub_path = (export != ".").then_some(export); let sub_path = (export != ".").then_some(export);
let mut req = None; let mut req = None;
req = req.or_else(|| { req = req.or_else(|| {
let import_map = self.maybe_import_map.as_ref()?; let import_map = self.maybe_import_map?;
for entry in import_map.entries_for_referrer(referrer) { for entry in import_map.entries_for_referrer(referrer) {
let Some(value) = entry.raw_value else { let Some(value) = entry.raw_value else {
continue; continue;
@ -297,7 +296,7 @@ impl<'a> TsResponseImportMapper<'a> {
JsrPackageNvReference::new(nv_ref).to_string() JsrPackageNvReference::new(nv_ref).to_string()
}; };
let specifier = ModuleSpecifier::parse(&spec_str).ok()?; let specifier = ModuleSpecifier::parse(&spec_str).ok()?;
if let Some(import_map) = &self.maybe_import_map { if let Some(import_map) = self.maybe_import_map {
if let Some(result) = import_map.lookup(&specifier, referrer) { if let Some(result) = import_map.lookup(&specifier, referrer) {
return Some(result); return Some(result);
} }
@ -316,7 +315,7 @@ impl<'a> TsResponseImportMapper<'a> {
// check if any pkg reqs match what is found in an import map // check if any pkg reqs match what is found in an import map
if !pkg_reqs.is_empty() { if !pkg_reqs.is_empty() {
let sub_path = self.resolve_package_path(specifier); let sub_path = self.resolve_package_path(specifier);
if let Some(import_map) = &self.maybe_import_map { if let Some(import_map) = self.maybe_import_map {
let pkg_reqs = pkg_reqs.iter().collect::<HashSet<_>>(); let pkg_reqs = pkg_reqs.iter().collect::<HashSet<_>>();
let mut matches = Vec::new(); let mut matches = Vec::new();
for entry in import_map.entries_for_referrer(referrer) { for entry in import_map.entries_for_referrer(referrer) {
@ -358,7 +357,7 @@ impl<'a> TsResponseImportMapper<'a> {
} }
// check if the import map has this specifier // check if the import map has this specifier
if let Some(import_map) = &self.maybe_import_map { if let Some(import_map) = self.maybe_import_map {
if let Some(result) = import_map.lookup(specifier, referrer) { if let Some(result) = import_map.lookup(specifier, referrer) {
return Some(result); return Some(result);
} }

View file

@ -32,7 +32,6 @@ use deno_semver::package::PackageNv;
use import_map::ImportMap; use import_map::ImportMap;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use std::sync::Arc;
use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types as lsp;
static FILE_PROTO_RE: Lazy<Regex> = static FILE_PROTO_RE: Lazy<Regex> =
@ -155,7 +154,7 @@ pub async fn get_import_completions(
jsr_search_api: &CliJsrSearchApi, jsr_search_api: &CliJsrSearchApi,
npm_search_api: &CliNpmSearchApi, npm_search_api: &CliNpmSearchApi,
documents: &Documents, documents: &Documents,
maybe_import_map: Option<Arc<ImportMap>>, maybe_import_map: Option<&ImportMap>,
) -> Option<lsp::CompletionResponse> { ) -> Option<lsp::CompletionResponse> {
let document = documents.get(specifier)?; let document = documents.get(specifier)?;
let (text, _, range) = document.get_maybe_dependency(position)?; let (text, _, range) = document.get_maybe_dependency(position)?;
@ -164,7 +163,7 @@ pub async fn get_import_completions(
specifier, specifier,
&text, &text,
&range, &range,
maybe_import_map.clone(), maybe_import_map,
documents, documents,
) { ) {
// completions for import map specifiers // completions for import map specifiers
@ -238,7 +237,7 @@ pub async fn get_import_completions(
.collect(); .collect();
let mut is_incomplete = false; let mut is_incomplete = false;
if let Some(import_map) = maybe_import_map { if let Some(import_map) = maybe_import_map {
items.extend(get_base_import_map_completions(import_map.as_ref())); items.extend(get_base_import_map_completions(import_map));
} }
if let Some(origin_items) = if let Some(origin_items) =
module_registries.get_origin_completions(&text, &range) module_registries.get_origin_completions(&text, &range)
@ -301,7 +300,7 @@ fn get_import_map_completions(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
text: &str, text: &str,
range: &lsp::Range, range: &lsp::Range,
maybe_import_map: Option<Arc<ImportMap>>, maybe_import_map: Option<&ImportMap>,
documents: &Documents, documents: &Documents,
) -> Option<lsp::CompletionList> { ) -> Option<lsp::CompletionList> {
if !text.is_empty() { if !text.is_empty() {
@ -809,6 +808,7 @@ mod tests {
use deno_graph::Range; use deno_graph::Range;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use test_util::TempDir; use test_util::TempDir;
fn mock_documents( fn mock_documents(

View file

@ -731,7 +731,7 @@ pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
pub settings: Settings, pub settings: Settings,
pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
pub tree: Arc<ConfigTree>, pub tree: ConfigTree,
} }
impl ConfigSnapshot { impl ConfigSnapshot {
@ -745,7 +745,7 @@ impl ConfigSnapshot {
/// Determine if the provided specifier is enabled or not. /// Determine if the provided specifier is enabled or not.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
let config_file = self.tree.config_file_for_specifier(specifier); let config_file = self.tree.config_file_for_specifier(specifier);
if let Some(cf) = &config_file { if let Some(cf) = config_file {
if let Ok(files) = cf.to_files_config() { if let Ok(files) = cf.to_files_config() {
if !files.matches_specifier(specifier) { if !files.matches_specifier(specifier) {
return false; return false;
@ -781,10 +781,6 @@ pub struct Settings {
} }
impl Settings { impl Settings {
pub fn first_root_uri(&self) -> Option<&ModuleSpecifier> {
self.first_folder.as_ref()
}
/// Returns `None` if the value should be deferred to the presence of a /// Returns `None` if the value should be deferred to the presence of a
/// `deno.json` file. /// `deno.json` file.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> Option<bool> { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> Option<bool> {
@ -793,7 +789,7 @@ impl Settings {
return Some(true); return Some(true);
}; };
let (settings, mut folder_uri) = self.get_for_specifier(specifier); let (settings, mut folder_uri) = self.get_for_specifier(specifier);
folder_uri = folder_uri.or_else(|| self.first_root_uri()); folder_uri = folder_uri.or(self.first_folder.as_ref());
let mut disable_paths = vec![]; let mut disable_paths = vec![];
let mut enable_paths = None; let mut enable_paths = None;
if let Some(folder_uri) = folder_uri { if let Some(folder_uri) = folder_uri {
@ -879,7 +875,7 @@ pub struct Config {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
pub settings: Settings, pub settings: Settings,
pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
pub tree: Arc<ConfigTree>, pub tree: ConfigTree,
} }
impl Config { impl Config {
@ -997,7 +993,7 @@ impl Config {
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
let config_file = self.tree.config_file_for_specifier(specifier); let config_file = self.tree.config_file_for_specifier(specifier);
if let Some(cf) = &config_file { if let Some(cf) = config_file {
if let Ok(files) = cf.to_files_config() { if let Ok(files) = cf.to_files_config() {
if !files.matches_specifier(specifier) { if !files.matches_specifier(specifier) {
return false; return false;
@ -1086,23 +1082,51 @@ impl Config {
} }
} }
pub fn default_ts_config() -> TsConfig { #[derive(Debug, Serialize)]
TsConfig::new(json!({ pub struct LspTsConfig {
"allowJs": true, #[serde(flatten)]
"esModuleInterop": true, inner: TsConfig,
"experimentalDecorators": false, }
"isolatedModules": true,
"jsx": "react", impl Default for LspTsConfig {
"lib": ["deno.ns", "deno.window", "deno.unstable"], fn default() -> Self {
"module": "esnext", Self {
"moduleDetection": "force", inner: TsConfig::new(json!({
"noEmit": true, "allowJs": true,
"resolveJsonModule": true, "esModuleInterop": true,
"strict": true, "experimentalDecorators": false,
"target": "esnext", "isolatedModules": true,
"useDefineForClassFields": true, "jsx": "react",
"useUnknownInCatchVariables": false, "lib": ["deno.ns", "deno.window", "deno.unstable"],
})) "module": "esnext",
"moduleDetection": "force",
"noEmit": true,
"resolveJsonModule": true,
"strict": true,
"target": "esnext",
"useDefineForClassFields": true,
"useUnknownInCatchVariables": false,
})),
}
}
}
impl LspTsConfig {
pub fn new(config_file: Option<&ConfigFile>) -> Self {
let mut ts_config = Self::default();
if let Some(config_file) = config_file {
match config_file.to_compiler_options() {
Ok((value, maybe_ignored_options)) => {
ts_config.inner.merge(&value);
if let Some(ignored_options) = maybe_ignored_options {
lsp_warn!("{}", ignored_options);
}
}
Err(err) => lsp_warn!("{}", err),
}
}
ts_config
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -1120,7 +1144,7 @@ pub struct ConfigData {
pub fmt_options: Arc<FmtOptions>, pub fmt_options: Arc<FmtOptions>,
pub lint_options: Arc<LintOptions>, pub lint_options: Arc<LintOptions>,
pub lint_rules: Arc<ConfiguredRules>, pub lint_rules: Arc<ConfiguredRules>,
pub ts_config: Arc<TsConfig>, pub ts_config: Arc<LspTsConfig>,
pub node_modules_dir: Option<PathBuf>, pub node_modules_dir: Option<PathBuf>,
pub vendor_dir: Option<PathBuf>, pub vendor_dir: Option<PathBuf>,
pub lockfile: Option<Arc<Mutex<Lockfile>>>, pub lockfile: Option<Arc<Mutex<Lockfile>>>,
@ -1242,18 +1266,7 @@ impl ConfigData {
.unwrap_or_default(); .unwrap_or_default();
let lint_rules = let lint_rules =
get_configured_rules(lint_options.rules.clone(), config_file.as_ref()); get_configured_rules(lint_options.rules.clone(), config_file.as_ref());
let mut ts_config = default_ts_config(); let ts_config = LspTsConfig::new(config_file.as_ref());
if let Some(config_file) = &config_file {
match config_file.to_compiler_options() {
Ok((value, maybe_ignored_options)) => {
ts_config.merge(&value);
if let Some(ignored_options) = maybe_ignored_options {
lsp_warn!("{}", ignored_options);
}
}
Err(err) => lsp_warn!("{}", err),
}
}
let node_modules_dir = let node_modules_dir =
config_file.as_ref().and_then(resolve_node_modules_dir); config_file.as_ref().and_then(resolve_node_modules_dir);
let vendor_dir = config_file.as_ref().and_then(|c| c.vendor_dir_path()); let vendor_dir = config_file.as_ref().and_then(|c| c.vendor_dir_path());
@ -1425,185 +1438,103 @@ impl ConfigData {
} }
} }
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ConfigTree { pub struct ConfigTree {
root: Mutex<Option<(ModuleSpecifier, Arc<ConfigData>)>>, first_folder: Option<ModuleSpecifier>,
scopes: Arc<BTreeMap<ModuleSpecifier, ConfigData>>,
} }
impl ConfigTree { impl ConfigTree {
pub fn root_data(&self) -> Option<Arc<ConfigData>> { pub fn root_data(&self) -> Option<&ConfigData> {
self.root.lock().as_ref().map(|(_, d)| d.clone()) self.first_folder.as_ref().and_then(|s| self.scopes.get(s))
} }
pub fn root_config_file(&self) -> Option<Arc<ConfigFile>> { pub fn root_ts_config(&self) -> Arc<LspTsConfig> {
self self
.root .root_data()
.lock() .map(|d| d.ts_config.clone())
.as_ref() .unwrap_or_default()
.and_then(|(_, d)| d.config_file.clone())
} }
pub fn root_ts_config(&self) -> Arc<TsConfig> { pub fn root_vendor_dir(&self) -> Option<&PathBuf> {
self self.root_data().and_then(|d| d.vendor_dir.as_ref())
.root
.lock()
.as_ref()
.map(|(_, d)| d.ts_config.clone())
.unwrap_or_else(|| Arc::new(default_ts_config()))
} }
pub fn root_vendor_dir(&self) -> Option<PathBuf> { pub fn root_lockfile(&self) -> Option<&Arc<Mutex<Lockfile>>> {
self self.root_data().and_then(|d| d.lockfile.as_ref())
.root
.lock()
.as_ref()
.and_then(|(_, d)| d.vendor_dir.clone())
} }
pub fn root_lockfile(&self) -> Option<Arc<Mutex<Lockfile>>> { pub fn root_import_map(&self) -> Option<&Arc<ImportMap>> {
self self.root_data().and_then(|d| d.import_map.as_ref())
.root
.lock()
.as_ref()
.and_then(|(_, d)| d.lockfile.clone())
} }
pub fn scope_for_specifier( pub fn scope_for_specifier(
&self, &self,
_specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<ModuleSpecifier> { ) -> Option<&ModuleSpecifier> {
self.root.lock().as_ref().map(|r| r.0.clone()) self
.scopes
.keys()
.rfind(|s| specifier.as_str().starts_with(s.as_str()))
.or(self.first_folder.as_ref())
} }
pub fn data_for_specifier( pub fn data_for_specifier(
&self, &self,
_specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<Arc<ConfigData>> { ) -> Option<&ConfigData> {
self.root_data() self
.scope_for_specifier(specifier)
.and_then(|s| self.scopes.get(s))
} }
pub fn data_by_scope(&self) -> BTreeMap<ModuleSpecifier, Arc<ConfigData>> { pub fn data_by_scope(&self) -> &Arc<BTreeMap<ModuleSpecifier, ConfigData>> {
self.root.lock().iter().cloned().collect() &self.scopes
} }
pub fn config_file_for_specifier( pub fn config_file_for_specifier(
&self, &self,
_specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<Arc<ConfigFile>> { ) -> Option<&Arc<ConfigFile>> {
self.root_config_file()
}
pub fn has_config_file_for_specifier(
&self,
_specifier: &ModuleSpecifier,
) -> bool {
self self
.root .data_for_specifier(specifier)
.lock() .and_then(|d| d.config_file.as_ref())
.as_ref()
.map(|(_, d)| d.config_file.is_some())
.unwrap_or(false)
} }
pub fn config_files(&self) -> Vec<Arc<ConfigFile>> { pub fn config_files(&self) -> Vec<&Arc<ConfigFile>> {
self.root_config_file().into_iter().collect()
}
pub fn package_jsons(&self) -> Vec<Arc<PackageJson>> {
self self
.root .scopes
.lock() .iter()
.as_ref() .filter_map(|(_, d)| d.config_file.as_ref())
.and_then(|(_, d)| d.package_json.clone()) .collect()
.into_iter() }
pub fn package_jsons(&self) -> Vec<&Arc<PackageJson>> {
self
.scopes
.iter()
.filter_map(|(_, d)| d.package_json.as_ref())
.collect() .collect()
} }
pub fn fmt_options_for_specifier( pub fn fmt_options_for_specifier(
&self, &self,
_specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Arc<FmtOptions> { ) -> Arc<FmtOptions> {
self self
.root .data_for_specifier(specifier)
.lock() .map(|d| d.fmt_options.clone())
.as_ref()
.map(|(_, d)| d.fmt_options.clone())
.unwrap_or_default() .unwrap_or_default()
} }
pub fn lockfile_for_specifier(
&self,
_specifier: &ModuleSpecifier,
) -> Option<Arc<Mutex<Lockfile>>> {
self.root_lockfile()
}
pub fn import_map_for_specifier(
&self,
_specifier: &ModuleSpecifier,
) -> Option<Arc<ImportMap>> {
self
.root
.lock()
.as_ref()
.and_then(|(_, d)| d.import_map.clone())
}
pub async fn refresh(
&self,
settings: &Settings,
root_uri: &ModuleSpecifier,
workspace_files: &BTreeSet<ModuleSpecifier>,
file_fetcher: &FileFetcher,
) {
lsp_log!("Refreshing configuration tree...");
let mut root = None;
if let Some(config_path) = &settings.unscoped.config {
if let Ok(config_uri) = root_uri.join(config_path) {
root = Some((
root_uri.clone(),
Arc::new(
ConfigData::load(
Some(&config_uri),
root_uri,
settings,
Some(file_fetcher),
)
.await,
),
));
}
} else {
let get_uri_if_exists = |name| {
let uri = root_uri.join(name).ok();
uri.filter(|s| workspace_files.contains(s))
};
let config_uri = get_uri_if_exists("deno.jsonc")
.or_else(|| get_uri_if_exists("deno.json"));
root = Some((
root_uri.clone(),
Arc::new(
ConfigData::load(
config_uri.as_ref(),
root_uri,
settings,
Some(file_fetcher),
)
.await,
),
));
}
*self.root.lock() = root;
}
/// Returns (scope_uri, type). /// Returns (scope_uri, type).
pub fn watched_file_type( pub fn watched_file_type(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<(ModuleSpecifier, ConfigWatchedFileType)> { ) -> Option<(&ModuleSpecifier, ConfigWatchedFileType)> {
if let Some((scope_uri, data)) = &*self.root.lock() { for (scope_uri, data) in self.scopes.iter() {
if let Some(typ) = data.watched_files.get(specifier) { if let Some(typ) = data.watched_files.get(specifier) {
return Some((scope_uri.clone(), *typ)); return Some((scope_uri, *typ));
} }
} }
None None
@ -1617,14 +1548,81 @@ impl ConfigTree {
return true; return true;
} }
self self
.root .scopes
.lock() .values()
.as_ref() .any(|data| data.watched_files.contains_key(specifier))
.is_some_and(|(_, d)| d.watched_files.contains_key(specifier)) }
pub async fn refresh(
&mut self,
settings: &Settings,
workspace_files: &BTreeSet<ModuleSpecifier>,
file_fetcher: &FileFetcher,
) {
lsp_log!("Refreshing configuration tree...");
let mut scopes = BTreeMap::new();
for (folder_uri, ws_settings) in &settings.by_workspace_folder {
let mut ws_settings = ws_settings.as_ref();
if Some(folder_uri) == settings.first_folder.as_ref() {
ws_settings = ws_settings.or(Some(&settings.unscoped));
}
if let Some(ws_settings) = ws_settings {
if let Some(config_path) = &ws_settings.config {
if let Ok(config_uri) = folder_uri.join(config_path) {
scopes.insert(
folder_uri.clone(),
ConfigData::load(
Some(&config_uri),
folder_uri,
settings,
Some(file_fetcher),
)
.await,
);
}
}
}
}
for specifier in workspace_files {
if specifier.path().ends_with("/deno.json")
|| specifier.path().ends_with("/deno.jsonc")
{
if let Ok(scope) = specifier.join(".") {
let entry = scopes.entry(scope.clone());
#[allow(clippy::map_entry)]
if matches!(entry, std::collections::btree_map::Entry::Vacant(_)) {
let data = ConfigData::load(
Some(specifier),
&scope,
settings,
Some(file_fetcher),
)
.await;
entry.or_insert(data);
}
}
}
}
for folder_uri in settings.by_workspace_folder.keys() {
if !scopes
.keys()
.any(|s| folder_uri.as_str().starts_with(s.as_str()))
{
scopes.insert(
folder_uri.clone(),
ConfigData::load(None, folder_uri, settings, Some(file_fetcher))
.await,
);
}
}
self.first_folder = settings.first_folder.clone();
self.scopes = Arc::new(scopes);
} }
#[cfg(test)] #[cfg(test)]
pub async fn inject_config_file(&self, config_file: ConfigFile) { pub async fn inject_config_file(&mut self, config_file: ConfigFile) {
let scope = config_file.specifier.join(".").unwrap(); let scope = config_file.specifier.join(".").unwrap();
let data = ConfigData::load_inner( let data = ConfigData::load_inner(
Some(config_file), Some(config_file),
@ -1633,7 +1631,8 @@ impl ConfigTree {
None, None,
) )
.await; .await;
*self.root.lock() = Some((scope, Arc::new(data))); self.first_folder = Some(scope.clone());
self.scopes = Arc::new([(scope, data)].into_iter().collect());
} }
} }

View file

@ -811,7 +811,7 @@ fn generate_lint_diagnostics(
let (lint_options, lint_rules) = config let (lint_options, lint_rules) = config
.tree .tree
.scope_for_specifier(document.specifier()) .scope_for_specifier(document.specifier())
.and_then(|s| config_data_by_scope.get(&s)) .and_then(|s| config_data_by_scope.get(s))
.map(|d| (d.lint_options.clone(), d.lint_rules.clone())) .map(|d| (d.lint_options.clone(), d.lint_rules.clone()))
.unwrap_or_default(); .unwrap_or_default();
diagnostics_vec.push(DiagnosticRecord { diagnostics_vec.push(DiagnosticRecord {
@ -1452,8 +1452,8 @@ fn diagnose_dependency(
} }
} }
let import_map = snapshot.config.tree.import_map_for_specifier(referrer); let import_map = snapshot.config.tree.root_import_map();
if let Some(import_map) = &import_map { if let Some(import_map) = import_map {
if let Resolution::Ok(resolved) = &dependency.maybe_code { if let Resolution::Ok(resolved) = &dependency.maybe_code {
if let Some(to) = import_map.lookup(&resolved.specifier, referrer) { if let Some(to) = import_map.lookup(&resolved.specifier, referrer) {
if dependency_key != to { if dependency_key != to {
@ -1502,7 +1502,7 @@ fn diagnose_dependency(
}, },
dependency.is_dynamic, dependency.is_dynamic,
dependency.maybe_attribute_type.as_deref(), dependency.maybe_attribute_type.as_deref(),
import_map.as_deref(), import_map.map(|i| i.as_ref()),
) )
.iter() .iter()
.flat_map(|diag| { .flat_map(|diag| {
@ -1525,7 +1525,7 @@ fn diagnose_dependency(
&dependency.maybe_type, &dependency.maybe_type,
dependency.is_dynamic, dependency.is_dynamic,
dependency.maybe_attribute_type.as_deref(), dependency.maybe_attribute_type.as_deref(),
import_map.as_deref(), import_map.map(|i| i.as_ref()),
) )
.iter() .iter()
.map(|diag| diag.to_lsp_diagnostic(&range)), .map(|diag| diag.to_lsp_diagnostic(&range)),
@ -1614,7 +1614,7 @@ mod tests {
(*source).into(), (*source).into(),
); );
} }
let config = Config::new_with_roots([resolve_url("file:///").unwrap()]); let mut config = Config::new_with_roots([resolve_url("file:///").unwrap()]);
if let Some((base_url, json_string)) = maybe_import_map { if let Some((base_url, json_string)) = maybe_import_map {
let base_url = resolve_url(base_url).unwrap(); let base_url = resolve_url(base_url).unwrap();
let config_file = ConfigFile::new( let config_file = ConfigFile::new(
@ -1689,8 +1689,7 @@ let c: number = "a";
let snapshot = Arc::new(snapshot); let snapshot = Arc::new(snapshot);
let cache = let cache =
Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv)); Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv));
let ts_server = let ts_server = TsServer::new(Default::default(), cache);
TsServer::new(Default::default(), cache, Default::default());
ts_server.start(None); ts_server.start(None);
// test enabled // test enabled

View file

@ -1309,14 +1309,12 @@ impl Documents {
workspace_files: &BTreeSet<ModuleSpecifier>, workspace_files: &BTreeSet<ModuleSpecifier>,
) { ) {
let config_data = config.tree.root_data(); let config_data = config.tree.root_data();
let config_file = let config_file = config_data.and_then(|d| d.config_file.as_deref());
config_data.as_ref().and_then(|d| d.config_file.as_deref());
self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions { self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
node_resolver, node_resolver,
npm_resolver, npm_resolver,
package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new( package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new(
config_data config_data
.as_ref()
.and_then(|d| d.package_json.as_ref()) .and_then(|d| d.package_json.as_ref())
.map(|package_json| { .map(|package_json| {
package_json::get_local_package_json_version_reqs(package_json) package_json::get_local_package_json_version_reqs(package_json)
@ -1324,10 +1322,8 @@ impl Documents {
)), )),
maybe_jsx_import_source_config: config_file maybe_jsx_import_source_config: config_file
.and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()), .and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()),
maybe_import_map: config_data.as_ref().and_then(|d| d.import_map.clone()), maybe_import_map: config_data.and_then(|d| d.import_map.clone()),
maybe_vendor_dir: config_data maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()),
.as_ref()
.and_then(|d| d.vendor_dir.as_ref()),
bare_node_builtins_enabled: config_file bare_node_builtins_enabled: config_file
.map(|config| config.has_unstable("bare-node-builtins")) .map(|config| config.has_unstable("bare-node-builtins"))
.unwrap_or(false), .unwrap_or(false),
@ -1338,7 +1334,7 @@ impl Documents {
})); }));
self.jsr_resolver = Arc::new(JsrCacheResolver::new( self.jsr_resolver = Arc::new(JsrCacheResolver::new(
self.cache.clone(), self.cache.clone(),
config.tree.root_lockfile(), config.tree.root_lockfile().cloned(),
)); ));
self.redirect_resolver = self.redirect_resolver =
Arc::new(RedirectResolver::new(self.cache.clone())); Arc::new(RedirectResolver::new(self.cache.clone()));

View file

@ -334,11 +334,7 @@ impl LanguageServer {
// do as much as possible in a read, then do a write outside // do as much as possible in a read, then do a write outside
let maybe_prepare_cache_result = { let maybe_prepare_cache_result = {
let inner = self.0.read().await; // ensure dropped let inner = self.0.read().await; // ensure dropped
match inner.prepare_cache( match inner.prepare_cache(specifiers, referrer, force_global_cache) {
specifiers,
referrer.clone(),
force_global_cache,
) {
Ok(maybe_cache_result) => maybe_cache_result, Ok(maybe_cache_result) => maybe_cache_result,
Err(err) => { Err(err) => {
lsp_warn!("Error preparing caching: {:#}", err); lsp_warn!("Error preparing caching: {:#}", err);
@ -370,7 +366,7 @@ impl LanguageServer {
} }
{ {
let mut inner = self.0.write().await; let mut inner = self.0.write().await;
let lockfile = inner.config.tree.lockfile_for_specifier(&referrer); let lockfile = inner.config.tree.root_lockfile().cloned();
inner.documents.refresh_jsr_resolver(lockfile); inner.documents.refresh_jsr_resolver(lockfile);
inner.refresh_npm_specifiers().await; inner.refresh_npm_specifiers().await;
} }
@ -516,11 +512,8 @@ impl Inner {
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());
let config = Config::default(); let config = Config::default();
let ts_server = Arc::new(TsServer::new( let ts_server =
performance.clone(), Arc::new(TsServer::new(performance.clone(), deps_http_cache.clone()));
deps_http_cache.clone(),
config.tree.clone(),
));
let diagnostics_state = Arc::new(DiagnosticsState::default()); let diagnostics_state = Arc::new(DiagnosticsState::default());
let diagnostics_server = DiagnosticsServer::new( let diagnostics_server = DiagnosticsServer::new(
client.clone(), client.clone(),
@ -765,7 +758,10 @@ impl Inner {
)); ));
let maybe_local_cache = let maybe_local_cache =
self.config.tree.root_vendor_dir().map(|local_path| { self.config.tree.root_vendor_dir().map(|local_path| {
Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone())) Arc::new(LocalLspHttpCache::new(
local_path.clone(),
global_cache.clone(),
))
}); });
let cache: Arc<dyn HttpCache> = maybe_local_cache let cache: Arc<dyn HttpCache> = maybe_local_cache
.clone() .clone()
@ -1154,42 +1150,33 @@ impl Inner {
async fn refresh_config_tree(&mut self) { async fn refresh_config_tree(&mut self) {
let file_fetcher = self.create_file_fetcher(CacheSetting::RespectHeaders); let file_fetcher = self.create_file_fetcher(CacheSetting::RespectHeaders);
if let Some(root_uri) = self.config.root_uri() { self
self .config
.config .tree
.tree .refresh(&self.config.settings, &self.workspace_files, &file_fetcher)
.refresh( .await;
&self.config.settings, for config_file in self.config.tree.config_files() {
root_uri, if let Ok((compiler_options, _)) = config_file.to_compiler_options() {
&self.workspace_files, if let Some(compiler_options_obj) = compiler_options.as_object() {
&file_fetcher, if let Some(jsx_import_source) =
) compiler_options_obj.get("jsxImportSource")
.await; {
for config_file in self.config.tree.config_files() { if let Some(jsx_import_source) = jsx_import_source.as_str() {
if let Ok((compiler_options, _)) = config_file.to_compiler_options() { let specifiers = vec![Url::parse(&format!(
if let Some(compiler_options_obj) = compiler_options.as_object() { "data:application/typescript;base64,{}",
if let Some(jsx_import_source) = base64::engine::general_purpose::STANDARD
compiler_options_obj.get("jsxImportSource") .encode(format!("import '{jsx_import_source}/jsx-runtime';"))
{ ))
if let Some(jsx_import_source) = jsx_import_source.as_str() { .unwrap()];
let specifiers = vec![Url::parse(&format!( let referrer = config_file.specifier.clone();
"data:application/typescript;base64,{}", self.task_queue.queue_task(Box::new(|ls: LanguageServer| {
base64::engine::general_purpose::STANDARD.encode(format!( spawn(async move {
"import '{jsx_import_source}/jsx-runtime';" if let Err(err) = ls.cache(specifiers, referrer, false).await
)) {
)) lsp_warn!("{:#}", err);
.unwrap()]; }
let referrer = config_file.specifier.clone(); });
self.task_queue.queue_task(Box::new(|ls: LanguageServer| { }));
spawn(async move {
if let Err(err) =
ls.cache(specifiers, referrer, false).await
{
lsp_warn!("{:#}", err);
}
});
}));
}
} }
} }
} }
@ -1383,7 +1370,7 @@ impl Inner {
_ => return None, _ => return None,
}; };
Some(lsp_custom::DenoConfigurationChangeEvent { Some(lsp_custom::DenoConfigurationChangeEvent {
scope_uri: t.0, scope_uri: t.0.clone(),
file_uri: e.uri.clone(), file_uri: e.uri.clone(),
typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type( typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type(
e.typ, e.typ,
@ -1407,7 +1394,7 @@ impl Inner {
_ => return None, _ => return None,
}; };
Some(lsp_custom::DenoConfigurationChangeEvent { Some(lsp_custom::DenoConfigurationChangeEvent {
scope_uri: t.0, scope_uri: t.0.clone(),
file_uri: e.uri.clone(), file_uri: e.uri.clone(),
typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type( typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type(
e.typ, e.typ,
@ -2010,11 +1997,11 @@ impl Inner {
pub fn get_ts_response_import_mapper( pub fn get_ts_response_import_mapper(
&self, &self,
referrer: &ModuleSpecifier, _referrer: &ModuleSpecifier,
) -> TsResponseImportMapper { ) -> TsResponseImportMapper {
TsResponseImportMapper::new( TsResponseImportMapper::new(
&self.documents, &self.documents,
self.config.tree.import_map_for_specifier(referrer), self.config.tree.root_import_map().map(|i| i.as_ref()),
self.npm.node_resolver.as_deref(), self.npm.node_resolver.as_deref(),
self.npm.resolver.as_deref(), self.npm.resolver.as_deref(),
) )
@ -2327,7 +2314,7 @@ impl Inner {
&self.jsr_search_api, &self.jsr_search_api,
&self.npm.search_api, &self.npm.search_api,
&self.documents, &self.documents,
self.config.tree.import_map_for_specifier(&specifier), self.config.tree.root_import_map().map(|i| i.as_ref()),
) )
.await; .await;
} }
@ -3112,7 +3099,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
} }
let mut config_events = vec![]; let mut config_events = vec![];
for (scope_uri, config_data) in ls.config.tree.data_by_scope() { for (scope_uri, config_data) in ls.config.tree.data_by_scope().iter() {
if let Some(config_file) = &config_data.config_file { if let Some(config_file) = &config_data.config_file {
config_events.push(lsp_custom::DenoConfigurationChangeEvent { config_events.push(lsp_custom::DenoConfigurationChangeEvent {
scope_uri: scope_uri.clone(), scope_uri: scope_uri.clone(),
@ -3493,7 +3480,7 @@ impl Inner {
let mark = self let mark = self
.performance .performance
.mark_with_args("lsp.cache", (&specifiers, &referrer)); .mark_with_args("lsp.cache", (&specifiers, &referrer));
let config_data = self.config.tree.data_for_specifier(&referrer); let config_data = self.config.tree.root_data();
let roots = if !specifiers.is_empty() { let roots = if !specifiers.is_empty() {
specifiers specifiers
} else { } else {
@ -3508,7 +3495,7 @@ impl Inner {
unsafely_ignore_certificate_errors: workspace_settings unsafely_ignore_certificate_errors: workspace_settings
.unsafely_ignore_certificate_errors .unsafely_ignore_certificate_errors
.clone(), .clone(),
import_map_path: config_data.as_ref().and_then(|d| { import_map_path: config_data.and_then(|d| {
if d.import_map_from_settings { if d.import_map_from_settings {
return Some(d.import_map.as_ref()?.base_url().to_string()); return Some(d.import_map.as_ref()?.base_url().to_string());
} }
@ -3516,7 +3503,6 @@ impl Inner {
}), }),
node_modules_dir: Some( node_modules_dir: Some(
config_data config_data
.as_ref()
.and_then(|d| d.node_modules_dir.as_ref()) .and_then(|d| d.node_modules_dir.as_ref())
.is_some(), .is_some(),
), ),
@ -3525,13 +3511,9 @@ impl Inner {
..Default::default() ..Default::default()
}, },
self.initial_cwd.clone(), self.initial_cwd.clone(),
config_data config_data.and_then(|d| d.config_file.as_deref().cloned()),
.as_ref() config_data.and_then(|d| d.lockfile.clone()),
.and_then(|d| d.config_file.as_deref().cloned()), config_data.and_then(|d| d.package_json.as_deref().cloned()),
config_data.as_ref().and_then(|d| d.lockfile.clone()),
config_data
.as_ref()
.and_then(|d| d.package_json.as_deref().cloned()),
force_global_cache, force_global_cache,
)?; )?;

View file

@ -3,7 +3,6 @@
use super::analysis::CodeActionData; use super::analysis::CodeActionData;
use super::code_lens; use super::code_lens;
use super::config; use super::config;
use super::config::ConfigTree;
use super::documents::AssetOrDocument; use super::documents::AssetOrDocument;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
use super::language_server; use super::language_server;
@ -222,7 +221,6 @@ pub struct TsServer {
sender: mpsc::UnboundedSender<Request>, sender: mpsc::UnboundedSender<Request>,
receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>, receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
inspector_server: Mutex<Option<Arc<InspectorServer>>>, inspector_server: Mutex<Option<Arc<InspectorServer>>>,
} }
@ -240,11 +238,7 @@ impl std::fmt::Debug for TsServer {
} }
impl TsServer { impl TsServer {
pub fn new( pub fn new(performance: Arc<Performance>, cache: Arc<dyn HttpCache>) -> Self {
performance: Arc<Performance>,
cache: Arc<dyn HttpCache>,
config_tree: Arc<ConfigTree>,
) -> Self {
let (tx, request_rx) = mpsc::unbounded_channel::<Request>(); let (tx, request_rx) = mpsc::unbounded_channel::<Request>();
Self { Self {
performance, performance,
@ -252,7 +246,6 @@ impl TsServer {
sender: tx, sender: tx,
receiver: Mutex::new(Some(request_rx)), receiver: Mutex::new(Some(request_rx)),
specifier_map: Arc::new(TscSpecifierMap::new()), specifier_map: Arc::new(TscSpecifierMap::new()),
config_tree,
inspector_server: Mutex::new(None), inspector_server: Mutex::new(None),
} }
} }
@ -275,7 +268,6 @@ impl TsServer {
let performance = self.performance.clone(); let performance = self.performance.clone();
let cache = self.cache.clone(); let cache = self.cache.clone();
let specifier_map = self.specifier_map.clone(); let specifier_map = self.specifier_map.clone();
let config_tree = self.config_tree.clone();
let _join_handle = thread::spawn(move || { let _join_handle = thread::spawn(move || {
run_tsc_thread( run_tsc_thread(
receiver, receiver,
@ -283,7 +275,6 @@ impl TsServer {
cache.clone(), cache.clone(),
specifier_map.clone(), specifier_map.clone(),
maybe_inspector_server, maybe_inspector_server,
config_tree,
) )
}); });
} }
@ -3884,7 +3875,6 @@ struct State {
response: Option<Response>, response: Option<Response>,
state_snapshot: Arc<StateSnapshot>, state_snapshot: Arc<StateSnapshot>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
token: CancellationToken, token: CancellationToken,
} }
@ -3892,7 +3882,6 @@ impl State {
fn new( fn new(
state_snapshot: Arc<StateSnapshot>, state_snapshot: Arc<StateSnapshot>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
performance: Arc<Performance>, performance: Arc<Performance>,
) -> Self { ) -> Self {
Self { Self {
@ -3901,7 +3890,6 @@ impl State {
response: None, response: None,
state_snapshot, state_snapshot,
specifier_map, specifier_map,
config_tree,
token: Default::default(), token: Default::default(),
} }
} }
@ -4120,7 +4108,7 @@ fn op_script_version(
fn op_ts_config(state: &mut OpState) -> serde_json::Value { fn op_ts_config(state: &mut OpState) -> serde_json::Value {
let state = state.borrow_mut::<State>(); let state = state.borrow_mut::<State>();
let mark = state.performance.mark("tsc.op.op_ts_config"); let mark = state.performance.mark("tsc.op.op_ts_config");
let r = json!(state.config_tree.root_ts_config()); let r = json!(state.state_snapshot.config.tree.root_ts_config());
state.performance.measure(mark); state.performance.measure(mark);
r r
} }
@ -4141,19 +4129,13 @@ fn run_tsc_thread(
cache: Arc<dyn HttpCache>, cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
maybe_inspector_server: Option<Arc<InspectorServer>>, maybe_inspector_server: Option<Arc<InspectorServer>>,
config_tree: Arc<ConfigTree>,
) { ) {
let has_inspector_server = maybe_inspector_server.is_some(); let has_inspector_server = maybe_inspector_server.is_some();
// Create and setup a JsRuntime based on a snapshot. It is expected that the // Create and setup a JsRuntime based on a snapshot. It is expected that the
// supplied snapshot is an isolate that contains the TypeScript language // supplied snapshot is an isolate that contains the TypeScript language
// server. // server.
let mut tsc_runtime = JsRuntime::new(RuntimeOptions { let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![deno_tsc::init_ops( extensions: vec![deno_tsc::init_ops(performance, cache, specifier_map)],
performance,
cache,
specifier_map,
config_tree,
)],
startup_snapshot: Some(tsc::compiler_snapshot()), startup_snapshot: Some(tsc::compiler_snapshot()),
inspector: maybe_inspector_server.is_some(), inspector: maybe_inspector_server.is_some(),
..Default::default() ..Default::default()
@ -4227,7 +4209,6 @@ deno_core::extension!(deno_tsc,
performance: Arc<Performance>, performance: Arc<Performance>,
cache: Arc<dyn HttpCache>, cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
}, },
state = |state, options| { state = |state, options| {
state.put(State::new( state.put(State::new(
@ -4239,7 +4220,6 @@ deno_core::extension!(deno_tsc,
npm: None, npm: None,
}), }),
options.specifier_map, options.specifier_map,
options.config_tree,
options.performance, options.performance,
)); ));
}, },
@ -4507,7 +4487,10 @@ impl UserPreferences {
language_settings.preferences.use_aliases_for_renames, language_settings.preferences.use_aliases_for_renames,
), ),
// Only use workspace settings for quote style if there's no `deno.json`. // Only use workspace settings for quote style if there's no `deno.json`.
quote_preference: if config.tree.has_config_file_for_specifier(specifier) quote_preference: if config
.tree
.config_file_for_specifier(specifier)
.is_some()
{ {
base_preferences.quote_preference base_preferences.quote_preference
} else { } else {
@ -4650,12 +4633,14 @@ fn request(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::cache::GlobalHttpCache; use crate::cache::GlobalHttpCache;
use crate::cache::HttpCache; use crate::cache::HttpCache;
use crate::cache::RealDenoCacheEnv; use crate::cache::RealDenoCacheEnv;
use crate::http_util::HeadersMap; use crate::http_util::HeadersMap;
use crate::lsp::cache::CacheMetadata; use crate::lsp::cache::CacheMetadata;
use crate::lsp::config::ConfigSnapshot;
use crate::lsp::config::WorkspaceSettings; use crate::lsp::config::WorkspaceSettings;
use crate::lsp::documents::Documents; use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId; use crate::lsp::documents::LanguageId;
@ -4664,9 +4649,10 @@ mod tests {
use std::path::Path; use std::path::Path;
use test_util::TempDir; use test_util::TempDir;
fn mock_state_snapshot( async fn mock_state_snapshot(
fixtures: &[(&str, &str, i32, LanguageId)], fixtures: &[(&str, &str, i32, LanguageId)],
location: &Path, location: &Path,
ts_config: Value,
) -> StateSnapshot { ) -> StateSnapshot {
let cache = Arc::new(GlobalHttpCache::new( let cache = Arc::new(GlobalHttpCache::new(
location.to_path_buf(), location.to_path_buf(),
@ -4683,11 +4669,26 @@ mod tests {
(*source).into(), (*source).into(),
); );
} }
let mut config = ConfigSnapshot::default();
config
.tree
.inject_config_file(
deno_config::ConfigFile::new(
&json!({
"compilerOptions": ts_config,
})
.to_string(),
resolve_url("file:///deno.json").unwrap(),
&deno_config::ParseOptions::default(),
)
.unwrap(),
)
.await;
StateSnapshot { StateSnapshot {
documents, documents,
assets: Default::default(), assets: Default::default(),
cache_metadata: CacheMetadata::new(cache), cache_metadata: CacheMetadata::new(cache),
config: Default::default(), config: Arc::new(config),
npm: None, npm: None,
} }
} }
@ -4700,23 +4701,10 @@ mod tests {
let location = temp_dir.path().join("deps").to_path_buf(); let location = temp_dir.path().join("deps").to_path_buf();
let cache = let cache =
Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv)); Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv));
let snapshot = Arc::new(mock_state_snapshot(sources, &location)); let snapshot =
Arc::new(mock_state_snapshot(sources, &location, config).await);
let performance = Arc::new(Performance::default()); let performance = Arc::new(Performance::default());
let config_tree = Arc::new(ConfigTree::default()); let ts_server = TsServer::new(performance, cache.clone());
config_tree
.inject_config_file(
deno_config::ConfigFile::new(
&json!({
"compilerOptions": config,
})
.to_string(),
resolve_url("file:///deno.json").unwrap(),
&deno_config::ParseOptions::default(),
)
.unwrap(),
)
.await;
let ts_server = TsServer::new(performance, cache.clone(), config_tree);
ts_server.start(None); ts_server.start(None);
(ts_server, snapshot, cache) (ts_server, snapshot, cache)
} }

View file

@ -1061,6 +1061,7 @@ fn lsp_did_change_deno_configuration_notification() {
}], }],
})) }))
); );
client.shutdown();
} }
#[test] #[test]
@ -1098,6 +1099,7 @@ fn lsp_deno_task() {
} }
]) ])
); );
client.shutdown();
} }
#[test] #[test]
@ -1110,6 +1112,7 @@ fn lsp_reload_import_registries_command() {
json!({ "command": "deno.reloadImportRegistries" }), json!({ "command": "deno.reloadImportRegistries" }),
); );
assert_eq!(res, json!(true)); assert_eq!(res, json!(true));
client.shutdown();
} }
#[test] #[test]
@ -1598,6 +1601,7 @@ fn lsp_inlay_hints() {
} }
]) ])
); );
client.shutdown();
} }
#[test] #[test]
@ -1645,6 +1649,7 @@ fn lsp_inlay_hints_not_enabled() {
}), }),
); );
assert_eq!(res, json!(null)); assert_eq!(res, json!(null));
client.shutdown();
} }
#[test] #[test]
@ -2409,6 +2414,7 @@ fn lsp_hover_dependency() {
} }
}) })
); );
client.shutdown();
} }
// This tests for a regression covered by denoland/deno#12753 where the lsp was // This tests for a regression covered by denoland/deno#12753 where the lsp was
@ -4822,6 +4828,7 @@ fn test_lsp_code_actions_ordering() {
}, },
]) ])
); );
client.shutdown();
} }
#[test] #[test]
@ -4851,6 +4858,7 @@ fn lsp_status_file() {
); );
let res = res.as_str().unwrap().to_string(); let res = res.as_str().unwrap().to_string();
assert!(res.starts_with("# Deno Language Server Status")); assert!(res.starts_with("# Deno Language Server Status"));
client.shutdown();
} }
#[test] #[test]
@ -5598,6 +5606,7 @@ fn lsp_cache_then_definition() {
}, },
}]), }]),
); );
client.shutdown();
} }
#[test] #[test]
@ -6355,6 +6364,7 @@ fn lsp_quote_style_from_workspace_settings() {
}, },
}]), }]),
); );
client.shutdown();
} }
#[test] #[test]
@ -6812,6 +6822,7 @@ fn lsp_completions_auto_import() {
] ]
}) })
); );
client.shutdown();
} }
#[test] #[test]
@ -7063,6 +7074,7 @@ fn lsp_npm_completions_auto_import_and_quick_fix_no_import_map() {
} }
}]) }])
); );
client.shutdown();
} }
#[test] #[test]
@ -7102,6 +7114,7 @@ fn lsp_semantic_tokens_for_disabled_module() {
"data": [0, 6, 9, 7, 9, 0, 15, 9, 7, 8], "data": [0, 6, 9, 7, 9, 0, 15, 9, 7, 8],
}) })
); );
client.shutdown();
} }
#[test] #[test]
@ -7538,6 +7551,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() {
] ]
}) })
); );
client.shutdown();
} }
#[test] #[test]
@ -7633,6 +7647,7 @@ fn lsp_completions_snippet() {
"insertTextFormat": 2 "insertTextFormat": 2
}) })
); );
client.shutdown();
} }
#[test] #[test]
@ -7688,6 +7703,7 @@ fn lsp_completions_no_snippet() {
] ]
}) })
); );
client.shutdown();
} }
#[test] #[test]
@ -7919,6 +7935,7 @@ fn lsp_npm_specifier_unopened_file() {
assert!(!list.is_incomplete); assert!(!list.is_incomplete);
assert_eq!(list.items.len(), 63); assert_eq!(list.items.len(), 63);
assert!(list.items.iter().any(|i| i.label == "ansi256")); assert!(list.items.iter().any(|i| i.label == "ansi256"));
client.shutdown();
} }
#[test] #[test]
@ -11432,6 +11449,193 @@ fn lsp_vendor_dir() {
client.shutdown(); client.shutdown();
} }
#[test]
fn lsp_deno_json_scopes_fmt_config() {
let context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = context.temp_dir();
temp_dir.create_dir_all("project1");
temp_dir.write(
"project1/deno.json",
json!({
"fmt": {
"semiColons": false,
},
})
.to_string(),
);
temp_dir.create_dir_all("project2");
temp_dir.write(
"project2/deno.json",
json!({
"fmt": {
"singleQuote": true,
},
})
.to_string(),
);
let mut client = context.new_lsp_command().build();
client.initialize_default();
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "console.log(\"\");\n",
},
}));
let res = client.write_request(
"textDocument/formatting",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
},
"options": {
"tabSize": 2,
"insertSpaces": true,
},
}),
);
assert_eq!(
res,
json!([{
"range": {
"start": { "line": 0, "character": 15 },
"end": { "line": 0, "character": 16 },
},
"newText": "",
}])
);
client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": "console.log(\"\");\n",
},
}));
let res = client.write_request(
"textDocument/formatting",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
},
"options": {
"tabSize": 2,
"insertSpaces": true,
},
}),
);
assert_eq!(
res,
json!([{
"range": {
"start": { "line": 0, "character": 12 },
"end": { "line": 0, "character": 14 },
},
"newText": "''",
}])
);
client.shutdown();
}
#[test]
fn lsp_deno_json_scopes_lint_config() {
let context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = context.temp_dir();
temp_dir.create_dir_all("project1");
temp_dir.write(
"project1/deno.json",
json!({
"lint": {
"rules": {
"include": ["camelcase"],
},
},
})
.to_string(),
);
temp_dir.create_dir_all("project2");
temp_dir.write(
"project2/deno.json",
json!({
"lint": {
"rules": {
"include": ["ban-untagged-todo"],
},
},
})
.to_string(),
);
let mut client = context.new_lsp_command().build();
client.initialize_default();
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": r#"
// TODO: Unused var
const snake_case_var = 1;
console.log(snake_case_var);
"#,
},
}));
assert_eq!(
json!(diagnostics.messages_with_source("deno-lint")),
json!({
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
"diagnostics": [{
"range": {
"start": { "line": 2, "character": 14 },
"end": { "line": 2, "character": 28 },
},
"severity": 2,
"code": "camelcase",
"source": "deno-lint",
"message": "Identifier 'snake_case_var' is not in camel case.\nConsider renaming `snake_case_var` to `snakeCaseVar`",
}],
"version": 1,
})
);
client.write_notification(
"textDocument/didClose",
json!({
"textDocument": {
"uri": temp_dir.uri().join("project1/file.ts").unwrap(),
},
}),
);
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": r#"
// TODO: Unused var
const snake_case_var = 1;
console.log(snake_case_var);
"#,
},
}));
assert_eq!(
json!(diagnostics.messages_with_source("deno-lint")),
json!({
"uri": temp_dir.uri().join("project2/file.ts").unwrap(),
"diagnostics": [{
"range": {
"start": { "line": 1, "character": 8 },
"end": { "line": 1, "character": 27 },
},
"severity": 2,
"code": "ban-untagged-todo",
"source": "deno-lint",
"message": "TODO should be tagged with (@username) or (#issue)\nAdd a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)",
}],
"version": 1,
})
);
client.shutdown();
}
#[test] #[test]
fn lsp_import_unstable_bare_node_builtins_auto_discovered() { fn lsp_import_unstable_bare_node_builtins_auto_discovered() {
@ -11995,7 +12199,7 @@ C.test();
})) }))
.all(); .all();
assert_eq!(diagnostics.len(), 0); assert_eq!(json!(diagnostics), json!([]));
client.shutdown(); client.shutdown();
} }

View file

@ -225,9 +225,6 @@ impl TestContextBuilder {
} }
let deno_exe = deno_exe_path(); let deno_exe = deno_exe_path();
self
.diagnostic_logger
.writeln(format!("deno_exe path {}", deno_exe));
let http_server_guard = if self.use_http_server { let http_server_guard = if self.use_http_server {
Some(Rc::new(http_server())) Some(Rc::new(http_server()))