Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/paths/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/bin/main.rs2
-rw-r--r--crates/rust-analyzer/src/cli/scip.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs232
-rw-r--r--crates/rust-analyzer/src/global_state.rs114
-rw-r--r--crates/rust-analyzer/src/handlers/notification.rs3
-rw-r--r--crates/rust-analyzer/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/reload.rs3
-rw-r--r--crates/rust-analyzer/tests/slow-tests/main.rs1045
-rw-r--r--crates/rust-analyzer/tests/slow-tests/ratoml.rs1019
-rw-r--r--crates/rust-analyzer/tests/slow-tests/support.rs4
11 files changed, 1159 insertions, 1269 deletions
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs
index 7d7cf0220e..33c3f83db5 100644
--- a/crates/paths/src/lib.rs
+++ b/crates/paths/src/lib.rs
@@ -150,7 +150,7 @@ impl AbsPathBuf {
/// * if `self` has a verbatim prefix (e.g. `\\?\C:\windows`)
/// and `path` is not empty, the new path is normalized: all references
/// to `.` and `..` are removed.
- pub fn push(&mut self, suffix: &str) {
+ pub fn push<P: AsRef<Utf8Path>>(&mut self, suffix: P) {
self.0.push(suffix)
}
}
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 7e58cd70c4..316384f963 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -229,7 +229,7 @@ fn run_server() -> anyhow::Result<()> {
let mut change = ConfigChange::default();
change.change_client_config(json);
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
if !error_sink.is_empty() {
use lsp_types::{
notification::{Notification, ShowMessage},
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index b2d7056289..d73e4028e5 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -46,7 +46,7 @@ impl flags::Scip {
let mut change = ConfigChange::default();
change.change_client_config(json);
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
// FIXME @alibektas : What happens to errors without logging?
error!(?error_sink, "Config Error(s)");
}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 51664dd799..646bae4467 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -29,12 +29,11 @@ use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version;
use serde::{
de::{DeserializeOwned, Error},
- ser::SerializeStruct,
Deserialize, Serialize,
};
use stdx::format_to_acc;
use triomphe::Arc;
-use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
+use vfs::{AbsPath, AbsPathBuf, VfsPath};
use crate::{
caps::completion_item_edit_resolve,
@@ -667,7 +666,7 @@ pub struct Config {
default_config: DefaultConfigData,
/// Config node that obtains its initial value during the server initialization and
/// by receiving a `lsp_types::notification::DidChangeConfiguration`.
- client_config: ClientConfig,
+ client_config: FullConfigInput,
/// Path to the root configuration file. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer/rust-analyzer.toml` in Linux.
/// If not specified by init of a `Config` object this value defaults to :
@@ -681,139 +680,48 @@ pub struct Config {
/// FIXME @alibektas : Change this to sth better.
/// Config node whose values apply to **every** Rust project.
- user_config: Option<RatomlNode>,
+ user_config: Option<GlobalLocalConfigInput>,
/// A special file for this session whose path is set to `self.root_path.join("rust-analyzer.toml")`
root_ratoml_path: VfsPath,
/// This file can be used to make global changes while having only a workspace-wide scope.
- root_ratoml: Option<RatomlNode>,
+ root_ratoml: Option<GlobalLocalConfigInput>,
/// For every `SourceRoot` there can be at most one RATOML file.
- ratoml_files: FxHashMap<SourceRootId, RatomlNode>,
+ ratoml_files: FxHashMap<SourceRootId, GlobalLocalConfigInput>,
/// Clone of the value that is stored inside a `GlobalState`.
source_root_parent_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
- /// Changes made to client and global configurations will partially not be reflected even after `.apply_change()` was called.
- /// This field signals that the `GlobalState` should call its `update_configuration()` method.
- should_update: bool,
-}
-
-#[derive(Clone, Debug)]
-struct RatomlNode {
- node: GlobalLocalConfigInput,
- file_id: FileId,
-}
-
-#[derive(Debug, Clone, Default)]
-struct ClientConfig {
- node: FullConfigInput,
detached_files: Vec<AbsPathBuf>,
}
-impl Serialize for RatomlNode {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- let mut s = serializer.serialize_struct("RatomlNode", 2)?;
- s.serialize_field("file_id", &self.file_id.index())?;
- s.serialize_field("config", &self.node)?;
- s.end()
- }
-}
-
-#[derive(Debug, Hash, Eq, PartialEq)]
-pub(crate) enum ConfigNodeKey {
- Ratoml(SourceRootId),
- Client,
- User,
-}
-
-impl Serialize for ConfigNodeKey {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- match self {
- ConfigNodeKey::Ratoml(source_root_id) => serializer.serialize_u32(source_root_id.0),
- ConfigNodeKey::Client => serializer.serialize_str("client"),
- ConfigNodeKey::User => serializer.serialize_str("user"),
- }
- }
-}
-
-#[derive(Debug, Serialize)]
-enum ConfigNodeValue<'a> {
- /// `rust-analyzer::config` module works by setting
- /// a mapping between `SourceRootId` and `ConfigInput`.
- /// Storing a `FileId` is mostly for debugging purposes.
- Ratoml(&'a RatomlNode),
- Client(&'a FullConfigInput),
-}
-
impl Config {
- /// FIXME @alibektas : Before integration tests, I thought I would
- /// get the debug output of the config tree and do assertions based on it.
- /// The reason why I didn't delete this is that we may want to have a lsp_ext
- /// like "DebugConfigTree" so that it is easier for users to get a snapshot of
- /// the config state for us to debug.
- #[allow(dead_code)]
- /// Walk towards the root starting from a specified `ConfigNode`
- fn traverse(
- &self,
- start: ConfigNodeKey,
- ) -> impl Iterator<Item = (ConfigNodeKey, ConfigNodeValue<'_>)> {
- let mut v = vec![];
-
- if let ConfigNodeKey::Ratoml(start) = start {
- let mut par: Option<SourceRootId> = Some(start);
- while let Some(source_root_id) = par {
- par = self.source_root_parent_map.get(&start).copied();
- if let Some(config) = self.ratoml_files.get(&source_root_id) {
- v.push((
- ConfigNodeKey::Ratoml(source_root_id),
- ConfigNodeValue::Ratoml(config),
- ));
- }
- }
- }
-
- v.push((ConfigNodeKey::Client, ConfigNodeValue::Client(&self.client_config.node)));
-
- if let Some(user_config) = self.user_config.as_ref() {
- v.push((ConfigNodeKey::User, ConfigNodeValue::Ratoml(user_config)));
- }
-
- v.into_iter()
- }
-
pub fn user_config_path(&self) -> &VfsPath {
&self.user_config_path
}
- pub fn should_update(&self) -> bool {
- self.should_update
- }
-
// FIXME @alibektas : Server's health uses error sink but in other places it is not used atm.
- pub fn apply_change(&self, change: ConfigChange, error_sink: &mut ConfigError) -> Config {
+ /// Changes made to client and global configurations will partially not be reflected even after `.apply_change()` was called.
+ /// The return tuple's bool component signals whether the `GlobalState` should call its `update_configuration()` method.
+ pub fn apply_change(
+ &self,
+ change: ConfigChange,
+ error_sink: &mut ConfigError,
+ ) -> (Config, bool) {
let mut config = self.clone();
let mut toml_errors = vec![];
let mut json_errors = vec![];
- config.should_update = false;
-
- if let Some((file_id, change)) = change.user_config_change {
- config.user_config = Some(RatomlNode {
- file_id,
- node: GlobalLocalConfigInput::from_toml(
- toml::from_str(change.to_string().as_str()).unwrap(),
- &mut toml_errors,
- ),
- });
- config.should_update = true;
+ let mut should_update = false;
+
+ if let Some(change) = change.user_config_change {
+ if let Ok(change) = toml::from_str(&change) {
+ config.user_config =
+ Some(GlobalLocalConfigInput::from_toml(change, &mut toml_errors));
+ should_update = true;
+ }
}
if let Some(mut json) = change.client_config_change {
@@ -832,38 +740,29 @@ impl Config {
patch_old_style::patch_json_for_outdated_configs(&mut json);
- config.client_config = ClientConfig {
- node: FullConfigInput::from_json(json, &mut json_errors),
- detached_files,
- }
+ config.client_config = FullConfigInput::from_json(json, &mut json_errors);
+ config.detached_files = detached_files;
}
- config.should_update = true;
+ should_update = true;
}
- if let Some((file_id, change)) = change.root_ratoml_change {
- config.root_ratoml = Some(RatomlNode {
- file_id,
- node: GlobalLocalConfigInput::from_toml(
- toml::from_str(change.to_string().as_str()).unwrap(),
- &mut toml_errors,
- ),
- });
- config.should_update = true;
+ if let Some(change) = change.root_ratoml_change {
+ if let Ok(change) = toml::from_str(&change) {
+ config.root_ratoml =
+ Some(GlobalLocalConfigInput::from_toml(change, &mut toml_errors));
+ should_update = true;
+ }
}
if let Some(change) = change.ratoml_file_change {
- for (source_root_id, (file_id, _, text)) in change {
+ for (source_root_id, (_, text)) in change {
if let Some(text) = text {
- config.ratoml_files.insert(
- source_root_id,
- RatomlNode {
- file_id,
- node: GlobalLocalConfigInput::from_toml(
- toml::from_str(&text).unwrap(),
- &mut toml_errors,
- ),
- },
- );
+ if let Ok(change) = toml::from_str(&text) {
+ config.ratoml_files.insert(
+ source_root_id,
+ GlobalLocalConfigInput::from_toml(change, &mut toml_errors),
+ );
+ }
}
}
}
@@ -907,16 +806,22 @@ impl Config {
serde_json::Error::custom("expected a non-empty string"),
));
}
- config
+ (config, should_update)
+ }
+
+ pub fn apply_change_whatever(&self, change: ConfigChange) -> (Config, ConfigError) {
+ let mut e = ConfigError(vec![]);
+ let (config, _) = self.apply_change(change, &mut e);
+ (config, e)
}
}
#[derive(Default, Debug)]
pub struct ConfigChange {
- user_config_change: Option<(FileId, String)>,
- root_ratoml_change: Option<(FileId, String)>,
+ user_config_change: Option<String>,
+ root_ratoml_change: Option<String>,
client_config_change: Option<serde_json::Value>,
- ratoml_file_change: Option<FxHashMap<SourceRootId, (FileId, VfsPath, Option<String>)>>,
+ ratoml_file_change: Option<FxHashMap<SourceRootId, (VfsPath, Option<String>)>>,
source_map_change: Option<Arc<FxHashMap<SourceRootId, SourceRootId>>>,
}
@@ -924,26 +829,25 @@ impl ConfigChange {
pub fn change_ratoml(
&mut self,
source_root: SourceRootId,
- file_id: FileId,
vfs_path: VfsPath,
content: Option<String>,
- ) -> Option<(FileId, VfsPath, Option<String>)> {
+ ) -> Option<(VfsPath, Option<String>)> {
if let Some(changes) = self.ratoml_file_change.as_mut() {
- changes.insert(source_root, (file_id, vfs_path, content))
+ changes.insert(source_root, (vfs_path, content))
} else {
let mut map = FxHashMap::default();
- map.insert(source_root, (file_id, vfs_path, content));
+ map.insert(source_root, (vfs_path, content));
self.ratoml_file_change = Some(map);
None
}
}
- pub fn change_user_config(&mut self, content: Option<(FileId, String)>) {
+ pub fn change_user_config(&mut self, content: Option<String>) {
assert!(self.user_config_change.is_none()); // Otherwise it is a double write.
self.user_config_change = content;
}
- pub fn change_root_ratoml(&mut self, content: Option<(FileId, String)>) {
+ pub fn change_root_ratoml(&mut self, content: Option<String>) {
assert!(self.user_config_change.is_none()); // Otherwise it is a double write.
self.root_ratoml_change = content;
}
@@ -1163,8 +1067,6 @@ impl ConfigError {
}
}
-impl ConfigError {}
-
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let errors = self.0.iter().format_with("\n", |inner, f| match inner {
@@ -1221,7 +1123,7 @@ impl Config {
snippets: Default::default(),
workspace_roots,
visual_studio_code_version,
- client_config: ClientConfig::default(),
+ client_config: FullConfigInput::default(),
user_config: None,
ratoml_files: FxHashMap::default(),
default_config: DefaultConfigData::default(),
@@ -1229,7 +1131,7 @@ impl Config {
user_config_path,
root_ratoml: None,
root_ratoml_path,
- should_update: false,
+ detached_files: Default::default(),
}
}
@@ -1318,7 +1220,7 @@ impl Config {
pub fn detached_files(&self) -> &Vec<AbsPathBuf> {
// FIXME @alibektas : This is the only config that is confusing. If it's a proper configuration
// why is it not among the others? If it's client only which I doubt it is current state should be alright
- &self.client_config.detached_files
+ &self.detached_files
}
pub fn diagnostics(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
@@ -2587,19 +2489,19 @@ macro_rules! _impl_for_config_data {
while let Some(source_root_id) = par {
par = self.source_root_parent_map.get(&source_root_id).copied();
if let Some(config) = self.ratoml_files.get(&source_root_id) {
- if let Some(value) = config.node.local.$field.as_ref() {
+ if let Some(value) = config.local.$field.as_ref() {
return value;
}
}
}
}
- if let Some(v) = self.client_config.node.local.$field.as_ref() {
+ if let Some(v) = self.client_config.local.$field.as_ref() {
return &v;
}
if let Some(user_config) = self.user_config.as_ref() {
- if let Some(v) = user_config.node.local.$field.as_ref() {
+ if let Some(v) = user_config.local.$field.as_ref() {
return &v;
}
}
@@ -2621,17 +2523,17 @@ macro_rules! _impl_for_config_data {
$vis fn $field(&self) -> &$ty {
if let Some(root_path_ratoml) = self.root_ratoml.as_ref() {
- if let Some(v) = root_path_ratoml.node.global.$field.as_ref() {
+ if let Some(v) = root_path_ratoml.global.$field.as_ref() {
return &v;
}
}
- if let Some(v) = self.client_config.node.global.$field.as_ref() {
+ if let Some(v) = self.client_config.global.$field.as_ref() {
return &v;
}
if let Some(user_config) = self.user_config.as_ref() {
- if let Some(v) = user_config.node.global.$field.as_ref() {
+ if let Some(v) = user_config.global.$field.as_ref() {
return &v;
}
}
@@ -2673,7 +2575,7 @@ macro_rules! _config_data {
}) => {
/// Default config values for this grouping.
#[allow(non_snake_case)]
- #[derive(Debug, Clone, Serialize)]
+ #[derive(Debug, Clone )]
struct $name { $($field: $ty,)* }
impl_for_config_data!{
@@ -3399,7 +3301,7 @@ mod tests {
}}));
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
assert_eq!(config.proc_macro_srv(), None);
}
@@ -3419,7 +3321,7 @@ mod tests {
}}));
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
assert_eq!(config.proc_macro_srv(), Some(AbsPathBuf::try_from(project_root()).unwrap()));
}
@@ -3441,7 +3343,7 @@ mod tests {
}}));
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
assert_eq!(
config.proc_macro_srv(),
@@ -3466,7 +3368,7 @@ mod tests {
}));
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
assert_eq!(config.cargo_targetDir(), &None);
assert!(
matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none())
@@ -3489,7 +3391,7 @@ mod tests {
}));
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
assert_eq!(config.cargo_targetDir(), &Some(TargetDirectory::UseSubdirectory(true)));
assert!(
@@ -3513,7 +3415,7 @@ mod tests {
}));
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
assert_eq!(
config.cargo_targetDir(),
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 2210dab0f5..c289a07978 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -9,7 +9,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::FlycheckHandle;
use hir::ChangeWithProcMacros;
use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
-use ide_db::base_db::{CrateId, ProcMacroPaths};
+use ide_db::base_db::{CrateId, ProcMacroPaths, SourceDatabaseExt};
use load_cargo::SourceRootConfig;
use lsp_types::{SemanticTokens, Url};
use nohash_hasher::IntMap;
@@ -266,8 +266,8 @@ impl GlobalState {
let mut ratoml_text_map: FxHashMap<FileId, (vfs::VfsPath, Option<String>)> =
FxHashMap::default();
- let mut user_config_file: Option<(FileId, Option<String>)> = None;
- let mut root_path_ratoml: Option<(FileId, Option<String>)> = None;
+ let mut user_config_file: Option<Option<String>> = None;
+ let mut root_path_ratoml: Option<Option<String>> = None;
let root_vfs_path = {
let mut root_vfs_path = self.config.root_path().to_path_buf();
@@ -336,30 +336,23 @@ impl GlobalState {
bytes.push((file.file_id, text));
}
let (vfs, line_endings_map) = &mut *RwLockUpgradableReadGuard::upgrade(guard);
- bytes.into_iter().for_each(|(file_id, text)| match text {
- None => {
- change.change_file(file_id, None);
- if let Some(vfs_path) = modified_ratoml_files.get(&file_id) {
- if vfs_path == self.config.user_config_path() {
- user_config_file = Some((file_id, None));
- } else if vfs_path == &root_vfs_path {
- root_path_ratoml = Some((file_id, None));
- } else {
- ratoml_text_map.insert(file_id, (vfs_path.clone(), None));
- }
+ bytes.into_iter().for_each(|(file_id, text)| {
+ let text = match text {
+ None => None,
+ Some((text, line_endings)) => {
+ line_endings_map.insert(file_id, line_endings);
+ Some(text)
}
- }
- Some((text, line_endings)) => {
- line_endings_map.insert(file_id, line_endings);
- change.change_file(file_id, Some(text.clone()));
- if let Some(vfs_path) = modified_ratoml_files.get(&file_id) {
- if vfs_path == self.config.user_config_path() {
- user_config_file = Some((file_id, Some(text.clone())));
- } else if vfs_path == &root_vfs_path {
- root_path_ratoml = Some((file_id, Some(text.clone())));
- } else {
- ratoml_text_map.insert(file_id, (vfs_path.clone(), Some(text.clone())));
- }
+ };
+
+ change.change_file(file_id, text.clone());
+ if let Some(vfs_path) = modified_ratoml_files.get(&file_id) {
+ if vfs_path == self.config.user_config_path() {
+ user_config_file = Some(text);
+ } else if vfs_path == &root_vfs_path {
+ root_path_ratoml = Some(text);
+ } else {
+ ratoml_text_map.insert(file_id, (vfs_path.clone(), text));
}
}
});
@@ -372,53 +365,54 @@ impl GlobalState {
let _p = span!(Level::INFO, "GlobalState::process_changes/apply_change").entered();
self.analysis_host.apply_change(change);
-
- let config_change = {
- let mut change = ConfigChange::default();
- let snap = self.analysis_host.analysis();
-
- for (file_id, (vfs_path, text)) in ratoml_text_map {
- // If change has been made to a ratoml file that
- // belongs to a non-local source root, we will ignore it.
- // As it doesn't make sense a users to use external config files.
- if let Ok(source_root) = snap.source_root_id(file_id) {
- if let Ok(true) = snap.is_local_source_root(source_root) {
- if let Some((old_file, old_path, old_text)) =
- change.change_ratoml(source_root, file_id, vfs_path.clone(), text)
+ if !(ratoml_text_map.is_empty() && user_config_file.is_none() && root_path_ratoml.is_none())
+ {
+ let config_change = {
+ let mut change = ConfigChange::default();
+ let db = self.analysis_host.raw_database();
+
+ for (file_id, (vfs_path, text)) in ratoml_text_map {
+ // If change has been made to a ratoml file that
+ // belongs to a non-local source root, we will ignore it.
+ // As it doesn't make sense a users to use external config files.
+ let sr_id = db.file_source_root(file_id);
+ let sr = db.source_root(sr_id);
+ if !sr.is_library {
+ if let Some((old_path, old_text)) =
+ change.change_ratoml(sr_id, vfs_path.clone(), text)
{
// SourceRoot has more than 1 RATOML files. In this case lexicographically smaller wins.
if old_path < vfs_path {
span!(Level::ERROR, "Two `rust-analyzer.toml` files were found inside the same crate. {vfs_path} has no effect.");
// Put the old one back in.
- change.change_ratoml(source_root, old_file, old_path, old_text);
+ change.change_ratoml(sr_id, old_path, old_text);
}
}
+ } else {
+ // Mapping to a SourceRoot should always end up in `Ok`
+ span!(Level::ERROR, "Mapping to SourceRootId failed.");
}
- } else {
- // Mapping to a SourceRoot should always end up in `Ok`
- span!(Level::ERROR, "Mapping to SourceRootId failed.");
}
- }
- if let Some((file_id, Some(txt))) = user_config_file {
- change.change_user_config(Some((file_id, txt)));
- }
-
- if let Some((file_id, Some(txt))) = root_path_ratoml {
- change.change_root_ratoml(Some((file_id, txt)));
- }
+ if let Some(Some(txt)) = user_config_file {
+ change.change_user_config(Some(txt));
+ }
- change
- };
+ if let Some(Some(txt)) = root_path_ratoml {
+ change.change_root_ratoml(Some(txt));
+ }
- let mut error_sink = ConfigError::default();
- let config = self.config.apply_change(config_change, &mut error_sink);
+ change
+ };
+ let mut error_sink = ConfigError::default();
+ let (config, should_update) = self.config.apply_change(config_change, &mut error_sink);
- if config.should_update() {
- self.update_configuration(config);
- } else {
- // No global or client level config was changed. So we can just naively replace config.
- self.config = Arc::new(config);
+ if should_update {
+ self.update_configuration(config);
+ } else {
+ // No global or client level config was changed. So we can just naively replace config.
+ self.config = Arc::new(config);
+ }
}
{
diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index 123a9a06a3..bdb27043eb 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/crates/rust-analyzer/src/handlers/notification.rs
@@ -201,7 +201,8 @@ pub(crate) fn handle_did_change_configuration(
let mut change = ConfigChange::default();
change.change_client_config(json.take());
let mut error_sink = ConfigError::default();
- config = config.apply_change(change, &mut error_sink);
+ (config, _) = config.apply_change(change, &mut error_sink);
+ // Client config changes neccesitates .update_config method to be called.
this.update_configuration(config);
}
}
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index d9b31550c5..b3c11d0156 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -39,7 +39,7 @@ pub mod tracing {
}
pub mod config;
-pub mod global_state;
+mod global_state;
pub mod lsp;
use self::lsp::ext as lsp_ext;
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index e804ba3db9..364a0083e5 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -605,7 +605,8 @@ impl GlobalState {
let mut config_change = ConfigChange::default();
config_change.change_source_root_parent_map(self.local_roots_parent_map.clone());
let mut error_sink = ConfigError::default();
- self.config = Arc::new(self.config.apply_change(config_change, &mut error_sink));
+ let (config, _) = self.config.apply_change(config_change, &mut error_sink);
+ self.config = Arc::new(config);
self.recreate_crate_graph(cause);
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index fd6f79abc1..f886df60e6 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -11,36 +11,33 @@
#![warn(rust_2018_idioms, unused_lifetimes)]
#![allow(clippy::disallowed_types)]
+mod ratoml;
#[cfg(not(feature = "in-rust-tree"))]
mod sourcegen;
mod support;
mod testdir;
mod tidy;
-use std::{collections::HashMap, path::PathBuf, sync::Once, time::Instant};
+use std::{collections::HashMap, path::PathBuf, time::Instant};
use lsp_types::{
- notification::{DidChangeTextDocument, DidOpenTextDocument, DidSaveTextDocument},
+ notification::DidOpenTextDocument,
request::{
CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
InlayHintRequest, InlayHintResolveRequest, WillRenameFiles, WorkspaceSymbolRequest,
},
- CodeAction, CodeActionContext, CodeActionOrCommand, CodeActionParams, CodeActionResponse,
- CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
- DidSaveTextDocumentParams, DocumentFormattingParams, FileRename, FormattingOptions,
- GotoDefinitionParams, Hover, HoverParams, InlayHint, InlayHintLabel, InlayHintParams,
- PartialResultParams, Position, Range, RenameFilesParams, TextDocumentContentChangeEvent,
- TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url,
- VersionedTextDocumentIdentifier, WorkDoneProgressParams,
+ CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
+ DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
+ InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
+ RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
};
-use paths::Utf8PathBuf;
+
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
use serde_json::json;
use stdx::format_to_acc;
-use support::Server;
+
use test_utils::skip_slow_tests;
use testdir::TestDir;
-use tracing_subscriber::fmt::TestWriter;
use crate::support::{project, Project};
@@ -1471,1027 +1468,3 @@ version = "0.0.0"
server.request::<WorkspaceSymbolRequest>(Default::default(), json!([]));
}
-
-enum QueryType {
- AssistEmitMustUse,
- /// A query whose config key is a part of the global configs, so that
- /// testing for changes to this config means testing if global changes
- /// take affect.
- GlobalHover,
-}
-
-struct RatomlTest {
- urls: Vec<Url>,
- server: Server,
- tmp_path: Utf8PathBuf,
- user_config_dir: Utf8PathBuf,
-}
-
-impl RatomlTest {
- const EMIT_MUST_USE: &'static str = r#"assist.emitMustUse = true"#;
- const EMIT_MUST_NOT_USE: &'static str = r#"assist.emitMustUse = false"#;
- const EMIT_MUST_USE_SNIPPET: &'static str = r#"
-
-impl Value {
- #[must_use]
- fn as_text(&self) -> Option<&String> {
- if let Self::Text(v) = self {
- Some(v)
- } else {
- None
- }
- }
-}"#;
-
- const GLOBAL_TRAIT_ASSOC_ITEMS_ZERO: &'static str = r#"hover.show.traitAssocItems = 0"#;
- const GLOBAL_TRAIT_ASSOC_ITEMS_SNIPPET: &'static str = r#"
-```rust
-p1
-```
-
-```rust
-trait RandomTrait {
- type B;
- fn abc() -> i32;
- fn def() -> i64;
-}
-```"#;
-
- fn new(
- fixtures: Vec<&str>,
- roots: Vec<&str>,
- client_config: Option<serde_json::Value>,
- ) -> Self {
- // setup();
- let tmp_dir = TestDir::new();
- let tmp_path = tmp_dir.path().to_owned();
-
- let full_fixture = fixtures.join("\n");
-
- let user_cnf_dir = TestDir::new();
- let user_config_dir = user_cnf_dir.path().to_owned();
-
- let mut project =
- Project::with_fixture(&full_fixture).tmp_dir(tmp_dir).user_config_dir(user_cnf_dir);
-
- for root in roots {
- project = project.root(root);
- }
-
- if let Some(client_config) = client_config {
- project = project.with_config(client_config);
- }
-
- let server = project.server().wait_until_workspace_is_loaded();
-
- let mut case = Self { urls: vec![], server, tmp_path, user_config_dir };
- let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::<Vec<_>>();
- case.urls = urls;
- case
- }
-
- fn fixture_path(&self, fixture: &str) -> Url {
- let mut lines = fixture.trim().split('\n');
-
- let mut path =
- lines.next().expect("All files in a fixture are expected to have at least one line.");
-
- if path.starts_with("//- minicore") {
- path = lines.next().expect("A minicore line must be followed by a path.")
- }
-
- path = path.strip_prefix("//- ").expect("Path must be preceded by a //- prefix ");
-
- let spl = path[1..].split('/');
- let mut path = self.tmp_path.clone();
-
- let mut spl = spl.into_iter();
- if let Some(first) = spl.next() {
- if first == "$$CONFIG_DIR$$" {
- path = self.user_config_dir.clone();
- } else {
- path = path.join(first);
- }
- }
- for piece in spl {
- path = path.join(piece);
- }
-
- Url::parse(
- format!(
- "file://{}",
- path.into_string().to_owned().replace("C:\\", "/c:/").replace('\\', "/")
- )
- .as_str(),
- )
- .unwrap()
- }
-
- fn create(&mut self, fixture_path: &str, text: String) {
- let url = self.fixture_path(fixture_path);
-
- self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
- text_document: TextDocumentItem {
- uri: url.clone(),
- language_id: "rust".to_owned(),
- version: 0,
- text: String::new(),
- },
- });
-
- self.server.notification::<DidChangeTextDocument>(DidChangeTextDocumentParams {
- text_document: VersionedTextDocumentIdentifier { uri: url, version: 0 },
- content_changes: vec![TextDocumentContentChangeEvent {
- range: None,
- range_length: None,
- text,
- }],
- });
- }
-
- fn delete(&mut self, file_idx: usize) {
- self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
- text_document: TextDocumentItem {
- uri: self.urls[file_idx].clone(),
- language_id: "rust".to_owned(),
- version: 0,
- text: "".to_owned(),
- },
- });
-
- // See if deleting ratoml file will make the config of interest to return to its default value.
- self.server.notification::<DidSaveTextDocument>(DidSaveTextDocumentParams {
- text_document: TextDocumentIdentifier { uri: self.urls[file_idx].clone() },
- text: Some("".to_owned()),
- });
- }
-
- fn edit(&mut self, file_idx: usize, text: String) {
- self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
- text_document: TextDocumentItem {
- uri: self.urls[file_idx].clone(),
- language_id: "rust".to_owned(),
- version: 0,
- text: String::new(),
- },
- });
-
- self.server.notification::<DidChangeTextDocument>(DidChangeTextDocumentParams {
- text_document: VersionedTextDocumentIdentifier {
- uri: self.urls[file_idx].clone(),
- version: 0,
- },
- content_changes: vec![TextDocumentContentChangeEvent {
- range: None,
- range_length: None,
- text,
- }],
- });
- }
-
- fn query(&self, query: QueryType, source_file_idx: usize) -> bool {
- match query {
- QueryType::AssistEmitMustUse => {
- let res = self.server.send_request::<CodeActionRequest>(CodeActionParams {
- text_document: TextDocumentIdentifier {
- uri: self.urls[source_file_idx].clone(),
- },
- range: lsp_types::Range {
- start: Position::new(2, 13),
- end: Position::new(2, 15),
- },
- context: CodeActionContext {
- diagnostics: vec![],
- only: None,
- trigger_kind: None,
- },
- work_done_progress_params: WorkDoneProgressParams { work_done_token: None },
- partial_result_params: lsp_types::PartialResultParams {
- partial_result_token: None,
- },
- });
-
- let res = serde_json::de::from_str::<CodeActionResponse>(res.to_string().as_str())
- .unwrap();
-
- // The difference setting the new config key will cause can be seen in the lower layers of this nested response
- // so here are some ugly unwraps and other obscure stuff.
- let ca: CodeAction = res
- .into_iter()
- .find_map(|it| {
- if let CodeActionOrCommand::CodeAction(ca) = it {
- if ca.title.as_str() == "Generate an `as_` method for this enum variant"
- {
- return Some(ca);
- }
- }
-
- None
- })
- .unwrap();
-
- if let lsp_types::DocumentChanges::Edits(edits) =
- ca.edit.unwrap().document_changes.unwrap()
- {
- if let lsp_types::OneOf::Left(l) = &edits[0].edits[0] {
- return l.new_text.as_str() == RatomlTest::EMIT_MUST_USE_SNIPPET;
- }
- }
- }
- QueryType::GlobalHover => {
- let res = self.server.send_request::<HoverRequest>(HoverParams {
- work_done_progress_params: WorkDoneProgressParams { work_done_token: None },
- text_document_position_params: TextDocumentPositionParams {
- text_document: TextDocumentIdentifier {
- uri: self.urls[source_file_idx].clone(),
- },
- position: Position::new(7, 18),
- },
- });
- let res = serde_json::de::from_str::<Hover>(res.to_string().as_str()).unwrap();
- assert!(matches!(res.contents, lsp_types::HoverContents::Markup(_)));
- if let lsp_types::HoverContents::Markup(m) = res.contents {
- return m.value == RatomlTest::GLOBAL_TRAIT_ASSOC_ITEMS_SNIPPET;
- }
- }
- }
-
- panic!()
- }
-}
-
-static INIT: Once = Once::new();
-
-fn setup() {
- INIT.call_once(|| {
- let trc = rust_analyzer::tracing::Config {
- writer: TestWriter::default(),
- // Deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually
- // useful information in there for debugging.
- filter: std::env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_owned()),
- chalk_filter: std::env::var("CHALK_DEBUG").ok(),
- profile_filter: std::env::var("RA_PROFILE").ok(),
- };
-
- trc.init().unwrap();
- });
-}
-
-// /// Check if we are listening for changes in user's config file ( e.g on Linux `~/.config/rust-analyzer/.rust-analyzer.toml`)
-// #[test]
-// #[cfg(target_os = "windows")]
-// fn listen_to_user_config_scenario_windows() {
-// todo!()
-// }
-
-// #[test]
-// #[cfg(target_os = "linux")]
-// fn listen_to_user_config_scenario_linux() {
-// todo!()
-// }
-
-// #[test]
-// #[cfg(target_os = "macos")]
-// fn listen_to_user_config_scenario_macos() {
-// todo!()
-// }
-
-/// Check if made changes have had any effect on
-/// the client config.
-#[test]
-fn ratoml_client_config_basic() {
- let server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"//- /p1/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- ],
- vec!["p1"],
- Some(json!({
- "assist" : {
- "emitMustUse" : true
- }
- })),
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 1));
-}
-
-/// Checks if client config can be modified.
-/// FIXME @alibektas : This test is atm not valid.
-/// Asking for client config from the client is a 2 way communication
-/// which we cannot imitate with the current slow-tests infrastructure.
-/// See rust-analyzer::handlers::notifications#197
-// #[test]
-// fn client_config_update() {
-// setup();
-
-// let server = RatomlTest::new(
-// vec![
-// r#"
-// //- /p1/Cargo.toml
-// [package]
-// name = "p1"
-// version = "0.1.0"
-// edition = "2021"
-// "#,
-// r#"
-// //- /p1/src/lib.rs
-// enum Value {
-// Number(i32),
-// Text(String),
-// }"#,
-// ],
-// vec!["p1"],
-// None,
-// );
-
-// assert!(!server.query(QueryType::AssistEmitMustUse, 1));
-
-// // a.notification::<DidChangeConfiguration>(DidChangeConfigurationParams {
-// // settings: json!({
-// // "assists" : {
-// // "emitMustUse" : true
-// // }
-// // }),
-// // });
-
-// assert!(server.query(QueryType::AssistEmitMustUse, 1));
-// }
-
-// #[test]
-// fn ratoml_create_ratoml_basic() {
-// let server = RatomlTest::new(
-// vec![
-// r#"
-// //- /p1/Cargo.toml
-// [package]
-// name = "p1"
-// version = "0.1.0"
-// edition = "2021"
-// "#,
-// r#"
-// //- /p1/rust-analyzer.toml
-// assist.emitMustUse = true
-// "#,
-// r#"
-// //- /p1/src/lib.rs
-// enum Value {
-// Number(i32),
-// Text(String),
-// }
-// "#,
-// ],
-// vec!["p1"],
-// None,
-// );
-
-// assert!(server.query(QueryType::AssistEmitMustUse, 2));
-// }
-
-#[test]
-fn ratoml_user_config_detected() {
- let server = RatomlTest::new(
- vec![
- r#"
-//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
-assist.emitMustUse = true
-"#,
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"//- /p1/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 2));
-}
-
-#[test]
-fn ratoml_create_user_config() {
- setup();
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(!server.query(QueryType::AssistEmitMustUse, 1));
- server.create(
- "//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml",
- RatomlTest::EMIT_MUST_USE.to_owned(),
- );
- assert!(server.query(QueryType::AssistEmitMustUse, 1));
-}
-
-#[test]
-fn ratoml_modify_user_config() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021""#,
- r#"
-//- /p1/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
-assist.emitMustUse = true"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 1));
- server.edit(2, String::new());
- assert!(!server.query(QueryType::AssistEmitMustUse, 1));
-}
-
-#[test]
-fn ratoml_delete_user_config() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021""#,
- r#"
-//- /p1/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
-assist.emitMustUse = true"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 1));
- server.delete(2);
- assert!(!server.query(QueryType::AssistEmitMustUse, 1));
-}
-// #[test]
-// fn delete_user_config() {
-// todo!()
-// }
-
-// #[test]
-// fn modify_client_config() {
-// todo!()
-// }
-
-#[test]
-fn ratoml_inherit_config_from_ws_root() {
- let server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-workspace = { members = ["p2"] }
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/rust-analyzer.toml
-assist.emitMustUse = true
-"#,
- r#"
-//- /p1/p2/Cargo.toml
-[package]
-name = "p2"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/p2/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /p1/src/lib.rs
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
-"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
-}
-
-#[test]
-fn ratoml_modify_ratoml_at_ws_root() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-workspace = { members = ["p2"] }
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/rust-analyzer.toml
-assist.emitMustUse = false
-"#,
- r#"
-//- /p1/p2/Cargo.toml
-[package]
-name = "p2"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/p2/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /p1/src/lib.rs
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
-"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(!server.query(QueryType::AssistEmitMustUse, 3));
- server.edit(1, "assist.emitMustUse = true".to_owned());
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
-}
-
-#[test]
-fn ratoml_delete_ratoml_at_ws_root() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-workspace = { members = ["p2"] }
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/rust-analyzer.toml
-assist.emitMustUse = true
-"#,
- r#"
-//- /p1/p2/Cargo.toml
-[package]
-name = "p2"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/p2/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /p1/src/lib.rs
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
-"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
- server.delete(1);
- assert!(!server.query(QueryType::AssistEmitMustUse, 3));
-}
-
-#[test]
-fn ratoml_add_immediate_child_to_ws_root() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-workspace = { members = ["p2"] }
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/rust-analyzer.toml
-assist.emitMustUse = true
-"#,
- r#"
-//- /p1/p2/Cargo.toml
-[package]
-name = "p2"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/p2/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /p1/src/lib.rs
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
-"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
- server.create("//- /p1/p2/rust-analyzer.toml", RatomlTest::EMIT_MUST_NOT_USE.to_owned());
- assert!(!server.query(QueryType::AssistEmitMustUse, 3));
-}
-
-#[test]
-fn ratoml_rm_ws_root_ratoml_child_has_client_as_parent_now() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-workspace = { members = ["p2"] }
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/rust-analyzer.toml
-assist.emitMustUse = true
-"#,
- r#"
-//- /p1/p2/Cargo.toml
-[package]
-name = "p2"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/p2/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /p1/src/lib.rs
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
-"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
- server.delete(1);
- assert!(!server.query(QueryType::AssistEmitMustUse, 3));
-}
-
-#[test]
-fn ratoml_crates_both_roots() {
- let server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-workspace = { members = ["p2"] }
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/rust-analyzer.toml
-assist.emitMustUse = true
-"#,
- r#"
-//- /p1/p2/Cargo.toml
-[package]
-name = "p2"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/p2/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- r#"
-//- /p1/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}"#,
- ],
- vec!["p1", "p2"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
- assert!(server.query(QueryType::AssistEmitMustUse, 4));
-}
-
-#[test]
-fn ratoml_multiple_ratoml_in_single_source_root() {
- let server = RatomlTest::new(
- vec![
- r#"
- //- /p1/Cargo.toml
- [package]
- name = "p1"
- version = "0.1.0"
- edition = "2021"
- "#,
- r#"
- //- /p1/rust-analyzer.toml
- assist.emitMustUse = true
- "#,
- r#"
- //- /p1/src/rust-analyzer.toml
- assist.emitMustUse = false
- "#,
- r#"
- //- /p1/src/lib.rs
- enum Value {
- Number(i32),
- Text(String),
- }
- "#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
-
- let server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
-"#,
- r#"
-//- /p1/src/rust-analyzer.toml
-assist.emitMustUse = false
-"#,
- r#"
-//- /p1/rust-analyzer.toml
-assist.emitMustUse = true
-"#,
- r#"
-//- /p1/src/lib.rs
-enum Value {
- Number(i32),
- Text(String),
-}
-"#,
- ],
- vec!["p1"],
- None,
- );
-
- assert!(server.query(QueryType::AssistEmitMustUse, 3));
-}
-
-/// If a root is non-local, so we cannot find what its parent is
-/// in our `config.local_root_parent_map`. So if any config should
-/// apply, it must be looked for starting from the client level.
-/// FIXME @alibektas : "locality" is according to ra that, which is simply in the file system.
-/// This doesn't really help us with what we want to achieve here.
-// #[test]
-// fn ratoml_non_local_crates_start_inheriting_from_client() {
-// let server = RatomlTest::new(
-// vec![
-// r#"
-// //- /p1/Cargo.toml
-// [package]
-// name = "p1"
-// version = "0.1.0"
-// edition = "2021"
-
-// [dependencies]
-// p2 = { path = "../p2" }
-// #,
-// r#"
-// //- /p1/src/lib.rs
-// enum Value {
-// Number(i32),
-// Text(String),
-// }
-
-// use p2;
-
-// pub fn add(left: usize, right: usize) -> usize {
-// p2::add(left, right)
-// }
-
-// #[cfg(test)]
-// mod tests {
-// use super::*;
-
-// #[test]
-// fn it_works() {
-// let result = add(2, 2);
-// assert_eq!(result, 4);
-// }
-// }"#,
-// r#"
-// //- /p2/Cargo.toml
-// [package]
-// name = "p2"
-// version = "0.1.0"
-// edition = "2021"
-
-// # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-// [dependencies]
-// "#,
-// r#"
-// //- /p2/rust-analyzer.toml
-// # DEF
-// assist.emitMustUse = true
-// "#,
-// r#"
-// //- /p2/src/lib.rs
-// enum Value {
-// Number(i32),
-// Text(String),
-// }"#,
-// ],
-// vec!["p1", "p2"],
-// None,
-// );
-
-// assert!(!server.query(QueryType::AssistEmitMustUse, 5));
-// }
-
-/// Having a ratoml file at the root of a project enables
-/// configuring global level configurations as well.
-#[test]
-fn ratoml_in_root_is_global() {
- let server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
- "#,
- r#"
-//- /rust-analyzer.toml
-hover.show.traitAssocItems = 4
- "#,
- r#"
-//- /p1/src/lib.rs
-trait RandomTrait {
- type B;
- fn abc() -> i32;
- fn def() -> i64;
-}
-
-fn main() {
- let a = RandomTrait;
-}"#,
- ],
- vec![],
- None,
- );
-
- server.query(QueryType::GlobalHover, 2);
-}
-
-#[test]
-fn ratoml_root_is_updateable() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
- "#,
- r#"
-//- /rust-analyzer.toml
-hover.show.traitAssocItems = 4
- "#,
- r#"
-//- /p1/src/lib.rs
-trait RandomTrait {
- type B;
- fn abc() -> i32;
- fn def() -> i64;
-}
-
-fn main() {
- let a = RandomTrait;
-}"#,
- ],
- vec![],
- None,
- );
-
- assert!(server.query(QueryType::GlobalHover, 2));
- server.edit(1, RatomlTest::GLOBAL_TRAIT_ASSOC_ITEMS_ZERO.to_owned());
- assert!(!server.query(QueryType::GlobalHover, 2));
-}
-
-#[test]
-fn ratoml_root_is_deletable() {
- let mut server = RatomlTest::new(
- vec![
- r#"
-//- /p1/Cargo.toml
-[package]
-name = "p1"
-version = "0.1.0"
-edition = "2021"
- "#,
- r#"
-//- /rust-analyzer.toml
-hover.show.traitAssocItems = 4
- "#,
- r#"
-//- /p1/src/lib.rs
-trait RandomTrait {
- type B;
- fn abc() -> i32;
- fn def() -> i64;
-}
-
-fn main() {
- let a = RandomTrait;
-}"#,
- ],
- vec![],
- None,
- );
-
- assert!(server.query(QueryType::GlobalHover, 2));
- server.delete(1);
- assert!(!server.query(QueryType::GlobalHover, 2));
-}
diff --git a/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
new file mode 100644
index 0000000000..739da9c998
--- /dev/null
+++ b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
@@ -0,0 +1,1019 @@
+use crate::support::{Project, Server};
+use crate::testdir::TestDir;
+use lsp_types::{
+ notification::{DidChangeTextDocument, DidOpenTextDocument, DidSaveTextDocument},
+ request::{CodeActionRequest, HoverRequest},
+ CodeAction, CodeActionContext, CodeActionOrCommand, CodeActionParams, CodeActionResponse,
+ DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, Hover,
+ HoverParams, Position, TextDocumentContentChangeEvent, TextDocumentIdentifier,
+ TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier,
+ WorkDoneProgressParams,
+};
+use paths::Utf8PathBuf;
+
+use serde_json::json;
+
+enum QueryType {
+ AssistEmitMustUse,
+ /// A query whose config key is a part of the global configs, so that
+ /// testing for changes to this config means testing if global changes
+ /// take affect.
+ GlobalHover,
+}
+
+struct RatomlTest {
+ urls: Vec<Url>,
+ server: Server,
+ tmp_path: Utf8PathBuf,
+ user_config_dir: Utf8PathBuf,
+}
+
+impl RatomlTest {
+ const EMIT_MUST_USE: &'static str = r#"assist.emitMustUse = true"#;
+ const EMIT_MUST_NOT_USE: &'static str = r#"assist.emitMustUse = false"#;
+ const EMIT_MUST_USE_SNIPPET: &'static str = r#"
+
+impl Value {
+ #[must_use]
+ fn as_text(&self) -> Option<&String> {
+ if let Self::Text(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+}"#;
+
+ const GLOBAL_TRAIT_ASSOC_ITEMS_ZERO: &'static str = r#"hover.show.traitAssocItems = 0"#;
+ const GLOBAL_TRAIT_ASSOC_ITEMS_SNIPPET: &'static str = r#"
+```rust
+p1
+```
+
+```rust
+trait RandomTrait {
+ type B;
+ fn abc() -> i32;
+ fn def() -> i64;
+}
+```"#;
+
+ fn new(
+ fixtures: Vec<&str>,
+ roots: Vec<&str>,
+ client_config: Option<serde_json::Value>,
+ ) -> Self {
+ let tmp_dir = TestDir::new();
+ let tmp_path = tmp_dir.path().to_owned();
+
+ let full_fixture = fixtures.join("\n");
+
+ let user_cnf_dir = TestDir::new();
+ let user_config_dir = user_cnf_dir.path().to_owned();
+
+ let mut project =
+ Project::with_fixture(&full_fixture).tmp_dir(tmp_dir).user_config_dir(user_cnf_dir);
+
+ for root in roots {
+ project = project.root(root);
+ }
+
+ if let Some(client_config) = client_config {
+ project = project.with_config(client_config);
+ }
+
+ let server = project.server().wait_until_workspace_is_loaded();
+
+ let mut case = Self { urls: vec![], server, tmp_path, user_config_dir };
+ let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::<Vec<_>>();
+ case.urls = urls;
+ case
+ }
+
+ fn fixture_path(&self, fixture: &str) -> Url {
+ let mut lines = fixture.trim().split('\n');
+
+ let mut path =
+ lines.next().expect("All files in a fixture are expected to have at least one line.");
+
+ if path.starts_with("//- minicore") {
+ path = lines.next().expect("A minicore line must be followed by a path.")
+ }
+
+ path = path.strip_prefix("//- ").expect("Path must be preceded by a //- prefix ");
+
+ let spl = path[1..].split('/');
+ let mut path = self.tmp_path.clone();
+
+ let mut spl = spl.into_iter();
+ if let Some(first) = spl.next() {
+ if first == "$$CONFIG_DIR$$" {
+ path = self.user_config_dir.clone();
+ } else {
+ path = path.join(first);
+ }
+ }
+ for piece in spl {
+ path = path.join(piece);
+ }
+
+ Url::parse(
+ format!(
+ "file://{}",
+ path.into_string().to_owned().replace("C:\\", "/c:/").replace('\\', "/")
+ )
+ .as_str(),
+ )
+ .unwrap()
+ }
+
+ fn create(&mut self, fixture_path: &str, text: String) {
+ let url = self.fixture_path(fixture_path);
+
+ self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
+ text_document: TextDocumentItem {
+ uri: url.clone(),
+ language_id: "rust".to_owned(),
+ version: 0,
+ text: String::new(),
+ },
+ });
+
+ self.server.notification::<DidChangeTextDocument>(DidChangeTextDocumentParams {
+ text_document: VersionedTextDocumentIdentifier { uri: url, version: 0 },
+ content_changes: vec![TextDocumentContentChangeEvent {
+ range: None,
+ range_length: None,
+ text,
+ }],
+ });
+ }
+
+ fn delete(&mut self, file_idx: usize) {
+ self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
+ text_document: TextDocumentItem {
+ uri: self.urls[file_idx].clone(),
+ language_id: "rust".to_owned(),
+ version: 0,
+ text: "".to_owned(),
+ },
+ });
+
+ // See if deleting ratoml file will make the config of interest to return to its default value.
+ self.server.notification::<DidSaveTextDocument>(DidSaveTextDocumentParams {
+ text_document: TextDocumentIdentifier { uri: self.urls[file_idx].clone() },
+ text: Some("".to_owned()),
+ });
+ }
+
+ fn edit(&mut self, file_idx: usize, text: String) {
+ self.server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
+ text_document: TextDocumentItem {
+ uri: self.urls[file_idx].clone(),
+ language_id: "rust".to_owned(),
+ version: 0,
+ text: String::new(),
+ },
+ });
+
+ self.server.notification::<DidChangeTextDocument>(DidChangeTextDocumentParams {
+ text_document: VersionedTextDocumentIdentifier {
+ uri: self.urls[file_idx].clone(),
+ version: 0,
+ },
+ content_changes: vec![TextDocumentContentChangeEvent {
+ range: None,
+ range_length: None,
+ text,
+ }],
+ });
+ }
+
+ fn query(&self, query: QueryType, source_file_idx: usize) -> bool {
+ match query {
+ QueryType::AssistEmitMustUse => {
+ let res = self.server.send_request::<CodeActionRequest>(CodeActionParams {
+ text_document: TextDocumentIdentifier {
+ uri: self.urls[source_file_idx].clone(),
+ },
+ range: lsp_types::Range {
+ start: Position::new(2, 13),
+ end: Position::new(2, 15),
+ },
+ context: CodeActionContext {
+ diagnostics: vec![],
+ only: None,
+ trigger_kind: None,
+ },
+ work_done_progress_params: WorkDoneProgressParams { work_done_token: None },
+ partial_result_params: lsp_types::PartialResultParams {
+ partial_result_token: None,
+ },
+ });
+
+ let res = serde_json::de::from_str::<CodeActionResponse>(res.to_string().as_str())
+ .unwrap();
+
+ // The difference setting the new config key will cause can be seen in the lower layers of this nested response
+ // so here are some ugly unwraps and other obscure stuff.
+ let ca: CodeAction = res
+ .into_iter()
+ .find_map(|it| {
+ if let CodeActionOrCommand::CodeAction(ca) = it {
+ if ca.title.as_str() == "Generate an `as_` method for this enum variant"
+ {
+ return Some(ca);
+ }
+ }
+
+ None
+ })
+ .unwrap();
+
+ if let lsp_types::DocumentChanges::Edits(edits) =
+ ca.edit.unwrap().document_changes.unwrap()
+ {
+ if let lsp_types::OneOf::Left(l) = &edits[0].edits[0] {
+ return l.new_text.as_str() == RatomlTest::EMIT_MUST_USE_SNIPPET;
+ }
+ }
+ }
+ QueryType::GlobalHover => {
+ let res = self.server.send_request::<HoverRequest>(HoverParams {
+ work_done_progress_params: WorkDoneProgressParams { work_done_token: None },
+ text_document_position_params: TextDocumentPositionParams {
+ text_document: TextDocumentIdentifier {
+ uri: self.urls[source_file_idx].clone(),
+ },
+ position: Position::new(7, 18),
+ },
+ });
+ let res = serde_json::de::from_str::<Hover>(res.to_string().as_str()).unwrap();
+ assert!(matches!(res.contents, lsp_types::HoverContents::Markup(_)));
+ if let lsp_types::HoverContents::Markup(m) = res.contents {
+ return m.value == RatomlTest::GLOBAL_TRAIT_ASSOC_ITEMS_SNIPPET;
+ }
+ }
+ }
+
+ panic!()
+ }
+}
+
+// /// Check if we are listening for changes in user's config file ( e.g on Linux `~/.config/rust-analyzer/.rust-analyzer.toml`)
+// #[test]
+// #[cfg(target_os = "windows")]
+// fn listen_to_user_config_scenario_windows() {
+// todo!()
+// }
+
+// #[test]
+// #[cfg(target_os = "linux")]
+// fn listen_to_user_config_scenario_linux() {
+// todo!()
+// }
+
+// #[test]
+// #[cfg(target_os = "macos")]
+// fn listen_to_user_config_scenario_macos() {
+// todo!()
+// }
+
+/// Check if made changes have had any effect on
+/// the client config.
+#[test]
+fn ratoml_client_config_basic() {
+ let server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"//- /p1/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ ],
+ vec!["p1"],
+ Some(json!({
+ "assist" : {
+ "emitMustUse" : true
+ }
+ })),
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 1));
+}
+
+/// Checks if client config can be modified.
+/// FIXME @alibektas : This test is atm not valid.
+/// Asking for client config from the client is a 2 way communication
+/// which we cannot imitate with the current slow-tests infrastructure.
+/// See rust-analyzer::handlers::notifications#197
+// #[test]
+// fn client_config_update() {
+// setup();
+
+// let server = RatomlTest::new(
+// vec![
+// r#"
+// //- /p1/Cargo.toml
+// [package]
+// name = "p1"
+// version = "0.1.0"
+// edition = "2021"
+// "#,
+// r#"
+// //- /p1/src/lib.rs
+// enum Value {
+// Number(i32),
+// Text(String),
+// }"#,
+// ],
+// vec!["p1"],
+// None,
+// );
+
+// assert!(!server.query(QueryType::AssistEmitMustUse, 1));
+
+// // a.notification::<DidChangeConfiguration>(DidChangeConfigurationParams {
+// // settings: json!({
+// // "assists" : {
+// // "emitMustUse" : true
+// // }
+// // }),
+// // });
+
+// assert!(server.query(QueryType::AssistEmitMustUse, 1));
+// }
+
+// #[test]
+// fn ratoml_create_ratoml_basic() {
+// let server = RatomlTest::new(
+// vec![
+// r#"
+// //- /p1/Cargo.toml
+// [package]
+// name = "p1"
+// version = "0.1.0"
+// edition = "2021"
+// "#,
+// r#"
+// //- /p1/rust-analyzer.toml
+// assist.emitMustUse = true
+// "#,
+// r#"
+// //- /p1/src/lib.rs
+// enum Value {
+// Number(i32),
+// Text(String),
+// }
+// "#,
+// ],
+// vec!["p1"],
+// None,
+// );
+
+// assert!(server.query(QueryType::AssistEmitMustUse, 2));
+// }
+
+#[test]
+fn ratoml_user_config_detected() {
+ let server = RatomlTest::new(
+ vec![
+ r#"
+//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
+assist.emitMustUse = true
+"#,
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"//- /p1/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 2));
+}
+
+#[test]
+fn ratoml_create_user_config() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(!server.query(QueryType::AssistEmitMustUse, 1));
+ server.create(
+ "//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml",
+ RatomlTest::EMIT_MUST_USE.to_owned(),
+ );
+ assert!(server.query(QueryType::AssistEmitMustUse, 1));
+}
+
+#[test]
+fn ratoml_modify_user_config() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021""#,
+ r#"
+//- /p1/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
+assist.emitMustUse = true"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 1));
+ server.edit(2, String::new());
+ assert!(!server.query(QueryType::AssistEmitMustUse, 1));
+}
+
+#[test]
+fn ratoml_delete_user_config() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021""#,
+ r#"
+//- /p1/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
+assist.emitMustUse = true"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 1));
+ server.delete(2);
+ assert!(!server.query(QueryType::AssistEmitMustUse, 1));
+}
+// #[test]
+// fn delete_user_config() {
+// todo!()
+// }
+
+// #[test]
+// fn modify_client_config() {
+// todo!()
+// }
+
+#[test]
+fn ratoml_inherit_config_from_ws_root() {
+ let server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+workspace = { members = ["p2"] }
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/rust-analyzer.toml
+assist.emitMustUse = true
+"#,
+ r#"
+//- /p1/p2/Cargo.toml
+[package]
+name = "p2"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/p2/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /p1/src/lib.rs
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+}
+
+#[test]
+fn ratoml_modify_ratoml_at_ws_root() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+workspace = { members = ["p2"] }
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/rust-analyzer.toml
+assist.emitMustUse = false
+"#,
+ r#"
+//- /p1/p2/Cargo.toml
+[package]
+name = "p2"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/p2/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /p1/src/lib.rs
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(!server.query(QueryType::AssistEmitMustUse, 3));
+ server.edit(1, "assist.emitMustUse = true".to_owned());
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+}
+
+#[test]
+fn ratoml_delete_ratoml_at_ws_root() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+workspace = { members = ["p2"] }
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/rust-analyzer.toml
+assist.emitMustUse = true
+"#,
+ r#"
+//- /p1/p2/Cargo.toml
+[package]
+name = "p2"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/p2/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /p1/src/lib.rs
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+ server.delete(1);
+ assert!(!server.query(QueryType::AssistEmitMustUse, 3));
+}
+
+#[test]
+fn ratoml_add_immediate_child_to_ws_root() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+workspace = { members = ["p2"] }
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/rust-analyzer.toml
+assist.emitMustUse = true
+"#,
+ r#"
+//- /p1/p2/Cargo.toml
+[package]
+name = "p2"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/p2/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /p1/src/lib.rs
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+ server.create("//- /p1/p2/rust-analyzer.toml", RatomlTest::EMIT_MUST_NOT_USE.to_owned());
+ assert!(!server.query(QueryType::AssistEmitMustUse, 3));
+}
+
+#[test]
+fn ratoml_rm_ws_root_ratoml_child_has_client_as_parent_now() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+workspace = { members = ["p2"] }
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/rust-analyzer.toml
+assist.emitMustUse = true
+"#,
+ r#"
+//- /p1/p2/Cargo.toml
+[package]
+name = "p2"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/p2/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /p1/src/lib.rs
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+ server.delete(1);
+ assert!(!server.query(QueryType::AssistEmitMustUse, 3));
+}
+
+#[test]
+fn ratoml_crates_both_roots() {
+ let server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+workspace = { members = ["p2"] }
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/rust-analyzer.toml
+assist.emitMustUse = true
+"#,
+ r#"
+//- /p1/p2/Cargo.toml
+[package]
+name = "p2"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/p2/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ r#"
+//- /p1/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}"#,
+ ],
+ vec!["p1", "p2"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+ assert!(server.query(QueryType::AssistEmitMustUse, 4));
+}
+
+#[test]
+fn ratoml_multiple_ratoml_in_single_source_root() {
+ let server = RatomlTest::new(
+ vec![
+ r#"
+ //- /p1/Cargo.toml
+ [package]
+ name = "p1"
+ version = "0.1.0"
+ edition = "2021"
+ "#,
+ r#"
+ //- /p1/rust-analyzer.toml
+ assist.emitMustUse = true
+ "#,
+ r#"
+ //- /p1/src/rust-analyzer.toml
+ assist.emitMustUse = false
+ "#,
+ r#"
+ //- /p1/src/lib.rs
+ enum Value {
+ Number(i32),
+ Text(String),
+ }
+ "#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+
+ let server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+"#,
+ r#"
+//- /p1/src/rust-analyzer.toml
+assist.emitMustUse = false
+"#,
+ r#"
+//- /p1/rust-analyzer.toml
+assist.emitMustUse = true
+"#,
+ r#"
+//- /p1/src/lib.rs
+enum Value {
+ Number(i32),
+ Text(String),
+}
+"#,
+ ],
+ vec!["p1"],
+ None,
+ );
+
+ assert!(server.query(QueryType::AssistEmitMustUse, 3));
+}
+
+/// If a root is non-local, so we cannot find what its parent is
+/// in our `config.local_root_parent_map`. So if any config should
+/// apply, it must be looked for starting from the client level.
+/// FIXME @alibektas : "locality" is according to ra that, which is simply in the file system.
+/// This doesn't really help us with what we want to achieve here.
+// #[test]
+// fn ratoml_non_local_crates_start_inheriting_from_client() {
+// let server = RatomlTest::new(
+// vec![
+// r#"
+// //- /p1/Cargo.toml
+// [package]
+// name = "p1"
+// version = "0.1.0"
+// edition = "2021"
+
+// [dependencies]
+// p2 = { path = "../p2" }
+// #,
+// r#"
+// //- /p1/src/lib.rs
+// enum Value {
+// Number(i32),
+// Text(String),
+// }
+
+// use p2;
+
+// pub fn add(left: usize, right: usize) -> usize {
+// p2::add(left, right)
+// }
+
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+
+// #[test]
+// fn it_works() {
+// let result = add(2, 2);
+// assert_eq!(result, 4);
+// }
+// }"#,
+// r#"
+// //- /p2/Cargo.toml
+// [package]
+// name = "p2"
+// version = "0.1.0"
+// edition = "2021"
+
+// # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+// [dependencies]
+// "#,
+// r#"
+// //- /p2/rust-analyzer.toml
+// # DEF
+// assist.emitMustUse = true
+// "#,
+// r#"
+// //- /p2/src/lib.rs
+// enum Value {
+// Number(i32),
+// Text(String),
+// }"#,
+// ],
+// vec!["p1", "p2"],
+// None,
+// );
+
+// assert!(!server.query(QueryType::AssistEmitMustUse, 5));
+// }
+
+/// Having a ratoml file at the root of a project enables
+/// configuring global level configurations as well.
+#[test]
+fn ratoml_in_root_is_global() {
+ let server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+ "#,
+ r#"
+//- /rust-analyzer.toml
+hover.show.traitAssocItems = 4
+ "#,
+ r#"
+//- /p1/src/lib.rs
+trait RandomTrait {
+ type B;
+ fn abc() -> i32;
+ fn def() -> i64;
+}
+
+fn main() {
+ let a = RandomTrait;
+}"#,
+ ],
+ vec![],
+ None,
+ );
+
+ server.query(QueryType::GlobalHover, 2);
+}
+
+#[test]
+fn ratoml_root_is_updateable() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+ "#,
+ r#"
+//- /rust-analyzer.toml
+hover.show.traitAssocItems = 4
+ "#,
+ r#"
+//- /p1/src/lib.rs
+trait RandomTrait {
+ type B;
+ fn abc() -> i32;
+ fn def() -> i64;
+}
+
+fn main() {
+ let a = RandomTrait;
+}"#,
+ ],
+ vec![],
+ None,
+ );
+
+ assert!(server.query(QueryType::GlobalHover, 2));
+ server.edit(1, RatomlTest::GLOBAL_TRAIT_ASSOC_ITEMS_ZERO.to_owned());
+ assert!(!server.query(QueryType::GlobalHover, 2));
+}
+
+#[test]
+fn ratoml_root_is_deletable() {
+ let mut server = RatomlTest::new(
+ vec![
+ r#"
+//- /p1/Cargo.toml
+[package]
+name = "p1"
+version = "0.1.0"
+edition = "2021"
+ "#,
+ r#"
+//- /rust-analyzer.toml
+hover.show.traitAssocItems = 4
+ "#,
+ r#"
+//- /p1/src/lib.rs
+trait RandomTrait {
+ type B;
+ fn abc() -> i32;
+ fn def() -> i64;
+}
+
+fn main() {
+ let a = RandomTrait;
+}"#,
+ ],
+ vec![],
+ None,
+ );
+
+ assert!(server.query(QueryType::GlobalHover, 2));
+ server.delete(1);
+ assert!(!server.query(QueryType::GlobalHover, 2));
+}
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 17485ee3ae..d12d0be539 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -207,8 +207,8 @@ impl Project<'_> {
change.change_client_config(self.config);
let mut error_sink = ConfigError::default();
- assert!(error_sink.is_empty());
- config = config.apply_change(change, &mut error_sink);
+ assert!(error_sink.is_empty(), "{error_sink:?}");
+ (config, _) = config.apply_change(change, &mut error_sink);
config.rediscover_workspaces();
Server::new(tmp_dir.keep(), config)