Merge pull request #1202 from travisstaloch/issue-689-discard-caps
autofix: discard captures + some multi for loop support
This commit is contained in:
commit
0de454195c
@ -244,8 +244,9 @@ pub fn autofix(server: *Server, allocator: std.mem.Allocator, handle: *const Doc
|
|||||||
};
|
};
|
||||||
|
|
||||||
var actions = std.ArrayListUnmanaged(types.CodeAction){};
|
var actions = std.ArrayListUnmanaged(types.CodeAction){};
|
||||||
|
var remove_capture_actions = std.AutoHashMapUnmanaged(types.Range, void){};
|
||||||
for (diagnostics.items) |diagnostic| {
|
for (diagnostics.items) |diagnostic| {
|
||||||
try builder.generateCodeAction(diagnostic, &actions);
|
try builder.generateCodeAction(diagnostic, &actions, &remove_capture_actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
var text_edits = std.ArrayListUnmanaged(types.TextEdit){};
|
var text_edits = std.ArrayListUnmanaged(types.TextEdit){};
|
||||||
@ -1189,8 +1190,9 @@ fn codeActionHandler(server: *Server, request: types.CodeActionParams) Error!?[]
|
|||||||
}
|
}
|
||||||
|
|
||||||
var actions = std.ArrayListUnmanaged(types.CodeAction){};
|
var actions = std.ArrayListUnmanaged(types.CodeAction){};
|
||||||
|
var remove_capture_actions = std.AutoHashMapUnmanaged(types.Range, void){};
|
||||||
for (diagnostics.items) |diagnostic| {
|
for (diagnostics.items) |diagnostic| {
|
||||||
try builder.generateCodeAction(diagnostic, &actions);
|
try builder.generateCodeAction(diagnostic, &actions, &remove_capture_actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions.items;
|
return actions.items;
|
||||||
|
@ -14,7 +14,12 @@ pub const Builder = struct {
|
|||||||
handle: *const DocumentStore.Handle,
|
handle: *const DocumentStore.Handle,
|
||||||
offset_encoding: offsets.Encoding,
|
offset_encoding: offsets.Encoding,
|
||||||
|
|
||||||
pub fn generateCodeAction(builder: *Builder, diagnostic: types.Diagnostic, actions: *std.ArrayListUnmanaged(types.CodeAction)) error{OutOfMemory}!void {
|
pub fn generateCodeAction(
|
||||||
|
builder: *Builder,
|
||||||
|
diagnostic: types.Diagnostic,
|
||||||
|
actions: *std.ArrayListUnmanaged(types.CodeAction),
|
||||||
|
remove_capture_actions: *std.AutoHashMapUnmanaged(types.Range, void),
|
||||||
|
) error{OutOfMemory}!void {
|
||||||
const kind = DiagnosticKind.parse(diagnostic.message) orelse return;
|
const kind = DiagnosticKind.parse(diagnostic.message) orelse return;
|
||||||
|
|
||||||
const loc = offsets.rangeToLoc(builder.handle.text, diagnostic.range, builder.offset_encoding);
|
const loc = offsets.rangeToLoc(builder.handle.text, diagnostic.range, builder.offset_encoding);
|
||||||
@ -24,14 +29,12 @@ pub const Builder = struct {
|
|||||||
.@"function parameter" => try handleUnusedFunctionParameter(builder, actions, loc),
|
.@"function parameter" => try handleUnusedFunctionParameter(builder, actions, loc),
|
||||||
.@"local constant" => try handleUnusedVariableOrConstant(builder, actions, loc),
|
.@"local constant" => try handleUnusedVariableOrConstant(builder, actions, loc),
|
||||||
.@"local variable" => try handleUnusedVariableOrConstant(builder, actions, loc),
|
.@"local variable" => try handleUnusedVariableOrConstant(builder, actions, loc),
|
||||||
.@"loop index capture" => try handleUnusedIndexCapture(builder, actions, loc),
|
.@"switch tag capture", .capture => try handleUnusedCapture(builder, actions, loc, remove_capture_actions),
|
||||||
.capture => try handleUnusedCapture(builder, actions, loc),
|
|
||||||
},
|
},
|
||||||
.non_camelcase_fn => try handleNonCamelcaseFunction(builder, actions, loc),
|
.non_camelcase_fn => try handleNonCamelcaseFunction(builder, actions, loc),
|
||||||
.pointless_discard => try handlePointlessDiscard(builder, actions, loc),
|
.pointless_discard => try handlePointlessDiscard(builder, actions, loc),
|
||||||
.omit_discard => |id| switch (id) {
|
.omit_discard => |id| switch (id) {
|
||||||
.@"index capture" => try handleUnusedIndexCapture(builder, actions, loc),
|
.@"error capture" => try handleUnusedCapture(builder, actions, loc, remove_capture_actions),
|
||||||
.@"error capture" => try handleUnusedCapture(builder, actions, loc),
|
|
||||||
},
|
},
|
||||||
// the undeclared identifier may be a discard
|
// the undeclared identifier may be a discard
|
||||||
.undeclared_identifier => try handlePointlessDiscard(builder, actions, loc),
|
.undeclared_identifier => try handlePointlessDiscard(builder, actions, loc),
|
||||||
@ -166,64 +169,57 @@ fn handleUnusedVariableOrConstant(builder: *Builder, actions: *std.ArrayListUnma
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleUnusedIndexCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(types.CodeAction), loc: offsets.Loc) !void {
|
fn handleUnusedCapture(
|
||||||
|
builder: *Builder,
|
||||||
|
actions: *std.ArrayListUnmanaged(types.CodeAction),
|
||||||
|
loc: offsets.Loc,
|
||||||
|
remove_capture_actions: *std.AutoHashMapUnmanaged(types.Range, void),
|
||||||
|
) !void {
|
||||||
const tracy_zone = tracy.trace(@src());
|
const tracy_zone = tracy.trace(@src());
|
||||||
defer tracy_zone.end();
|
defer tracy_zone.end();
|
||||||
|
|
||||||
const capture_locs = getCaptureLoc(builder.handle.text, loc, true) orelse return;
|
const capture_loc = getCaptureLoc(builder.handle.text, loc) orelse return;
|
||||||
|
|
||||||
// TODO support discarding without modifying the capture
|
// look for next non-whitespace after last '|'. if its a '{' we can insert discards.
|
||||||
// by adding a discard in the block scope
|
// this means bare loop/switch captures (w/out curlies) aren't supported.
|
||||||
const is_value_discarded = std.mem.eql(u8, offsets.locToSlice(builder.handle.text, capture_locs.value), "_");
|
var block_start = capture_loc.end + 1;
|
||||||
if (is_value_discarded) {
|
while (block_start < builder.handle.text.len and
|
||||||
// |_, i| ->
|
std.ascii.isWhitespace(builder.handle.text[block_start])) : (block_start += 1)
|
||||||
// TODO fix formatting
|
{}
|
||||||
try actions.append(builder.arena, .{
|
if (builder.handle.text[block_start] != '{') {
|
||||||
.title = "remove capture",
|
return;
|
||||||
.kind = .quickfix,
|
|
||||||
.isPreferred = true,
|
|
||||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.loc, "")}),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// |v, i| -> |v|
|
|
||||||
// |v, _| -> |v|
|
|
||||||
try actions.append(builder.arena, .{
|
|
||||||
.title = "remove index capture",
|
|
||||||
.kind = .quickfix,
|
|
||||||
.isPreferred = true,
|
|
||||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(
|
|
||||||
.{ .start = capture_locs.value.end, .end = capture_locs.loc.end - 1 },
|
|
||||||
"",
|
|
||||||
)}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleUnusedCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(types.CodeAction), loc: offsets.Loc) !void {
|
const block_start_loc = offsets.Loc{ .start = block_start + 1, .end = block_start + 1 };
|
||||||
const tracy_zone = tracy.trace(@src());
|
const identifier_name = builder.handle.text[loc.start..loc.end];
|
||||||
defer tracy_zone.end();
|
const new_text = try createDiscardText(builder, identifier_name, block_start, true);
|
||||||
|
const action1 = .{
|
||||||
const capture_locs = getCaptureLoc(builder.handle.text, loc, false) orelse return;
|
|
||||||
|
|
||||||
// TODO support discarding without modifying the capture
|
|
||||||
// by adding a discard in the block scope
|
|
||||||
if (capture_locs.index != null) {
|
|
||||||
// |v, i| -> |_, i|
|
|
||||||
try actions.append(builder.arena, .{
|
|
||||||
.title = "discard capture",
|
.title = "discard capture",
|
||||||
.kind = .quickfix,
|
.kind = .@"source.fixAll",
|
||||||
.isPreferred = true,
|
.isPreferred = true,
|
||||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.value, "_")}),
|
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(block_start_loc, new_text)}),
|
||||||
});
|
};
|
||||||
} else {
|
const action2 = .{
|
||||||
// |v| ->
|
.title = "discard capture name",
|
||||||
// TODO fix formatting
|
.kind = .quickfix,
|
||||||
try actions.append(builder.arena, .{
|
.isPreferred = false,
|
||||||
|
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(loc, "_")}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// prevent adding duplicate 'remove capture' action.
|
||||||
|
// search for a matching action by comparing ranges.
|
||||||
|
const remove_cap_loc = builder.createTextEditLoc(capture_loc, "");
|
||||||
|
const gop = try remove_capture_actions.getOrPut(builder.arena, remove_cap_loc.range);
|
||||||
|
if (gop.found_existing)
|
||||||
|
try actions.appendSlice(builder.arena, &.{ action1, action2 })
|
||||||
|
else {
|
||||||
|
const action0 = types.CodeAction{
|
||||||
.title = "remove capture",
|
.title = "remove capture",
|
||||||
.kind = .quickfix,
|
.kind = .quickfix,
|
||||||
.isPreferred = true,
|
.isPreferred = false,
|
||||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.loc, "")}),
|
.edit = try builder.createWorkspaceEdit(&.{remove_cap_loc}),
|
||||||
});
|
};
|
||||||
|
try actions.appendSlice(builder.arena, &.{ action0, action1, action2 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,13 +370,11 @@ const DiagnosticKind = union(enum) {
|
|||||||
@"function parameter",
|
@"function parameter",
|
||||||
@"local constant",
|
@"local constant",
|
||||||
@"local variable",
|
@"local variable",
|
||||||
@"loop index capture",
|
@"switch tag capture",
|
||||||
capture,
|
capture,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DiscardCat = enum {
|
const DiscardCat = enum {
|
||||||
// "discard of index capture; omit it instead"
|
|
||||||
@"index capture",
|
|
||||||
// "discard of error capture; omit it instead"
|
// "discard of error capture; omit it instead"
|
||||||
@"error capture",
|
@"error capture",
|
||||||
};
|
};
|
||||||
@ -478,126 +472,49 @@ fn getDiscardLoc(text: []const u8, loc: offsets.Loc) ?offsets.Loc {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const CaptureLocs = struct {
|
/// takes the location of a capture ie `value` from `...|value...|...`.
|
||||||
loc: offsets.Loc,
|
/// returns the location from '|' until '|'
|
||||||
value: offsets.Loc,
|
fn getCaptureLoc(text: []const u8, loc: offsets.Loc) ?offsets.Loc {
|
||||||
index: ?offsets.Loc,
|
const start_pipe_position = blk: {
|
||||||
|
var i = loc.start;
|
||||||
|
while (true) : (i -= 1) {
|
||||||
|
if (text[i] == '|') break;
|
||||||
|
if (i == 0) return null;
|
||||||
|
}
|
||||||
|
break :blk i;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// takes the location of an identifier which is part of a payload `|value, index|`
|
const end_pipe_position = (std.mem.indexOfScalarPos(u8, text, start_pipe_position + 1, '|') orelse
|
||||||
/// and returns the location from '|' until '|' or null on failure
|
return null) + 1;
|
||||||
/// use `is_index_payload` to indicate whether `loc` points to `value` or `index`
|
|
||||||
fn getCaptureLoc(text: []const u8, loc: offsets.Loc, is_index_payload: bool) ?CaptureLocs {
|
|
||||||
const value_end = if (!is_index_payload) loc.end else found: {
|
|
||||||
// move back until we find a comma
|
|
||||||
const comma_position = found_comma: {
|
|
||||||
var i = loc.start - 1;
|
|
||||||
while (i != 0) : (i -= 1) {
|
|
||||||
switch (text[i]) {
|
|
||||||
' ' => continue,
|
|
||||||
',' => break :found_comma i,
|
|
||||||
else => return null,
|
|
||||||
}
|
|
||||||
} else return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// trim space
|
const trimmed = std.mem.trim(u8, text[start_pipe_position + 1 .. end_pipe_position - 1], &std.ascii.whitespace);
|
||||||
var i = comma_position - 1;
|
if (trimmed.len == 0) return null;
|
||||||
while (i != 0) : (i -= 1) {
|
|
||||||
switch (text[i]) {
|
|
||||||
' ' => continue,
|
|
||||||
else => {
|
|
||||||
if (!isSymbolChar(text[i])) return null;
|
|
||||||
break :found i + 1;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const value_start = if (!is_index_payload) loc.start else found: {
|
return .{ .start = start_pipe_position, .end = end_pipe_position };
|
||||||
// move back until we find a non identifier character
|
|
||||||
var i = value_end - 1;
|
|
||||||
while (i != 0) : (i -= 1) {
|
|
||||||
if (isSymbolChar(text[i])) continue;
|
|
||||||
switch (text[i]) {
|
|
||||||
' ', '|', '*' => break :found i + 1,
|
|
||||||
else => return null,
|
|
||||||
}
|
|
||||||
} else return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
var index: ?offsets.Loc = null;
|
|
||||||
|
|
||||||
if (is_index_payload) {
|
|
||||||
index = loc;
|
|
||||||
} else blk: {
|
|
||||||
// move forward until we find a comma
|
|
||||||
const comma_position = found_comma: {
|
|
||||||
var i = value_end;
|
|
||||||
while (i < text.len) : (i += 1) {
|
|
||||||
switch (text[i]) {
|
|
||||||
' ' => continue,
|
|
||||||
',' => break :found_comma i,
|
|
||||||
else => break :blk,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break :blk;
|
|
||||||
};
|
|
||||||
|
|
||||||
// trim space
|
|
||||||
const index_start = found_start: {
|
|
||||||
var i = comma_position + 1;
|
|
||||||
while (i < text.len) : (i += 1) {
|
|
||||||
switch (text[i]) {
|
|
||||||
' ' => continue,
|
|
||||||
else => {
|
|
||||||
if (!isSymbolChar(text[i])) break :blk;
|
|
||||||
break :found_start i;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break :blk;
|
|
||||||
};
|
|
||||||
|
|
||||||
// move forward until we find a non identifier character
|
|
||||||
var i = index_start + 1;
|
|
||||||
while (i < text.len) : (i += 1) {
|
|
||||||
if (isSymbolChar(text[i])) continue;
|
|
||||||
index = offsets.Loc{
|
|
||||||
.start = index_start,
|
|
||||||
.end = i,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const start_pipe_position = found: {
|
test "getCaptureLoc" {
|
||||||
var i = value_start - 1;
|
{
|
||||||
while (i != 0) : (i -= 1) {
|
const text = "|i|";
|
||||||
switch (text[i]) {
|
const caploc = getCaptureLoc(text, .{ .start = 1, .end = 2 }) orelse
|
||||||
' ' => continue,
|
return std.testing.expect(false);
|
||||||
'|' => break :found i,
|
const captext = text[caploc.start..caploc.end];
|
||||||
else => return null,
|
try std.testing.expectEqualStrings(text, captext);
|
||||||
}
|
}
|
||||||
} else return null;
|
{
|
||||||
};
|
const text = "|i, jjj, foobar|";
|
||||||
|
const caploc = getCaptureLoc(text, .{ .start = 1, .end = 17 }) orelse
|
||||||
const end_pipe_position = found: {
|
return std.testing.expect(false);
|
||||||
var i: usize = if (index) |index_loc| index_loc.end else value_end;
|
const captext = text[caploc.start..caploc.end];
|
||||||
while (i < text.len) : (i += 1) {
|
try std.testing.expectEqualStrings(text, captext);
|
||||||
switch (text[i]) {
|
|
||||||
' ' => continue,
|
|
||||||
'|' => break :found i + 1,
|
|
||||||
else => return null,
|
|
||||||
}
|
}
|
||||||
} else return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return CaptureLocs{
|
try std.testing.expect(getCaptureLoc("||", .{ .start = 1, .end = 2 }) == null);
|
||||||
.loc = .{ .start = start_pipe_position, .end = end_pipe_position },
|
try std.testing.expect(getCaptureLoc(" |", .{ .start = 1, .end = 2 }) == null);
|
||||||
.value = .{ .start = value_start, .end = value_end },
|
try std.testing.expect(getCaptureLoc("| ", .{ .start = 1, .end = 2 }) == null);
|
||||||
.index = index,
|
try std.testing.expect(getCaptureLoc("||", .{ .start = 1, .end = 1 }) == null);
|
||||||
};
|
try std.testing.expect(getCaptureLoc("| |", .{ .start = 1, .end = 3 }) == null);
|
||||||
|
try std.testing.expect(getCaptureLoc("| |", .{ .start = 1, .end = 6 }) == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isSymbolChar(char: u8) bool {
|
fn isSymbolChar(char: u8) bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user