refactor(lsp): unify config file data into ConfigTree (#23032)

This commit is contained in:
Nayeem Rahman 2024-03-26 15:52:20 +00:00 committed by GitHub
parent d6452b3946
commit 3b61104e2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 1090 additions and 967 deletions

View file

@ -260,6 +260,12 @@ pub struct FmtOptions {
pub files: FilePatterns, pub files: FilePatterns,
} }
impl Default for FmtOptions {
fn default() -> Self {
Self::new_with_base(PathBuf::from("/"))
}
}
impl FmtOptions { impl FmtOptions {
pub fn new_with_base(base: PathBuf) -> Self { pub fn new_with_base(base: PathBuf) -> Self {
Self { Self {
@ -394,6 +400,12 @@ pub struct LintOptions {
pub fix: bool, pub fix: bool,
} }
impl Default for LintOptions {
fn default() -> Self {
Self::new_with_base(PathBuf::from("/"))
}
}
impl LintOptions { impl LintOptions {
pub fn new_with_base(base: PathBuf) -> Self { pub fn new_with_base(base: PathBuf) -> Self {
Self { Self {

View file

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

View file

@ -2,22 +2,34 @@
use super::logging::lsp_log; use super::logging::lsp_log;
use crate::args::ConfigFile; use crate::args::ConfigFile;
use crate::args::FmtOptions;
use crate::args::LintOptions;
use crate::cache::FastInsecureHasher; use crate::cache::FastInsecureHasher;
use crate::file_fetcher::FileFetcher;
use crate::lsp::logging::lsp_warn; 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::fs::canonicalize_path_maybe_not_exists;
use crate::util::path::specifier_to_file_path; use crate::util::path::specifier_to_file_path;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_config::FmtOptionsConfig; use deno_config::FmtOptionsConfig;
use deno_config::TsConfig;
use deno_core::anyhow::anyhow;
use deno_core::parking_lot::Mutex; use deno_core::parking_lot::Mutex;
use deno_core::serde::de::DeserializeOwned; use deno_core::serde::de::DeserializeOwned;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
use deno_core::serde::Serialize; use deno_core::serde::Serialize;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_lockfile::Lockfile; use deno_lockfile::Lockfile;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::permissions::PermissionsContainer;
use import_map::ImportMap;
use lsp::Url; use lsp::Url;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -717,9 +729,9 @@ impl WorkspaceSettings {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ConfigSnapshot { pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
pub config_file: Option<ConfigFile>,
pub settings: Settings, pub settings: Settings,
pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
pub tree: Arc<ConfigTree>,
} }
impl ConfigSnapshot { impl ConfigSnapshot {
@ -732,7 +744,8 @@ impl ConfigSnapshot {
/// Determine if the provided specifier is enabled or not. /// Determine if the provided specifier is enabled or not.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
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 let Ok(files) = cf.to_files_config() {
if !files.matches_specifier(specifier) { if !files.matches_specifier(specifier) {
return false; return false;
@ -742,14 +755,14 @@ impl ConfigSnapshot {
self self
.settings .settings
.specifier_enabled(specifier) .specifier_enabled(specifier)
.unwrap_or_else(|| self.config_file.is_some()) .unwrap_or_else(|| config_file.is_some())
} }
pub fn specifier_enabled_for_test( pub fn specifier_enabled_for_test(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> bool { ) -> 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 let Some(options) = cf.to_test_config().ok().flatten() {
if !options.files.matches_specifier(specifier) { if !options.files.matches_specifier(specifier) {
return false; return false;
@ -861,47 +874,18 @@ impl Settings {
} }
} }
#[derive(Debug)] #[derive(Debug, Default)]
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)]
pub struct Config { pub struct Config {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
pub settings: Settings, pub settings: Settings,
pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
/// An optional configuration file which has been specified in the client pub tree: Arc<ConfigTree>,
/// options along with some data that is computed after the config file is set.
maybe_config_file_info: Option<LspConfigFileInfo>,
} }
impl Config { 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)] #[cfg(test)]
pub fn new_with_roots(root_uris: impl IntoIterator<Item = Url>) -> Self { 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![]; let mut folders = vec![];
for root_uri in root_uris { for root_uri in root_uris {
let name = root_uri.path_segments().and_then(|s| s.last()); 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) 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> { pub fn snapshot(&self) -> Arc<ConfigSnapshot> {
Arc::new(ConfigSnapshot { Arc::new(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(), client_capabilities: self.client_capabilities.clone(),
config_file: self.maybe_config_file().cloned(),
settings: self.settings.clone(), settings: self.settings.clone(),
workspace_folders: self.workspace_folders.clone(), workspace_folders: self.workspace_folders.clone(),
tree: self.tree.clone(),
}) })
} }
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
let config_file = self.maybe_config_file(); let config_file = self.tree.config_file_for_specifier(specifier);
if let Some(cf) = config_file { if let Some(cf) = &config_file {
if let Ok(files) = cf.to_files_config() { if let Ok(files) = cf.to_files_config() {
if !files.matches_specifier(specifier) { if !files.matches_specifier(specifier) {
return false; return false;
@ -1114,7 +1013,7 @@ impl Config {
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> bool { ) -> 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 let Some(options) = cf.to_test_config().ok().flatten() {
if !options.files.matches_specifier(specifier) { if !options.files.matches_specifier(specifier) {
return false; 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> { fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> {
let lockfile_path = match config_file.resolve_lockfile_path() { let lockfile_path = match config_file.resolve_lockfile_path() {
Ok(Some(value)) => value, Ok(Some(value)) => value,
@ -1224,7 +1668,7 @@ fn resolve_lockfile_from_path(lockfile_path: PathBuf) -> Option<Lockfile> {
match Lockfile::new(lockfile_path, false) { match Lockfile::new(lockfile_path, false) {
Ok(value) => { Ok(value) => {
if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) { if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) {
lsp_log!(" Resolved lock file: \"{}\"", specifier); lsp_log!(" Resolved lockfile: \"{}\"", specifier);
} }
Some(value) Some(value)
} }
@ -1310,7 +1754,7 @@ mod tests {
#[test] #[test]
fn test_set_workspace_settings_defaults() { fn test_set_workspace_settings_defaults() {
let mut config = Config::new(); let mut config = Config::default();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({})).unwrap(), serde_json::from_value(json!({})).unwrap(),
vec![], vec![],
@ -1445,7 +1889,7 @@ mod tests {
#[test] #[test]
fn test_empty_cache() { fn test_empty_cache() {
let mut config = Config::new(); let mut config = Config::default();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "cache": "" })).unwrap(), serde_json::from_value(json!({ "cache": "" })).unwrap(),
vec![], vec![],
@ -1458,7 +1902,7 @@ mod tests {
#[test] #[test]
fn test_empty_import_map() { fn test_empty_import_map() {
let mut config = Config::new(); let mut config = Config::default();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "import_map": "" })).unwrap(), serde_json::from_value(json!({ "import_map": "" })).unwrap(),
vec![], vec![],
@ -1471,7 +1915,7 @@ mod tests {
#[test] #[test]
fn test_empty_tls_certificate() { fn test_empty_tls_certificate() {
let mut config = Config::new(); let mut config = Config::default();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(), serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(),
vec![], vec![],
@ -1484,7 +1928,7 @@ mod tests {
#[test] #[test]
fn test_empty_config() { fn test_empty_config() {
let mut config = Config::new(); let mut config = Config::default();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "config": "" })).unwrap(), serde_json::from_value(json!({ "config": "" })).unwrap(),
vec![], vec![],
@ -1495,16 +1939,19 @@ mod tests {
); );
} }
#[test] #[tokio::test]
fn config_enable_via_config_file_detection() { async fn config_enable_via_config_file_detection() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_roots(vec![root_uri.clone()]); let mut config = Config::new_with_roots(vec![root_uri.clone()]);
config.settings.unscoped.enable = None; config.settings.unscoped.enable = None;
assert!(!config.specifier_enabled(&root_uri)); assert!(!config.specifier_enabled(&root_uri));
config.set_config_file( config
.tree
.inject_config_file(
ConfigFile::new("{}", root_uri.join("deno.json").unwrap()).unwrap(), ConfigFile::new("{}", root_uri.join("deno.json").unwrap()).unwrap(),
); )
.await;
assert!(config.specifier_enabled(&root_uri)); assert!(config.specifier_enabled(&root_uri));
} }
@ -1517,8 +1964,8 @@ mod tests {
assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap())); assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap()));
} }
#[test] #[tokio::test]
fn config_specifier_enabled_for_test() { async fn config_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_roots(vec![root_uri.clone()]); let mut config = Config::new_with_roots(vec![root_uri.clone()]);
config.settings.unscoped.enable = Some(true); config.settings.unscoped.enable = Some(true);
@ -1537,7 +1984,9 @@ mod tests {
); );
config.settings.unscoped.enable_paths = None; config.settings.unscoped.enable_paths = None;
config.set_config_file( config
.tree
.inject_config_file(
ConfigFile::new( ConfigFile::new(
&json!({ &json!({
"exclude": ["mod2.ts"], "exclude": ["mod2.ts"],
@ -1549,7 +1998,8 @@ mod tests {
root_uri.join("deno.json").unwrap(), root_uri.join("deno.json").unwrap(),
) )
.unwrap(), .unwrap(),
); )
.await;
assert!( assert!(
config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
); );
@ -1560,7 +2010,9 @@ mod tests {
!config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap()) !config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap())
); );
config.set_config_file( config
.tree
.inject_config_file(
ConfigFile::new( ConfigFile::new(
&json!({ &json!({
"test": { "test": {
@ -1571,15 +2023,12 @@ mod tests {
root_uri.join("deno.json").unwrap(), root_uri.join("deno.json").unwrap(),
) )
.unwrap(), .unwrap(),
); )
assert!( .await;
config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
);
assert!(
!config.specifier_enabled_for_test(&root_uri.join("mod2.ts").unwrap())
);
config.set_config_file( config
.tree
.inject_config_file(
ConfigFile::new( ConfigFile::new(
&json!({ &json!({
"test": { "test": {
@ -1591,7 +2040,8 @@ mod tests {
root_uri.join("deno.json").unwrap(), root_uri.join("deno.json").unwrap(),
) )
.unwrap(), .unwrap(),
); )
.await;
assert!( assert!(
!config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) !config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
); );
@ -1600,12 +2050,14 @@ mod tests {
); );
} }
#[test] #[tokio::test]
fn config_snapshot_specifier_enabled_for_test() { async fn config_snapshot_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_roots(vec![root_uri.clone()]); let mut config = Config::new_with_roots(vec![root_uri.clone()]);
config.settings.unscoped.enable = Some(true); config.settings.unscoped.enable = Some(true);
config.set_config_file( config
.tree
.inject_config_file(
ConfigFile::new( ConfigFile::new(
&json!({ &json!({
"exclude": ["mod2.ts"], "exclude": ["mod2.ts"],
@ -1617,7 +2069,8 @@ mod tests {
root_uri.join("deno.json").unwrap(), root_uri.join("deno.json").unwrap(),
) )
.unwrap(), .unwrap(),
); )
.await;
let config_snapshot = config.snapshot(); let config_snapshot = config.snapshot();
assert!(config_snapshot assert!(config_snapshot
.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())); .specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()));

View file

@ -21,7 +21,6 @@ use crate::graph_util::enhanced_resolution_error_message;
use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams; use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
use crate::resolver::SloppyImportsResolution; use crate::resolver::SloppyImportsResolution;
use crate::resolver::SloppyImportsResolver; use crate::resolver::SloppyImportsResolver;
use crate::tools::lint::get_configured_rules;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::anyhow::anyhow; 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::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use import_map::ImportMap;
use log::error; use log::error;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
@ -62,10 +62,10 @@ use tower_lsp::lsp_types as lsp;
pub struct DiagnosticServerUpdateMessage { pub struct DiagnosticServerUpdateMessage {
pub snapshot: Arc<StateSnapshot>, pub snapshot: Arc<StateSnapshot>,
pub config: Arc<ConfigSnapshot>, pub config: Arc<ConfigSnapshot>,
pub lint_options: LintOptions,
pub url_map: LspUrlMap, pub url_map: LspUrlMap,
} }
#[derive(Debug)]
struct DiagnosticRecord { struct DiagnosticRecord {
pub specifier: ModuleSpecifier, pub specifier: ModuleSpecifier,
pub versioned: VersionedDiagnostics, pub versioned: VersionedDiagnostics,
@ -461,7 +461,6 @@ impl DiagnosticsServer {
DiagnosticServerUpdateMessage { DiagnosticServerUpdateMessage {
snapshot, snapshot,
config, config,
lint_options,
url_map, url_map,
}, },
batch_index, batch_index,
@ -612,14 +611,7 @@ impl DiagnosticsServer {
let mark = performance.mark("lsp.update_diagnostics_lint"); let mark = performance.mark("lsp.update_diagnostics_lint");
let diagnostics = spawn_blocking({ let diagnostics = spawn_blocking({
let token = token.clone(); let token = token.clone();
move || { move || generate_lint_diagnostics(&snapshot, &config, token)
generate_lint_diagnostics(
&snapshot,
&config,
&lint_options,
token,
)
}
}) })
.await .await
.unwrap(); .unwrap();
@ -791,17 +783,12 @@ fn ts_json_to_diagnostics(
fn generate_lint_diagnostics( fn generate_lint_diagnostics(
snapshot: &language_server::StateSnapshot, snapshot: &language_server::StateSnapshot,
config: &ConfigSnapshot, config: &ConfigSnapshot,
lint_options: &LintOptions,
token: CancellationToken, token: CancellationToken,
) -> DiagnosticVec { ) -> DiagnosticVec {
let documents = snapshot let documents = snapshot
.documents .documents
.documents(DocumentsFilter::OpenDiagnosable); .documents(DocumentsFilter::OpenDiagnosable);
let lint_rules = get_configured_rules( let config_data_by_scope = config.tree.data_by_scope();
lint_options.rules.clone(),
config.config_file.as_ref(),
)
.rules;
let mut diagnostics_vec = Vec::new(); let mut diagnostics_vec = Vec::new();
for document in documents { for document in documents {
let settings = let settings =
@ -820,14 +807,20 @@ fn generate_lint_diagnostics(
} }
} }
let version = document.maybe_lsp_version(); 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 { diagnostics_vec.push(DiagnosticRecord {
specifier: document.specifier().clone(), specifier: document.specifier().clone(),
versioned: VersionedDiagnostics { versioned: VersionedDiagnostics {
version, version,
diagnostics: generate_document_lint_diagnostics( diagnostics: generate_document_lint_diagnostics(
config, config,
lint_options, &lint_options,
lint_rules.clone(), lint_rules.rules.clone(),
&document, &document,
), ),
}, },
@ -1304,6 +1297,7 @@ fn diagnose_resolution(
resolution: &Resolution, resolution: &Resolution,
is_dynamic: bool, is_dynamic: bool,
maybe_assert_type: Option<&str>, maybe_assert_type: Option<&str>,
import_map: Option<&ImportMap>,
) -> Vec<DenoDiagnostic> { ) -> Vec<DenoDiagnostic> {
fn check_redirect_diagnostic( fn check_redirect_diagnostic(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
@ -1392,7 +1386,7 @@ fn diagnose_resolution(
.push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone())); .push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone()));
} else if module_name == dependency_key { } else if module_name == dependency_key {
let mut is_mapped = false; 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 let Resolution::Ok(resolved) = &resolution {
if import_map.resolve(module_name, &resolved.specifier).is_ok() { if import_map.resolve(module_name, &resolved.specifier).is_ok() {
is_mapped = true; 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 Resolution::Ok(resolved) = &dependency.maybe_code {
if let Some(to) = import_map.lookup(&resolved.specifier, referrer) { if let Some(to) = import_map.lookup(&resolved.specifier, referrer) {
if dependency_key != to { if dependency_key != to {
@ -1504,6 +1499,7 @@ fn diagnose_dependency(
}, },
dependency.is_dynamic, dependency.is_dynamic,
dependency.maybe_attribute_type.as_deref(), dependency.maybe_attribute_type.as_deref(),
import_map.as_deref(),
) )
.iter() .iter()
.flat_map(|diag| { .flat_map(|diag| {
@ -1526,6 +1522,7 @@ fn diagnose_dependency(
&dependency.maybe_type, &dependency.maybe_type,
dependency.is_dynamic, dependency.is_dynamic,
dependency.maybe_attribute_type.as_deref(), dependency.maybe_attribute_type.as_deref(),
import_map.as_deref(),
) )
.iter() .iter()
.map(|diag| diag.to_lsp_diagnostic(&range)), .map(|diag| diag.to_lsp_diagnostic(&range)),
@ -1580,20 +1577,21 @@ mod tests {
use super::*; use super::*;
use crate::cache::GlobalHttpCache; use crate::cache::GlobalHttpCache;
use crate::cache::RealDenoCacheEnv; use crate::cache::RealDenoCacheEnv;
use crate::lsp::config::Config;
use crate::lsp::config::ConfigSnapshot; use crate::lsp::config::ConfigSnapshot;
use crate::lsp::config::Settings; use crate::lsp::config::Settings;
use crate::lsp::config::WorkspaceSettings; use crate::lsp::config::WorkspaceSettings;
use crate::lsp::documents::Documents; use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId; use crate::lsp::documents::LanguageId;
use crate::lsp::language_server::StateSnapshot; use crate::lsp::language_server::StateSnapshot;
use deno_config::glob::FilePatterns; use deno_config::ConfigFile;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use test_util::TempDir; use test_util::TempDir;
fn mock_state_snapshot( async fn mock_state_snapshot(
fixtures: &[(&str, &str, i32, LanguageId)], fixtures: &[(&str, &str, i32, LanguageId)],
location: &Path, location: &Path,
maybe_import_map: Option<(&str, &str)>, maybe_import_map: Option<(&str, &str)>,
@ -1613,22 +1611,19 @@ mod tests {
(*source).into(), (*source).into(),
); );
} }
let maybe_import_map = maybe_import_map.map(|(base, json_string)| { let config = Config::new_with_roots([resolve_url("file:///").unwrap()]);
let base_url = ModuleSpecifier::parse(base).unwrap(); if let Some((base_url, json_string)) = maybe_import_map {
let result = import_map::parse_from_json(&base_url, json_string).unwrap(); let base_url = resolve_url(base_url).unwrap();
if !result.diagnostics.is_empty() { let config_file = ConfigFile::new(json_string, base_url).unwrap();
panic!("unexpected import map diagnostics"); config.tree.inject_config_file(config_file).await;
} }
Arc::new(result.import_map)
});
StateSnapshot { StateSnapshot {
documents, documents,
maybe_import_map,
assets: Default::default(), assets: Default::default(),
cache_metadata: cache::CacheMetadata::new(Arc::new( cache_metadata: cache::CacheMetadata::new(Arc::new(
GlobalHttpCache::new(location.to_path_buf(), RealDenoCacheEnv), GlobalHttpCache::new(location.to_path_buf(), RealDenoCacheEnv),
)), )),
config: Default::default(), config: config.snapshot(),
npm: None, npm: None,
} }
} }
@ -1655,14 +1650,14 @@ mod tests {
} }
} }
fn setup( async fn setup(
temp_dir: &TempDir, temp_dir: &TempDir,
sources: &[(&str, &str, i32, LanguageId)], sources: &[(&str, &str, i32, LanguageId)],
maybe_import_map: Option<(&str, &str)>, maybe_import_map: Option<(&str, &str)>,
) -> (StateSnapshot, PathBuf) { ) -> (StateSnapshot, PathBuf) {
let location = temp_dir.path().join("deps").to_path_buf(); let location = temp_dir.path().join("deps").to_path_buf();
let state_snapshot = let state_snapshot =
mock_state_snapshot(sources, &location, maybe_import_map); mock_state_snapshot(sources, &location, maybe_import_map).await;
(state_snapshot, location) (state_snapshot, location)
} }
@ -1681,18 +1676,14 @@ let c: number = "a";
LanguageId::TypeScript, LanguageId::TypeScript,
)], )],
None, None,
); )
.await;
let snapshot = Arc::new(snapshot); let snapshot = Arc::new(snapshot);
let cache = let cache =
Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv)); Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv));
let ts_server = TsServer::new(Default::default(), cache); let ts_server =
TsServer::new(Default::default(), cache, Default::default());
ts_server.start(None); 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 // test enabled
{ {
@ -1700,7 +1691,6 @@ let c: number = "a";
let diagnostics = generate_lint_diagnostics( let diagnostics = generate_lint_diagnostics(
&snapshot, &snapshot,
&enabled_config, &enabled_config,
&lint_options,
Default::default(), Default::default(),
); );
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 6); assert_eq!(get_diagnostics_for_single(diagnostics).len(), 6);
@ -1712,7 +1702,7 @@ let c: number = "a";
) )
.await .await
.unwrap(); .unwrap();
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 5); assert_eq!(get_diagnostics_for_single(diagnostics).len(), 4);
let diagnostics = generate_deno_diagnostics( let diagnostics = generate_deno_diagnostics(
&snapshot, &snapshot,
&enabled_config, &enabled_config,
@ -1732,7 +1722,6 @@ let c: number = "a";
let diagnostics = generate_lint_diagnostics( let diagnostics = generate_lint_diagnostics(
&snapshot, &snapshot,
&disabled_config, &disabled_config,
&lint_options,
Default::default(), Default::default(),
); );
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 0); assert_eq!(get_diagnostics_for_single(diagnostics).len(), 0);
@ -1793,7 +1782,8 @@ let c: number = "a";
} }
}"#, }"#,
)), )),
); )
.await;
let config = mock_config(); let config = mock_config();
let token = CancellationToken::new(); let token = CancellationToken::new();
let actual = generate_deno_diagnostics(&snapshot, &config, token); let actual = generate_deno_diagnostics(&snapshot, &config, token);
@ -1919,7 +1909,8 @@ let c: number = "a";
LanguageId::TypeScript, LanguageId::TypeScript,
)], )],
None, None,
); )
.await;
let config = mock_config(); let config = mock_config();
let token = CancellationToken::new(); let token = CancellationToken::new();
let actual = generate_deno_diagnostics(&snapshot, &config, token); let actual = generate_deno_diagnostics(&snapshot, &config, token);

View file

@ -37,7 +37,6 @@ use deno_lockfile::Lockfile;
use deno_runtime::deno_node; use deno_runtime::deno_node;
use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolution;
use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::permissions::PermissionsContainer; use deno_runtime::permissions::PermissionsContainer;
use deno_semver::jsr::JsrPackageReqReference; use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference; 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. /// Specify the documents to include on a `documents.documents(...)` call.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum DocumentsFilter { pub enum DocumentsFilter {
@ -1309,26 +1299,34 @@ impl Documents {
Arc::new(JsrCacheResolver::new(self.cache.clone(), lockfile)); Arc::new(JsrCacheResolver::new(self.cache.clone(), lockfile));
} }
pub fn update_config(&mut self, options: UpdateDocumentConfigOptions) { pub fn update_config(
let maybe_config_file = options.config.maybe_config_file(); &mut self,
let maybe_package_json_deps = config: &Config,
options.maybe_package_json.map(|package_json| { node_resolver: Option<Arc<CliNodeResolver>>,
package_json::get_local_package_json_version_reqs(package_json) npm_resolver: Option<Arc<dyn CliNpmResolver>>,
}); workspace_files: &BTreeSet<ModuleSpecifier>,
let maybe_jsx_config = maybe_config_file ) {
.and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()); let config_data = config.tree.root_data();
let deps_provider = let config_file =
Arc::new(PackageJsonDepsProvider::new(maybe_package_json_deps)); config_data.as_ref().and_then(|d| d.config_file.as_deref());
self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions { self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
node_resolver: options.node_resolver, node_resolver,
npm_resolver: options.npm_resolver, npm_resolver,
package_json_deps_provider: deps_provider, package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new(
maybe_jsx_import_source_config: maybe_jsx_config, config_data
maybe_import_map: options.maybe_import_map, .as_ref()
maybe_vendor_dir: maybe_config_file .and_then(|d| d.package_json.as_ref())
.and_then(|c| c.vendor_dir_path()) .map(|package_json| {
.as_ref(), package_json::get_local_package_json_version_reqs(package_json)
bare_node_builtins_enabled: maybe_config_file }),
)),
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")) .map(|config| config.has_unstable("bare-node-builtins"))
.unwrap_or(false), .unwrap_or(false),
// Don't set this for the LSP because instead we'll use the OpenDocumentsLoader // 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.jsr_resolver = Arc::new(JsrCacheResolver::new(
self.cache.clone(), self.cache.clone(),
options.config.maybe_lockfile().cloned(), config.tree.root_lockfile(),
)); ));
self.redirect_resolver = self.redirect_resolver =
Arc::new(RedirectResolver::new(self.cache.clone())); Arc::new(RedirectResolver::new(self.cache.clone()));
let resolver = self.resolver.as_graph_resolver(); let resolver = self.resolver.as_graph_resolver();
let npm_resolver = self.resolver.as_graph_npm_resolver(); let npm_resolver = self.resolver.as_graph_npm_resolver();
self.imports = Arc::new( self.imports = Arc::new(
if let Some(Ok(imports)) = if let Some(Ok(imports)) = config_file.map(|cf| cf.to_maybe_imports()) {
maybe_config_file.map(|cf| cf.to_maybe_imports())
{
imports imports
.into_iter() .into_iter()
.map(|(referrer, imports)| { .map(|(referrer, imports)| {
@ -1364,7 +1360,7 @@ impl Documents {
IndexMap::new() IndexMap::new()
}, },
); );
self.unstable_sloppy_imports = maybe_config_file self.unstable_sloppy_imports = config_file
.map(|c| c.has_unstable("sloppy-imports")) .map(|c| c.has_unstable("sloppy-imports"))
.unwrap_or(false); .unwrap_or(false);
{ {
@ -1376,7 +1372,7 @@ impl Documents {
// anymore after updating resolvers. // anymore after updating resolvers.
return false; return false;
}; };
if !options.config.specifier_enabled(specifier) { if !config.specifier_enabled(specifier) {
return false; return false;
} }
path.is_file() path.is_file()
@ -1384,7 +1380,7 @@ impl Documents {
let mut open_docs = std::mem::take(&mut self.open_docs); let mut open_docs = std::mem::take(&mut self.open_docs);
for docs in [&mut open_docs, &mut fs_docs.docs] { for docs in [&mut open_docs, &mut fs_docs.docs] {
for doc in docs.values_mut() { for doc in docs.values_mut() {
if !options.config.specifier_enabled(doc.specifier()) { if !config.specifier_enabled(doc.specifier()) {
continue; continue;
} }
if let Some(new_doc) = if let Some(new_doc) =
@ -1395,8 +1391,8 @@ impl Documents {
} }
} }
self.open_docs = open_docs; self.open_docs = open_docs;
for specifier in options.workspace_files { for specifier in workspace_files {
if !options.config.specifier_enabled(specifier) { if !config.specifier_enabled(specifier) {
continue; continue;
} }
if !self.open_docs.contains_key(specifier) if !self.open_docs.contains_key(specifier)
@ -1738,8 +1734,9 @@ mod tests {
use crate::cache::RealDenoCacheEnv; use crate::cache::RealDenoCacheEnv;
use super::*; use super::*;
use deno_config::ConfigFile;
use deno_core::serde_json; use deno_core::serde_json;
use import_map::ImportMap; use deno_core::serde_json::json;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use test_util::PathRef; use test_util::PathRef;
use test_util::TempDir; use test_util::TempDir;
@ -1842,8 +1839,8 @@ console.log(b, "hello deno");
assert_eq!(documents.documents(DocumentsFilter::All).len(), 1); assert_eq!(documents.documents(DocumentsFilter::All).len(), 1);
} }
#[test] #[tokio::test]
fn test_documents_refresh_dependencies_config_change() { async fn test_documents_refresh_dependencies_config_change() {
// it should never happen that a user of this API causes this to happen, // it should never happen that a user of this API causes this to happen,
// but we'll guard against it anyway // but we'll guard against it anyway
let temp_dir = TempDir::new(); let temp_dir = TempDir::new();
@ -1878,23 +1875,23 @@ console.log(b, "hello deno");
// set the initial import map and point to file 2 // set the initial import map and point to file 2
{ {
let mut import_map = ImportMap::new( config
ModuleSpecifier::from_file_path(documents_path.join("import_map.json")) .tree
.inject_config_file(
ConfigFile::new(
&json!({
"imports": {
"test": "./file2.ts",
},
})
.to_string(),
config.root_uri().unwrap().join("deno.json").unwrap(),
)
.unwrap(), .unwrap(),
); )
import_map .await;
.imports_mut()
.append("test".to_string(), "./file2.ts".to_string())
.unwrap();
documents.update_config(UpdateDocumentConfigOptions { documents.update_config(&config, None, None, &workspace_files);
config: &config,
maybe_import_map: Some(Arc::new(import_map)),
maybe_package_json: None,
node_resolver: None,
npm_resolver: None,
workspace_files: &workspace_files,
});
// open the document // open the document
let document = documents.open( let document = documents.open(
@ -1918,23 +1915,23 @@ console.log(b, "hello deno");
// now point at file 3 // now point at file 3
{ {
let mut import_map = ImportMap::new( config
ModuleSpecifier::from_file_path(documents_path.join("import_map.json")) .tree
.inject_config_file(
ConfigFile::new(
&json!({
"imports": {
"test": "./file3.ts",
},
})
.to_string(),
config.root_uri().unwrap().join("deno.json").unwrap(),
)
.unwrap(), .unwrap(),
); )
import_map .await;
.imports_mut()
.append("test".to_string(), "./file3.ts".to_string())
.unwrap();
documents.update_config(UpdateDocumentConfigOptions { documents.update_config(&config, None, None, &workspace_files);
config: &config,
maybe_import_map: Some(Arc::new(import_map)),
maybe_package_json: None,
node_resolver: None,
npm_resolver: None,
workspace_files: &workspace_files,
});
// check the document's dependencies // check the document's dependencies
let document = documents.get(&file1_specifier).unwrap(); let document = documents.get(&file1_specifier).unwrap();

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@
use super::analysis::CodeActionData; use super::analysis::CodeActionData;
use super::code_lens; use super::code_lens;
use super::config; use super::config;
use super::config::ConfigTree;
use super::documents::AssetOrDocument; use super::documents::AssetOrDocument;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
use super::language_server; use super::language_server;
@ -22,7 +23,6 @@ use super::urls::INVALID_SPECIFIER;
use crate::args::jsr_url; use crate::args::jsr_url;
use crate::args::FmtOptionsConfig; use crate::args::FmtOptionsConfig;
use crate::args::TsConfig;
use crate::cache::HttpCache; use crate::cache::HttpCache;
use crate::lsp::cache::CacheMetadata; use crate::lsp::cache::CacheMetadata;
use crate::lsp::documents::Documents; use crate::lsp::documents::Documents;
@ -221,6 +221,7 @@ pub struct TsServer {
sender: mpsc::UnboundedSender<Request>, sender: mpsc::UnboundedSender<Request>,
receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>, receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
inspector_server: Mutex<Option<Arc<InspectorServer>>>, inspector_server: Mutex<Option<Arc<InspectorServer>>>,
} }
@ -238,7 +239,11 @@ impl std::fmt::Debug for TsServer {
} }
impl 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>(); let (tx, request_rx) = mpsc::unbounded_channel::<Request>();
Self { Self {
performance, performance,
@ -246,6 +251,7 @@ impl TsServer {
sender: tx, sender: tx,
receiver: Mutex::new(Some(request_rx)), receiver: Mutex::new(Some(request_rx)),
specifier_map: Arc::new(TscSpecifierMap::new()), specifier_map: Arc::new(TscSpecifierMap::new()),
config_tree,
inspector_server: Mutex::new(None), inspector_server: Mutex::new(None),
} }
} }
@ -268,6 +274,7 @@ impl TsServer {
let performance = self.performance.clone(); let performance = self.performance.clone();
let cache = self.cache.clone(); let cache = self.cache.clone();
let specifier_map = self.specifier_map.clone(); let specifier_map = self.specifier_map.clone();
let config_tree = self.config_tree.clone();
let _join_handle = thread::spawn(move || { let _join_handle = thread::spawn(move || {
run_tsc_thread( run_tsc_thread(
receiver, receiver,
@ -275,6 +282,7 @@ impl TsServer {
cache.clone(), cache.clone(),
specifier_map.clone(), specifier_map.clone(),
maybe_inspector_server, maybe_inspector_server,
config_tree,
) )
}); });
} }
@ -343,18 +351,6 @@ impl TsServer {
self.request(snapshot, req).await 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( pub async fn get_supported_code_fixes(
&self, &self,
snapshot: Arc<StateSnapshot>, snapshot: Arc<StateSnapshot>,
@ -3482,7 +3478,7 @@ impl CompletionEntry {
{ {
if let Ok(import_specifier) = resolve_url(&import_data.file_name) { if let Ok(import_specifier) = resolve_url(&import_data.file_name) {
if let Some(new_module_specifier) = language_server if let Some(new_module_specifier) = language_server
.get_ts_response_import_mapper() .get_ts_response_import_mapper(specifier)
.check_specifier(&import_specifier, specifier) .check_specifier(&import_specifier, specifier)
.or_else(|| relative_specifier(specifier, &import_specifier)) .or_else(|| relative_specifier(specifier, &import_specifier))
{ {
@ -3849,6 +3845,7 @@ struct State {
response: Option<Response>, response: Option<Response>,
state_snapshot: Arc<StateSnapshot>, state_snapshot: Arc<StateSnapshot>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
token: CancellationToken, token: CancellationToken,
} }
@ -3856,6 +3853,7 @@ impl State {
fn new( fn new(
state_snapshot: Arc<StateSnapshot>, state_snapshot: Arc<StateSnapshot>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
performance: Arc<Performance>, performance: Arc<Performance>,
) -> Self { ) -> Self {
Self { Self {
@ -3864,6 +3862,7 @@ impl State {
response: None, response: None,
state_snapshot, state_snapshot,
specifier_map, specifier_map,
config_tree,
token: Default::default(), token: Default::default(),
} }
} }
@ -4077,10 +4076,20 @@ fn op_script_version(
Ok(r) Ok(r)
} }
#[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] #[op2]
#[string] #[string]
fn op_project_version(state: &mut OpState) -> 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 mark = state.performance.mark("tsc.op.op_project_version");
let r = state.state_snapshot.documents.project_version(); let r = state.state_snapshot.documents.project_version();
state.performance.measure(mark); state.performance.measure(mark);
@ -4093,13 +4102,19 @@ fn run_tsc_thread(
cache: Arc<dyn HttpCache>, cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
maybe_inspector_server: Option<Arc<InspectorServer>>, maybe_inspector_server: Option<Arc<InspectorServer>>,
config_tree: Arc<ConfigTree>,
) { ) {
let has_inspector_server = maybe_inspector_server.is_some(); let has_inspector_server = maybe_inspector_server.is_some();
// Create and setup a JsRuntime based on a snapshot. It is expected that the // Create and setup a JsRuntime based on a snapshot. It is expected that the
// supplied snapshot is an isolate that contains the TypeScript language // supplied snapshot is an isolate that contains the TypeScript language
// server. // server.
let mut tsc_runtime = JsRuntime::new(RuntimeOptions { let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![deno_tsc::init_ops(performance, cache, specifier_map)], extensions: vec![deno_tsc::init_ops(
performance,
cache,
specifier_map,
config_tree,
)],
startup_snapshot: Some(tsc::compiler_snapshot()), startup_snapshot: Some(tsc::compiler_snapshot()),
inspector: maybe_inspector_server.is_some(), inspector: maybe_inspector_server.is_some(),
..Default::default() ..Default::default()
@ -4166,12 +4181,14 @@ deno_core::extension!(deno_tsc,
op_respond, op_respond,
op_script_names, op_script_names,
op_script_version, op_script_version,
op_ts_config,
op_project_version, op_project_version,
], ],
options = { options = {
performance: Arc<Performance>, performance: Arc<Performance>,
cache: Arc<dyn HttpCache>, cache: Arc<dyn HttpCache>,
specifier_map: Arc<TscSpecifierMap>, specifier_map: Arc<TscSpecifierMap>,
config_tree: Arc<ConfigTree>,
}, },
state = |state, options| { state = |state, options| {
state.put(State::new( state.put(State::new(
@ -4180,10 +4197,10 @@ deno_core::extension!(deno_tsc,
cache_metadata: CacheMetadata::new(options.cache.clone()), cache_metadata: CacheMetadata::new(options.cache.clone()),
config: Default::default(), config: Default::default(),
documents: Documents::new(options.cache.clone()), documents: Documents::new(options.cache.clone()),
maybe_import_map: None,
npm: None, npm: None,
}), }),
options.specifier_map, options.specifier_map,
options.config_tree,
options.performance, options.performance,
)); ));
}, },
@ -4344,9 +4361,10 @@ pub struct UserPreferences {
impl UserPreferences { impl UserPreferences {
pub fn from_config_for_specifier( pub fn from_config_for_specifier(
config: &config::Config, config: &config::Config,
fmt_config: &FmtOptionsConfig,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Self { ) -> Self {
let fmt_options = config.tree.fmt_options_for_specifier(specifier);
let fmt_config = &fmt_options.options;
let base_preferences = Self { let base_preferences = Self {
allow_incomplete_completions: Some(true), allow_incomplete_completions: Some(true),
allow_text_changes_in_new_files: Some(specifier.scheme() == "file"), allow_text_changes_in_new_files: Some(specifier.scheme() == "file"),
@ -4450,7 +4468,8 @@ impl UserPreferences {
language_settings.preferences.use_aliases_for_renames, language_settings.preferences.use_aliases_for_renames,
), ),
// Only use workspace settings for quote style if there's no `deno.json`. // Only use workspace settings for quote style if there's no `deno.json`.
quote_preference: if config.has_config_file() { quote_preference: if config.tree.has_config_file_for_specifier(specifier)
{
base_preferences.quote_preference base_preferences.quote_preference
} else { } else {
Some(language_settings.preferences.quote_style) Some(language_settings.preferences.quote_style)
@ -4630,7 +4649,6 @@ mod tests {
assets: Default::default(), assets: Default::default(),
cache_metadata: CacheMetadata::new(cache), cache_metadata: CacheMetadata::new(cache),
config: Default::default(), config: Default::default(),
maybe_import_map: None,
npm: None, npm: None,
} }
} }
@ -4645,13 +4663,21 @@ mod tests {
Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv)); Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv));
let snapshot = Arc::new(mock_state_snapshot(sources, &location)); let snapshot = Arc::new(mock_state_snapshot(sources, &location));
let performance = Arc::new(Performance::default()); 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); ts_server.start(None);
let ts_config = TsConfig::new(config);
assert!(ts_server
.configure(snapshot.clone(), ts_config,)
.await
.unwrap());
(ts_server, snapshot, cache) (ts_server, snapshot, cache)
} }
@ -4670,43 +4696,6 @@ mod tests {
assert_eq!(actual, r"test [`a link`](http://deno.land/x/mod.ts) test"); assert_eq!(actual, r"test [`a link`](http://deno.land/x/mod.ts) test");
} }
#[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] #[tokio::test]
async fn test_get_diagnostics() { async fn test_get_diagnostics() {
let temp_dir = TempDir::new(); let temp_dir = TempDir::new();
@ -4716,6 +4705,7 @@ mod tests {
"target": "esnext", "target": "esnext",
"module": "esnext", "module": "esnext",
"noEmit": true, "noEmit": true,
"lib": [],
}), }),
&[( &[(
"file:///a.ts", "file:///a.ts",
@ -5468,11 +5458,10 @@ mod tests {
.inlay_hints .inlay_hints
.variable_types .variable_types
.suppress_when_type_matches_name = true; .suppress_when_type_matches_name = true;
let mut config = config::Config::new(); let mut config = config::Config::default();
config.set_workspace_settings(settings, vec![]); config.set_workspace_settings(settings, vec![]);
let user_preferences = UserPreferences::from_config_for_specifier( let user_preferences = UserPreferences::from_config_for_specifier(
&config, &config,
&Default::default(),
&ModuleSpecifier::parse("file:///foo.ts").unwrap(), &ModuleSpecifier::parse("file:///foo.ts").unwrap(),
); );
assert_eq!( assert_eq!(

View file

@ -857,6 +857,12 @@ pub struct ConfiguredRules {
pub no_slow_types: bool, pub no_slow_types: bool,
} }
impl Default for ConfiguredRules {
fn default() -> Self {
get_configured_rules(Default::default(), None)
}
}
impl ConfiguredRules { impl ConfiguredRules {
fn incremental_cache_state(&self) -> Vec<&str> { fn incremental_cache_state(&self) -> Vec<&str> {
// use a hash of the rule names in order to bust the cache // use a hash of the rule names in order to bust the cache

View file

@ -503,9 +503,6 @@ delete Object.prototype.__proto__;
} }
} }
/** @type {ts.CompilerOptions} */
let compilationSettings = {};
/** @type {ts.LanguageService} */ /** @type {ts.LanguageService} */
let languageService; let languageService;
@ -720,7 +717,17 @@ delete Object.prototype.__proto__;
if (logDebug) { if (logDebug) {
debug("host.getCompilationSettings()"); 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() { getScriptFileNames() {
if (logDebug) { if (logDebug) {
@ -1010,21 +1017,6 @@ delete Object.prototype.__proto__;
serverRestart(); serverRestart();
return respond(id, true); 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": { case "$getSupportedCodeFixes": {
return respond( return respond(
id, id,

View file

@ -145,32 +145,6 @@ fn lsp_tsconfig_types_config_sub_dir() {
client.shutdown(); client.shutdown();
} }
#[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] #[test]
fn lsp_triple_slash_types() { fn lsp_triple_slash_types() {
let context = TestContextBuilder::new().use_temp_cwd().build(); let context = TestContextBuilder::new().use_temp_cwd().build();
@ -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( let res = client.write_request(
"textDocument/hover", "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( let res = client.write_request(
"textDocument/hover", "textDocument/hover",
@ -546,7 +520,7 @@ fn lsp_import_map_config_file_auto_discovered() {
}] }]
})); }));
client.wait_until_stderr_line(|line| { 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(); 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| { client.wait_until_stderr_line(|line| {
line.contains("Auto-resolved configuration file:") line.contains(" Resolved Deno configuration file:")
}); });
let res = client.write_request( let res = client.write_request(
"textDocument/hover", "textDocument/hover",
@ -675,7 +649,7 @@ fn lsp_import_map_config_file_auto_discovered_symlink() {
// this will discover the deno.json in the root // this will discover the deno.json in the root
let search_line = format!( let search_line = format!(
"Auto-resolved configuration file: \"{}\"", " Resolved Deno configuration file: \"{}\"",
temp_dir.uri().join("deno.json").unwrap().as_str() temp_dir.uri().join("deno.json").unwrap().as_str()
); );
client.wait_until_stderr_line(|line| line.contains(&search_line)); client.wait_until_stderr_line(|line| line.contains(&search_line));
@ -8946,10 +8920,7 @@ fn lsp_performance() {
"lsp.update_diagnostics_deps", "lsp.update_diagnostics_deps",
"lsp.update_diagnostics_lint", "lsp.update_diagnostics_lint",
"lsp.update_diagnostics_ts", "lsp.update_diagnostics_ts",
"lsp.update_import_map",
"lsp.update_registries", "lsp.update_registries",
"lsp.update_tsconfig",
"tsc.host.$configure",
"tsc.host.$getAssets", "tsc.host.$getAssets",
"tsc.host.$getDiagnostics", "tsc.host.$getDiagnostics",
"tsc.host.$getSupportedCodeFixes", "tsc.host.$getSupportedCodeFixes",
@ -8959,7 +8930,7 @@ fn lsp_performance() {
"tsc.op.op_project_version", "tsc.op.op_project_version",
"tsc.op.op_script_names", "tsc.op.op_script_names",
"tsc.op.op_script_version", "tsc.op.op_script_version",
"tsc.request.$configure", "tsc.op.op_ts_config",
"tsc.request.$getAssets", "tsc.request.$getAssets",
"tsc.request.$getSupportedCodeFixes", "tsc.request.$getSupportedCodeFixes",
"tsc.request.getQuickInfoAtPosition", "tsc.request.getQuickInfoAtPosition",