Skip to content

Commit cb56106

Browse files
Update pyrefly/lib/test/lsp/code_actions.rs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> . fmt
1 parent 41df4d4 commit cb56106

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

pyrefly/lib/state/lsp/quick_fixes/invert_boolean.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ fn collect_load_edits(
262262
range: unary.range(),
263263
replacement: name.id.to_string(),
264264
});
265+
// Avoid also visiting the inner `abc` node and producing overlapping edits
266+
// (`not abc` -> `abc` and `abc` -> `not abc`) for the same source span.
265267
return;
266268
}
267269
if let Expr::Name(name) = expr

pyrefly/lib/test/lsp/code_actions.rs

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::state::lsp::LocalRefactorCodeAction;
1717
use crate::state::require::Require;
1818
use crate::state::state::State;
1919
use crate::test::util::get_batched_lsp_operations_report_allow_error;
20+
use crate::test::util::mk_multi_file_state;
2021
use crate::test::util::mk_multi_file_state_assert_no_errors;
2122

2223
fn apply_patch(info: &ModuleInfo, range: TextRange, patch: String) -> (String, String) {
@@ -115,7 +116,10 @@ fn find_nth_range(source: &str, needle: &str, occurrence: usize) -> TextRange {
115116
}
116117
start = abs + needle.len();
117118
}
118-
panic!("missing selection marker in invert-boolean test");
119+
panic!(
120+
"could not find occurrence {} of '{}' in source",
121+
occurrence, needle
122+
);
119123
}
120124

121125
fn compute_extract_actions(
@@ -211,6 +215,36 @@ fn assert_no_invert_boolean_action(code: &str, selection: TextRange) {
211215
);
212216
}
213217

218+
fn compute_invert_boolean_actions_allow_errors(
219+
code: &str,
220+
selection: TextRange,
221+
) -> (
222+
ModuleInfo,
223+
Vec<Vec<(Module, TextRange, String)>>,
224+
Vec<String>,
225+
) {
226+
let (handles, state) = mk_multi_file_state(&[("main", code)], Require::Everything, false);
227+
let handle = handles.get("main").unwrap();
228+
let transaction = state.transaction();
229+
let module_info = transaction.get_module_info(handle).unwrap();
230+
let actions = transaction
231+
.invert_boolean_code_actions(handle, selection)
232+
.unwrap_or_default();
233+
let edit_sets: Vec<Vec<(Module, TextRange, String)>> =
234+
actions.iter().map(|action| action.edits.clone()).collect();
235+
let titles = actions.iter().map(|action| action.title.clone()).collect();
236+
(module_info, edit_sets, titles)
237+
}
238+
239+
fn assert_no_invert_boolean_action_allow_errors(code: &str, selection: TextRange) {
240+
let (_, actions, _) = compute_invert_boolean_actions_allow_errors(code, selection);
241+
assert!(
242+
actions.is_empty(),
243+
"expected no invert-boolean actions, found {}",
244+
actions.len()
245+
);
246+
}
247+
214248
fn assert_no_extract_variable_action(code: &str) {
215249
let (_, actions, _) = compute_extract_variable_actions(code);
216250
assert!(
@@ -911,6 +945,94 @@ def foo():
911945
assert_no_invert_boolean_action(code, selection);
912946
}
913947

948+
#[test]
949+
fn invert_boolean_annotated_assignment() {
950+
let code = r#"
951+
def foo():
952+
abc: bool = True
953+
return abc
954+
"#;
955+
let selection = find_nth_range(code, "abc", 2);
956+
let updated =
957+
apply_first_invert_boolean_action(code, selection).expect("expected invert-boolean action");
958+
let expected = r#"
959+
def foo():
960+
abc: bool = False
961+
return (not abc)
962+
"#;
963+
assert_eq!(expected.trim(), updated.trim());
964+
}
965+
966+
#[test]
967+
fn invert_boolean_rejects_deleted_variable() {
968+
let code = r#"
969+
def foo():
970+
abc = True
971+
del abc
972+
return abc
973+
"#;
974+
let selection = find_nth_range(code, "abc", 3);
975+
assert_no_invert_boolean_action_allow_errors(code, selection);
976+
}
977+
978+
#[test]
979+
fn invert_boolean_multiple_assignments() {
980+
let code = r#"
981+
def foo():
982+
abc = True
983+
abc = False
984+
return abc
985+
"#;
986+
let selection = find_nth_range(code, "abc", 3);
987+
let updated =
988+
apply_first_invert_boolean_action(code, selection).expect("expected invert-boolean action");
989+
let expected = r#"
990+
def foo():
991+
abc = True
992+
abc = True
993+
return (not abc)
994+
"#;
995+
assert_eq!(expected.trim(), updated.trim());
996+
}
997+
998+
#[test]
999+
fn invert_boolean_nested_expression_keeps_outer_not() {
1000+
let code = r#"
1001+
def foo():
1002+
abc = True
1003+
other = True
1004+
return not (abc and other)
1005+
"#;
1006+
let selection = find_nth_range(code, "abc", 2);
1007+
let updated =
1008+
apply_first_invert_boolean_action(code, selection).expect("expected invert-boolean action");
1009+
let expected = r#"
1010+
def foo():
1011+
abc = False
1012+
other = True
1013+
return not ((not abc) and other)
1014+
"#;
1015+
assert_eq!(expected.trim(), updated.trim());
1016+
}
1017+
1018+
#[test]
1019+
fn invert_boolean_inverts_unary_not_assignment_value() {
1020+
let code = r#"
1021+
def foo(other_var):
1022+
abc = not other_var
1023+
return abc
1024+
"#;
1025+
let selection = find_nth_range(code, "abc", 2);
1026+
let updated =
1027+
apply_first_invert_boolean_action(code, selection).expect("expected invert-boolean action");
1028+
let expected = r#"
1029+
def foo(other_var):
1030+
abc = other_var
1031+
return (not abc)
1032+
"#;
1033+
assert_eq!(expected.trim(), updated.trim());
1034+
}
1035+
9141036
#[test]
9151037
fn pull_member_up_basic() {
9161038
let code = r#"

0 commit comments

Comments
 (0)