add tests for inlay hints
This commit is contained in:
parent
f0b71f3861
commit
4c0fe6428d
102
tests/helper.zig
Normal file
102
tests/helper.zig
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Placeholder = struct {
|
||||||
|
loc: Loc,
|
||||||
|
|
||||||
|
pub const Loc = std.zig.Token.Loc;
|
||||||
|
|
||||||
|
pub fn placeholderSlice(self: Placeholder, source: []const u8) []const u8 {
|
||||||
|
return source[self.loc.start..self.loc.end];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// returns an array of all placeholder locations
|
||||||
|
pub fn collectPlaceholder(allocator: std.mem.Allocator, source: []const u8) ![]Placeholder {
|
||||||
|
var placeholders = std.ArrayListUnmanaged(Placeholder){};
|
||||||
|
errdefer placeholders.deinit(allocator);
|
||||||
|
|
||||||
|
var source_index: usize = 0;
|
||||||
|
while (std.mem.indexOfScalarPos(u8, source, source_index, '<')) |start_index| {
|
||||||
|
const end_index = std.mem.indexOfScalarPos(u8, source, start_index + 1, '>') orelse return error.Invalid;
|
||||||
|
|
||||||
|
try placeholders.append(allocator, .{ .loc = .{
|
||||||
|
.start = start_index,
|
||||||
|
.end = end_index,
|
||||||
|
} });
|
||||||
|
|
||||||
|
source_index = end_index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return placeholders.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns `source` without any placeholders
|
||||||
|
pub fn clearPlaceholders(allocator: std.mem.Allocator, source: []const u8) ![]const u8 {
|
||||||
|
var output = std.ArrayListUnmanaged(u8){};
|
||||||
|
errdefer output.deinit(allocator);
|
||||||
|
|
||||||
|
var source_index: usize = 0;
|
||||||
|
while (std.mem.indexOfScalarPos(u8, source, source_index, '<')) |start_index| {
|
||||||
|
try output.appendSlice(allocator, source[source_index..start_index]);
|
||||||
|
|
||||||
|
source_index = std.mem.indexOfScalarPos(u8, source, start_index + 1, '>') orelse return error.Invalid;
|
||||||
|
source_index += 1;
|
||||||
|
}
|
||||||
|
try output.appendSlice(allocator, source[source_index..source.len]);
|
||||||
|
|
||||||
|
return output.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectClearPlaceholdersResult = struct {
|
||||||
|
/// placeholders relative to the `source` parameter in `collectClearPlaceholders`
|
||||||
|
placeholders: []Placeholder,
|
||||||
|
/// placeholders locations to `source`
|
||||||
|
placeholder_locations: []usize,
|
||||||
|
/// source without any placeholders
|
||||||
|
source: []const u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.placeholders);
|
||||||
|
allocator.free(self.placeholder_locations);
|
||||||
|
allocator.free(self.source);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// see `CollectClearPlaceholdersResult`
|
||||||
|
pub fn collectClearPlaceholders(allocator: std.mem.Allocator, source: []const u8) !CollectClearPlaceholdersResult {
|
||||||
|
var placeholders = std.ArrayListUnmanaged(Placeholder){};
|
||||||
|
errdefer placeholders.deinit(allocator);
|
||||||
|
|
||||||
|
var locations = std.ArrayListUnmanaged(usize){};
|
||||||
|
errdefer locations.deinit(allocator);
|
||||||
|
|
||||||
|
var new_source = std.ArrayListUnmanaged(u8){};
|
||||||
|
errdefer new_source.deinit(allocator);
|
||||||
|
|
||||||
|
var source_index: usize = 0;
|
||||||
|
var new_source_index: usize = 0;
|
||||||
|
while (std.mem.indexOfScalarPos(u8, source, source_index, '<')) |start_index| {
|
||||||
|
const end_index = std.mem.indexOfScalarPos(u8, source, start_index + 1, '>') orelse return error.Invalid;
|
||||||
|
|
||||||
|
const placeholder = Placeholder{ .loc = .{
|
||||||
|
.start = start_index + 1,
|
||||||
|
.end = end_index,
|
||||||
|
} };
|
||||||
|
|
||||||
|
try placeholders.append(allocator, placeholder);
|
||||||
|
|
||||||
|
new_source_index = new_source_index + (start_index - source_index);
|
||||||
|
try locations.append(allocator, new_source_index);
|
||||||
|
try new_source.appendSlice(allocator, source[source_index..start_index]);
|
||||||
|
|
||||||
|
source_index = end_index + 1;
|
||||||
|
}
|
||||||
|
try new_source.appendSlice(allocator, source[source_index..source.len]);
|
||||||
|
|
||||||
|
return CollectClearPlaceholdersResult{
|
||||||
|
.placeholders = placeholders.toOwnedSlice(allocator),
|
||||||
|
.placeholder_locations = locations.toOwnedSlice(allocator),
|
||||||
|
.source = new_source.toOwnedSlice(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
163
tests/lsp_features/inlay_hints.zig
Normal file
163
tests/lsp_features/inlay_hints.zig
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const zls = @import("zls");
|
||||||
|
|
||||||
|
const helper = @import("helper");
|
||||||
|
const Context = @import("context").Context;
|
||||||
|
|
||||||
|
const types = zls.types;
|
||||||
|
const requests = zls.requests;
|
||||||
|
|
||||||
|
const allocator: std.mem.Allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
test "inlayhints - empty" {
|
||||||
|
try testInlayHints("");
|
||||||
|
}
|
||||||
|
|
||||||
|
test "inlayhints - function call" {
|
||||||
|
try testInlayHints(
|
||||||
|
\\fn foo(alpha: u32) void {}
|
||||||
|
\\const _ = foo(<alpha>5);
|
||||||
|
);
|
||||||
|
try testInlayHints(
|
||||||
|
\\fn foo(alpha: u32, beta: u64) void {}
|
||||||
|
\\const _ = foo(<alpha>5,<beta>4);
|
||||||
|
);
|
||||||
|
try testInlayHints(
|
||||||
|
\\fn foo(alpha: u32, beta: u64) void {}
|
||||||
|
\\const _ = foo( <alpha>3 + 2 , <beta>(3 - 2));
|
||||||
|
);
|
||||||
|
try testInlayHints(
|
||||||
|
\\fn foo(alpha: u32, beta: u64) void {}
|
||||||
|
\\const _ = foo(
|
||||||
|
\\ <alpha>3 + 2,
|
||||||
|
\\ <beta>(3 - 2),
|
||||||
|
\\);
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "inlayhints - function self parameter" {
|
||||||
|
try testInlayHints(
|
||||||
|
\\const Foo = struct { pub fn bar(self: *Foo, alpha: u32) void {} };
|
||||||
|
\\const foo: Foo = .{};
|
||||||
|
\\const _ = foo.bar(<alpha>5);
|
||||||
|
);
|
||||||
|
try testInlayHints(
|
||||||
|
\\const Foo = struct { pub fn bar(_: Foo, alpha: u32, beta: []const u8) void {} };
|
||||||
|
\\const foo: Foo = .{};
|
||||||
|
\\const _ = foo.bar(<alpha>5,<beta>"");
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "inlayhints - builtin call" {
|
||||||
|
try testInlayHints(
|
||||||
|
\\const _ = @intCast(<DestType>u32,<int>5);
|
||||||
|
);
|
||||||
|
try testInlayHints(
|
||||||
|
\\const _ = @memcpy(<dest>null,<source>null,<byte_count>0);
|
||||||
|
);
|
||||||
|
|
||||||
|
try testInlayHints(
|
||||||
|
\\const _ = @sizeOf(u32);
|
||||||
|
);
|
||||||
|
try testInlayHints(
|
||||||
|
\\const _ = @TypeOf(5);
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testInlayHints(source: []const u8) !void {
|
||||||
|
const phr = try helper.collectClearPlaceholders(allocator, source);
|
||||||
|
defer phr.deinit(allocator);
|
||||||
|
|
||||||
|
var ctx = try Context.init();
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
ctx.server.config.enable_inlay_hints = true;
|
||||||
|
ctx.server.config.inlay_hints_exclude_single_argument = false;
|
||||||
|
ctx.server.config.inlay_hints_show_builtin = true;
|
||||||
|
|
||||||
|
const open_document = requests.OpenDocument{
|
||||||
|
.params = .{
|
||||||
|
.textDocument = .{
|
||||||
|
.uri = "file:///test.zig",
|
||||||
|
// .languageId = "zig",
|
||||||
|
// .version = 420,
|
||||||
|
.text = phr.source,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const did_open_method = try std.json.stringifyAlloc(allocator, open_document.params, .{});
|
||||||
|
defer allocator.free(did_open_method);
|
||||||
|
|
||||||
|
try ctx.request("textDocument/didOpen", did_open_method, null);
|
||||||
|
|
||||||
|
const range = types.Range{
|
||||||
|
.start = types.Position{ .line = 0, .character = 0 },
|
||||||
|
.end = sourceIndexPosition(phr.source, phr.source.len),
|
||||||
|
};
|
||||||
|
|
||||||
|
const method = try std.json.stringifyAlloc(allocator, .{
|
||||||
|
.textDocument = .{
|
||||||
|
.uri = "file:///test.zig",
|
||||||
|
},
|
||||||
|
.range = range,
|
||||||
|
}, .{});
|
||||||
|
defer allocator.free(method);
|
||||||
|
|
||||||
|
const response_bytes = try ctx.requestAlloc("textDocument/inlayHint", method);
|
||||||
|
defer allocator.free(response_bytes);
|
||||||
|
|
||||||
|
const InlayHint = struct {
|
||||||
|
position: types.Position,
|
||||||
|
label: []const u8,
|
||||||
|
kind: types.InlayHintKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Response = struct {
|
||||||
|
jsonrpc: []const u8,
|
||||||
|
id: types.RequestId,
|
||||||
|
result: []InlayHint,
|
||||||
|
};
|
||||||
|
|
||||||
|
const parse_options = std.json.ParseOptions{
|
||||||
|
.allocator = allocator,
|
||||||
|
.ignore_unknown_fields = true,
|
||||||
|
};
|
||||||
|
var token_stream = std.json.TokenStream.init(response_bytes);
|
||||||
|
var response = try std.json.parse(Response, &token_stream, parse_options);
|
||||||
|
defer std.json.parseFree(Response, response, parse_options);
|
||||||
|
|
||||||
|
const hints = response.result;
|
||||||
|
|
||||||
|
try std.testing.expectEqual(phr.placeholder_locations.len, hints.len);
|
||||||
|
|
||||||
|
outer: for (phr.placeholder_locations) |loc, i| {
|
||||||
|
const name = phr.placeholders[i].placeholderSlice(source);
|
||||||
|
|
||||||
|
const position = sourceIndexPosition(phr.source, loc);
|
||||||
|
|
||||||
|
for (hints) |hint| {
|
||||||
|
if (position.line != hint.position.line or position.character != hint.position.character) continue;
|
||||||
|
|
||||||
|
try std.testing.expect(hint.label.len != 0);
|
||||||
|
const trimmedLabel = hint.label[0 .. hint.label.len - 1]; // exclude :
|
||||||
|
try std.testing.expectEqualStrings(name, trimmedLabel);
|
||||||
|
try std.testing.expectEqual(types.InlayHintKind.Parameter, hint.kind);
|
||||||
|
|
||||||
|
continue :outer;
|
||||||
|
}
|
||||||
|
std.debug.print("Placeholder '{s}' at {}:{} (line:colon) not found!", .{ name, position.line, position.character });
|
||||||
|
return error.PlaceholderNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sourceIndexPosition(source: []const u8, index: usize) types.Position {
|
||||||
|
const line = std.mem.count(u8, source[0..index], &.{'\n'});
|
||||||
|
const last_line_index = if (std.mem.lastIndexOfScalar(u8, source[0..index], '\n')) |idx| idx + 1 else 0;
|
||||||
|
const last_line_character = index - last_line_index;
|
||||||
|
|
||||||
|
return types.Position{
|
||||||
|
.line = @intCast(i64, line),
|
||||||
|
.character = @intCast(i64, last_line_character),
|
||||||
|
};
|
||||||
|
}
|
@ -7,6 +7,7 @@ test {
|
|||||||
|
|
||||||
// LSP features
|
// LSP features
|
||||||
_ = @import("lsp_features/semantic_tokens.zig");
|
_ = @import("lsp_features/semantic_tokens.zig");
|
||||||
|
_ = @import("lsp_features/inlay_hints.zig");
|
||||||
|
|
||||||
// TODO Language features
|
// TODO Language features
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user