ModuleDef::Module(it) => it.name(db),
ModuleDef::Const(it) => it.name(db),
ModuleDef::Static(it) => it.name(db),
-
ModuleDef::BuiltinType(it) => Some(it.name()),
}
}
}
}
- pub(super) fn resolve_path_fp_with_macro_single(
+ fn resolve_path_fp_with_macro_single(
&self,
db: &dyn DefDatabase,
mode: ResolveMode,
}
}
};
- let from_extern_prelude = self
- .extern_prelude
- .get(name)
- .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public));
+ // Give precedence to names in outer `DefMap`s over the extern prelude; only check prelude
+ // from the crate DefMap.
+ let from_extern_prelude = match self.block {
+ Some(_) => PerNs::none(),
+ None => self
+ .extern_prelude
+ .get(name)
+ .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public)),
+ };
+
let from_prelude = self.resolve_in_prelude(db, name);
from_legacy_macro.or(from_scope_or_builtin).or(from_extern_prelude).or(from_prelude)
)
}
+ #[test]
+ fn import_extern_crate_clash_with_inner_item() {
+ // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
+
+ check_diagnostics(
+ r#"
+//- /lib.rs crate:lib deps:jwt
+mod permissions;
+
+use permissions::jwt;
+
+fn f() {
+ fn inner() {}
+ jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
+}
+
+//- /permissions.rs
+pub mod jwt {
+ pub struct Claims {}
+}
+
+//- /jwt/lib.rs crate:jwt
+pub struct Claims {
+ field: u8,
+}
+ "#,
+ );
+ }
+
#[test]
fn break_outside_of_loop() {
check_diagnostics(
mod references;
mod fn_references;
mod runnables;
+mod ssr;
mod status;
mod syntax_highlighting;
mod syntax_tree;
use std::sync::Arc;
use cfg::CfgOptions;
+
use ide_db::base_db::{
salsa::{self, ParallelDatabase},
CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
resolve: bool,
frange: FileRange,
) -> Cancelable<Vec<Assist>> {
- self.with_db(|db| Assist::get(db, config, resolve, frange))
+ self.with_db(|db| {
+ let mut acc = Assist::get(db, config, resolve, frange);
+ ssr::add_ssr_assist(db, &mut acc, resolve, frange);
+ acc
+ })
}
/// Computes the set of diagnostics for the given file.
}
}
+/// Called by the client when it is about to rename a file.
pub(crate) fn will_rename_file(
db: &RootDatabase,
file_id: FileId,
--- /dev/null
+//! This module provides an SSR assist. It is not desirable to include this
+//! assist in ide_assists because that would require the ide_assists crate
+//! depend on the ide_ssr crate.
+
+use ide_assists::{Assist, AssistId, AssistKind, GroupLabel};
+use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase};
+
+pub(crate) fn add_ssr_assist(
+ db: &RootDatabase,
+ base: &mut Vec<Assist>,
+ resolve: bool,
+ frange: FileRange,
+) -> Option<()> {
+ let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?;
+
+ let (source_change_for_file, source_change_for_workspace) = if resolve {
+ let edits = match_finder.edits();
+
+ let source_change_for_file = {
+ let text_edit_for_file = edits.get(&frange.file_id).cloned().unwrap_or_default();
+ SourceChange::from_text_edit(frange.file_id, text_edit_for_file)
+ };
+
+ let source_change_for_workspace = SourceChange::from(match_finder.edits());
+
+ (Some(source_change_for_file), Some(source_change_for_workspace))
+ } else {
+ (None, None)
+ };
+
+ let assists = vec![
+ ("Apply SSR in file", source_change_for_file),
+ ("Apply SSR in workspace", source_change_for_workspace),
+ ];
+
+ for (label, source_change) in assists.into_iter() {
+ let assist = Assist {
+ id: AssistId("ssr", AssistKind::RefactorRewrite),
+ label: Label::new(label),
+ group: Some(GroupLabel("Apply SSR".into())),
+ target: comment_range,
+ source_change,
+ };
+
+ base.push(assist);
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use std::sync::Arc;
+
+ use expect_test::expect;
+ use ide_assists::Assist;
+ use ide_db::{
+ base_db::{fixture::WithFixture, salsa::Durability, FileRange},
+ symbol_index::SymbolsDatabase,
+ RootDatabase,
+ };
+ use rustc_hash::FxHashSet;
+
+ use super::add_ssr_assist;
+
+ fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> {
+ let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
+ let mut local_roots = FxHashSet::default();
+ local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
+ db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
+
+ let mut assists = vec![];
+
+ add_ssr_assist(
+ &db,
+ &mut assists,
+ resolve,
+ FileRange { file_id, range: range_or_offset.into() },
+ );
+
+ assists
+ }
+
+ #[test]
+ fn not_applicable_comment_not_ssr() {
+ let ra_fixture = r#"
+ //- /lib.rs
+
+ // This is foo $0
+ fn foo() {}
+ "#;
+ let resolve = true;
+
+ let assists = get_assists(ra_fixture, resolve);
+
+ assert_eq!(0, assists.len());
+ }
+
+ #[test]
+ fn resolve_edits_true() {
+ let resolve = true;
+ let assists = get_assists(
+ r#"
+ //- /lib.rs
+ mod bar;
+
+ // 2 ==>> 3$0
+ fn foo() { 2 }
+
+ //- /bar.rs
+ fn bar() { 2 }
+ "#,
+ resolve,
+ );
+
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let apply_in_file_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in file",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 33..34,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: false,
+ },
+ ),
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_file_assist);
+
+ let apply_in_workspace_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in workspace",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 33..34,
+ },
+ ],
+ },
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 11..12,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: false,
+ },
+ ),
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_workspace_assist);
+ }
+
+ #[test]
+ fn resolve_edits_false() {
+ let resolve = false;
+ let assists = get_assists(
+ r#"
+ //- /lib.rs
+ mod bar;
+
+ // 2 ==>> 3$0
+ fn foo() { 2 }
+
+ //- /bar.rs
+ fn bar() { 2 }
+ "#,
+ resolve,
+ );
+
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let apply_in_file_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in file",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: None,
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_file_assist);
+
+ let apply_in_workspace_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in workspace",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: None,
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_workspace_assist);
+ }
+}
--- /dev/null
+//! This module allows building an SSR MatchFinder by parsing the SSR rule
+//! from a comment.
+
+use ide_db::{
+ base_db::{FilePosition, FileRange, SourceDatabase},
+ RootDatabase,
+};
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ TextRange,
+};
+
+use crate::MatchFinder;
+
+/// Attempts to build an SSR MatchFinder from a comment at the given file
+/// range. If successful, returns the MatchFinder and a TextRange covering
+/// comment.
+pub fn ssr_from_comment(db: &RootDatabase, frange: FileRange) -> Option<(MatchFinder, TextRange)> {
+ let comment = {
+ let file = db.parse(frange.file_id);
+ file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast)
+ }?;
+ let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap();
+ let ssr_rule = comment_text_without_prefix.parse().ok()?;
+
+ let lookup_context = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
+
+ let mut match_finder = MatchFinder::in_context(db, lookup_context, vec![]);
+ match_finder.add_rule(ssr_rule).ok()?;
+
+ Some((match_finder, comment.syntax().text_range()))
+}
// // foo($a, $b) ==>> ($a).foo($b)
// ```
+mod from_comment;
mod matching;
mod nester;
mod parsing;
use crate::errors::bail;
pub use crate::errors::SsrError;
+pub use crate::from_comment::ssr_from_comment;
pub use crate::matching::Match;
use crate::matching::MatchFailureReason;
use hir::Semantics;
pub fn work_done_progress(&self) -> bool {
try_or!(self.caps.window.as_ref()?.work_done_progress?, false)
}
+ pub fn will_rename(&self) -> bool {
+ try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false)
+ }
pub fn code_action_resolve(&self) -> bool {
try_or!(
self.caps
let _p = profile::span("handle_rename");
let position = from_proto::file_position(&snap, params.text_document_position)?;
- let change =
+ let mut change =
snap.analysis.rename(position, &*params.new_name)?.map_err(to_proto::rename_error)?;
+
+ // this is kind of a hack to prevent double edits from happening when moving files
+ // When a module gets renamed by renaming the mod declaration this causes the file to move
+ // which in turn will trigger a WillRenameFiles request to the server for which we reply with a
+ // a second identical set of renames, the client will then apply both edits causing incorrect edits
+ // with this we only emit source_file_edits in the WillRenameFiles response which will do the rename instead
+ // See https://github.com/microsoft/vscode-languageserver-node/issues/752 for more info
+ if !change.file_system_edits.is_empty() && snap.config.will_rename() {
+ change.source_file_edits.clear();
+ }
let workspace_edit = to_proto::workspace_edit(&snap, change)?;
Ok(Some(workspace_edit))
}