summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/args/mod.rs12
-rw-r--r--cli/lsp/analysis.rs15
-rw-r--r--cli/lsp/config.rs839
-rw-r--r--cli/lsp/diagnostics.rs87
-rw-r--r--cli/lsp/documents.rs143
-rw-r--r--cli/lsp/language_server.rs763
-rw-r--r--cli/lsp/tsc.rs121
-rw-r--r--cli/tools/lint/mod.rs6
-rw-r--r--cli/tsc/99_main_compiler.js30
-rw-r--r--tests/integration/lsp_tests.rs41
10 files changed, 1090 insertions, 967 deletions
diff --git a/cli/args/mod.rs b/cli/args/mod.rs
index cb4473ca2..de889c654 100644
--- a/cli/args/mod.rs
+++ b/cli/args/mod.rs
@@ -260,6 +260,12 @@ pub struct FmtOptions {
pub files: FilePatterns,
}
+impl Default for FmtOptions {
+ fn default() -> Self {
+ Self::new_with_base(PathBuf::from("/"))
+ }
+}
+
impl FmtOptions {
pub fn new_with_base(base: PathBuf) -> Self {
Self {
@@ -394,6 +400,12 @@ pub struct LintOptions {
pub fix: bool,
}
+impl Default for LintOptions {
+ fn default() -> Self {
+ Self::new_with_base(PathBuf::from("/"))
+ }
+}
+
impl LintOptions {
pub fn new_with_base(base: PathBuf) -> Self {
Self {
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 54073edaf..652697a9e 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -43,6 +43,7 @@ use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
+use std::sync::Arc;
use tower_lsp::lsp_types as lsp;
use tower_lsp::lsp_types::Position;
use tower_lsp::lsp_types::Range;
@@ -216,7 +217,7 @@ fn code_as_string(code: &Option<lsp::NumberOrString>) -> String {
/// Rewrites imports in quick fixes and code changes to be Deno specific.
pub struct TsResponseImportMapper<'a> {
documents: &'a Documents,
- maybe_import_map: Option<&'a ImportMap>,
+ maybe_import_map: Option<Arc<ImportMap>>,
node_resolver: Option<&'a CliNodeResolver>,
npm_resolver: Option<&'a dyn CliNpmResolver>,
}
@@ -224,7 +225,7 @@ pub struct TsResponseImportMapper<'a> {
impl<'a> TsResponseImportMapper<'a> {
pub fn new(
documents: &'a Documents,
- maybe_import_map: Option<&'a ImportMap>,
+ maybe_import_map: Option<Arc<ImportMap>>,
node_resolver: Option<&'a CliNodeResolver>,
npm_resolver: Option<&'a dyn CliNpmResolver>,
) -> Self {
@@ -269,7 +270,7 @@ impl<'a> TsResponseImportMapper<'a> {
let sub_path = (export != ".").then_some(export);
let mut req = None;
req = req.or_else(|| {
- let import_map = self.maybe_import_map?;
+ let import_map = self.maybe_import_map.as_ref()?;
for entry in import_map.entries_for_referrer(referrer) {
let Some(value) = entry.raw_value else {
continue;
@@ -296,7 +297,7 @@ impl<'a> TsResponseImportMapper<'a> {
JsrPackageNvReference::new(nv_ref).to_string()
};
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) {
return Some(result);
}
@@ -315,7 +316,7 @@ impl<'a> TsResponseImportMapper<'a> {
// check if any pkg reqs match what is found in an import map
if !pkg_reqs.is_empty() {
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 mut matches = Vec::new();
for entry in import_map.entries_for_referrer(referrer) {
@@ -357,7 +358,7 @@ impl<'a> TsResponseImportMapper<'a> {
}
// 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) {
return Some(result);
}
@@ -942,7 +943,7 @@ impl CodeActionCollection {
let action = fix_ts_import_action(
specifier,
action,
- &language_server.get_ts_response_import_mapper(),
+ &language_server.get_ts_response_import_mapper(specifier),
)?;
let edit = ts_changes_to_edit(&action.changes, language_server)?;
let code_action = lsp::CodeAction {
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index a87c49108..5fa2a00a6 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -2,22 +2,34 @@
use super::logging::lsp_log;
use crate::args::ConfigFile;
+use crate::args::FmtOptions;
+use crate::args::LintOptions;
use crate::cache::FastInsecureHasher;
+use crate::file_fetcher::FileFetcher;
use crate::lsp::logging::lsp_warn;
+use crate::tools::lint::get_configured_rules;
+use crate::tools::lint::ConfiguredRules;
use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::util::path::specifier_to_file_path;
use deno_ast::MediaType;
use deno_config::FmtOptionsConfig;
+use deno_config::TsConfig;
+use deno_core::anyhow::anyhow;
use deno_core::parking_lot::Mutex;
use deno_core::serde::de::DeserializeOwned;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::serde_json;
+use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use deno_lockfile::Lockfile;
+use deno_runtime::deno_node::PackageJson;
+use deno_runtime::permissions::PermissionsContainer;
+use import_map::ImportMap;
use lsp::Url;
use std::collections::BTreeMap;
+use std::collections::BTreeSet;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
@@ -717,9 +729,9 @@ impl WorkspaceSettings {
#[derive(Debug, Clone, Default)]
pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities,
- pub config_file: Option<ConfigFile>,
pub settings: Settings,
pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
+ pub tree: Arc<ConfigTree>,
}
impl ConfigSnapshot {
@@ -732,7 +744,8 @@ impl ConfigSnapshot {
/// Determine if the provided specifier is enabled or not.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
- if let Some(cf) = &self.config_file {
+ let config_file = self.tree.config_file_for_specifier(specifier);
+ if let Some(cf) = &config_file {
if let Ok(files) = cf.to_files_config() {
if !files.matches_specifier(specifier) {
return false;
@@ -742,14 +755,14 @@ impl ConfigSnapshot {
self
.settings
.specifier_enabled(specifier)
- .unwrap_or_else(|| self.config_file.is_some())
+ .unwrap_or_else(|| config_file.is_some())
}
pub fn specifier_enabled_for_test(
&self,
specifier: &ModuleSpecifier,
) -> bool {
- if let Some(cf) = &self.config_file {
+ if let Some(cf) = self.tree.config_file_for_specifier(specifier) {
if let Some(options) = cf.to_test_config().ok().flatten() {
if !options.files.matches_specifier(specifier) {
return false;
@@ -861,47 +874,18 @@ impl Settings {
}
}
-#[derive(Debug)]
-struct WithCanonicalizedSpecifier<T> {
- /// Stored canonicalized specifier, which is used for file watcher events.
- canonicalized_specifier: ModuleSpecifier,
- file: T,
-}
-
-/// Contains the config file and dependent information.
-#[derive(Debug)]
-struct LspConfigFileInfo {
- config_file: WithCanonicalizedSpecifier<ConfigFile>,
- /// An optional deno.lock file, which is resolved relative to the config file.
- maybe_lockfile: Option<WithCanonicalizedSpecifier<Arc<Mutex<Lockfile>>>>,
- /// The canonicalized node_modules directory, which is found relative to the config file.
- maybe_node_modules_dir: Option<PathBuf>,
-}
-
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct Config {
pub client_capabilities: ClientCapabilities,
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.
- maybe_config_file_info: Option<LspConfigFileInfo>,
+ pub tree: Arc<ConfigTree>,
}
impl Config {
- pub fn new() -> Self {
- Self {
- client_capabilities: ClientCapabilities::default(),
- // Root provided by the initialization parameters.
- settings: Default::default(),
- workspace_folders: vec![],
- maybe_config_file_info: None,
- }
- }
-
#[cfg(test)]
pub fn new_with_roots(root_uris: impl IntoIterator<Item = Url>) -> Self {
- let mut config = Self::new();
+ let mut config = Self::default();
let mut folders = vec![];
for root_uri in root_uris {
let name = root_uri.path_segments().and_then(|s| s.last());
@@ -1001,103 +985,18 @@ impl Config {
self.workspace_folders.first().map(|p| &p.0)
}
- pub fn maybe_node_modules_dir_path(&self) -> Option<&PathBuf> {
- self
- .maybe_config_file_info
- .as_ref()
- .and_then(|p| p.maybe_node_modules_dir.as_ref())
- }
-
- pub fn maybe_vendor_dir_path(&self) -> Option<PathBuf> {
- self.maybe_config_file().and_then(|c| c.vendor_dir_path())
- }
-
- pub fn maybe_config_file(&self) -> Option<&ConfigFile> {
- self
- .maybe_config_file_info
- .as_ref()
- .map(|c| &c.config_file.file)
- }
-
- /// Canonicalized specifier of the config file, which should only be used for
- /// file watcher events. Otherwise, prefer using the non-canonicalized path
- /// as the rest of the CLI does for config files.
- pub fn maybe_config_file_canonicalized_specifier(
- &self,
- ) -> Option<&ModuleSpecifier> {
- self
- .maybe_config_file_info
- .as_ref()
- .map(|c| &c.config_file.canonicalized_specifier)
- }
-
- pub fn maybe_lockfile(&self) -> Option<&Arc<Mutex<Lockfile>>> {
- self
- .maybe_config_file_info
- .as_ref()
- .and_then(|c| c.maybe_lockfile.as_ref().map(|l| &l.file))
- }
-
- /// Canonicalized specifier of the lockfile, which should only be used for
- /// file watcher events. Otherwise, prefer using the non-canonicalized path
- /// as the rest of the CLI does for config files.
- pub fn maybe_lockfile_canonicalized_specifier(
- &self,
- ) -> Option<&ModuleSpecifier> {
- self.maybe_config_file_info.as_ref().and_then(|c| {
- c.maybe_lockfile
- .as_ref()
- .map(|l| &l.canonicalized_specifier)
- })
- }
-
- pub fn clear_config_file(&mut self) {
- self.maybe_config_file_info = None;
- }
-
- pub fn has_config_file(&self) -> bool {
- self.maybe_config_file_info.is_some()
- }
-
- pub fn set_config_file(&mut self, config_file: ConfigFile) {
- self.maybe_config_file_info = Some(LspConfigFileInfo {
- maybe_lockfile: resolve_lockfile_from_config(&config_file).map(
- |lockfile| {
- let path = canonicalize_path_maybe_not_exists(&lockfile.filename)
- .unwrap_or_else(|_| lockfile.filename.clone());
- WithCanonicalizedSpecifier {
- canonicalized_specifier: ModuleSpecifier::from_file_path(path)
- .unwrap(),
- file: Arc::new(Mutex::new(lockfile)),
- }
- },
- ),
- maybe_node_modules_dir: resolve_node_modules_dir(&config_file),
- config_file: WithCanonicalizedSpecifier {
- canonicalized_specifier: config_file
- .specifier
- .to_file_path()
- .ok()
- .and_then(|p| canonicalize_path_maybe_not_exists(&p).ok())
- .and_then(|p| ModuleSpecifier::from_file_path(p).ok())
- .unwrap_or_else(|| config_file.specifier.clone()),
- file: config_file,
- },
- });
- }
-
pub fn snapshot(&self) -> Arc<ConfigSnapshot> {
Arc::new(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(),
- config_file: self.maybe_config_file().cloned(),
settings: self.settings.clone(),
workspace_folders: self.workspace_folders.clone(),
+ tree: self.tree.clone(),
})
}
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
- let config_file = self.maybe_config_file();
- if let Some(cf) = config_file {
+ let config_file = self.tree.config_file_for_specifier(specifier);
+ if let Some(cf) = &config_file {
if let Ok(files) = cf.to_files_config() {
if !files.matches_specifier(specifier) {
return false;
@@ -1114,7 +1013,7 @@ impl Config {
&self,
specifier: &ModuleSpecifier,
) -> bool {
- if let Some(cf) = self.maybe_config_file() {
+ if let Some(cf) = self.tree.config_file_for_specifier(specifier) {
if let Some(options) = cf.to_test_config().ok().flatten() {
if !options.files.matches_specifier(specifier) {
return false;
@@ -1186,6 +1085,551 @@ impl Config {
}
}
+pub fn default_ts_config() -> TsConfig {
+ TsConfig::new(json!({
+ "allowJs": true,
+ "esModuleInterop": true,
+ "experimentalDecorators": false,
+ "isolatedModules": true,
+ "jsx": "react",
+ "lib": ["deno.ns", "deno.window", "deno.unstable"],
+ "module": "esnext",
+ "moduleDetection": "force",
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "strict": true,
+ "target": "esnext",
+ "useDefineForClassFields": true,
+ "useUnknownInCatchVariables": false,
+ }))
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ConfigWatchedFileType {
+ DenoJson,
+ Lockfile,
+ PackageJson,
+ ImportMap,
+}
+
+/// Contains the config file and dependent information.
+#[derive(Debug, Clone)]
+pub struct ConfigData {
+ pub config_file: Option<Arc<ConfigFile>>,
+ pub fmt_options: Arc<FmtOptions>,
+ pub lint_options: Arc<LintOptions>,
+ pub lint_rules: Arc<ConfiguredRules>,
+ pub ts_config: Arc<TsConfig>,
+ pub node_modules_dir: Option<PathBuf>,
+ pub vendor_dir: Option<PathBuf>,
+ pub lockfile: Option<Arc<Mutex<Lockfile>>>,
+ pub package_json: Option<Arc<PackageJson>>,
+ pub import_map: Option<Arc<ImportMap>>,
+ watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
+}
+
+impl ConfigData {
+ async fn load(
+ config_file_specifier: Option<&ModuleSpecifier>,
+ scope: &ModuleSpecifier,
+ settings: &Settings,
+ file_fetcher: Option<&FileFetcher>,
+ ) -> Self {
+ if let Some(specifier) = config_file_specifier {
+ match ConfigFile::from_specifier(specifier.clone()) {
+ Ok(config_file) => {
+ lsp_log!(
+ " Resolved Deno configuration file: \"{}\"",
+ config_file.specifier.as_str()
+ );
+ Self::load_inner(Some(config_file), scope, settings, file_fetcher)
+ .await
+ }
+ Err(err) => {
+ lsp_warn!(
+ " Couldn't read Deno configuration file \"{}\": {}",
+ specifier.as_str(),
+ err
+ );
+ let mut data =
+ Self::load_inner(None, scope, settings, file_fetcher).await;
+ data
+ .watched_files
+ .insert(specifier.clone(), ConfigWatchedFileType::DenoJson);
+ let canonicalized_specifier = specifier
+ .to_file_path()
+ .ok()
+ .and_then(|p| canonicalize_path_maybe_not_exists(&p).ok())
+ .and_then(|p| ModuleSpecifier::from_file_path(p).ok());
+ if let Some(specifier) = canonicalized_specifier {
+ data
+ .watched_files
+ .insert(specifier, ConfigWatchedFileType::DenoJson);
+ }
+ data
+ }
+ }
+ } else {
+ Self::load_inner(None, scope, settings, file_fetcher).await
+ }
+ }
+
+ async fn load_inner(
+ config_file: Option<ConfigFile>,
+ scope: &ModuleSpecifier,
+ settings: &Settings,
+ file_fetcher: Option<&FileFetcher>,
+ ) -> Self {
+ let (settings, workspace_folder) = settings.get_for_specifier(scope);
+ let mut watched_files = HashMap::with_capacity(6);
+ if let Some(config_file) = &config_file {
+ watched_files
+ .entry(config_file.specifier.clone())
+ .or_insert(ConfigWatchedFileType::DenoJson);
+ }
+ let config_file_canonicalized_specifier = config_file
+ .as_ref()
+ .and_then(|c| c.specifier.to_file_path().ok())
+ .and_then(|p| canonicalize_path_maybe_not_exists(&p).ok())
+ .and_then(|p| ModuleSpecifier::from_file_path(p).ok());
+ if let Some(specifier) = config_file_canonicalized_specifier {
+ watched_files
+ .entry(specifier)
+ .or_insert(ConfigWatchedFileType::DenoJson);
+ }
+
+ // Resolve some config file fields ahead of time
+ let fmt_options = config_file
+ .as_ref()
+ .and_then(|config_file| {
+ config_file
+ .to_fmt_config()
+ .and_then(|o| {
+ let base_path = config_file
+ .specifier
+ .to_file_path()
+ .map_err(|_| anyhow!("Invalid base path."))?;
+ FmtOptions::resolve(o, None, &base_path)
+ })
+ .inspect_err(|err| {
+ lsp_warn!(" Couldn't read formatter configuration: {}", err)
+ })
+ .ok()
+ })
+ .unwrap_or_default();
+ let lint_options = config_file
+ .as_ref()
+ .and_then(|config_file| {
+ config_file
+ .to_lint_config()
+ .and_then(|o| {
+ let base_path = config_file
+ .specifier
+ .to_file_path()
+ .map_err(|_| anyhow!("Invalid base path."))?;
+ LintOptions::resolve(o, None, &base_path)
+ })
+ .inspect_err(|err| {
+ lsp_warn!(" Couldn't read lint configuration: {}", err)
+ })
+ .ok()
+ })
+ .unwrap_or_default();
+ let lint_rules =
+ get_configured_rules(lint_options.rules.clone(), config_file.as_ref());
+ let mut ts_config = default_ts_config();
+ if let Some(config_file) = &config_file {
+ match config_file.to_compiler_options() {
+ Ok((value, maybe_ignored_options)) => {
+ ts_config.merge(&value);
+ if let Some(ignored_options) = maybe_ignored_options {
+ lsp_warn!("{}", ignored_options);
+ }
+ }
+ Err(err) => lsp_warn!("{}", err),
+ }
+ }
+ let 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());
+
+ // Load lockfile
+ let lockfile = config_file.as_ref().and_then(resolve_lockfile_from_config);
+ if let Some(lockfile) = &lockfile {
+ if let Ok(specifier) = ModuleSpecifier::from_file_path(&lockfile.filename)
+ {
+ watched_files
+ .entry(specifier)
+ .or_insert(ConfigWatchedFileType::Lockfile);
+ }
+ }
+ let lockfile_canonicalized_specifier = lockfile
+ .as_ref()
+ .and_then(|lockfile| {
+ canonicalize_path_maybe_not_exists(&lockfile.filename).ok()
+ })
+ .and_then(|p| ModuleSpecifier::from_file_path(p).ok());
+ if let Some(specifier) = lockfile_canonicalized_specifier {
+ watched_files
+ .entry(specifier)
+ .or_insert(ConfigWatchedFileType::Lockfile);
+ }
+
+ // Load package.json
+ let mut package_json = None;
+ if let Ok(path) = specifier_to_file_path(scope) {
+ let path = path.join("package.json");
+ if let Ok(specifier) = ModuleSpecifier::from_file_path(&path) {
+ watched_files
+ .entry(specifier)
+ .or_insert(ConfigWatchedFileType::PackageJson);
+ }
+ let package_json_canonicalized_specifier =
+ canonicalize_path_maybe_not_exists(&path)
+ .ok()
+ .and_then(|p| ModuleSpecifier::from_file_path(p).ok());
+ if let Some(specifier) = package_json_canonicalized_specifier {
+ watched_files
+ .entry(specifier)
+ .or_insert(ConfigWatchedFileType::PackageJson);
+ }
+ if let Ok(source) = std::fs::read_to_string(&path) {
+ match PackageJson::load_from_string(path.clone(), source) {
+ Ok(result) => {
+ lsp_log!(" Resolved package.json: \"{}\"", path.display());
+ package_json = Some(result);
+ }
+ Err(err) => {
+ lsp_warn!(
+ " Couldn't read package.json \"{}\": {}",
+ path.display(),
+ err
+ );
+ }
+ }
+ }
+ }
+
+ // Load import map
+ let mut import_map = None;
+ let mut import_map_value = None;
+ let mut import_map_specifier = None;
+ if let Some(config_file) = &config_file {
+ if config_file.is_an_import_map() {
+ import_map_value = Some(config_file.to_import_map_value_from_imports());
+ import_map_specifier = Some(config_file.specifier.clone());
+ } else if let Ok(Some(specifier)) = config_file.to_import_map_specifier()
+ {
+ import_map_specifier = Some(specifier);
+ }
+ } else if let Some(import_map_str) = &settings.import_map {
+ if let Ok(specifier) = Url::parse(import_map_str) {
+ import_map_specifier = Some(specifier);
+ } else if let Some(folder_uri) = workspace_folder {
+ if let Ok(specifier) = folder_uri.join(import_map_str) {
+ import_map_specifier = Some(specifier);
+ }
+ }
+ }
+ if let Some(specifier) = &import_map_specifier {
+ if let Ok(path) = specifier_to_file_path(specifier) {
+ watched_files
+ .entry(specifier.clone())
+ .or_insert(ConfigWatchedFileType::ImportMap);
+ let import_map_canonicalized_specifier =
+ canonicalize_path_maybe_not_exists(&path)
+ .ok()
+ .and_then(|p| ModuleSpecifier::from_file_path(p).ok());
+ if let Some(specifier) = import_map_canonicalized_specifier {
+ watched_files
+ .entry(specifier)
+ .or_insert(ConfigWatchedFileType::ImportMap);
+ }
+ }
+ if import_map_value.is_none() {
+ if let Some(file_fetcher) = file_fetcher {
+ let fetch_result = file_fetcher
+ .fetch(specifier, PermissionsContainer::allow_all())
+ .await;
+ let value_result = fetch_result.and_then(|f| {
+ serde_json::from_slice::<Value>(&f.source).map_err(|e| e.into())
+ });
+ match value_result {
+ Ok(value) => {
+ import_map_value = Some(value);
+ }
+ Err(err) => {
+ lsp_warn!(
+ " Couldn't read import map \"{}\": {}",
+ specifier.as_str(),
+ err
+ );
+ }
+ }
+ }
+ }
+ }
+ if let (Some(value), Some(specifier)) =
+ (import_map_value, import_map_specifier)
+ {
+ match import_map::parse_from_value(specifier.clone(), value) {
+ Ok(result) => {
+ if config_file.as_ref().map(|c| &c.specifier) == Some(&specifier) {
+ lsp_log!(" Resolved import map from configuration file");
+ } else {
+ lsp_log!(" Resolved import map: \"{}\"", specifier.as_str());
+ }
+ if !result.diagnostics.is_empty() {
+ lsp_warn!(
+ " Import map diagnostics:\n{}",
+ result
+ .diagnostics
+ .iter()
+ .map(|d| format!(" - {d}"))
+ .collect::<Vec<_>>()
+ .join("\n")
+ );
+ }
+ import_map = Some(result.import_map);
+ }
+ Err(err) => {
+ lsp_warn!(
+ "Couldn't read import map \"{}\": {}",
+ specifier.as_str(),
+ err
+ );
+ }
+ }
+ }
+
+ ConfigData {
+ config_file: config_file.map(Arc::new),
+ fmt_options: Arc::new(fmt_options),
+ lint_options: Arc::new(lint_options),
+ lint_rules: Arc::new(lint_rules),
+ ts_config: Arc::new(ts_config),
+ node_modules_dir,
+ vendor_dir,
+ lockfile: lockfile.map(Mutex::new).map(Arc::new),
+ package_json: package_json.map(Arc::new),
+ import_map: import_map.map(Arc::new),
+ watched_files,
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct ConfigTree {
+ root: Mutex<Option<(ModuleSpecifier, Arc<ConfigData>)>>,
+}
+
+impl ConfigTree {
+ pub fn root_data(&self) -> Option<Arc<ConfigData>> {
+ self.root.lock().as_ref().map(|(_, d)| d.clone())
+ }
+
+ pub fn root_config_file(&self) -> Option<Arc<ConfigFile>> {
+ self
+ .root
+ .lock()
+ .as_ref()
+ .and_then(|(_, d)| d.config_file.clone())
+ }
+
+ pub fn root_ts_config(&self) -> Arc<TsConfig> {
+ self
+ .root
+ .lock()
+ .as_ref()
+ .map(|(_, d)| d.ts_config.clone())
+ .unwrap_or_else(|| Arc::new(default_ts_config()))
+ }
+
+ pub fn root_vendor_dir(&self) -> Option<PathBuf> {
+ self
+ .root
+ .lock()
+ .as_ref()
+ .and_then(|(_, d)| d.vendor_dir.clone())
+ }
+
+ pub fn root_lockfile(&self) -> Option<Arc<Mutex<Lockfile>>> {
+ self
+ .root
+ .lock()
+ .as_ref()
+ .and_then(|(_, d)| d.lockfile.clone())
+ }
+
+ pub fn scope_for_specifier(
+ &self,
+ _specifier: &ModuleSpecifier,
+ ) -> Option<ModuleSpecifier> {
+ self.root.lock().as_ref().map(|r| r.0.clone())
+ }
+
+ pub fn data_for_specifier(
+ &self,
+ _specifier: &ModuleSpecifier,
+ ) -> Option<Arc<ConfigData>> {
+ self.root_data()
+ }
+
+ pub fn data_by_scope(&self) -> BTreeMap<ModuleSpecifier, Arc<ConfigData>> {
+ self.root.lock().iter().cloned().collect()
+ }
+
+ pub fn config_file_for_specifier(
+ &self,
+ _specifier: &ModuleSpecifier,
+ ) -> Option<Arc<ConfigFile>> {
+ self.root_config_file()
+ }
+
+ pub fn has_config_file_for_specifier(
+ &self,
+ _specifier: &ModuleSpecifier,
+ ) -> bool {
+ self
+ .root
+ .lock()
+ .as_ref()
+ .map(|(_, d)| d.config_file.is_some())
+ .unwrap_or(false)
+ }
+
+ pub fn config_files(&self) -> Vec<Arc<ConfigFile>> {
+ self.root_config_file().into_iter().collect()
+ }
+
+ pub fn package_jsons(&self) -> Vec<Arc<PackageJson>> {
+ self
+ .root
+ .lock()
+ .as_ref()
+ .and_then(|(_, d)| d.package_json.clone())
+ .into_iter()
+ .collect()
+ }
+
+ pub fn fmt_options_for_specifier(
+ &self,
+ _specifier: &ModuleSpecifier,
+ ) -> Arc<FmtOptions> {
+ self
+ .root
+ .lock()
+ .as_ref()
+ .map(|(_, d)| d.fmt_options.clone())
+ .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).
+ pub fn watched_file_type(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<(ModuleSpecifier, ConfigWatchedFileType)> {
+ if let Some((scope_uri, data)) = &*self.root.lock() {
+ if let Some(typ) = data.watched_files.get(specifier) {
+ return Some((scope_uri.clone(), *typ));
+ }
+ }
+ None
+ }
+
+ pub fn is_watched_file(&self, specifier: &ModuleSpecifier) -> bool {
+ if specifier.path().ends_with("/deno.json")
+ || specifier.path().ends_with("/deno.jsonc")
+ || specifier.path().ends_with("/package.json")
+ {
+ return true;
+ }
+ self
+ .root
+ .lock()
+ .as_ref()
+ .is_some_and(|(_, d)| d.watched_files.contains_key(specifier))
+ }
+
+ #[cfg(test)]
+ pub async fn inject_config_file(&self, config_file: ConfigFile) {
+ let scope = config_file.specifier.join(".").unwrap();
+ let data = ConfigData::load_inner(
+ Some(config_file),
+ &scope,
+ &Default::default(),
+ None,
+ )
+ .await;
+ *self.root.lock() = Some((scope, Arc::new(data)));
+ }
+}
+
fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> {
let lockfile_path = match config_file.resolve_lockfile_path() {
Ok(Some(value)) => value,
@@ -1224,7 +1668,7 @@ fn resolve_lockfile_from_path(lockfile_path: PathBuf) -> Option<Lockfile> {
match Lockfile::new(lockfile_path, false) {
Ok(value) => {
if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) {
- lsp_log!(" Resolved lock file: \"{}\"", specifier);
+ lsp_log!(" Resolved lockfile: \"{}\"", specifier);
}
Some(value)
}
@@ -1310,7 +1754,7 @@ mod tests {
#[test]
fn test_set_workspace_settings_defaults() {
- let mut config = Config::new();
+ let mut config = Config::default();
config.set_workspace_settings(
serde_json::from_value(json!({})).unwrap(),
vec![],
@@ -1445,7 +1889,7 @@ mod tests {
#[test]
fn test_empty_cache() {
- let mut config = Config::new();
+ let mut config = Config::default();
config.set_workspace_settings(
serde_json::from_value(json!({ "cache": "" })).unwrap(),
vec![],
@@ -1458,7 +1902,7 @@ mod tests {
#[test]
fn test_empty_import_map() {
- let mut config = Config::new();
+ let mut config = Config::default();
config.set_workspace_settings(
serde_json::from_value(json!({ "import_map": "" })).unwrap(),
vec![],
@@ -1471,7 +1915,7 @@ mod tests {
#[test]
fn test_empty_tls_certificate() {
- let mut config = Config::new();
+ let mut config = Config::default();
config.set_workspace_settings(
serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(),
vec![],
@@ -1484,7 +1928,7 @@ mod tests {
#[test]
fn test_empty_config() {
- let mut config = Config::new();
+ let mut config = Config::default();
config.set_workspace_settings(
serde_json::from_value(json!({ "config": "" })).unwrap(),
vec![],
@@ -1495,16 +1939,19 @@ mod tests {
);
}
- #[test]
- fn config_enable_via_config_file_detection() {
+ #[tokio::test]
+ async fn config_enable_via_config_file_detection() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
config.settings.unscoped.enable = None;
assert!(!config.specifier_enabled(&root_uri));
- config.set_config_file(
- ConfigFile::new("{}", root_uri.join("deno.json").unwrap()).unwrap(),
- );
+ config
+ .tree
+ .inject_config_file(
+ ConfigFile::new("{}", root_uri.join("deno.json").unwrap()).unwrap(),
+ )
+ .await;
assert!(config.specifier_enabled(&root_uri));
}
@@ -1517,8 +1964,8 @@ mod tests {
assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap()));
}
- #[test]
- fn config_specifier_enabled_for_test() {
+ #[tokio::test]
+ async fn config_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
config.settings.unscoped.enable = Some(true);
@@ -1537,19 +1984,22 @@ mod tests {
);
config.settings.unscoped.enable_paths = None;
- config.set_config_file(
- ConfigFile::new(
- &json!({
- "exclude": ["mod2.ts"],
- "test": {
- "exclude": ["mod3.ts"],
- },
- })
- .to_string(),
- root_uri.join("deno.json").unwrap(),
+ config
+ .tree
+ .inject_config_file(
+ ConfigFile::new(
+ &json!({
+ "exclude": ["mod2.ts"],
+ "test": {
+ "exclude": ["mod3.ts"],
+ },
+ })
+ .to_string(),
+ root_uri.join("deno.json").unwrap(),
+ )
+ .unwrap(),
)
- .unwrap(),
- );
+ .await;
assert!(
config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
);
@@ -1560,38 +2010,38 @@ mod tests {
!config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap())
);
- config.set_config_file(
- ConfigFile::new(
- &json!({
- "test": {
- "include": ["mod1.ts"],
- },
- })
- .to_string(),
- root_uri.join("deno.json").unwrap(),
+ config
+ .tree
+ .inject_config_file(
+ ConfigFile::new(
+ &json!({
+ "test": {
+ "include": ["mod1.ts"],
+ },
+ })
+ .to_string(),
+ root_uri.join("deno.json").unwrap(),
+ )
+ .unwrap(),
)
- .unwrap(),
- );
- assert!(
- config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
- );
- assert!(
- !config.specifier_enabled_for_test(&root_uri.join("mod2.ts").unwrap())
- );
+ .await;
- config.set_config_file(
- ConfigFile::new(
- &json!({
- "test": {
- "exclude": ["mod2.ts"],
- "include": ["mod2.ts"],
- },
- })
- .to_string(),
- root_uri.join("deno.json").unwrap(),
+ config
+ .tree
+ .inject_config_file(
+ ConfigFile::new(
+ &json!({
+ "test": {
+ "exclude": ["mod2.ts"],
+ "include": ["mod2.ts"],
+ },
+ })
+ .to_string(),
+ root_uri.join("deno.json").unwrap(),
+ )
+ .unwrap(),
)
- .unwrap(),
- );
+ .await;
assert!(
!config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
);
@@ -1600,24 +2050,27 @@ mod tests {
);
}
- #[test]
- fn config_snapshot_specifier_enabled_for_test() {
+ #[tokio::test]
+ async fn config_snapshot_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_roots(vec![root_uri.clone()]);
config.settings.unscoped.enable = Some(true);
- config.set_config_file(
- ConfigFile::new(
- &json!({
- "exclude": ["mod2.ts"],
- "test": {
- "exclude": ["mod3.ts"],
- },
- })
- .to_string(),
- root_uri.join("deno.json").unwrap(),
+ config
+ .tree
+ .inject_config_file(
+ ConfigFile::new(
+ &json!({
+ "exclude": ["mod2.ts"],
+ "test": {
+ "exclude": ["mod3.ts"],
+ },
+ })
+ .to_string(),
+ root_uri.join("deno.json").unwrap(),
+ )
+ .unwrap(),
)
- .unwrap(),
- );
+ .await;
let config_snapshot = config.snapshot();
assert!(config_snapshot
.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()));
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 7b969b8ab..e3b922ba8 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -21,7 +21,6 @@ use crate::graph_util::enhanced_resolution_error_message;
use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
use crate::resolver::SloppyImportsResolution;
use crate::resolver::SloppyImportsResolver;
-use crate::tools::lint::get_configured_rules;
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
@@ -46,6 +45,7 @@ use deno_runtime::tokio_util::create_basic_runtime;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
+use import_map::ImportMap;
use log::error;
use std::collections::HashMap;
use std::collections::HashSet;
@@ -62,10 +62,10 @@ use tower_lsp::lsp_types as lsp;
pub struct DiagnosticServerUpdateMessage {
pub snapshot: Arc<StateSnapshot>,
pub config: Arc<ConfigSnapshot>,
- pub lint_options: LintOptions,
pub url_map: LspUrlMap,
}
+#[derive(Debug)]
struct DiagnosticRecord {
pub specifier: ModuleSpecifier,
pub versioned: VersionedDiagnostics,
@@ -461,7 +461,6 @@ impl DiagnosticsServer {
DiagnosticServerUpdateMessage {
snapshot,
config,
- lint_options,
url_map,
},
batch_index,
@@ -612,14 +611,7 @@ impl DiagnosticsServer {
let mark = performance.mark("lsp.update_diagnostics_lint");
let diagnostics = spawn_blocking({
let token = token.clone();
- move || {
- generate_lint_diagnostics(
- &snapshot,
- &config,
- &lint_options,
- token,
- )
- }
+ move || generate_lint_diagnostics(&snapshot, &config, token)
})
.await
.unwrap();
@@ -791,17 +783,12 @@ fn ts_json_to_diagnostics(
fn generate_lint_diagnostics(
snapshot: &language_server::StateSnapshot,
config: &ConfigSnapshot,
- lint_options: &LintOptions,
token: CancellationToken,
) -> DiagnosticVec {
let documents = snapshot
.documents
.documents(DocumentsFilter::OpenDiagnosable);
- let lint_rules = get_configured_rules(
- lint_options.rules.clone(),
- config.config_file.as_ref(),
- )
- .rules;
+ let config_data_by_scope = config.tree.data_by_scope();
let mut diagnostics_vec = Vec::new();
for document in documents {
let settings =
@@ -820,14 +807,20 @@ fn generate_lint_diagnostics(
}
}
let version = document.maybe_lsp_version();
+ let (lint_options, lint_rules) = config
+ .tree
+ .scope_for_specifier(document.specifier())
+ .and_then(|s| config_data_by_scope.get(&s))
+ .map(|d| (d.lint_options.clone(), d.lint_rules.clone()))
+ .unwrap_or_default();
diagnostics_vec.push(DiagnosticRecord {
specifier: document.specifier().clone(),
versioned: VersionedDiagnostics {
version,
diagnostics: generate_document_lint_diagnostics(
config,
- lint_options,
- lint_rules.clone(),
+ &lint_options,
+ lint_rules.rules.clone(),
&document,
),
},
@@ -1304,6 +1297,7 @@ fn diagnose_resolution(
resolution: &Resolution,
is_dynamic: bool,
maybe_assert_type: Option<&str>,
+ import_map: Option<&ImportMap>,
) -> Vec<DenoDiagnostic> {
fn check_redirect_diagnostic(
specifier: &ModuleSpecifier,
@@ -1392,7 +1386,7 @@ fn diagnose_resolution(
.push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone()));
} else if module_name == dependency_key {
let mut is_mapped = false;
- if let Some(import_map) = &snapshot.maybe_import_map {
+ if let Some(import_map) = import_map {
if let Resolution::Ok(resolved) = &resolution {
if import_map.resolve(module_name, &resolved.specifier).is_ok() {
is_mapped = true;
@@ -1455,7 +1449,8 @@ fn diagnose_dependency(
}
}
- if let Some(import_map) = &snapshot.maybe_import_map {
+ let import_map = snapshot.config.tree.import_map_for_specifier(referrer);
+ if let Some(import_map) = &import_map {
if let Resolution::Ok(resolved) = &dependency.maybe_code {
if let Some(to) = import_map.lookup(&resolved.specifier, referrer) {
if dependency_key != to {
@@ -1504,6 +1499,7 @@ fn diagnose_dependency(
},
dependency.is_dynamic,
dependency.maybe_attribute_type.as_deref(),
+ import_map.as_deref(),
)
.iter()
.flat_map(|diag| {
@@ -1526,6 +1522,7 @@ fn diagnose_dependency(
&dependency.maybe_type,
dependency.is_dynamic,
dependency.maybe_attribute_type.as_deref(),
+ import_map.as_deref(),
)
.iter()
.map(|diag| diag.to_lsp_diagnostic(&range)),
@@ -1580,20 +1577,21 @@ mod tests {
use super::*;
use crate::cache::GlobalHttpCache;
use crate::cache::RealDenoCacheEnv;
+ use crate::lsp::config::Config;
use crate::lsp::config::ConfigSnapshot;
use crate::lsp::config::Settings;
use crate::lsp::config::WorkspaceSettings;
use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId;
use crate::lsp::language_server::StateSnapshot;
- use deno_config::glob::FilePatterns;
+ use deno_config::ConfigFile;
use pretty_assertions::assert_eq;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use test_util::TempDir;
- fn mock_state_snapshot(
+ async fn mock_state_snapshot(
fixtures: &[(&str, &str, i32, LanguageId)],
location: &Path,
maybe_import_map: Option<(&str, &str)>,
@@ -1613,22 +1611,19 @@ mod tests {
(*source).into(),
);
}
- let maybe_import_map = maybe_import_map.map(|(base, json_string)| {
- let base_url = ModuleSpecifier::parse(base).unwrap();
- let result = import_map::parse_from_json(&base_url, json_string).unwrap();
- if !result.diagnostics.is_empty() {
- panic!("unexpected import map diagnostics");
- }
- Arc::new(result.import_map)
- });
+ let config = Config::new_with_roots([resolve_url("file:///").unwrap()]);
+ if let Some((base_url, json_string)) = maybe_import_map {
+ let base_url = resolve_url(base_url).unwrap();
+ let config_file = ConfigFile::new(json_string, base_url).unwrap();
+ config.tree.inject_config_file(config_file).await;
+ }
StateSnapshot {
documents,
- maybe_import_map,
assets: Default::default(),
cache_metadata: cache::CacheMetadata::new(Arc::new(
GlobalHttpCache::new(location.to_path_buf(), RealDenoCacheEnv),
)),
- config: Default::default(),
+ config: config.snapshot(),
npm: None,
}
}
@@ -1655,14 +1650,14 @@ mod tests {
}
}
- fn setup(
+ async fn setup(
temp_dir: &TempDir,
sources: &[(&str, &str, i32, LanguageId)],
maybe_import_map: Option<(&str, &str)>,
) -> (StateSnapshot, PathBuf) {
let location = temp_dir.path().join("deps").to_path_buf();
let state_snapshot =
- mock_state_snapshot(sources, &location, maybe_import_map);
+ mock_state_snapshot(sources, &location, maybe_import_map).await;
(state_snapshot, location)
}
@@ -1681,18 +1676,14 @@ let c: number = "a";
LanguageId::TypeScript,
)],
None,
- );
+ )
+ .await;
let snapshot = Arc::new(snapshot);
let cache =
Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv));
- let ts_server = TsServer::new(Default::default(), cache);
+ let ts_server =
+ TsServer::new(Default::default(), cache, Default::default());
ts_server.start(None);
- let lint_options = LintOptions {
- rules: Default::default(),
- files: FilePatterns::new_with_base(temp_dir.path().to_path_buf()),
- reporter_kind: Default::default(),
- fix: false,
- };
// test enabled
{
@@ -1700,7 +1691,6 @@ let c: number = "a";
let diagnostics = generate_lint_diagnostics(
&snapshot,
&enabled_config,
- &lint_options,
Default::default(),
);
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 6);
@@ -1712,7 +1702,7 @@ let c: number = "a";
)
.await
.unwrap();
- assert_eq!(get_diagnostics_for_single(diagnostics).len(), 5);
+ assert_eq!(get_diagnostics_for_single(diagnostics).len(), 4);
let diagnostics = generate_deno_diagnostics(
&snapshot,
&enabled_config,
@@ -1732,7 +1722,6 @@ let c: number = "a";
let diagnostics = generate_lint_diagnostics(
&snapshot,
&disabled_config,
- &lint_options,
Default::default(),
);
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 0);
@@ -1793,7 +1782,8 @@ let c: number = "a";
}
}"#,
)),
- );
+ )
+ .await;
let config = mock_config();
let token = CancellationToken::new();
let actual = generate_deno_diagnostics(&snapshot, &config, token);
@@ -1919,7 +1909,8 @@ let c: number = "a";
LanguageId::TypeScript,
)],
None,
- );
+ )
+ .await;
let config = mock_config();
let token = CancellationToken::new();
let actual = generate_deno_diagnostics(&snapshot, &config, token);
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index 9aa862dbc..7d1d89b8d 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -37,7 +37,6 @@ use deno_lockfile::Lockfile;
use deno_runtime::deno_node;
use deno_runtime::deno_node::NodeResolution;
use deno_runtime::deno_node::NodeResolutionMode;
-use deno_runtime::deno_node::PackageJson;
use deno_runtime::permissions::PermissionsContainer;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
@@ -817,15 +816,6 @@ impl FileSystemDocuments {
}
}
-pub struct UpdateDocumentConfigOptions<'a> {
- pub config: &'a Config,
- pub maybe_import_map: Option<Arc<import_map::ImportMap>>,
- pub maybe_package_json: Option<&'a PackageJson>,
- 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.
#[derive(Debug, Clone, Copy)]
pub enum DocumentsFilter {
@@ -1309,26 +1299,34 @@ impl Documents {
Arc::new(JsrCacheResolver::new(self.cache.clone(), lockfile));
}
- pub fn update_config(&mut self, options: UpdateDocumentConfigOptions) {
- 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 = maybe_config_file
- .and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten());
- let deps_provider =
- Arc::new(PackageJsonDepsProvider::new(maybe_package_json_deps));
+ pub fn update_config(
+ &mut self,
+ config: &Config,
+ node_resolver: Option<Arc<CliNodeResolver>>,
+ npm_resolver: Option<Arc<dyn CliNpmResolver>>,
+ workspace_files: &BTreeSet<ModuleSpecifier>,
+ ) {
+ let config_data = config.tree.root_data();
+ let config_file =
+ config_data.as_ref().and_then(|d| d.config_file.as_deref());
self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
- node_resolver: options.node_resolver,
- npm_resolver: options.npm_resolver,
- package_json_deps_provider: deps_provider,
- maybe_jsx_import_source_config: maybe_jsx_config,
- maybe_import_map: options.maybe_import_map,
- maybe_vendor_dir: maybe_config_file
- .and_then(|c| c.vendor_dir_path())
- .as_ref(),
- bare_node_builtins_enabled: maybe_config_file
+ node_resolver,
+ npm_resolver,
+ package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new(
+ config_data
+ .as_ref()
+ .and_then(|d| d.package_json.as_ref())
+ .map(|package_json| {
+ package_json::get_local_package_json_version_reqs(package_json)
+ }),
+ )),
+ maybe_jsx_import_source_config: config_file
+ .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_vendor_dir: config_data
+ .as_ref()
+ .and_then(|d| d.vendor_dir.as_ref()),
+ bare_node_builtins_enabled: 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
@@ -1338,16 +1336,14 @@ impl Documents {
}));
self.jsr_resolver = Arc::new(JsrCacheResolver::new(
self.cache.clone(),
- options.config.maybe_lockfile().cloned(),
+ config.tree.root_lockfile(),
));
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)) =
- maybe_config_file.map(|cf| cf.to_maybe_imports())
- {
+ if let Some(Ok(imports)) = config_file.map(|cf| cf.to_maybe_imports()) {
imports
.into_iter()
.map(|(referrer, imports)| {
@@ -1364,7 +1360,7 @@ impl Documents {
IndexMap::new()
},
);
- self.unstable_sloppy_imports = maybe_config_file
+ self.unstable_sloppy_imports = config_file
.map(|c| c.has_unstable("sloppy-imports"))
.unwrap_or(false);
{
@@ -1376,7 +1372,7 @@ impl Documents {
// anymore after updating resolvers.
return false;
};
- if !options.config.specifier_enabled(specifier) {
+ if !config.specifier_enabled(specifier) {
return false;
}
path.is_file()
@@ -1384,7 +1380,7 @@ impl Documents {
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()) {
+ if !config.specifier_enabled(doc.specifier()) {
continue;
}
if let Some(new_doc) =
@@ -1395,8 +1391,8 @@ impl Documents {
}
}
self.open_docs = open_docs;
- for specifier in options.workspace_files {
- if !options.config.specifier_enabled(specifier) {
+ for specifier in workspace_files {
+ if !config.specifier_enabled(specifier) {
continue;
}
if !self.open_docs.contains_key(specifier)
@@ -1738,8 +1734,9 @@ mod tests {
use crate::cache::RealDenoCacheEnv;
use super::*;
+ use deno_config::ConfigFile;
use deno_core::serde_json;
- use import_map::ImportMap;
+ use deno_core::serde_json::json;
use pretty_assertions::assert_eq;
use test_util::PathRef;
use test_util::TempDir;
@@ -1842,8 +1839,8 @@ console.log(b, "hello deno");
assert_eq!(documents.documents(DocumentsFilter::All).len(), 1);
}
- #[test]
- fn test_documents_refresh_dependencies_config_change() {
+ #[tokio::test]
+ async fn test_documents_refresh_dependencies_config_change() {
// it should never happen that a user of this API causes this to happen,
// but we'll guard against it anyway
let temp_dir = TempDir::new();
@@ -1878,23 +1875,23 @@ console.log(b, "hello deno");
// set the initial import map and point to file 2
{
- let mut import_map = ImportMap::new(
- ModuleSpecifier::from_file_path(documents_path.join("import_map.json"))
+ config
+ .tree
+ .inject_config_file(
+ ConfigFile::new(
+ &json!({
+ "imports": {
+ "test": "./file2.ts",
+ },
+ })
+ .to_string(),
+ config.root_uri().unwrap().join("deno.json").unwrap(),
+ )
.unwrap(),
- );
- import_map
- .imports_mut()
- .append("test".to_string(), "./file2.ts".to_string())
- .unwrap();
-
- documents.update_config(UpdateDocumentConfigOptions {
- config: &config,
- maybe_import_map: Some(Arc::new(import_map)),
- maybe_package_json: None,
- node_resolver: None,
- npm_resolver: None,
- workspace_files: &workspace_files,
- });
+ )
+ .await;
+
+ documents.update_config(&config, None, None, &workspace_files);
// open the document
let document = documents.open(
@@ -1918,23 +1915,23 @@ console.log(b, "hello deno");
// now point at file 3
{
- let mut import_map = ImportMap::new(
- ModuleSpecifier::from_file_path(documents_path.join("import_map.json"))
+ config
+ .tree
+ .inject_config_file(
+ ConfigFile::new(
+ &json!({
+ "imports": {
+ "test": "./file3.ts",
+ },
+ })
+ .to_string(),
+ config.root_uri().unwrap().join("deno.json").unwrap(),
+ )
.unwrap(),
- );
- import_map
- .imports_mut()
- .append("test".to_string(), "./file3.ts".to_string())
- .unwrap();
-
- documents.update_config(UpdateDocumentConfigOptions {
- config: &config,
- maybe_import_map: Some(Arc::new(import_map)),
- maybe_package_json: None,
- node_resolver: None,
- npm_resolver: None,
- workspace_files: &workspace_files,
- });
+ )
+ .await;
+
+ documents.update_config(&config, None, None, &workspace_files);
// check the document's dependencies
let document = documents.get(&file1_specifier).unwrap();
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index de5f7e357..fafd9fe4c 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -18,11 +18,9 @@ use deno_lockfile::Lockfile;
use deno_npm::NpmSystemInfo;
use deno_runtime::deno_fs;
use deno_runtime::deno_node::NodeResolver;
-use deno_runtime::deno_node::PackageJson;
use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_tls::RootCertStoreProvider;
use deno_semver::jsr::JsrPackageReqReference;
-use import_map::ImportMap;
use indexmap::IndexSet;
use log::error;
use serde::Deserialize;
@@ -71,7 +69,6 @@ use super::documents::Document;
use super::documents::Documents;
use super::documents::DocumentsFilter;
use super::documents::LanguageId;
-use super::documents::UpdateDocumentConfigOptions;
use super::jsr::CliJsrSearchApi;
use super::logging::lsp_log;
use super::logging::lsp_warn;
@@ -92,16 +89,11 @@ use super::tsc::GetCompletionDetailsArgs;
use super::tsc::TsServer;
use super::urls;
use crate::args::get_root_cert_store;
-use crate::args::package_json;
-use crate::args::resolve_import_map;
use crate::args::CaData;
use crate::args::CacheSetting;
use crate::args::CliOptions;
use crate::args::ConfigFile;
use crate::args::Flags;
-use crate::args::FmtOptions;
-use crate::args::LintOptions;
-use crate::args::TsConfig;
use crate::cache::DenoDir;
use crate::cache::FastInsecureHasher;
use crate::cache::GlobalHttpCache;
@@ -111,6 +103,7 @@ use crate::factory::CliFactory;
use crate::file_fetcher::FileFetcher;
use crate::graph_util;
use crate::http_util::HttpClient;
+use crate::lsp::config::ConfigWatchedFileType;
use crate::lsp::logging::init_log_file;
use crate::lsp::tsc::file_text_changes_to_workspace_edit;
use crate::lsp::urls::LspUrlKind;
@@ -157,10 +150,15 @@ struct LspNpmConfigHash(u64);
impl LspNpmConfigHash {
pub fn from_inner(inner: &Inner) -> Self {
+ let config_data = inner.config.tree.root_data();
+ let node_modules_dir = config_data
+ .as_ref()
+ .and_then(|d| d.node_modules_dir.as_ref());
+ let lockfile = config_data.as_ref().and_then(|d| d.lockfile.as_ref());
let mut hasher = FastInsecureHasher::new();
- hasher.write_hashable(inner.config.maybe_node_modules_dir_path());
+ hasher.write_hashable(node_modules_dir);
hasher.write_hashable(&inner.maybe_global_cache_path);
- if let Some(lockfile) = inner.config.maybe_lockfile() {
+ if let Some(lockfile) = lockfile {
hasher.write_hashable(&*lockfile.lock());
}
Self(hasher.finish())
@@ -183,7 +181,6 @@ pub struct StateSnapshot {
pub cache_metadata: cache::CacheMetadata,
pub config: Arc<ConfigSnapshot>,
pub documents: Documents,
- pub maybe_import_map: Option<Arc<ImportMap>>,
pub npm: Option<StateNpmSnapshot>,
}
@@ -254,14 +251,6 @@ pub struct Inner {
/// An optional path to the DENO_DIR which has been specified in the client
/// options.
maybe_global_cache_path: Option<PathBuf>,
- /// An optional import map which is used to resolve modules.
- maybe_import_map: Option<Arc<ImportMap>>,
- /// An optional package.json configuration file.
- maybe_package_json: Option<PackageJson>,
- /// Configuration for formatter which has been taken from specified config file.
- fmt_options: FmtOptions,
- /// An optional configuration for linter which has been taken from specified config file.
- lint_options: LintOptions,
/// A lazily create "server" for handling test run requests.
maybe_testing_server: Option<testing::TestServer>,
/// Services used for dealing with npm related functionality.
@@ -344,7 +333,11 @@ impl LanguageServer {
// do as much as possible in a read, then do a write outside
let maybe_prepare_cache_result = {
let inner = self.0.read().await; // ensure dropped
- match inner.prepare_cache(specifiers, referrer, force_global_cache) {
+ match inner.prepare_cache(
+ specifiers,
+ referrer.clone(),
+ force_global_cache,
+ ) {
Ok(maybe_cache_result) => maybe_cache_result,
Err(err) => {
lsp_warn!("Error preparing caching: {:#}", err);
@@ -376,7 +369,7 @@ impl LanguageServer {
}
{
let mut inner = self.0.write().await;
- let lockfile = inner.config.maybe_lockfile().cloned();
+ let lockfile = inner.config.tree.lockfile_for_specifier(&referrer);
inner.documents.refresh_jsr_resolver(lockfile);
inner.refresh_npm_specifiers().await;
}
@@ -530,9 +523,12 @@ impl Inner {
let documents = Documents::new(deps_http_cache.clone());
let cache_metadata = cache::CacheMetadata::new(deps_http_cache.clone());
let performance = Arc::new(Performance::default());
- let ts_server =
- Arc::new(TsServer::new(performance.clone(), deps_http_cache.clone()));
- let config = Config::new();
+ let config = Config::default();
+ let ts_server = Arc::new(TsServer::new(
+ performance.clone(),
+ deps_http_cache.clone(),
+ config.tree.clone(),
+ ));
let diagnostics_state = Arc::new(DiagnosticsState::default());
let diagnostics_server = DiagnosticsServer::new(
client.clone(),
@@ -558,11 +554,7 @@ impl Inner {
initial_cwd: initial_cwd.clone(),
jsr_search_api,
maybe_global_cache_path: None,
- maybe_import_map: None,
- maybe_package_json: None,
- fmt_options: FmtOptions::new_with_base(initial_cwd.clone()),
task_queue: Default::default(),
- lint_options: LintOptions::new_with_base(initial_cwd),
maybe_testing_server: None,
module_registries,
module_registries_location,
@@ -645,76 +637,6 @@ impl Inner {
Ok(navigation_tree)
}
- fn get_config_file(&self) -> Result<Option<ConfigFile>, AnyError> {
- let workspace_settings = self.config.workspace_settings();
- let maybe_config = &workspace_settings.config;
- if let Some(config_str) = maybe_config {
- if !config_str.is_empty() {
- lsp_log!("Setting Deno configuration from: \"{}\"", config_str);
- let config_url = if let Ok(url) = Url::from_file_path(config_str) {
- Ok(url)
- } else if let Some(root_uri) = self.config.root_uri() {
- root_uri.join(config_str).map_err(|_| {
- anyhow!("Bad file path for configuration file: \"{}\"", config_str)
- })
- } else {
- Err(anyhow!(
- "The path to the configuration file (\"{}\") is not resolvable.",
- config_str
- ))
- }?;
- lsp_log!(" Resolved configuration file: \"{}\"", config_url);
-
- let config_file = ConfigFile::from_specifier(config_url)?;
- return Ok(Some(config_file));
- }
- }
-
- // Auto-discover config
-
- // It is possible that root_uri is not set, for example when having a single
- // file open and not a workspace. In those situations we can't
- // automatically discover the configuration
- if let Some(root_uri) = self.config.root_uri() {
- let root_path = specifier_to_file_path(root_uri)?;
- let mut checked = std::collections::HashSet::new();
- let maybe_config =
- ConfigFile::discover_from(&root_path, &mut checked, None)?;
- Ok(maybe_config.map(|c| {
- lsp_log!(" Auto-resolved configuration file: \"{}\"", c.specifier);
- c
- }))
- } else {
- Ok(None)
- }
- }
-
- fn get_package_json(
- &self,
- maybe_config_file: Option<&ConfigFile>,
- ) -> Result<Option<PackageJson>, AnyError> {
- if crate::args::has_flag_env_var("DENO_NO_PACKAGE_JSON") {
- return Ok(None);
- }
-
- // It is possible that root_uri is not set, for example when having a single
- // file open and not a workspace. In those situations we can't
- // automatically discover the configuration
- if let Some(root_uri) = self.config.root_uri() {
- let root_path = specifier_to_file_path(root_uri)?;
- let maybe_package_json = package_json::discover_from(
- &root_path,
- maybe_config_file.and_then(|f| f.specifier.to_file_path().ok()),
- )?;
- Ok(maybe_package_json.map(|c| {
- lsp_log!(" Auto-resolved package.json: \"{}\"", c.specifier());
- c
- }))
- } else {
- Ok(None)
- }
- }
-
fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool {
if specifier.scheme() == "asset" {
matches!(
@@ -740,23 +662,6 @@ impl Inner {
}
}
- fn merge_user_tsconfig(
- &self,
- tsconfig: &mut TsConfig,
- ) -> Result<(), AnyError> {
- if let Some(config_file) = self.config.maybe_config_file() {
- let (value, maybe_ignored_options) = config_file.to_compiler_options()?;
- tsconfig.merge(&value);
- if let Some(ignored_options) = maybe_ignored_options {
- // TODO(@kitsonk) turn these into diagnostics that can be sent to the
- // client
- lsp_warn!("{}", ignored_options);
- }
- }
-
- Ok(())
- }
-
pub fn snapshot(&self) -> Arc<StateSnapshot> {
let maybe_state_npm_snapshot = self
.npm
@@ -785,7 +690,6 @@ impl Inner {
cache_metadata: self.cache_metadata.clone(),
config: self.config.snapshot(),
documents: self.documents.clone(),
- maybe_import_map: self.maybe_import_map.clone(),
npm: maybe_state_npm_snapshot,
})
}
@@ -876,7 +780,7 @@ impl Inner {
self.jsr_search_api = CliJsrSearchApi::new(deps_file_fetcher.clone());
self.npm.search_api = CliNpmSearchApi::new(deps_file_fetcher);
let maybe_local_cache =
- self.config.maybe_vendor_dir_path().map(|local_path| {
+ self.config.tree.root_vendor_dir().map(|local_path| {
Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone()))
});
let cache: Arc<dyn HttpCache> = maybe_local_cache
@@ -903,14 +807,16 @@ impl Inner {
if config_hash == self.npm.config_hash {
return; // no need to do anything
}
-
+ let config_data = self.config.tree.root_data();
let npm_resolver = create_npm_resolver(
&deno_dir,
&self.initial_cwd,
&self.http_client,
- self.config.maybe_config_file(),
- self.config.maybe_lockfile(),
- self.config.maybe_node_modules_dir_path().cloned(),
+ config_data.as_ref().and_then(|d| d.config_file.as_deref()),
+ config_data.as_ref().and_then(|d| d.lockfile.as_ref()),
+ config_data
+ .as_ref()
+ .and_then(|d| d.node_modules_dir.clone()),
)
.await;
let node_resolver = Arc::new(NodeResolver::new(
@@ -929,41 +835,6 @@ impl Inner {
self.npm.config_hash = config_hash;
}
- pub async fn update_import_map(&mut self) -> Result<(), AnyError> {
- let mark = self.performance.mark("lsp.update_import_map");
-
- let maybe_import_map_url = self.resolve_import_map_specifier()?;
- let maybe_import_map = self
- .fetch_import_map(
- maybe_import_map_url.as_ref(),
- CacheSetting::RespectHeaders,
- )
- .await?;
- if let Some(import_map) = maybe_import_map {
- if import_map.base_url().scheme() != "data" {
- lsp_log!(" Resolved import map: \"{}\"", import_map.base_url());
- }
- self.maybe_import_map = Some(Arc::new(import_map));
- } else {
- self.maybe_import_map = None;
- }
- self.performance.measure(mark);
- Ok(())
- }
-
- async fn fetch_import_map(
- &self,
- import_map_url: Option<&ModuleSpecifier>,
- cache_setting: CacheSetting,
- ) -> Result<Option<ImportMap>, AnyError> {
- resolve_import_map(
- import_map_url,
- self.config.maybe_config_file(),
- &self.create_file_fetcher(cache_setting),
- )
- .await
- }
-
fn create_file_fetcher(&self, cache_setting: CacheSetting) -> FileFetcher {
let mut file_fetcher = FileFetcher::new(
self.deps_http_cache.clone(),
@@ -979,10 +850,13 @@ impl Inner {
fn resolve_import_map_specifier(
&self,
+ referrer: &ModuleSpecifier,
) -> Result<Option<ModuleSpecifier>, AnyError> {
let Some(import_map_str) = self
.config
- .workspace_settings()
+ .settings
+ .get_for_specifier(referrer)
+ .0
.import_map
.clone()
.and_then(|s| if s.is_empty() { None } else { Some(s) })
@@ -993,7 +867,9 @@ impl Inner {
"Using import map from workspace settings: \"{}\"",
import_map_str
);
- if let Some(config_file) = self.config.maybe_config_file() {
+ if let Some(config_file) =
+ self.config.tree.config_file_for_specifier(referrer)
+ {
if let Some(import_map_path) = &config_file.json.import_map {
lsp_log!("Warning: Import map \"{}\" configured in \"{}\" being ignored due to an import map being explicitly configured in workspace settings.", import_map_path, config_file.specifier);
}
@@ -1036,103 +912,6 @@ impl Inner {
self.performance.measure(mark);
Ok(())
}
-
- fn update_config_file(&mut self) -> Result<(), AnyError> {
- self.config.clear_config_file();
- self.fmt_options = FmtOptions::new_with_base(self.initial_cwd.clone());
- self.lint_options = LintOptions::new_with_base(self.initial_cwd.clone());
- if let Some(config_file) = self.get_config_file()? {
- let lint_options = config_file
- .to_lint_config()
- .and_then(|maybe_lint_config| {
- LintOptions::resolve(maybe_lint_config, None, &self.initial_cwd)
- })
- .map_err(|err| {
- anyhow!("Unable to update lint configuration: {:?}", err)
- })?;
- let fmt_options = config_file
- .to_fmt_config()
- .and_then(|maybe_fmt_config| {
- FmtOptions::resolve(maybe_fmt_config, None, &self.initial_cwd)
- })
- .map_err(|err| {
- anyhow!("Unable to update formatter configuration: {:?}", err)
- })?;
-
- self.config.set_config_file(config_file);
- self.lint_options = lint_options;
- self.fmt_options = fmt_options;
- self.recreate_http_client_and_dependents()?;
- if let Some(config_file) = self.config.maybe_config_file() {
- if let Ok((compiler_options, _)) = config_file.to_compiler_options() {
- if let Some(compiler_options_obj) = compiler_options.as_object() {
- if let Some(jsx_import_source) =
- compiler_options_obj.get("jsxImportSource")
- {
- if let Some(jsx_import_source) = jsx_import_source.as_str() {
- let specifiers = vec![Url::parse(&format!(
- "data:application/typescript;base64,{}",
- base64::engine::general_purpose::STANDARD.encode(format!(
- "import '{jsx_import_source}/jsx-runtime';"
- ))
- ))
- .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);
- }
- });
- }));
- }
- }
- }
- }
- }
- }
-
- Ok(())
- }
-
- /// Updates the package.json. Always ensure this is done after updating
- /// the configuration file as the resolution of this depends on that.
- fn update_package_json(&mut self) -> Result<(), AnyError> {
- self.maybe_package_json = None;
- self.maybe_package_json =
- self.get_package_json(self.config.maybe_config_file())?;
- Ok(())
- }
-
- async fn update_tsconfig(&mut self) -> Result<(), AnyError> {
- let mark = self.performance.mark("lsp.update_tsconfig");
- let mut tsconfig = TsConfig::new(json!({
- "allowJs": true,
- "esModuleInterop": true,
- "experimentalDecorators": false,
- "isolatedModules": true,
- "jsx": "react",
- "lib": ["deno.ns", "deno.window", "deno.unstable"],
- "module": "esnext",
- "moduleDetection": "force",
- "noEmit": true,
- "resolveJsonModule": true,
- "strict": true,
- "target": "esnext",
- "useDefineForClassFields": true,
- // TODO(@kitsonk) remove for Deno 1.15
- "useUnknownInCatchVariables": false,
- }));
- if let Err(err) = self.merge_user_tsconfig(&mut tsconfig) {
- lsp_warn!("Error merging tsconfig: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- let _ok = self.ts_server.configure(self.snapshot(), tsconfig).await?;
- self.performance.measure(mark);
- Ok(())
- }
}
async fn create_npm_resolver(
@@ -1272,23 +1051,12 @@ impl Inner {
.start(self.config.internal_inspect().to_address());
self.update_debug_flag();
- // Check to see if we need to change the cache path
+ self.refresh_workspace_files();
+ self.refresh_config_tree().await;
if let Err(err) = self.update_cache() {
lsp_warn!("Error updating cache: {:#}", err);
self.client.show_message(MessageType::WARNING, err);
}
- if let Err(err) = self.update_config_file() {
- lsp_warn!("Error updating config file: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- if let Err(err) = self.update_package_json() {
- lsp_warn!("Error updating package.json: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- if let Err(err) = self.update_tsconfig().await {
- lsp_warn!("Error updating tsconfig: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
if capabilities.code_action_provider.is_some() {
let fixable_diagnostics = self
@@ -1298,11 +1066,6 @@ impl Inner {
self.ts_fixable_diagnostics = fixable_diagnostics;
}
- // Check to see if we need to setup the import map
- if let Err(err) = self.update_import_map().await {
- lsp_warn!("Error updating import map: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
// Check to see if we need to setup any module registries
if let Err(err) = self.update_registries().await {
lsp_warn!("Error updating registries: {:#}", err);
@@ -1450,15 +1213,58 @@ impl Inner {
self.workspace_files_hash = enable_settings_hash;
}
+ async fn refresh_config_tree(&mut self) {
+ let file_fetcher = self.create_file_fetcher(CacheSetting::RespectHeaders);
+ if let Some(root_uri) = self.config.root_uri() {
+ self
+ .config
+ .tree
+ .refresh(
+ &self.config.settings,
+ root_uri,
+ &self.workspace_files,
+ &file_fetcher,
+ )
+ .await;
+ for config_file in self.config.tree.config_files() {
+ if let Ok((compiler_options, _)) = config_file.to_compiler_options() {
+ if let Some(compiler_options_obj) = compiler_options.as_object() {
+ if let Some(jsx_import_source) =
+ compiler_options_obj.get("jsxImportSource")
+ {
+ if let Some(jsx_import_source) = jsx_import_source.as_str() {
+ let specifiers = vec![Url::parse(&format!(
+ "data:application/typescript;base64,{}",
+ base64::engine::general_purpose::STANDARD.encode(format!(
+ "import '{jsx_import_source}/jsx-runtime';"
+ ))
+ ))
+ .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);
+ }
+ });
+ }));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
async fn refresh_documents_config(&mut self) {
- self.documents.update_config(UpdateDocumentConfigOptions {
- config: &self.config,
- maybe_import_map: self.maybe_import_map.clone(),
- maybe_package_json: self.maybe_package_json.as_ref(),
- node_resolver: self.npm.node_resolver.clone(),
- npm_resolver: self.npm.resolver.clone(),
- workspace_files: &self.workspace_files,
- });
+ self.documents.update_config(
+ &self.config,
+ self.npm.node_resolver.clone(),
+ self.npm.resolver.clone(),
+ &self.workspace_files,
+ );
// refresh the npm specifiers because it might have discovered
// a @types/node package and now's a good time to do that anyway
@@ -1591,6 +1397,8 @@ impl Inner {
};
self.update_debug_flag();
+ self.refresh_workspace_files();
+ self.refresh_config_tree().await;
if let Err(err) = self.update_cache() {
lsp_warn!("Error updating cache: {:#}", err);
self.client.show_message(MessageType::WARNING, err);
@@ -1599,27 +1407,8 @@ impl Inner {
lsp_warn!("Error updating registries: {:#}", err);
self.client.show_message(MessageType::WARNING, err);
}
- if let Err(err) = self.update_config_file() {
- lsp_warn!("Error updating config file: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- if let Err(err) = self.update_package_json() {
- lsp_warn!("Error updating package.json: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- if let Err(err) = self.update_import_map().await {
- lsp_warn!("Error updating import map: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- if let Err(err) = self.update_tsconfig().await {
- lsp_warn!("Error updating tsconfig: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
-
self.recreate_npm_services_if_necessary().await;
- self.refresh_workspace_files();
self.refresh_documents_config().await;
-
self.diagnostics_server.invalidate_all();
self.send_diagnostics_update();
self.send_testing_update();
@@ -1629,194 +1418,73 @@ impl Inner {
&mut self,
params: DidChangeWatchedFilesParams,
) {
- fn has_lockfile_content_changed(lockfile: &Lockfile) -> bool {
- match Lockfile::new(lockfile.filename.clone(), false) {
- Ok(new_lockfile) => {
- // only update if the lockfile has changed
- FastInsecureHasher::hash(lockfile)
- != FastInsecureHasher::hash(new_lockfile)
- }
- Err(err) => {
- lsp_warn!("Error loading lockfile: {:#}", err);
- false
- }
- }
- }
-
- fn has_config_changed(config: &Config, changes: &IndexSet<Url>) -> bool {
- // Check the canonicalized specifier here because file watcher
- // changes will be for the canonicalized path in vscode, but also check the
- // non-canonicalized specifier in order to please the tests and handle
- // a client that might send that instead.
- if config
- .maybe_config_file_canonicalized_specifier()
- .map(|s| changes.contains(s))
- .unwrap_or(false)
- {
- return true;
- }
- match config.maybe_config_file() {
- Some(file) => {
- if changes.contains(&file.specifier) {
- return true;
- }
- }
- None => {
- // check for auto-discovery
- if changes.iter().any(|url| {
- url.path().ends_with("/deno.json")
- || url.path().ends_with("/deno.jsonc")
- }) {
- return true;
- }
- }
- }
-
- // if the lockfile has changed, reload the config as well
- if let Some(lockfile) = config.maybe_lockfile() {
- let lockfile_matches = config
- .maybe_lockfile_canonicalized_specifier()
- .map(|s| changes.contains(s))
- .or_else(|| {
- ModuleSpecifier::from_file_path(&lockfile.lock().filename)
- .ok()
- .map(|s| changes.contains(&s))
- })
- .unwrap_or(false);
- lockfile_matches && has_lockfile_content_changed(&lockfile.lock())
- } else {
- // check for auto-discovery
- changes.iter().any(|url| url.path().ends_with("/deno.lock"))
- }
- }
-
let mark = self
.performance
.mark_with_args("lsp.did_change_watched_files", &params);
- let mut touched = false;
- let changes: IndexSet<Url> = params
- .changes
- .iter()
- .map(|f| self.url_map.normalize_url(&f.uri, LspUrlKind::File))
- .collect();
- let mut config_changes = IndexSet::with_capacity(changes.len());
-
- // if the current deno.json has changed, we need to reload it
- if has_config_changed(&self.config, &changes) {
- // Check the 'current' config specifier from both before and after it's
- // updated. Check canonicalized and uncanonicalized variants for each.
- // If any are included in `changes`, send our custom notification for
- // `deno.json` changes: `deno/didChangeDenoConfigurationNotification`.
- let mut files_to_check = IndexSet::with_capacity(4);
- // Collect previous config specifiers.
- if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) {
- files_to_check.insert(url.clone());
- }
- if let Some(url) = self.config.maybe_config_file_canonicalized_specifier()
- {
- files_to_check.insert(url.clone());
- }
- // Update config.
- if let Err(err) = self.update_config_file() {
- lsp_warn!("Error updating config file: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- // Collect new config specifiers.
- if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) {
- files_to_check.insert(url.clone());
- }
- if let Some(url) = self.config.maybe_config_file_canonicalized_specifier()
- {
- files_to_check.insert(url.clone());
- }
- if let Some(root_uri) = self.config.root_uri() {
- config_changes.extend(
- params
- .changes
- .iter()
- .filter(|e| files_to_check.contains(&e.uri))
- .map(|e| lsp_custom::DenoConfigurationChangeEvent {
- scope_uri: root_uri.clone(),
- file_uri: e.uri.clone(),
- typ:
- lsp_custom::DenoConfigurationChangeType::from_file_change_type(
- e.typ,
- ),
- configuration_type: lsp_custom::DenoConfigurationType::DenoJson,
- }),
- );
- }
- if let Err(err) = self.update_tsconfig().await {
- lsp_warn!("Error updating tsconfig: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- touched = true;
- }
-
- let has_package_json_changed = changes
+ let changes = params
+ .changes
+ .into_iter()
+ .map(|e| (self.url_map.normalize_url(&e.uri, LspUrlKind::File), e))
+ .collect::<Vec<_>>();
+ if changes
.iter()
- .any(|e| e.as_str().ends_with("/package.json"));
-
- if has_package_json_changed {
- let mut files_to_check = IndexSet::with_capacity(2);
- if let Some(package_json) = &self.maybe_package_json {
- files_to_check.insert(package_json.specifier());
- }
- if let Err(err) = self.update_package_json() {
- lsp_warn!("Error updating package.json: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- if let Some(package_json) = &self.maybe_package_json {
- files_to_check.insert(package_json.specifier());
- }
- if let Some(root_uri) = self.config.root_uri() {
- config_changes.extend(
- params
- .changes
- .iter()
- .filter(|e| files_to_check.contains(&e.uri))
- .map(|e| lsp_custom::DenoConfigurationChangeEvent {
- scope_uri: root_uri.clone(),
- file_uri: e.uri.clone(),
- typ:
- lsp_custom::DenoConfigurationChangeType::from_file_change_type(
- e.typ,
- ),
- configuration_type:
- lsp_custom::DenoConfigurationType::PackageJson,
- }),
+ .any(|(s, _)| self.config.tree.is_watched_file(s))
+ {
+ let mut deno_config_changes = IndexSet::with_capacity(changes.len());
+ deno_config_changes.extend(changes.iter().filter_map(|(s, e)| {
+ self.config.tree.watched_file_type(s).and_then(|t| {
+ let configuration_type = match t.1 {
+ ConfigWatchedFileType::DenoJson => {
+ lsp_custom::DenoConfigurationType::DenoJson
+ }
+ ConfigWatchedFileType::PackageJson => {
+ lsp_custom::DenoConfigurationType::PackageJson
+ }
+ _ => return None,
+ };
+ Some(lsp_custom::DenoConfigurationChangeEvent {
+ scope_uri: t.0,
+ file_uri: e.uri.clone(),
+ typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type(
+ e.typ,
+ ),
+ configuration_type,
+ })
+ })
+ }));
+ self.workspace_files_hash = 0;
+ self.refresh_workspace_files();
+ self.refresh_config_tree().await;
+ deno_config_changes.extend(changes.iter().filter_map(|(s, e)| {
+ self.config.tree.watched_file_type(s).and_then(|t| {
+ let configuration_type = match t.1 {
+ ConfigWatchedFileType::DenoJson => {
+ lsp_custom::DenoConfigurationType::DenoJson
+ }
+ ConfigWatchedFileType::PackageJson => {
+ lsp_custom::DenoConfigurationType::PackageJson
+ }
+ _ => return None,
+ };
+ Some(lsp_custom::DenoConfigurationChangeEvent {
+ scope_uri: t.0,
+ file_uri: e.uri.clone(),
+ typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type(
+ e.typ,
+ ),
+ configuration_type,
+ })
+ })
+ }));
+ if !deno_config_changes.is_empty() {
+ self.client.send_did_change_deno_configuration_notification(
+ lsp_custom::DidChangeDenoConfigurationNotificationParams {
+ changes: deno_config_changes.into_iter().collect(),
+ },
);
}
- touched = true;
- }
-
- if !config_changes.is_empty() {
- self.client.send_did_change_deno_configuration_notification(
- lsp_custom::DidChangeDenoConfigurationNotificationParams {
- changes: config_changes.into_iter().collect(),
- },
- );
- }
-
- // if the current import map, or config file has changed, we need to
- // reload the import map
- let import_map_changed = self
- .maybe_import_map
- .as_ref()
- .map(|import_map| changes.contains(import_map.base_url()))
- .unwrap_or(false);
- if touched || import_map_changed {
- if let Err(err) = self.update_import_map().await {
- lsp_warn!("Error updating import map: {:#}", err);
- self.client.show_message(MessageType::WARNING, err);
- }
- touched = true;
- }
-
- 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;
@@ -1902,7 +1570,13 @@ impl Inner {
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
// skip formatting any files ignored by the config file
- if !self.fmt_options.files.matches_specifier(&specifier) {
+ if !self
+ .config
+ .tree
+ .fmt_options_for_specifier(&specifier)
+ .files
+ .matches_specifier(&specifier)
+ {
return Ok(None);
}
let document = match self.documents.get(&specifier) {
@@ -1925,7 +1599,12 @@ impl Inner {
// spawn a blocking task to allow doing other work while this is occurring
let text_edits = deno_core::unsync::spawn_blocking({
- let fmt_options = self.fmt_options.options.clone();
+ let fmt_options = self
+ .config
+ .tree
+ .fmt_options_for_specifier(&specifier)
+ .options
+ .clone();
let document = document.clone();
move || {
let format_result = match document.maybe_parsed_source() {
@@ -2152,10 +1831,14 @@ impl Inner {
line_index.offset_tsc(diagnostic.range.start)?
..line_index.offset_tsc(diagnostic.range.end)?,
codes,
- (&self.fmt_options.options).into(),
+ (&self
+ .config
+ .tree
+ .fmt_options_for_specifier(&specifier)
+ .options)
+ .into(),
tsc::UserPreferences::from_config_for_specifier(
&self.config,
- &self.fmt_options.options,
&specifier,
),
)
@@ -2246,7 +1929,6 @@ impl Inner {
..line_index.offset_tsc(params.range.end)?,
Some(tsc::UserPreferences::from_config_for_specifier(
&self.config,
- &self.fmt_options.options,
&specifier,
)),
only,
@@ -2305,10 +1987,14 @@ impl Inner {
.get_combined_code_fix(
self.snapshot(),
&code_action_data,
- (&self.fmt_options.options).into(),
+ (&self
+ .config
+ .tree
+ .fmt_options_for_specifier(&code_action_data.specifier)
+ .options)
+ .into(),
tsc::UserPreferences::from_config_for_specifier(
&self.config,
- &self.fmt_options.options,
&code_action_data.specifier,
),
)
@@ -2322,7 +2008,7 @@ impl Inner {
fix_ts_import_changes(
&code_action_data.specifier,
&combined_code_actions.changes,
- &self.get_ts_response_import_mapper(),
+ &self.get_ts_response_import_mapper(&code_action_data.specifier),
)
.map_err(|err| {
error!("Unable to remap changes: {:#}", err);
@@ -2351,14 +2037,18 @@ impl Inner {
.get_edits_for_refactor(
self.snapshot(),
action_data.specifier.clone(),
- (&self.fmt_options.options).into(),
+ (&self
+ .config
+ .tree
+ .fmt_options_for_specifier(&action_data.specifier)
+ .options)
+ .into(),
line_index.offset_tsc(action_data.range.start)?
..line_index.offset_tsc(action_data.range.end)?,
action_data.refactor_name,
action_data.action_name,
Some(tsc::UserPreferences::from_config_for_specifier(
&self.config,
- &self.fmt_options.options,
&action_data.specifier,
)),
)
@@ -2374,10 +2064,13 @@ impl Inner {
Ok(result)
}
- pub fn get_ts_response_import_mapper(&self) -> TsResponseImportMapper {
+ pub fn get_ts_response_import_mapper(
+ &self,
+ referrer: &ModuleSpecifier,
+ ) -> TsResponseImportMapper {
TsResponseImportMapper::new(
&self.documents,
- self.maybe_import_map.as_deref(),
+ self.config.tree.import_map_for_specifier(referrer),
self.npm.node_resolver.as_deref(),
self.npm.resolver.as_deref(),
)
@@ -2690,7 +2383,7 @@ impl Inner {
&self.jsr_search_api,
&self.npm.search_api,
&self.documents,
- self.maybe_import_map.clone(),
+ self.config.tree.import_map_for_specifier(&specifier),
)
.await;
}
@@ -2716,13 +2409,17 @@ impl Inner {
tsc::GetCompletionsAtPositionOptions {
user_preferences: tsc::UserPreferences::from_config_for_specifier(
&self.config,
- &self.fmt_options.options,
&specifier,
),
trigger_character,
trigger_kind,
},
- (&self.fmt_options.options).into(),
+ (&self
+ .config
+ .tree
+ .fmt_options_for_specifier(&specifier)
+ .options)
+ .into(),
)
.await;
@@ -2769,11 +2466,17 @@ impl Inner {
.get_completion_details(
self.snapshot(),
GetCompletionDetailsArgs {
- format_code_settings: Some((&self.fmt_options.options).into()),
+ format_code_settings: Some(
+ (&self
+ .config
+ .tree
+ .fmt_options_for_specifier(specifier)
+ .options)
+ .into(),
+ ),
preferences: Some(
tsc::UserPreferences::from_config_for_specifier(
&self.config,
- &self.fmt_options.options,
specifier,
),
),
@@ -3289,6 +2992,12 @@ impl Inner {
if options.enabled == UpdateImportsOnFileMoveEnabled::Never {
continue;
}
+ let format_code_settings = (&self
+ .config
+ .tree
+ .fmt_options_for_specifier(&old_specifier)
+ .options)
+ .into();
changes.extend(
self
.ts_server
@@ -3299,7 +3008,7 @@ impl Inner {
&resolve_url(&rename.new_uri).unwrap(),
LspUrlKind::File,
),
- (&self.fmt_options.options).into(),
+ format_code_settings,
tsc::UserPreferences {
allow_text_changes_in_new_files: Some(true),
..Default::default()
@@ -3350,7 +3059,6 @@ impl Inner {
let snapshot = DiagnosticServerUpdateMessage {
snapshot: self.snapshot(),
config: self.config.snapshot(),
- lint_options: self.lint_options.clone(),
url_map: self.url_map.clone(),
};
if let Err(err) = self.diagnostics_server.update(snapshot) {
@@ -3459,31 +3167,31 @@ impl tower_lsp::LanguageServer for LanguageServer {
ls.maybe_testing_server = Some(test_server);
}
- if let Some(root_uri) = ls.config.root_uri() {
- let mut config_events = vec![];
- if let Some(config_file) = ls.config.maybe_config_file() {
+ let mut config_events = vec![];
+ for (scope_uri, config_data) in ls.config.tree.data_by_scope() {
+ if let Some(config_file) = &config_data.config_file {
config_events.push(lsp_custom::DenoConfigurationChangeEvent {
- scope_uri: root_uri.clone(),
+ scope_uri: scope_uri.clone(),
file_uri: config_file.specifier.clone(),
typ: lsp_custom::DenoConfigurationChangeType::Added,
configuration_type: lsp_custom::DenoConfigurationType::DenoJson,
});
}
- if let Some(package_json) = &ls.maybe_package_json {
+ if let Some(package_json) = &config_data.package_json {
config_events.push(lsp_custom::DenoConfigurationChangeEvent {
- scope_uri: root_uri.clone(),
+ scope_uri: scope_uri.clone(),
file_uri: package_json.specifier(),
typ: lsp_custom::DenoConfigurationChangeType::Added,
configuration_type: lsp_custom::DenoConfigurationType::PackageJson,
});
}
- if !config_events.is_empty() {
- ls.client.send_did_change_deno_configuration_notification(
- lsp_custom::DidChangeDenoConfigurationNotificationParams {
- changes: config_events,
- },
- );
- }
+ }
+ if !config_events.is_empty() {
+ ls.client.send_did_change_deno_configuration_notification(
+ lsp_custom::DidChangeDenoConfigurationNotificationParams {
+ changes: config_events,
+ },
+ );
}
(ls.client.clone(), ls.http_client.clone())
@@ -3504,11 +3212,6 @@ impl tower_lsp::LanguageServer for LanguageServer {
{
let mut ls = self.0.write().await;
init_log_file(ls.config.log_file());
- if let Err(err) = ls.update_tsconfig().await {
- 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();
@@ -3643,6 +3346,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
{
let mut ls = self.0.write().await;
ls.refresh_workspace_files();
+ ls.refresh_config_tree().await;
ls.refresh_documents_config().await;
ls.diagnostics_server.invalidate_all();
ls.send_diagnostics_update();
@@ -3845,10 +3549,11 @@ impl Inner {
let mark = self
.performance
.mark_with_args("lsp.cache", (&specifiers, &referrer));
+ let config_data = self.config.tree.data_for_specifier(&referrer);
let roots = if !specifiers.is_empty() {
specifiers
} else {
- vec![referrer]
+ vec![referrer.clone()]
};
let workspace_settings = self.config.workspace_settings();
let mut cli_options = CliOptions::new(
@@ -3860,21 +3565,28 @@ impl Inner {
.unsafely_ignore_certificate_errors
.clone(),
node_modules_dir: Some(
- self.config.maybe_node_modules_dir_path().is_some(),
+ config_data
+ .as_ref()
+ .and_then(|d| d.node_modules_dir.as_ref())
+ .is_some(),
),
// bit of a hack to force the lsp to cache the @types/node package
type_check_mode: crate::args::TypeCheckMode::Local,
..Default::default()
},
self.initial_cwd.clone(),
- self.config.maybe_config_file().cloned(),
- self.config.maybe_lockfile().cloned(),
- self.maybe_package_json.clone(),
+ config_data
+ .as_ref()
+ .and_then(|d| d.config_file.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,
)?;
// don't use the specifier in self.maybe_import_map because it's not
// necessarily an import map specifier (could be a deno.json)
- if let Some(import_map) = self.resolve_import_map_specifier()? {
+ if let Some(import_map) = self.resolve_import_map_specifier(&referrer)? {
cli_options.set_import_map_specifier(Some(import_map));
}
@@ -3907,7 +3619,7 @@ impl Inner {
fn task_definitions(&self) -> LspResult<Vec<TaskDefinition>> {
let mut result = vec![];
- if let Some(config_file) = self.config.maybe_config_file() {
+ for config_file in self.config.tree.config_files() {
if let Some(tasks) = json!(&config_file.json.tasks).as_object() {
for (name, value) in tasks {
let Some(command) = value.as_str() else {
@@ -3921,7 +3633,7 @@ impl Inner {
}
};
}
- if let Some(package_json) = &self.maybe_package_json {
+ for package_json in self.config.tree.package_jsons() {
if let Some(scripts) = &package_json.scripts {
for (name, command) in scripts {
result.push(TaskDefinition {
@@ -3968,7 +3680,6 @@ impl Inner {
text_span,
tsc::UserPreferences::from_config_for_specifier(
&self.config,
- &self.fmt_options.options,
&specifier,
),
)
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index e88aba07b..1d7dac4e9 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -3,6 +3,7 @@
use super::analysis::CodeActionData;
use super::code_lens;
use super::config;
+use super::config::ConfigTree;
use super::documents::AssetOrDocument;
use super::documents::DocumentsFilter;
use super::language_server;
@@ -22,7 +23,6 @@ use super::urls::INVALID_SPECIFIER;
use crate::args::jsr_url;
use crate::args::FmtOptionsConfig;
-use crate::args::TsConfig;
use crate::cache::HttpCache;
use crate::lsp::cache::CacheMetadata;
use crate::lsp::documents::Documents;
@@ -221,6 +221,7 @@ pub struct TsServer {
sender: mpsc::UnboundedSender<Request>,
receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>,
specifier_map: Arc<TscSpecifierMap>,
+ config_tree: Arc<ConfigTree>,
inspector_server: Mutex<Option<Arc<InspectorServer>>>,
}
@@ -238,7 +239,11 @@ impl std::fmt::Debug for TsServer {
}
impl TsServer {
- pub fn new(performance: Arc<Performance>, cache: Arc<dyn HttpCache>) -> Self {
+ pub fn new(
+ performance: Arc<Performance>,
+ cache: Arc<dyn HttpCache>,
+ config_tree: Arc<ConfigTree>,
+ ) -> Self {
let (tx, request_rx) = mpsc::unbounded_channel::<Request>();
Self {
performance,
@@ -246,6 +251,7 @@ impl TsServer {
sender: tx,
receiver: Mutex::new(Some(request_rx)),
specifier_map: Arc::new(TscSpecifierMap::new()),
+ config_tree,
inspector_server: Mutex::new(None),
}
}
@@ -268,6 +274,7 @@ impl TsServer {
let performance = self.performance.clone();
let cache = self.cache.clone();
let specifier_map = self.specifier_map.clone();
+ let config_tree = self.config_tree.clone();
let _join_handle = thread::spawn(move || {
run_tsc_thread(
receiver,
@@ -275,6 +282,7 @@ impl TsServer {
cache.clone(),
specifier_map.clone(),
maybe_inspector_server,
+ config_tree,
)
});
}
@@ -343,18 +351,6 @@ impl TsServer {
self.request(snapshot, req).await
}
- pub async fn configure(
- &self,
- snapshot: Arc<StateSnapshot>,
- tsconfig: TsConfig,
- ) -> Result<bool, AnyError> {
- let req = TscRequest {
- method: "$configure",
- args: json!([tsconfig]),
- };
- self.request(snapshot, req).await
- }
-
pub async fn get_supported_code_fixes(
&self,
snapshot: Arc<StateSnapshot>,
@@ -3482,7 +3478,7 @@ impl CompletionEntry {
{
if let Ok(import_specifier) = resolve_url(&import_data.file_name) {
if let Some(new_module_specifier) = language_server
- .get_ts_response_import_mapper()
+ .get_ts_response_import_mapper(specifier)
.check_specifier(&import_specifier, specifier)
.or_else(|| relative_specifier(specifier, &import_specifier))
{
@@ -3849,6 +3845,7 @@ struct State {
response: Option<Response>,
state_snapshot: Arc<StateSnapshot>,
specifier_map: Arc<TscSpecifierMap>,
+ config_tree: Arc<ConfigTree>,
token: CancellationToken,
}
@@ -3856,6 +3853,7 @@ impl State {
fn new(
state_snapshot: Arc<StateSnapshot>,
specifier_map: Arc<TscSpecifierMap>,
+ config_tree: Arc<ConfigTree>,
performance: Arc<Performance>,
) -> Self {
Self {
@@ -3864,6 +3862,7 @@ impl State {
response: None,
state_snapshot,
specifier_map,
+ config_tree,
token: Default::default(),
}
}
@@ -4078,9 +4077,19 @@ fn op_script_version(
}
#[op2]
+#[serde]
+fn op_ts_config(state: &mut OpState) -> serde_json::Value {
+ let state = state.borrow_mut::<State>();
+ let mark = state.performance.mark("tsc.op.op_ts_config");
+ let r = json!(state.config_tree.root_ts_config());
+ state.performance.measure(mark);
+ r
+}
+
+#[op2]
#[string]
fn op_project_version(state: &mut OpState) -> String {
- let state = state.borrow_mut::<State>();
+ let state: &mut State = state.borrow_mut::<State>();
let mark = state.performance.mark("tsc.op.op_project_version");
let r = state.state_snapshot.documents.project_version();
state.performance.measure(mark);
@@ -4093,13 +4102,19 @@ fn run_tsc_thread(
cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>,
maybe_inspector_server: Option<Arc<InspectorServer>>,
+ config_tree: Arc<ConfigTree>,
) {
let has_inspector_server = maybe_inspector_server.is_some();
// Create and setup a JsRuntime based on a snapshot. It is expected that the
// supplied snapshot is an isolate that contains the TypeScript language
// server.
let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
- extensions: vec![deno_tsc::init_ops(performance, cache, specifier_map)],
+ extensions: vec![deno_tsc::init_ops(
+ performance,
+ cache,
+ specifier_map,
+ config_tree,
+ )],
startup_snapshot: Some(tsc::compiler_snapshot()),
inspector: maybe_inspector_server.is_some(),
..Default::default()
@@ -4166,12 +4181,14 @@ deno_core::extension!(deno_tsc,
op_respond,
op_script_names,
op_script_version,
+ op_ts_config,
op_project_version,
],
options = {
performance: Arc<Performance>,
cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>,
+ config_tree: Arc<ConfigTree>,
},
state = |state, options| {
state.put(State::new(
@@ -4180,10 +4197,10 @@ deno_core::extension!(deno_tsc,
cache_metadata: CacheMetadata::new(options.cache.clone()),
config: Default::default(),
documents: Documents::new(options.cache.clone()),
- maybe_import_map: None,
npm: None,
}),
options.specifier_map,
+ options.config_tree,
options.performance,
));
},
@@ -4344,9 +4361,10 @@ pub struct UserPreferences {
impl UserPreferences {
pub fn from_config_for_specifier(
config: &config::Config,
- fmt_config: &FmtOptionsConfig,
specifier: &ModuleSpecifier,
) -> Self {
+ let fmt_options = config.tree.fmt_options_for_specifier(specifier);
+ let fmt_config = &fmt_options.options;
let base_preferences = Self {
allow_incomplete_completions: Some(true),
allow_text_changes_in_new_files: Some(specifier.scheme() == "file"),
@@ -4450,7 +4468,8 @@ impl UserPreferences {
language_settings.preferences.use_aliases_for_renames,
),
// Only use workspace settings for quote style if there's no `deno.json`.
- quote_preference: if config.has_config_file() {
+ quote_preference: if config.tree.has_config_file_for_specifier(specifier)
+ {
base_preferences.quote_preference
} else {
Some(language_settings.preferences.quote_style)
@@ -4630,7 +4649,6 @@ mod tests {
assets: Default::default(),
cache_metadata: CacheMetadata::new(cache),
config: Default::default(),
- maybe_import_map: None,
npm: None,
}
}
@@ -4645,13 +4663,21 @@ mod tests {
Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv));
let snapshot = Arc::new(mock_state_snapshot(sources, &location));
let performance = Arc::new(Performance::default());
- let ts_server = TsServer::new(performance, cache.clone());
+ let config_tree = Arc::new(ConfigTree::default());
+ config_tree
+ .inject_config_file(
+ deno_config::ConfigFile::new(
+ &json!({
+ "compilerOptions": config,
+ })
+ .to_string(),
+ resolve_url("file:///deno.json").unwrap(),
+ )
+ .unwrap(),
+ )
+ .await;
+ let ts_server = TsServer::new(performance, cache.clone(), config_tree);
ts_server.start(None);
- let ts_config = TsConfig::new(config);
- assert!(ts_server
- .configure(snapshot.clone(), ts_config,)
- .await
- .unwrap());
(ts_server, snapshot, cache)
}
@@ -4671,43 +4697,6 @@ mod tests {
}
#[tokio::test]
- async fn test_project_configure() {
- let temp_dir = TempDir::new();
- setup(
- &temp_dir,
- json!({
- "target": "esnext",
- "module": "esnext",
- "noEmit": true,
- }),
- &[],
- )
- .await;
- }
-
- #[tokio::test]
- async fn test_project_reconfigure() {
- let temp_dir = TempDir::new();
- let (ts_server, snapshot, _) = setup(
- &temp_dir,
- json!({
- "target": "esnext",
- "module": "esnext",
- "noEmit": true,
- }),
- &[],
- )
- .await;
- let ts_config = TsConfig::new(json!({
- "target": "esnext",
- "module": "esnext",
- "noEmit": true,
- "lib": ["deno.ns", "deno.worker"]
- }));
- assert!(ts_server.configure(snapshot, ts_config).await.unwrap());
- }
-
- #[tokio::test]
async fn test_get_diagnostics() {
let temp_dir = TempDir::new();
let (ts_server, snapshot, _) = setup(
@@ -4716,6 +4705,7 @@ mod tests {
"target": "esnext",
"module": "esnext",
"noEmit": true,
+ "lib": [],
}),
&[(
"file:///a.ts",
@@ -5468,11 +5458,10 @@ mod tests {
.inlay_hints
.variable_types
.suppress_when_type_matches_name = true;
- let mut config = config::Config::new();
+ let mut config = config::Config::default();
config.set_workspace_settings(settings, vec![]);
let user_preferences = UserPreferences::from_config_for_specifier(
&config,
- &Default::default(),
&ModuleSpecifier::parse("file:///foo.ts").unwrap(),
);
assert_eq!(
diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs
index cb282663c..fec664784 100644
--- a/cli/tools/lint/mod.rs
+++ b/cli/tools/lint/mod.rs
@@ -857,6 +857,12 @@ pub struct ConfiguredRules {
pub no_slow_types: bool,
}
+impl Default for ConfiguredRules {
+ fn default() -> Self {
+ get_configured_rules(Default::default(), None)
+ }
+}
+
impl ConfiguredRules {
fn incremental_cache_state(&self) -> Vec<&str> {
// use a hash of the rule names in order to bust the cache
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index 32c3bf035..e6123d25e 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -503,9 +503,6 @@ delete Object.prototype.__proto__;
}
}
- /** @type {ts.CompilerOptions} */
- let compilationSettings = {};
-
/** @type {ts.LanguageService} */
let languageService;
@@ -720,7 +717,17 @@ delete Object.prototype.__proto__;
if (logDebug) {
debug("host.getCompilationSettings()");
}
- return compilationSettings;
+ const tsConfig = normalizeConfig(ops.op_ts_config());
+ const { options, errors } = ts
+ .convertCompilerOptionsFromJson(tsConfig, "");
+ Object.assign(options, {
+ allowNonTsExtensions: true,
+ allowImportingTsExtensions: true,
+ });
+ if (errors.length > 0 && logDebug) {
+ debug(ts.formatDiagnostics(errors, host));
+ }
+ return options;
},
getScriptFileNames() {
if (logDebug) {
@@ -1010,21 +1017,6 @@ delete Object.prototype.__proto__;
serverRestart();
return respond(id, true);
}
- case "$configure": {
- const config = normalizeConfig(args[0]);
- const { options, errors } = ts
- .convertCompilerOptionsFromJson(config, "");
- Object.assign(options, {
- allowNonTsExtensions: true,
- allowImportingTsExtensions: true,
- });
- if (errors.length > 0 && logDebug) {
- debug(ts.formatDiagnostics(errors, host));
- }
- compilationSettings = options;
- moduleSpecifierCache.clear();
- return respond(id, true);
- }
case "$getSupportedCodeFixes": {
return respond(
id,
diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs
index c5913e07b..61b1d1bd1 100644
--- a/tests/integration/lsp_tests.rs
+++ b/tests/integration/lsp_tests.rs
@@ -146,32 +146,6 @@ fn lsp_tsconfig_types_config_sub_dir() {
}
#[test]
-fn lsp_tsconfig_bad_config_path() {
- let context = TestContextBuilder::new().use_temp_cwd().build();
- let mut client = context.new_lsp_command().build();
- client.initialize(|builder| {
- builder
- .set_config("bad_tsconfig.json")
- .set_maybe_root_uri(None);
- });
- let (method, maybe_params) = client.read_notification();
- assert_eq!(method, "window/showMessage");
- assert_eq!(maybe_params, Some(lsp::ShowMessageParams {
- typ: lsp::MessageType::WARNING,
- message: "The path to the configuration file (\"bad_tsconfig.json\") is not resolvable.".to_string()
- }));
- let diagnostics = client.did_open(json!({
- "textDocument": {
- "uri": "file:///a/file.ts",
- "languageId": "typescript",
- "version": 1,
- "text": "console.log(Deno.args);\n"
- }
- }));
- assert_eq!(diagnostics.all().len(), 0);
-}
-
-#[test]
fn lsp_triple_slash_types() {
let context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = context.temp_dir();
@@ -223,7 +197,7 @@ fn lsp_import_map() {
}
}));
- assert_eq!(diagnostics.all().len(), 0);
+ assert_eq!(json!(diagnostics.all()), json!([]));
let res = client.write_request(
"textDocument/hover",
@@ -497,7 +471,7 @@ fn lsp_import_map_embedded_in_config_file_after_initialize() {
}]
}));
- assert_eq!(client.read_diagnostics().all().len(), 0);
+ assert_eq!(json!(client.read_diagnostics().all()), json!([]));
let res = client.write_request(
"textDocument/hover",
@@ -546,7 +520,7 @@ fn lsp_import_map_config_file_auto_discovered() {
}]
}));
client.wait_until_stderr_line(|line| {
- line.contains("Auto-resolved configuration file:")
+ line.contains(" Resolved Deno configuration file:")
});
let uri = temp_dir.uri().join("a.ts").unwrap();
@@ -607,7 +581,7 @@ fn lsp_import_map_config_file_auto_discovered() {
}]
}));
client.wait_until_stderr_line(|line| {
- line.contains("Auto-resolved configuration file:")
+ line.contains(" Resolved Deno configuration file:")
});
let res = client.write_request(
"textDocument/hover",
@@ -675,7 +649,7 @@ fn lsp_import_map_config_file_auto_discovered_symlink() {
// this will discover the deno.json in the root
let search_line = format!(
- "Auto-resolved configuration file: \"{}\"",
+ " Resolved Deno configuration file: \"{}\"",
temp_dir.uri().join("deno.json").unwrap().as_str()
);
client.wait_until_stderr_line(|line| line.contains(&search_line));
@@ -8946,10 +8920,7 @@ fn lsp_performance() {
"lsp.update_diagnostics_deps",
"lsp.update_diagnostics_lint",
"lsp.update_diagnostics_ts",
- "lsp.update_import_map",
"lsp.update_registries",
- "lsp.update_tsconfig",
- "tsc.host.$configure",
"tsc.host.$getAssets",
"tsc.host.$getDiagnostics",
"tsc.host.$getSupportedCodeFixes",
@@ -8959,7 +8930,7 @@ fn lsp_performance() {
"tsc.op.op_project_version",
"tsc.op.op_script_names",
"tsc.op.op_script_version",
- "tsc.request.$configure",
+ "tsc.op.op_ts_config",
"tsc.request.$getAssets",
"tsc.request.$getSupportedCodeFixes",
"tsc.request.getQuickInfoAtPosition",