Skip to content

Commit 9e89048

Browse files
xdBronchTechatrix
authored andcommitted
ensure document symbols don't have empty names
1 parent 7b9d079 commit 9e89048

File tree

2 files changed

+48
-10
lines changed

2 files changed

+48
-10
lines changed

src/features/document_symbol.zig

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const analysis = @import("../analysis.zig");
1010
const tracy = @import("tracy");
1111

1212
const Symbol = struct {
13-
name: []const u8,
13+
name_token: Ast.TokenIndex,
1414
detail: ?[]const u8 = null,
1515
kind: types.SymbolKind,
1616
loc: offsets.Loc,
@@ -27,6 +27,26 @@ const Context = struct {
2727
total_symbol_count: *usize,
2828
};
2929

30+
fn tokenNameMaybeQuotes(tree: *const Ast, token: Ast.TokenIndex) []const u8 {
31+
const token_slice = tree.tokenSlice(token);
32+
switch (tree.tokenTag(token)) {
33+
.identifier => return token_slice,
34+
.string_literal => {
35+
const name = token_slice[1 .. token_slice.len - 1];
36+
const trimmed = std.mem.trim(u8, name, &std.ascii.whitespace);
37+
// LSP spec requires that a symbol name not be empty or consisting only of whitespace,
38+
// don't trim the quotes in that case so there's something to present.
39+
// Leading and trailing whitespace might cause ambiguity depending on how the client shows symbols
40+
// so compensate for that as well
41+
if (name.len == 0 or name.len != trimmed.len)
42+
return token_slice;
43+
44+
return name;
45+
},
46+
else => unreachable,
47+
}
48+
}
49+
3050
fn callback(ctx: *Context, tree: *const Ast, node: Ast.Node.Index) error{OutOfMemory}!void {
3151
std.debug.assert(node != .root);
3252

@@ -52,7 +72,7 @@ fn callback(ctx: *Context, tree: *const Ast, node: Ast.Node.Index) error{OutOfMe
5272
};
5373

5474
break :blk .{
55-
.name = var_decl_name,
75+
.name_token = var_decl_name_token,
5676
.detail = null,
5777
.kind = kind,
5878
.loc = offsets.nodeToLoc(tree, node),
@@ -62,10 +82,10 @@ fn callback(ctx: *Context, tree: *const Ast, node: Ast.Node.Index) error{OutOfMe
6282
},
6383

6484
.test_decl => blk: {
65-
const test_name_token, const test_name = ast.testDeclNameAndToken(tree, node) orelse break :blk null;
85+
const test_name_token = tree.nodeData(node).opt_token_and_node[0].unwrap() orelse break :blk null;
6686

6787
break :blk .{
68-
.name = test_name,
88+
.name_token = test_name_token,
6989
.kind = .Method, // there is no SymbolKind that represents a tests
7090
.loc = offsets.nodeToLoc(tree, node),
7191
.selection_loc = offsets.tokenToLoc(tree, test_name_token),
@@ -79,7 +99,7 @@ fn callback(ctx: *Context, tree: *const Ast, node: Ast.Node.Index) error{OutOfMe
7999
const name_token = fn_info.name_token orelse break :blk null;
80100

81101
break :blk .{
82-
.name = offsets.identifierTokenToNameSlice(tree, name_token),
102+
.name_token = name_token,
83103
.detail = analysis.getFunctionSignature(tree, fn_info),
84104
.kind = .Function,
85105
.loc = offsets.nodeToLoc(tree, node),
@@ -124,10 +144,9 @@ fn callback(ctx: *Context, tree: *const Ast, node: Ast.Node.Index) error{OutOfMe
124144
if (is_struct and container_field.ast.tuple_like) break :blk null;
125145

126146
const decl_name_token = container_field.ast.main_token;
127-
const decl_name = offsets.tokenToSlice(tree, decl_name_token);
128147

129148
break :blk .{
130-
.name = decl_name,
149+
.name_token = decl_name_token,
131150
.detail = ctx.last_var_decl_name,
132151
.kind = kind,
133152
.loc = offsets.nodeToLoc(tree, node),
@@ -185,14 +204,15 @@ fn convertSymbols(
185204
var mappings: std.ArrayList(offsets.multiple.IndexToPositionMapping) = .empty;
186205
try mappings.ensureTotalCapacityPrecise(arena, total_symbol_count * 4);
187206

188-
const result = convertSymbolsInternal(from, &symbol_buffer, &mappings);
207+
const result = convertSymbolsInternal(tree, from, &symbol_buffer, &mappings);
189208

190209
offsets.multiple.indexToPositionWithMappings(tree.source, mappings.items, encoding);
191210

192211
return result;
193212
}
194213

195214
fn convertSymbolsInternal(
215+
tree: *const Ast,
196216
from: []const Symbol,
197217
symbol_buffer: *std.ArrayList(types.DocumentSymbol),
198218
mappings: *std.ArrayList(offsets.multiple.IndexToPositionMapping),
@@ -204,13 +224,13 @@ fn convertSymbolsInternal(
204224

205225
for (from, to) |symbol, *out| {
206226
out.* = .{
207-
.name = symbol.name,
227+
.name = tokenNameMaybeQuotes(tree, symbol.name_token),
208228
.detail = symbol.detail,
209229
.kind = symbol.kind,
210230
// will be set later through the mapping below
211231
.range = undefined,
212232
.selectionRange = undefined,
213-
.children = convertSymbolsInternal(symbol.children.items, symbol_buffer, mappings),
233+
.children = convertSymbolsInternal(tree, symbol.children.items, symbol_buffer, mappings),
214234
};
215235
mappings.appendSliceAssumeCapacity(&.{
216236
.{ .output = &out.range.start, .source_index = symbol.loc.start },

tests/lsp_features/document_symbol.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,24 @@ test "nested struct with self" {
9393
);
9494
}
9595

96+
test "decl names that are empty or contain whitespace return non-empty document symbol" {
97+
try testDocumentSymbol(
98+
\\test "" {}
99+
\\test " " {}
100+
\\test " a " {}
101+
\\const @"" = 0;
102+
\\const @" " = 0;
103+
\\const @" a " = 0;
104+
,
105+
\\Method ""
106+
\\Method " "
107+
\\Method " a "
108+
\\Constant @""
109+
\\Constant @" "
110+
\\Constant @" a "
111+
);
112+
}
113+
96114
fn testDocumentSymbol(source: []const u8, expected: []const u8) !void {
97115
var ctx: Context = try .init();
98116
defer ctx.deinit();

0 commit comments

Comments
 (0)