diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-04-19 08:57:40 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2021-04-19 08:57:40 +0100 |
commit | 75bf8328994e0966d381061647ac7dbbce374b39 (patch) | |
tree | 1fadb1bc57f4d666a427137dee8da7922b46f304 | |
parent | 0308fd6dabfce2d13e7e9c5b2a779be73579fa31 (diff) | |
parent | 493aaa140325f3b8fa40de3de58b34e4b96c5d13 (diff) |
Merge #8540
8540: Prevent being able to rename items that are not part of the workspace r=Veykril a=Veykril
This change causes renames that happen on items coming from crates outside the workspace to fail. I believe this should be the right approach, but usage of cargo's workspace might not be entirely correct for preventing these kinds of refactoring from touching things they shouldn't. I'm not entirely sure?
cc #6623, this is one of the bigger footguns when it comes to refactoring, especially in combination with import aliases people tend to rename items coming from a crates dependency which this prevents.
Co-authored-by: Lukas Wirth <[email protected]>
11 files changed, 103 insertions, 30 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index b24c664ba..99e45633e 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -244,6 +244,12 @@ impl Analysis { | |||
244 | self.with_db(|db| db.parse(file_id).tree()) | 244 | self.with_db(|db| db.parse(file_id).tree()) |
245 | } | 245 | } |
246 | 246 | ||
247 | /// Returns true if this file belongs to an immutable library. | ||
248 | pub fn is_library_file(&self, file_id: FileId) -> Cancelable<bool> { | ||
249 | use ide_db::base_db::SourceDatabaseExt; | ||
250 | self.with_db(|db| db.source_root(db.file_source_root(file_id)).is_library) | ||
251 | } | ||
252 | |||
247 | /// Gets the file's `LineIndex`: data structure to convert between absolute | 253 | /// Gets the file's `LineIndex`: data structure to convert between absolute |
248 | /// offsets and line/column representation. | 254 | /// offsets and line/column representation. |
249 | pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> { | 255 | pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> { |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 1edaa394a..7ddea22c8 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -400,6 +400,17 @@ impl Config { | |||
400 | pub fn will_rename(&self) -> bool { | 400 | pub fn will_rename(&self) -> bool { |
401 | try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false) | 401 | try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false) |
402 | } | 402 | } |
403 | pub fn change_annotation_support(&self) -> bool { | ||
404 | try_!(self | ||
405 | .caps | ||
406 | .workspace | ||
407 | .as_ref()? | ||
408 | .workspace_edit | ||
409 | .as_ref()? | ||
410 | .change_annotation_support | ||
411 | .as_ref()?) | ||
412 | .is_some() | ||
413 | } | ||
403 | pub fn code_action_resolve(&self) -> bool { | 414 | pub fn code_action_resolve(&self) -> bool { |
404 | try_or!( | 415 | try_or!( |
405 | self.caps | 416 | self.caps |
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt index 23ec2efba..227d96d51 100644 --- a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt +++ b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt | |||
@@ -326,6 +326,7 @@ | |||
326 | }, | 326 | }, |
327 | ), | 327 | ), |
328 | document_changes: None, | 328 | document_changes: None, |
329 | change_annotations: None, | ||
329 | }, | 330 | }, |
330 | ), | 331 | ), |
331 | is_preferred: Some( | 332 | is_preferred: Some( |
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt index b6acb5f42..f8adfad3b 100644 --- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt +++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt | |||
@@ -179,6 +179,7 @@ | |||
179 | }, | 179 | }, |
180 | ), | 180 | ), |
181 | document_changes: None, | 181 | document_changes: None, |
182 | change_annotations: None, | ||
182 | }, | 183 | }, |
183 | ), | 184 | ), |
184 | is_preferred: Some( | 185 | is_preferred: Some( |
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt index d765257c4..5a70d2ed7 100644 --- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt +++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt | |||
@@ -179,6 +179,7 @@ | |||
179 | }, | 179 | }, |
180 | ), | 180 | ), |
181 | document_changes: None, | 181 | document_changes: None, |
182 | change_annotations: None, | ||
182 | }, | 183 | }, |
183 | ), | 184 | ), |
184 | is_preferred: Some( | 185 | is_preferred: Some( |
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt index 6b0d94878..04ca0c9c2 100644 --- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt +++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt | |||
@@ -179,6 +179,7 @@ | |||
179 | }, | 179 | }, |
180 | ), | 180 | ), |
181 | document_changes: None, | 181 | document_changes: None, |
182 | change_annotations: None, | ||
182 | }, | 183 | }, |
183 | ), | 184 | ), |
184 | is_preferred: Some( | 185 | is_preferred: Some( |
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt index a0cfb8d33..57d2f1ae3 100644 --- a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt +++ b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt | |||
@@ -339,6 +339,7 @@ | |||
339 | }, | 339 | }, |
340 | ), | 340 | ), |
341 | document_changes: None, | 341 | document_changes: None, |
342 | change_annotations: None, | ||
342 | }, | 343 | }, |
343 | ), | 344 | ), |
344 | is_preferred: Some( | 345 | is_preferred: Some( |
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index e2f319f6b..ca18997e4 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs | |||
@@ -136,6 +136,7 @@ fn map_rust_child_diagnostic( | |||
136 | // FIXME: there's no good reason to use edit_map here.... | 136 | // FIXME: there's no good reason to use edit_map here.... |
137 | changes: Some(edit_map), | 137 | changes: Some(edit_map), |
138 | document_changes: None, | 138 | document_changes: None, |
139 | change_annotations: None, | ||
139 | }), | 140 | }), |
140 | is_preferred: Some(true), | 141 | is_preferred: Some(true), |
141 | data: None, | 142 | data: None, |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index d648cda32..b8835a534 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -312,6 +312,9 @@ pub struct SnippetWorkspaceEdit { | |||
312 | pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>, | 312 | pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>, |
313 | #[serde(skip_serializing_if = "Option::is_none")] | 313 | #[serde(skip_serializing_if = "Option::is_none")] |
314 | pub document_changes: Option<Vec<SnippetDocumentChangeOperation>>, | 314 | pub document_changes: Option<Vec<SnippetDocumentChangeOperation>>, |
315 | #[serde(skip_serializing_if = "Option::is_none")] | ||
316 | pub change_annotations: | ||
317 | Option<HashMap<lsp_types::ChangeAnnotationIdentifier, lsp_types::ChangeAnnotation>>, | ||
315 | } | 318 | } |
316 | 319 | ||
317 | #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] | 320 | #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] |
@@ -335,6 +338,9 @@ pub struct SnippetTextEdit { | |||
335 | pub new_text: String, | 338 | pub new_text: String, |
336 | #[serde(skip_serializing_if = "Option::is_none")] | 339 | #[serde(skip_serializing_if = "Option::is_none")] |
337 | pub insert_text_format: Option<lsp_types::InsertTextFormat>, | 340 | pub insert_text_format: Option<lsp_types::InsertTextFormat>, |
341 | /// The annotation id if this is an annotated | ||
342 | #[serde(skip_serializing_if = "Option::is_none")] | ||
343 | pub annotation_id: Option<lsp_types::ChangeAnnotationIdentifier>, | ||
338 | } | 344 | } |
339 | 345 | ||
340 | pub enum HoverRequest {} | 346 | pub enum HoverRequest {} |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 1a1f65f3b..fe4d0733d 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -1,15 +1,16 @@ | |||
1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. | 1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. |
2 | use std::{ | 2 | use std::{ |
3 | iter::once, | ||
3 | path::{self, Path}, | 4 | path::{self, Path}, |
4 | sync::atomic::{AtomicU32, Ordering}, | 5 | sync::atomic::{AtomicU32, Ordering}, |
5 | }; | 6 | }; |
6 | 7 | ||
7 | use ide::{ | 8 | use ide::{ |
8 | Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, | 9 | Annotation, AnnotationKind, Assist, AssistKind, CallInfo, Cancelable, CompletionItem, |
9 | CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, | 10 | CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, |
10 | Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, InlayKind, | 11 | Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, |
11 | InsertTextFormat, Markup, NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, | 12 | InlayKind, InsertTextFormat, Markup, NavigationTarget, ReferenceAccess, RenameError, Runnable, |
12 | SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, | 13 | Severity, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, |
13 | }; | 14 | }; |
14 | use itertools::Itertools; | 15 | use itertools::Itertools; |
15 | use serde_json::to_value; | 16 | use serde_json::to_value; |
@@ -174,6 +175,7 @@ pub(crate) fn snippet_text_edit( | |||
174 | range: text_edit.range, | 175 | range: text_edit.range, |
175 | new_text: text_edit.new_text, | 176 | new_text: text_edit.new_text, |
176 | insert_text_format, | 177 | insert_text_format, |
178 | annotation_id: None, | ||
177 | } | 179 | } |
178 | } | 180 | } |
179 | 181 | ||
@@ -688,6 +690,10 @@ pub(crate) fn goto_definition_response( | |||
688 | } | 690 | } |
689 | } | 691 | } |
690 | 692 | ||
693 | fn outside_workspace_annotation_id() -> String { | ||
694 | String::from("OutsideWorkspace") | ||
695 | } | ||
696 | |||
691 | pub(crate) fn snippet_text_document_edit( | 697 | pub(crate) fn snippet_text_document_edit( |
692 | snap: &GlobalStateSnapshot, | 698 | snap: &GlobalStateSnapshot, |
693 | is_snippet: bool, | 699 | is_snippet: bool, |
@@ -696,14 +702,21 @@ pub(crate) fn snippet_text_document_edit( | |||
696 | ) -> Result<lsp_ext::SnippetTextDocumentEdit> { | 702 | ) -> Result<lsp_ext::SnippetTextDocumentEdit> { |
697 | let text_document = optional_versioned_text_document_identifier(snap, file_id); | 703 | let text_document = optional_versioned_text_document_identifier(snap, file_id); |
698 | let line_index = snap.file_line_index(file_id)?; | 704 | let line_index = snap.file_line_index(file_id)?; |
699 | let edits = edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect(); | 705 | let mut edits: Vec<_> = |
706 | edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect(); | ||
707 | |||
708 | if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() { | ||
709 | for edit in &mut edits { | ||
710 | edit.annotation_id = Some(outside_workspace_annotation_id()) | ||
711 | } | ||
712 | } | ||
700 | Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) | 713 | Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) |
701 | } | 714 | } |
702 | 715 | ||
703 | pub(crate) fn snippet_text_document_ops( | 716 | pub(crate) fn snippet_text_document_ops( |
704 | snap: &GlobalStateSnapshot, | 717 | snap: &GlobalStateSnapshot, |
705 | file_system_edit: FileSystemEdit, | 718 | file_system_edit: FileSystemEdit, |
706 | ) -> Vec<lsp_ext::SnippetDocumentChangeOperation> { | 719 | ) -> Cancelable<Vec<lsp_ext::SnippetDocumentChangeOperation>> { |
707 | let mut ops = Vec::new(); | 720 | let mut ops = Vec::new(); |
708 | match file_system_edit { | 721 | match file_system_edit { |
709 | FileSystemEdit::CreateFile { dst, initial_contents } => { | 722 | FileSystemEdit::CreateFile { dst, initial_contents } => { |
@@ -721,6 +734,7 @@ pub(crate) fn snippet_text_document_ops( | |||
721 | range: lsp_types::Range::default(), | 734 | range: lsp_types::Range::default(), |
722 | new_text: initial_contents, | 735 | new_text: initial_contents, |
723 | insert_text_format: Some(lsp_types::InsertTextFormat::PlainText), | 736 | insert_text_format: Some(lsp_types::InsertTextFormat::PlainText), |
737 | annotation_id: None, | ||
724 | }; | 738 | }; |
725 | let edit_file = | 739 | let edit_file = |
726 | lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] }; | 740 | lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] }; |
@@ -730,16 +744,19 @@ pub(crate) fn snippet_text_document_ops( | |||
730 | FileSystemEdit::MoveFile { src, dst } => { | 744 | FileSystemEdit::MoveFile { src, dst } => { |
731 | let old_uri = snap.file_id_to_url(src); | 745 | let old_uri = snap.file_id_to_url(src); |
732 | let new_uri = snap.anchored_path(&dst); | 746 | let new_uri = snap.anchored_path(&dst); |
733 | let rename_file = lsp_types::ResourceOp::Rename(lsp_types::RenameFile { | 747 | let mut rename_file = |
734 | old_uri, | 748 | lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None }; |
735 | new_uri, | 749 | if snap.analysis.is_library_file(src) == Ok(true) |
736 | options: None, | 750 | && snap.config.change_annotation_support() |
737 | annotation_id: None, | 751 | { |
738 | }); | 752 | rename_file.annotation_id = Some(outside_workspace_annotation_id()) |
739 | ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(rename_file)) | 753 | } |
754 | ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename( | ||
755 | rename_file, | ||
756 | ))) | ||
740 | } | 757 | } |
741 | } | 758 | } |
742 | ops | 759 | Ok(ops) |
743 | } | 760 | } |
744 | 761 | ||
745 | pub(crate) fn snippet_workspace_edit( | 762 | pub(crate) fn snippet_workspace_edit( |
@@ -747,16 +764,35 @@ pub(crate) fn snippet_workspace_edit( | |||
747 | source_change: SourceChange, | 764 | source_change: SourceChange, |
748 | ) -> Result<lsp_ext::SnippetWorkspaceEdit> { | 765 | ) -> Result<lsp_ext::SnippetWorkspaceEdit> { |
749 | let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); | 766 | let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); |
767 | |||
750 | for op in source_change.file_system_edits { | 768 | for op in source_change.file_system_edits { |
751 | let ops = snippet_text_document_ops(snap, op); | 769 | let ops = snippet_text_document_ops(snap, op)?; |
752 | document_changes.extend_from_slice(&ops); | 770 | document_changes.extend_from_slice(&ops); |
753 | } | 771 | } |
754 | for (file_id, edit) in source_change.source_file_edits { | 772 | for (file_id, edit) in source_change.source_file_edits { |
755 | let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?; | 773 | let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?; |
756 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); | 774 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); |
757 | } | 775 | } |
758 | let workspace_edit = | 776 | let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit { |
759 | lsp_ext::SnippetWorkspaceEdit { changes: None, document_changes: Some(document_changes) }; | 777 | changes: None, |
778 | document_changes: Some(document_changes), | ||
779 | change_annotations: None, | ||
780 | }; | ||
781 | if snap.config.change_annotation_support() { | ||
782 | workspace_edit.change_annotations = Some( | ||
783 | once(( | ||
784 | outside_workspace_annotation_id(), | ||
785 | lsp_types::ChangeAnnotation { | ||
786 | label: String::from("Edit outside of the workspace"), | ||
787 | needs_confirmation: Some(true), | ||
788 | description: Some(String::from( | ||
789 | "This edit lies outside of the workspace and may affect dependencies", | ||
790 | )), | ||
791 | }, | ||
792 | )) | ||
793 | .collect(), | ||
794 | ) | ||
795 | } | ||
760 | Ok(workspace_edit) | 796 | Ok(workspace_edit) |
761 | } | 797 | } |
762 | 798 | ||
@@ -784,16 +820,7 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit { | |||
784 | lsp_types::DocumentChangeOperation::Edit( | 820 | lsp_types::DocumentChangeOperation::Edit( |
785 | lsp_types::TextDocumentEdit { | 821 | lsp_types::TextDocumentEdit { |
786 | text_document: edit.text_document, | 822 | text_document: edit.text_document, |
787 | edits: edit | 823 | edits: edit.edits.into_iter().map(From::from).collect(), |
788 | .edits | ||
789 | .into_iter() | ||
790 | .map(|edit| { | ||
791 | lsp_types::OneOf::Left(lsp_types::TextEdit { | ||
792 | range: edit.range, | ||
793 | new_text: edit.new_text, | ||
794 | }) | ||
795 | }) | ||
796 | .collect(), | ||
797 | }, | 824 | }, |
798 | ) | 825 | ) |
799 | } | 826 | } |
@@ -801,7 +828,23 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit { | |||
801 | .collect(), | 828 | .collect(), |
802 | ) | 829 | ) |
803 | }), | 830 | }), |
804 | change_annotations: None, | 831 | change_annotations: snippet_workspace_edit.change_annotations, |
832 | } | ||
833 | } | ||
834 | } | ||
835 | |||
836 | impl From<lsp_ext::SnippetTextEdit> | ||
837 | for lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit> | ||
838 | { | ||
839 | fn from( | ||
840 | lsp_ext::SnippetTextEdit { annotation_id, insert_text_format:_, new_text, range }: lsp_ext::SnippetTextEdit, | ||
841 | ) -> Self { | ||
842 | match annotation_id { | ||
843 | Some(annotation_id) => lsp_types::OneOf::Right(lsp_types::AnnotatedTextEdit { | ||
844 | text_edit: lsp_types::TextEdit { range, new_text }, | ||
845 | annotation_id, | ||
846 | }), | ||
847 | None => lsp_types::OneOf::Left(lsp_types::TextEdit { range, new_text }), | ||
805 | } | 848 | } |
806 | } | 849 | } |
807 | } | 850 | } |
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index a4d92242b..a112477de 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -1,5 +1,5 @@ | |||
1 | <!--- | 1 | <!--- |
2 | lsp_ext.rs hash: b19ddc3ab8767af9 | 2 | lsp_ext.rs hash: 28a9d5a24b7ca396 |
3 | 3 | ||
4 | If you need to change the above hash to make the test pass, please check if you | 4 | If you need to change the above hash to make the test pass, please check if you |
5 | need to adjust this doc as well and ping this issue: | 5 | need to adjust this doc as well and ping this issue: |
@@ -46,6 +46,7 @@ If this capability is set, `WorkspaceEdit`s returned from `codeAction` requests | |||
46 | ```typescript | 46 | ```typescript |
47 | interface SnippetTextEdit extends TextEdit { | 47 | interface SnippetTextEdit extends TextEdit { |
48 | insertTextFormat?: InsertTextFormat; | 48 | insertTextFormat?: InsertTextFormat; |
49 | annotationId?: ChangeAnnotationIdentifier; | ||
49 | } | 50 | } |
50 | ``` | 51 | ``` |
51 | 52 | ||