aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/rust-analyzer/src/handlers.rs17
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs160
2 files changed, 176 insertions, 1 deletions
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index f80c55df7..d6865e1d6 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -36,6 +36,7 @@ use crate::{
36 from_json, from_proto, 36 from_json, from_proto,
37 global_state::{GlobalState, GlobalStateSnapshot}, 37 global_state::{GlobalState, GlobalStateSnapshot},
38 lsp_ext::{self, InlayHint, InlayHintsParams}, 38 lsp_ext::{self, InlayHint, InlayHintsParams},
39 lsp_utils::all_edits_are_disjoint,
39 to_proto, LspError, Result, 40 to_proto, LspError, Result,
40}; 41};
41 42
@@ -601,6 +602,14 @@ pub(crate) fn handle_completion_resolve(
601) -> Result<CompletionItem> { 602) -> Result<CompletionItem> {
602 let _p = profile::span("handle_resolve_completion"); 603 let _p = profile::span("handle_resolve_completion");
603 604
605 if !all_edits_are_disjoint(&original_completion, &[]) {
606 return Err(LspError::new(
607 ErrorCode::InvalidParams as i32,
608 "Received a completion with disjoint edits".into(),
609 )
610 .into());
611 }
612
604 // FIXME resolve the other capabilities also? 613 // FIXME resolve the other capabilities also?
605 if !snap 614 if !snap
606 .config 615 .config
@@ -640,6 +649,14 @@ pub(crate) fn handle_completion_resolve(
640 }) 649 })
641 .collect_vec(); 650 .collect_vec();
642 651
652 if !all_edits_are_disjoint(&original_completion, &additional_edits) {
653 return Err(LspError::new(
654 ErrorCode::InternalError as i32,
655 "Import edit is not disjoint with the original completion edits".into(),
656 )
657 .into());
658 }
659
643 if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() { 660 if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
644 original_additional_edits.extend(additional_edits.drain(..)) 661 original_additional_edits.extend(additional_edits.drain(..))
645 } else { 662 } else {
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 6427c7367..d5c1c1ad0 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -129,9 +129,36 @@ pub(crate) fn apply_document_changes(
129 } 129 }
130} 130}
131 131
132/// Checks that the edits inside the completion and the additional edits are disjoint.
133pub(crate) fn all_edits_are_disjoint(
134 completion: &lsp_types::CompletionItem,
135 additional_edits: &[lsp_types::TextEdit],
136) -> bool {
137 let mut edit_ranges = Vec::new();
138 match completion.text_edit.as_ref() {
139 Some(lsp_types::CompletionTextEdit::Edit(edit)) => {
140 edit_ranges.push(edit.range);
141 }
142 Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
143 edit_ranges.push(edit.insert);
144 edit_ranges.push(edit.replace);
145 }
146 None => {}
147 }
148 if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
149 edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
150 };
151 edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
152 edit_ranges.sort_by_key(|range| (range.start, range.end));
153 edit_ranges.iter().zip(edit_ranges.iter().skip(1)).all(|(l, r)| l.end <= r.start)
154}
155
132#[cfg(test)] 156#[cfg(test)]
133mod tests { 157mod tests {
134 use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; 158 use lsp_types::{
159 CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
160 TextDocumentContentChangeEvent,
161 };
135 162
136 use super::*; 163 use super::*;
137 164
@@ -197,4 +224,135 @@ mod tests {
197 apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); 224 apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
198 assert_eq!(text, "ațc\ncb"); 225 assert_eq!(text, "ațc\ncb");
199 } 226 }
227
228 #[test]
229 fn empty_completion_disjoint_tests() {
230 let empty_completion =
231 CompletionItem::new_simple("label".to_string(), "detail".to_string());
232
233 let disjoint_edit_1 = lsp_types::TextEdit::new(
234 Range::new(Position::new(2, 2), Position::new(3, 3)),
235 "new_text".to_string(),
236 );
237 let disjoint_edit_2 = lsp_types::TextEdit::new(
238 Range::new(Position::new(3, 3), Position::new(4, 4)),
239 "new_text".to_string(),
240 );
241
242 let joint_edit = lsp_types::TextEdit::new(
243 Range::new(Position::new(1, 1), Position::new(5, 5)),
244 "new_text".to_string(),
245 );
246
247 assert!(
248 all_edits_are_disjoint(&empty_completion, &[]),
249 "Empty completion has all its edits disjoint"
250 );
251 assert!(
252 all_edits_are_disjoint(
253 &empty_completion,
254 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
255 ),
256 "Empty completion is disjoint to whatever disjoint extra edits added"
257 );
258
259 assert!(
260 !all_edits_are_disjoint(
261 &empty_completion,
262 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
263 ),
264 "Empty completion does not prevent joint extra edits from failing the validation"
265 );
266 }
267
268 #[test]
269 fn completion_with_joint_edits_disjoint_tests() {
270 let disjoint_edit = lsp_types::TextEdit::new(
271 Range::new(Position::new(1, 1), Position::new(2, 2)),
272 "new_text".to_string(),
273 );
274 let disjoint_edit_2 = lsp_types::TextEdit::new(
275 Range::new(Position::new(2, 2), Position::new(3, 3)),
276 "new_text".to_string(),
277 );
278 let joint_edit = lsp_types::TextEdit::new(
279 Range::new(Position::new(1, 1), Position::new(5, 5)),
280 "new_text".to_string(),
281 );
282
283 let mut completion_with_joint_edits =
284 CompletionItem::new_simple("label".to_string(), "detail".to_string());
285 completion_with_joint_edits.additional_text_edits =
286 Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
287 assert!(
288 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
289 "Completion with disjoint edits fails the validaton even with empty extra edits"
290 );
291
292 completion_with_joint_edits.text_edit =
293 Some(CompletionTextEdit::Edit(disjoint_edit.clone()));
294 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
295 assert!(
296 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
297 "Completion with disjoint edits fails the validaton even with empty extra edits"
298 );
299
300 completion_with_joint_edits.text_edit =
301 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
302 new_text: "new_text".to_string(),
303 insert: disjoint_edit.range,
304 replace: joint_edit.range,
305 }));
306 completion_with_joint_edits.additional_text_edits = None;
307 assert!(
308 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
309 "Completion with disjoint edits fails the validaton even with empty extra edits"
310 );
311
312 completion_with_joint_edits.text_edit =
313 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
314 new_text: "new_text".to_string(),
315 insert: disjoint_edit.range,
316 replace: disjoint_edit_2.range,
317 }));
318 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
319 assert!(
320 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
321 "Completion with disjoint edits fails the validaton even with empty extra edits"
322 );
323 }
324
325 #[test]
326 fn completion_with_disjoint_edits_disjoint_tests() {
327 let disjoint_edit = lsp_types::TextEdit::new(
328 Range::new(Position::new(1, 1), Position::new(2, 2)),
329 "new_text".to_string(),
330 );
331 let disjoint_edit_2 = lsp_types::TextEdit::new(
332 Range::new(Position::new(2, 2), Position::new(3, 3)),
333 "new_text".to_string(),
334 );
335 let joint_edit = lsp_types::TextEdit::new(
336 Range::new(Position::new(1, 1), Position::new(5, 5)),
337 "new_text".to_string(),
338 );
339
340 let mut completion_with_disjoint_edits =
341 CompletionItem::new_simple("label".to_string(), "detail".to_string());
342 completion_with_disjoint_edits.text_edit = Some(CompletionTextEdit::Edit(disjoint_edit));
343 let completion_with_disjoint_edits = completion_with_disjoint_edits;
344
345 assert!(
346 all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
347 "Completion with disjoint edits is valid"
348 );
349 assert!(
350 !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit.clone()]),
351 "Completion with disjoint edits and joint extra edit is invalid"
352 );
353 assert!(
354 all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2.clone()]),
355 "Completion with disjoint edits and joint extra edit is valid"
356 );
357 }
200} 358}