Skip to content

Commit c68ea9b

Browse files
committed
unwrap anonymous functions with comments in
The indentation of comments when we're done with them might be a bit off, but it's nothing an autoformat shouldn't be able to fix
1 parent 9a951b0 commit c68ea9b

7 files changed

+212
-28
lines changed

compiler-core/src/language_server/code_action.rs

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8323,7 +8323,8 @@ impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> {
83238323
}
83248324
}
83258325

8326-
/// Code action to unwrap trivial one-statement anonymous functions into just a reference to the function called
8326+
/// Code action to unwrap trivial one-statement anonymous functions into just a
8327+
/// reference to the function called
83278328
///
83288329
/// For example, if the code action was used on the anonymous function here:
83298330
///
@@ -8348,9 +8349,10 @@ pub struct UnwrapAnonymousFunction<'a> {
83488349
/// Helper struct, a target for [UnwrapAnonymousFunction]
83498350
struct FunctionToUnwrap {
83508351
/// Location of the anonymous function to apply the action to
8351-
anonymous_function_location: SrcSpan,
8352-
/// Location of the function being called inside the anonymous function. This will be all that's left after the action.
8353-
inner_function_location: SrcSpan,
8352+
outer_function: SrcSpan,
8353+
/// Location of the function being called inside the anonymous function.
8354+
/// This will be all that's left after the action, plus any comments.
8355+
inner_function: SrcSpan,
83548356
}
83558357

83568358
impl<'a> UnwrapAnonymousFunction<'a> {
@@ -8371,16 +8373,29 @@ impl<'a> UnwrapAnonymousFunction<'a> {
83718373
self.visit_typed_module(&self.module.ast);
83728374

83738375
let mut actions = Vec::with_capacity(self.functions.len());
8374-
for target in self.functions {
8376+
for function in &self.functions {
83758377
let mut edits = TextEdits::new(self.line_numbers);
83768378

8379+
// We need to delete the anonymous function's head but preserve
8380+
// comments between it and the inner function call.
8381+
edits.delete(self.span_until_comment(SrcSpan {
8382+
start: function.outer_function.start,
8383+
end: function.inner_function.start,
8384+
}));
8385+
8386+
// Now we need to delete the inner function call's arguments,
8387+
// preserving comments before the outer function tail.
8388+
edits.delete(self.span_until_comment(SrcSpan {
8389+
start: function.inner_function.end,
8390+
end: function.outer_function.end - 1,
8391+
}));
8392+
8393+
// To delete the tail we nip one character to make sure we get the
8394+
// `}`. This could be redundant with the above if there were no
8395+
// comments, but that's fine.
83778396
edits.delete(SrcSpan {
8378-
start: target.anonymous_function_location.start,
8379-
end: target.inner_function_location.start,
8380-
});
8381-
edits.delete(SrcSpan {
8382-
start: target.inner_function_location.end,
8383-
end: target.anonymous_function_location.end,
8397+
start: function.outer_function.end - 1,
8398+
end: function.outer_function.end,
83848399
});
83858400

83868401
CodeActionBuilder::new("Remove anonymous function wrapper")
@@ -8391,6 +8406,24 @@ impl<'a> UnwrapAnonymousFunction<'a> {
83918406
actions
83928407
}
83938408

8409+
// Returns the given span, but with the end point adjusted to the start
8410+
// of the first comment in the span, if any.
8411+
fn span_until_comment(&self, span: SrcSpan) -> SrcSpan {
8412+
let SrcSpan { start, end } = span;
8413+
let adjusted_end = self
8414+
.module
8415+
.extra
8416+
.first_comment_between(start, end)
8417+
// The above will return the start of the comment's text, so we need
8418+
// to step backwards a bit to get the `//`.
8419+
.map(|comment| comment.start - 2)
8420+
.unwrap_or(end);
8421+
SrcSpan {
8422+
start,
8423+
end: adjusted_end,
8424+
}
8425+
}
8426+
83948427
/// If an anonymous function can be unwrapped, save it to our list
83958428
///
83968429
/// We need to ensure our subjects:
@@ -8411,15 +8444,6 @@ impl<'a> UnwrapAnonymousFunction<'a> {
84118444
_ => return,
84128445
}
84138446

8414-
// We can't apply to functions with comments in (yet)
8415-
if self
8416-
.module
8417-
.extra
8418-
.has_comment_between(location.start, location.end)
8419-
{
8420-
return;
8421-
}
8422-
84238447
// We can only apply to anonymous functions containing a single function call
84248448
let [
84258449
TypedStatement::Expression(TypedExpr::Call {
@@ -8458,8 +8482,8 @@ impl<'a> UnwrapAnonymousFunction<'a> {
84588482
}
84598483

84608484
self.functions.push(FunctionToUnwrap {
8461-
anonymous_function_location: *location,
8462-
inner_function_location: called_function.location(),
8485+
outer_function: *location,
8486+
inner_function: called_function.location(),
84638487
})
84648488
}
84658489
}

compiler-core/src/language_server/tests/action.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10076,8 +10076,8 @@ fn op(first a, second b) {
1007610076
}
1007710077

1007810078
#[test]
10079-
fn dont_unwrap_anonymous_function_with_comment_after() {
10080-
assert_no_code_actions!(
10079+
fn unwrap_anonymous_function_with_comment_after() {
10080+
assert_code_action!(
1008110081
UNWRAP_ANONYMOUS_FUNCTION,
1008210082
"pub fn main() {
1008310083
fn(a) {
@@ -10095,12 +10095,54 @@ fn op(a) {
1009510095
}
1009610096

1009710097
#[test]
10098-
fn dont_unwrap_anonymous_function_with_comment_before() {
10099-
assert_no_code_actions!(
10098+
fn unwrap_anonymous_function_with_comment_on_line() {
10099+
assert_code_action!(
1010010100
UNWRAP_ANONYMOUS_FUNCTION,
1010110101
"pub fn main() {
1010210102
fn(a) {
10103-
// look out!
10103+
op(a) // look out!
10104+
}
10105+
}
10106+
10107+
fn op(a) {
10108+
todo
10109+
}
10110+
",
10111+
find_position_of("fn(a)").to_selection()
10112+
);
10113+
}
10114+
10115+
#[test]
10116+
fn unwrap_anonymous_function_with_comment_on_head_line() {
10117+
assert_code_action!(
10118+
UNWRAP_ANONYMOUS_FUNCTION,
10119+
"pub fn main() {
10120+
fn(a) { // look out!
10121+
op(a)
10122+
}
10123+
}
10124+
10125+
fn op(a) {
10126+
todo
10127+
}
10128+
",
10129+
find_position_of("fn(a)").to_selection()
10130+
);
10131+
}
10132+
10133+
#[test]
10134+
fn unwrap_anonymous_function_with_comments_before() {
10135+
assert_code_action!(
10136+
UNWRAP_ANONYMOUS_FUNCTION,
10137+
"pub fn main() {
10138+
fn(a) {
10139+
// look out,
10140+
// there's a comment!
10141+
10142+
// another comment!
10143+
//here's one without a leading space
10144+
// here's one indented wrong
10145+
// here's one indented even wronger
1010410146
op(a)
1010510147
}
1010610148
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main() {\n fn(a) {\n op(a)\n // look out!\n }\n}\n\nfn op(a) {\n todo\n}\n"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main() {
7+
fn(a) {
8+
9+
op(a)
10+
// look out!
11+
}
12+
}
13+
14+
fn op(a) {
15+
todo
16+
}
17+
18+
19+
----- AFTER ACTION
20+
pub fn main() {
21+
op// look out!
22+
23+
}
24+
25+
fn op(a) {
26+
todo
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main() {\n fn(a) { // look out!\n op(a)\n }\n}\n\nfn op(a) {\n todo\n}\n"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main() {
7+
fn(a) { // look out!
8+
9+
op(a)
10+
}
11+
}
12+
13+
fn op(a) {
14+
todo
15+
}
16+
17+
18+
----- AFTER ACTION
19+
pub fn main() {
20+
// look out!
21+
op
22+
}
23+
24+
fn op(a) {
25+
todo
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main() {\n fn(a) {\n op(a) // look out!\n }\n}\n\nfn op(a) {\n todo\n}\n"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main() {
7+
fn(a) {
8+
9+
op(a) // look out!
10+
}
11+
}
12+
13+
fn op(a) {
14+
todo
15+
}
16+
17+
18+
----- AFTER ACTION
19+
pub fn main() {
20+
op// look out!
21+
22+
}
23+
24+
fn op(a) {
25+
todo
26+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main() {\n fn(a) {\n // look out,\n // there's a comment!\n\n // another comment!\n //here's one without a leading space\n // here's one indented wrong\n // here's one indented even wronger\n op(a)\n }\n}\n\nfn op(a) {\n todo\n}\n"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main() {
7+
fn(a) {
8+
9+
// look out,
10+
// there's a comment!
11+
12+
// another comment!
13+
//here's one without a leading space
14+
// here's one indented wrong
15+
// here's one indented even wronger
16+
op(a)
17+
}
18+
}
19+
20+
fn op(a) {
21+
todo
22+
}
23+
24+
25+
----- AFTER ACTION
26+
pub fn main() {
27+
// look out,
28+
// there's a comment!
29+
30+
// another comment!
31+
//here's one without a leading space
32+
// here's one indented wrong
33+
// here's one indented even wronger
34+
op
35+
}
36+
37+
fn op(a) {
38+
todo
39+
}

compiler-core/src/parse/extra.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl ModuleExtra {
6565
let mut search_list = &self.comments[..];
6666
while let Some(index) = inner(search_list, start, end) {
6767
best = self.comments.get(index).copied();
68-
search_list = &search_list[0..index];
68+
search_list = search_list.get(0..index).unwrap_or(&[]);
6969
}
7070
best
7171
}

0 commit comments

Comments
 (0)