simplify analysis by introducing a Analysis Struct

This commit is contained in:
Techatrix 2023-03-12 23:48:31 +01:00 committed by Lee Cannon
parent 973d33d435
commit 0ff0a193cc
9 changed files with 375 additions and 432 deletions

View File

@ -7,7 +7,7 @@ const Config = @import("Config.zig");
const configuration = @import("configuration.zig");
const DocumentStore = @import("DocumentStore.zig");
const types = @import("lsp.zig");
const analysis = @import("analysis.zig");
const Analyser = @import("analysis.zig");
const ast = @import("ast.zig");
const references = @import("references.zig");
const offsets = @import("offsets.zig");
@ -37,6 +37,7 @@ const log = std.log.scoped(.zls_server);
config: *Config,
allocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
analyser: Analyser,
document_store: DocumentStore,
builtin_completions: ?std.ArrayListUnmanaged(types.CompletionItem),
client_capabilities: ClientCapabilities = .{},
@ -283,10 +284,10 @@ fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) error{OutO
if (func.extern_export_inline_token != null) break :blk;
if (func.name_token) |name_token| {
const is_type_function = analysis.isTypeFunction(tree, func);
const is_type_function = Analyser.isTypeFunction(tree, func);
const func_name = tree.tokenSlice(name_token);
if (!is_type_function and !analysis.isCamelCase(func_name)) {
if (!is_type_function and !Analyser.isCamelCase(func_name)) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, name_token, server.offset_encoding),
.severity = .Hint,
@ -294,7 +295,7 @@ fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) error{OutO
.source = "zls",
.message = "Functions should be camelCase",
});
} else if (is_type_function and !analysis.isPascalCase(func_name)) {
} else if (is_type_function and !Analyser.isPascalCase(func_name)) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, name_token, server.offset_encoding),
.severity = .Hint,
@ -512,7 +513,7 @@ pub fn autofix(server: *Server, allocator: std.mem.Allocator, handle: *const Doc
var builder = code_actions.Builder{
.arena = server.arena.allocator(),
.document_store = &server.document_store,
.analyser = &server.analyser,
.handle = handle,
.offset_encoding = server.offset_encoding,
};
@ -544,7 +545,7 @@ pub fn autofix(server: *Server, allocator: std.mem.Allocator, handle: *const Doc
pub fn typeToCompletion(
server: *Server,
list: *std.ArrayListUnmanaged(types.CompletionItem),
field_access: analysis.FieldAccessReturn,
field_access: Analyser.FieldAccessReturn,
orig_handle: *const DocumentStore.Handle,
either_descriptor: ?[]const u8,
) error{OutOfMemory}!void {
@ -620,8 +621,8 @@ pub fn typeToCompletion(
pub fn nodeToCompletion(
server: *Server,
list: *std.ArrayListUnmanaged(types.CompletionItem),
node_handle: analysis.NodeWithHandle,
unwrapped: ?analysis.TypeWithHandle,
node_handle: Analyser.NodeWithHandle,
unwrapped: ?Analyser.TypeWithHandle,
orig_handle: *const DocumentStore.Handle,
is_type_val: bool,
parent_is_type_val: ?bool,
@ -645,7 +646,7 @@ pub fn nodeToCompletion(
const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation);
const doc: Documentation = if (try analysis.getDocComments(
const doc: Documentation = if (try Analyser.getDocComments(
allocator,
handle.tree,
node,
@ -672,9 +673,7 @@ pub fn nodeToCompletion(
.parent_is_type_val = is_type_val,
.either_descriptor = either_descriptor,
};
try analysis.iterateSymbolsContainer(
allocator,
&server.document_store,
try server.analyser.iterateSymbolsContainer(
node_handle,
orig_handle,
declToCompletion,
@ -698,17 +697,17 @@ pub fn nodeToCompletion(
const use_snippets = server.config.enable_snippets and server.client_capabilities.supports_snippets;
const insert_text = if (use_snippets) blk: {
const skip_self_param = !(parent_is_type_val orelse true) and
try analysis.hasSelfParam(allocator, &server.document_store, handle, func);
break :blk try analysis.getFunctionSnippet(server.arena.allocator(), tree, func, skip_self_param);
try server.analyser.hasSelfParam(handle, func);
break :blk try Analyser.getFunctionSnippet(server.arena.allocator(), tree, func, skip_self_param);
} else tree.tokenSlice(func.name_token.?);
const is_type_function = analysis.isTypeFunction(handle.tree, func);
const is_type_function = Analyser.isTypeFunction(handle.tree, func);
try list.append(allocator, .{
.label = handle.tree.tokenSlice(name_token),
.kind = if (is_type_function) .Struct else .Function,
.documentation = doc,
.detail = analysis.getFunctionSignature(handle.tree, func),
.detail = Analyser.getFunctionSignature(handle.tree, func),
.insertText = insert_text,
.insertTextFormat = if (use_snippets) .Snippet else .PlainText,
});
@ -722,7 +721,7 @@ pub fn nodeToCompletion(
const var_decl = tree.fullVarDecl(node).?;
const is_const = token_tags[var_decl.ast.mut_token] == .keyword_const;
if (try analysis.resolveVarDeclAlias(allocator, &server.document_store, node_handle)) |result| {
if (try server.analyser.resolveVarDeclAlias(node_handle)) |result| {
const context = DeclToCompletionContext{
.server = server,
.completions = list,
@ -736,7 +735,7 @@ pub fn nodeToCompletion(
.label = handle.tree.tokenSlice(var_decl.ast.mut_token + 1),
.kind = if (is_const) .Constant else .Variable,
.documentation = doc,
.detail = analysis.getVariableSignature(tree, var_decl),
.detail = Analyser.getVariableSignature(tree, var_decl),
.insertText = tree.tokenSlice(var_decl.ast.mut_token + 1),
.insertTextFormat = .PlainText,
});
@ -750,7 +749,7 @@ pub fn nodeToCompletion(
.label = handle.tree.tokenSlice(field.ast.main_token),
.kind = if (field.ast.tuple_like) .Enum else .Field,
.documentation = doc,
.detail = analysis.getContainerFieldSignature(handle.tree, field),
.detail = Analyser.getContainerFieldSignature(handle.tree, field),
.insertText = tree.tokenSlice(field.ast.main_token),
.insertTextFormat = .PlainText,
});
@ -825,7 +824,7 @@ pub fn nodeToCompletion(
.insertTextFormat = .PlainText,
});
},
else => if (analysis.nodeToString(tree, node)) |string| {
else => if (Analyser.nodeToString(tree, node)) |string| {
try list.append(allocator, .{
.label = string,
.kind = .Field,
@ -842,12 +841,12 @@ pub fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) []
if (pos_index + 1 >= handle.text.len) return "";
var start_idx = pos_index;
while (start_idx > 0 and analysis.isSymbolChar(handle.text[start_idx - 1])) {
while (start_idx > 0 and Analyser.isSymbolChar(handle.text[start_idx - 1])) {
start_idx -= 1;
}
var end_idx = pos_index;
while (end_idx < handle.text.len and analysis.isSymbolChar(handle.text[end_idx])) {
while (end_idx < handle.text.len and Analyser.isSymbolChar(handle.text[end_idx])) {
end_idx += 1;
}
@ -857,7 +856,7 @@ pub fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) []
pub fn gotoDefinitionSymbol(
server: *Server,
decl_handle: analysis.DeclWithHandle,
decl_handle: Analyser.DeclWithHandle,
resolve_alias: bool,
) error{OutOfMemory}!?types.Location {
const tracy_zone = tracy.trace(@src());
@ -868,14 +867,14 @@ pub fn gotoDefinitionSymbol(
const name_token = switch (decl_handle.decl.*) {
.ast_node => |node| block: {
if (resolve_alias) {
if (try analysis.resolveVarDeclAlias(server.arena.allocator(), &server.document_store, .{ .node = node, .handle = handle })) |result| {
if (try server.analyser.resolveVarDeclAlias(.{ .node = node, .handle = handle })) |result| {
handle = result.handle;
break :block result.nameToken();
}
}
break :block analysis.getDeclNameToken(handle.tree, node) orelse return null;
break :block Analyser.getDeclNameToken(handle.tree, node) orelse return null;
},
else => decl_handle.nameToken(),
};
@ -886,7 +885,7 @@ pub fn gotoDefinitionSymbol(
};
}
pub fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle, markup_kind: types.MarkupKind) error{OutOfMemory}!?[]const u8 {
pub fn hoverSymbol(server: *Server, decl_handle: Analyser.DeclWithHandle, markup_kind: types.MarkupKind) error{OutOfMemory}!?[]const u8 {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
@ -897,27 +896,27 @@ pub fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle, markup
const def_str = switch (decl_handle.decl.*) {
.ast_node => |node| def: {
if (try analysis.resolveVarDeclAlias(server.arena.allocator(), &server.document_store, .{ .node = node, .handle = handle })) |result| {
if (try server.analyser.resolveVarDeclAlias(.{ .node = node, .handle = handle })) |result| {
return try server.hoverSymbol(result, markup_kind);
}
doc_str = try analysis.getDocComments(server.arena.allocator(), tree, node, markup_kind);
doc_str = try Analyser.getDocComments(server.arena.allocator(), tree, node, markup_kind);
var buf: [1]Ast.Node.Index = undefined;
if (tree.fullVarDecl(node)) |var_decl| {
break :def analysis.getVariableSignature(tree, var_decl);
break :def Analyser.getVariableSignature(tree, var_decl);
} else if (tree.fullFnProto(&buf, node)) |fn_proto| {
break :def analysis.getFunctionSignature(tree, fn_proto);
break :def Analyser.getFunctionSignature(tree, fn_proto);
} else if (tree.fullContainerField(node)) |field| {
break :def analysis.getContainerFieldSignature(tree, field);
break :def Analyser.getContainerFieldSignature(tree, field);
} else {
break :def analysis.nodeToString(tree, node) orelse return null;
break :def Analyser.nodeToString(tree, node) orelse return null;
}
},
.param_payload => |pay| def: {
const param = pay.param;
if (param.first_doc_comment) |doc_comments| {
doc_str = try analysis.collectDocComments(server.arena.allocator(), handle.tree, doc_comments, markup_kind, false);
doc_str = try Analyser.collectDocComments(server.arena.allocator(), handle.tree, doc_comments, markup_kind, false);
}
const first_token = ast.paramFirstToken(tree, param);
@ -936,9 +935,7 @@ pub fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle, markup
=> tree.tokenSlice(decl_handle.nameToken()),
};
var bound_type_params = analysis.BoundTypeParams{};
defer bound_type_params.deinit(server.document_store.allocator);
const resolved_type = try decl_handle.resolveType(server.arena.allocator(), &server.document_store, &bound_type_params);
const resolved_type = try decl_handle.resolveType(&server.analyser);
const resolved_type_str = if (resolved_type) |rt|
if (rt.type.is_type_val) switch (rt.type.data) {
@ -1002,28 +999,28 @@ pub fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle, markup
return hover_text;
}
pub fn getLabelGlobal(pos_index: usize, handle: *const DocumentStore.Handle) error{OutOfMemory}!?analysis.DeclWithHandle {
pub fn getLabelGlobal(pos_index: usize, handle: *const DocumentStore.Handle) error{OutOfMemory}!?Analyser.DeclWithHandle {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const name = identifierFromPosition(pos_index, handle.*);
if (name.len == 0) return null;
return try analysis.lookupLabel(handle, name, pos_index);
return try Analyser.lookupLabel(handle, name, pos_index);
}
pub fn getSymbolGlobal(
server: *Server,
pos_index: usize,
handle: *const DocumentStore.Handle,
) error{OutOfMemory}!?analysis.DeclWithHandle {
) error{OutOfMemory}!?Analyser.DeclWithHandle {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const name = identifierFromPosition(pos_index, handle.*);
if (name.len == 0) return null;
return try analysis.lookupSymbolGlobal(server.arena.allocator(), &server.document_store, handle, name, pos_index);
return try server.analyser.lookupSymbolGlobal(handle, name, pos_index);
}
pub fn gotoDefinitionLabel(
@ -1173,7 +1170,7 @@ pub fn getSymbolFieldAccesses(
handle: *const DocumentStore.Handle,
source_index: usize,
loc: offsets.Loc,
) error{OutOfMemory}!?[]const analysis.DeclWithHandle {
) error{OutOfMemory}!?[]const Analyser.DeclWithHandle {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
@ -1183,9 +1180,9 @@ pub fn getSymbolFieldAccesses(
var held_range = try server.arena.allocator().dupeZ(u8, offsets.locToSlice(handle.text, loc));
var tokenizer = std.zig.Tokenizer.init(held_range);
var decls_with_handles = std.ArrayListUnmanaged(analysis.DeclWithHandle){};
var decls_with_handles = std.ArrayListUnmanaged(Analyser.DeclWithHandle){};
if (try analysis.getFieldAccessType(server.arena.allocator(), &server.document_store, handle, source_index, &tokenizer)) |result| {
if (try server.analyser.getFieldAccessType(handle, source_index, &tokenizer)) |result| {
const container_handle = result.unwrapped orelse result.original;
const container_handle_nodes = try container_handle.getAllTypesWithHandles(server.arena.allocator());
@ -1195,9 +1192,7 @@ pub fn getSymbolFieldAccesses(
.other => |n| n,
else => continue,
};
try decls_with_handles.append(server.arena.allocator(), (try analysis.lookupSymbolContainer(
server.arena.allocator(),
&server.document_store,
try decls_with_handles.append(server.arena.allocator(), (try server.analyser.lookupSymbolContainer(
.{ .node = container_handle_node, .handle = ty.handle },
name,
true,
@ -1263,7 +1258,7 @@ pub fn hoverDefinitionFieldAccess(
pub fn gotoDefinitionString(
server: *Server,
pos_context: analysis.PositionContext,
pos_context: Analyser.PositionContext,
handle: *const DocumentStore.Handle,
) error{OutOfMemory}!?types.Location {
const tracy_zone = tracy.trace(@src());
@ -1320,7 +1315,7 @@ const DeclToCompletionContext = struct {
either_descriptor: ?[]const u8 = null,
};
pub fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) error{OutOfMemory}!void {
pub fn declToCompletion(context: DeclToCompletionContext, decl_handle: Analyser.DeclWithHandle) error{OutOfMemory}!void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
@ -1345,9 +1340,9 @@ pub fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.
const doc: Documentation = if (param.first_doc_comment) |doc_comments| .{ .MarkupContent = types.MarkupContent{
.kind = doc_kind,
.value = if (context.either_descriptor) |ed|
try std.fmt.allocPrint(allocator, "`Conditionally available: {s}`\n\n{s}", .{ ed, try analysis.collectDocComments(allocator, tree, doc_comments, doc_kind, false) })
try std.fmt.allocPrint(allocator, "`Conditionally available: {s}`\n\n{s}", .{ ed, try Analyser.collectDocComments(allocator, tree, doc_comments, doc_kind, false) })
else
try analysis.collectDocComments(allocator, tree, doc_comments, doc_kind, false),
try Analyser.collectDocComments(allocator, tree, doc_comments, doc_kind, false),
} } else null;
const first_token = ast.paramFirstToken(tree, param);
@ -1406,7 +1401,7 @@ pub fn completeLabel(
.completions = &completions,
.orig_handle = handle,
};
try analysis.iterateLabels(handle, pos_index, declToCompletion, context);
try Analyser.iterateLabels(handle, pos_index, declToCompletion, context);
return completions.toOwnedSlice(server.arena.allocator());
}
@ -1490,7 +1485,7 @@ pub fn completeGlobal(server: *Server, pos_index: usize, handle: *const Document
.completions = &completions,
.orig_handle = handle,
};
try analysis.iterateSymbolsGlobal(server.arena.allocator(), &server.document_store, handle, pos_index, declToCompletion, context);
try server.analyser.iterateSymbolsGlobal(handle, pos_index, declToCompletion, context);
try populateSnippedCompletions(server.arena.allocator(), &completions, &snipped_data.generic, server.config.*, null);
if (server.client_capabilities.label_details_support) {
@ -1513,7 +1508,7 @@ pub fn completeFieldAccess(server: *Server, handle: *const DocumentStore.Handle,
var held_loc = try allocator.dupeZ(u8, offsets.locToSlice(handle.text, loc));
var tokenizer = std.zig.Tokenizer.init(held_loc);
const result = (try analysis.getFieldAccessType(allocator, &server.document_store, handle, source_index, &tokenizer)) orelse return null;
const result = (try server.analyser.getFieldAccessType(handle, source_index, &tokenizer)) orelse return null;
try server.typeToCompletion(&completions, result, handle, null);
if (server.client_capabilities.label_details_support) {
for (completions.items) |*item| {
@ -1739,9 +1734,9 @@ pub fn completeFileSystemStringLiteral(
arena: std.mem.Allocator,
store: DocumentStore,
handle: DocumentStore.Handle,
pos_context: analysis.PositionContext,
pos_context: Analyser.PositionContext,
) ![]types.CompletionItem {
var completions: analysis.CompletionSet = .{};
var completions: Analyser.CompletionSet = .{};
const loc = pos_context.loc().?;
var completing = handle.tree.source[loc.start + 1 .. loc.end - 1];
@ -2217,6 +2212,9 @@ fn changeDocumentHandler(server: *Server, notification: types.DidChangeTextDocum
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
// whenever a document changes, any cached info is invalidated
server.analyser.invalidate();
const handle = server.document_store.getHandle(notification.textDocument.uri) orelse return;
const new_text = try diff.applyContentChanges(server.allocator, handle.text, notification.contentChanges, server.offset_encoding);
@ -2288,7 +2286,7 @@ fn semanticTokensFullHandler(server: *Server, request: types.SemanticTokensParam
const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null;
return try semantic_tokens.writeSemanticTokens(server.arena.allocator(), &server.document_store, handle, null, server.offset_encoding);
return try semantic_tokens.writeSemanticTokens(server.arena.allocator(), &server.analyser, handle, null, server.offset_encoding);
}
fn semanticTokensRangeHandler(server: *Server, request: types.SemanticTokensRangeParams) Error!?types.SemanticTokens {
@ -2300,7 +2298,7 @@ fn semanticTokensRangeHandler(server: *Server, request: types.SemanticTokensRang
const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null;
const loc = offsets.rangeToLoc(handle.tree.source, request.range, server.offset_encoding);
return try semantic_tokens.writeSemanticTokens(server.arena.allocator(), &server.document_store, handle, loc, server.offset_encoding);
return try semantic_tokens.writeSemanticTokens(server.arena.allocator(), &server.analyser, handle, loc, server.offset_encoding);
}
pub fn completionHandler(server: *Server, request: types.CompletionParams) Error!?types.CompletionList {
@ -2317,7 +2315,7 @@ pub fn completionHandler(server: *Server, request: types.CompletionParams) Error
}
const source_index = offsets.positionToIndex(handle.text, request.position, server.offset_encoding);
const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.text, source_index, false);
const pos_context = try Analyser.getPositionContext(server.arena.allocator(), handle.text, source_index, false);
const maybe_completions = switch (pos_context) {
.builtin => try server.completeBuiltin(),
@ -2345,7 +2343,7 @@ pub fn completionHandler(server: *Server, request: types.CompletionParams) Error
// The cursor is in the middle of a word or before a @, so we can replace
// the remaining identifier with the completion instead of just inserting.
// TODO Identify function call/struct init and replace the whole thing.
const lookahead_context = try analysis.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
const lookahead_context = try Analyser.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
if (server.client_capabilities.supports_apply_edits and
pos_context != .import_string_literal and
pos_context != .cinclude_string_literal and
@ -2403,7 +2401,7 @@ pub fn signatureHelpHandler(server: *Server, request: types.SignatureHelpParams)
const source_index = offsets.positionToIndex(handle.text, request.position, server.offset_encoding);
const signature_info = (try getSignatureInfo(
&server.document_store,
&server.analyser,
server.arena.allocator(),
handle,
source_index,
@ -2429,7 +2427,7 @@ pub fn gotoHandler(server: *Server, request: types.TextDocumentPositionParams, r
if (request.position.character == 0) return null;
const source_index = offsets.positionToIndex(handle.text, request.position, server.offset_encoding);
const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
const pos_context = try Analyser.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
return switch (pos_context) {
.builtin => |loc| .{ .Location = (try server.gotoDefinitionBuiltin(handle, loc)) orelse return null },
@ -2473,7 +2471,7 @@ pub fn hoverHandler(server: *Server, request: types.HoverParams) Error!?types.Ho
if (request.position.character == 0) return null;
const source_index = offsets.positionToIndex(handle.text, request.position, server.offset_encoding);
const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
const pos_context = try Analyser.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
const response = switch (pos_context) {
.builtin => try server.hoverDefinitionBuiltin(source_index, handle),
@ -2636,7 +2634,7 @@ pub fn generalReferencesHandler(server: *Server, request: GeneralReferencesReque
if (request.position().character <= 0) return null;
const source_index = offsets.positionToIndex(handle.text, request.position(), server.offset_encoding);
const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
const pos_context = try Analyser.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
// TODO: Make this work with branching types
const decl = switch (pos_context) {
@ -2663,7 +2661,7 @@ pub fn generalReferencesHandler(server: *Server, request: GeneralReferencesReque
else
try references.symbolReferences(
allocator,
&server.document_store,
&server.analyser,
decl,
server.offset_encoding,
include_decl,
@ -2736,7 +2734,7 @@ fn inlayHintHandler(server: *Server, request: types.InlayHintParams) Error!?[]ty
const hints = try inlay_hints.writeRangeInlayHint(
server.arena.allocator(),
server.config.*,
&server.document_store,
&server.analyser,
handle,
loc,
hover_kind,
@ -2786,7 +2784,7 @@ fn codeActionHandler(server: *Server, request: types.CodeActionParams) Error!?[]
var builder = code_actions.Builder{
.arena = server.arena.allocator(),
.document_store = &server.document_store,
.analyser = &server.analyser,
.handle = handle,
.offset_encoding = server.offset_encoding,
};
@ -3203,16 +3201,12 @@ pub fn create(
recording_enabled: bool,
replay_enabled: bool,
) !*Server {
// TODO replace global with something like an Analyser struct
// which contains using_trail & resolve_trail and place it inside Server
// see: https://github.com/zigtools/zls/issues/536
analysis.init(allocator);
const server = try allocator.create(Server);
server.* = Server{
.config = config,
.runtime_zig_version = null,
.allocator = allocator,
.analyser = undefined,
.arena = std.heap.ArenaAllocator.init(allocator),
.document_store = .{
.allocator = allocator,
@ -3224,6 +3218,7 @@ pub fn create(
.replay_enabled = replay_enabled,
.status = .uninitialized,
};
server.analyser = Analyser.init(allocator, server.arena.allocator(), &server.document_store);
try configuration.configChanged(config, &server.runtime_zig_version, allocator, config_path);
@ -3232,7 +3227,7 @@ pub fn create(
pub fn destroy(server: *Server) void {
server.document_store.deinit();
analysis.deinit();
server.analyser.deinit();
if (server.builtin_completions) |*completions| completions.deinit(server.allocator);

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Ast = std.zig.Ast;
const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig");
const Analyser = @import("analysis.zig");
const ast = @import("ast.zig");
const types = @import("lsp.zig");
@ -10,7 +10,7 @@ const offsets = @import("offsets.zig");
pub const Builder = struct {
arena: std.mem.Allocator,
document_store: *DocumentStore,
analyser: *Analyser,
handle: *const DocumentStore.Handle,
offset_encoding: offsets.Encoding,
@ -88,9 +88,7 @@ fn handleUnusedFunctionParameter(builder: *Builder, actions: *std.ArrayListUnman
const token_starts = tree.tokens.items(.start);
const decl = (try analysis.lookupSymbolGlobal(
builder.arena,
builder.document_store,
const decl = (try builder.analyser.lookupSymbolGlobal(
builder.handle,
identifier_name,
loc.start,
@ -134,9 +132,7 @@ fn handleUnusedVariableOrConstant(builder: *Builder, actions: *std.ArrayListUnma
const token_tags = tree.tokens.items(.tag);
const token_starts = tree.tokens.items(.start);
const decl = (try analysis.lookupSymbolGlobal(
builder.arena,
builder.document_store,
const decl = (try builder.analyser.lookupSymbolGlobal(
builder.handle,
identifier_name,
loc.start,

View File

@ -1,7 +1,7 @@
const std = @import("std");
const zig_builtin = @import("builtin");
const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig");
const Analyser = @import("analysis.zig");
const types = @import("lsp.zig");
const offsets = @import("offsets.zig");
const tracy = @import("tracy.zig");
@ -26,7 +26,7 @@ pub const InlayHint = struct {
const Builder = struct {
arena: std.mem.Allocator,
store: *DocumentStore,
analyser: *Analyser,
config: *const Config,
handle: *const DocumentStore.Handle,
hints: std.ArrayListUnmanaged(InlayHint),
@ -64,7 +64,7 @@ const Builder = struct {
/// `call` is the function call
/// `decl_handle` should be a function protototype
/// writes parameter hints into `builder.hints`
fn writeCallHint(builder: *Builder, call: Ast.full.Call, decl_handle: analysis.DeclWithHandle) !void {
fn writeCallHint(builder: *Builder, call: Ast.full.Call, decl_handle: Analyser.DeclWithHandle) !void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
@ -85,7 +85,7 @@ fn writeCallHint(builder: *Builder, call: Ast.full.Call, decl_handle: analysis.D
var i: usize = 0;
var it = fn_proto.iterate(&decl_tree);
if (try analysis.hasSelfParam(builder.arena, builder.store, decl_handle.handle, fn_proto)) {
if (try builder.analyser.hasSelfParam(decl_handle.handle, fn_proto)) {
_ = ast.nextFnParam(&it);
}
@ -187,7 +187,7 @@ fn writeCallNodeHint(builder: *Builder, call: Ast.full.Call) !void {
const source_index = offsets.tokenToIndex(tree, main_tokens[call.ast.fn_expr]);
const name = offsets.tokenToSlice(tree, main_tokens[call.ast.fn_expr]);
if (try analysis.lookupSymbolGlobal(builder.arena, builder.store, handle, name, source_index)) |decl_handle| {
if (try builder.analyser.lookupSymbolGlobal(handle, name, source_index)) |decl_handle| {
try writeCallHint(builder, call, decl_handle);
}
},
@ -204,13 +204,11 @@ fn writeCallNodeHint(builder: *Builder, call: Ast.full.Call) !void {
// note: we have the ast node, traversing it would probably yield better results
// than trying to re-tokenize and re-parse it
if (try analysis.getFieldAccessType(builder.arena, builder.store, handle, rhs_loc.end, &tokenizer)) |result| {
if (try builder.analyser.getFieldAccessType(handle, rhs_loc.end, &tokenizer)) |result| {
const container_handle = result.unwrapped orelse result.original;
switch (container_handle.type.data) {
.other => |container_handle_node| {
if (try analysis.lookupSymbolContainer(
builder.arena,
builder.store,
if (try builder.analyser.lookupSymbolContainer(
.{ .node = container_handle_node, .handle = container_handle.handle },
tree.tokenSlice(rhsToken),
true,
@ -285,7 +283,7 @@ fn writeNodeInlayHint(
pub fn writeRangeInlayHint(
arena: std.mem.Allocator,
config: Config,
store: *DocumentStore,
analyser: *Analyser,
handle: *const DocumentStore.Handle,
loc: offsets.Loc,
hover_kind: types.MarkupKind,
@ -295,7 +293,7 @@ pub fn writeRangeInlayHint(
var builder: Builder = .{
.arena = arena,
.store = store,
.analyser = analyser,
.config = &config,
.handle = handle,
.hints = .{},

View File

@ -1,7 +1,7 @@
const std = @import("std");
const Ast = std.zig.Ast;
const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig");
const Analyser = @import("analysis.zig");
const types = @import("lsp.zig");
const offsets = @import("offsets.zig");
const log = std.log.scoped(.zls_references);
@ -9,7 +9,7 @@ const ast = @import("ast.zig");
pub fn labelReferences(
allocator: std.mem.Allocator,
decl: analysis.DeclWithHandle,
decl: Analyser.DeclWithHandle,
encoding: offsets.Encoding,
include_decl: bool,
) error{OutOfMemory}!std.ArrayListUnmanaged(types.Location) {
@ -57,8 +57,8 @@ const Builder = struct {
allocator: std.mem.Allocator,
locations: std.ArrayListUnmanaged(types.Location) = .{},
/// this is the declaration we are searching for
decl_handle: analysis.DeclWithHandle,
store: *DocumentStore,
decl_handle: Analyser.DeclWithHandle,
analyser: *Analyser,
encoding: offsets.Encoding,
const Context = struct {
@ -98,12 +98,10 @@ const Builder = struct {
.identifier,
.test_decl,
=> {
const identifier_token = analysis.getDeclNameToken(tree, node).?;
const identifier_token = Analyser.getDeclNameToken(tree, node).?;
if (token_tags[identifier_token] != .identifier) return;
const child = (try analysis.lookupSymbolGlobal(
builder.allocator,
builder.store,
const child = (try builder.analyser.lookupSymbolGlobal(
handle,
offsets.tokenToSlice(tree, identifier_token),
starts[identifier_token],
@ -114,18 +112,8 @@ const Builder = struct {
}
},
.field_access => {
var bound_type_params = analysis.BoundTypeParams{};
defer bound_type_params.deinit(builder.store.allocator);
const left_type = try analysis.resolveFieldAccessLhsType(
builder.allocator,
builder.store,
(try analysis.resolveTypeOfNodeInternal(
builder.allocator,
builder.store,
.{ .node = datas[node].lhs, .handle = handle },
&bound_type_params,
)) orelse return,
&bound_type_params,
const left_type = try builder.analyser.resolveFieldAccessLhsType(
(try builder.analyser.resolveTypeOfNode(.{ .node = datas[node].lhs, .handle = handle })) orelse return,
);
const left_type_node = switch (left_type.type.data) {
@ -133,9 +121,7 @@ const Builder = struct {
else => return,
};
const child = (try analysis.lookupSymbolContainer(
self.builder.allocator,
builder.store,
const child = (try builder.analyser.lookupSymbolContainer(
.{ .node = left_type_node, .handle = left_type.handle },
offsets.tokenToSlice(tree, datas[node].rhs),
!left_type.type.is_type_val,
@ -152,8 +138,8 @@ const Builder = struct {
pub fn symbolReferences(
allocator: std.mem.Allocator,
store: *DocumentStore,
decl_handle: analysis.DeclWithHandle,
analyser: *Analyser,
decl_handle: Analyser.DeclWithHandle,
encoding: offsets.Encoding,
/// add `decl_handle` as a references
include_decl: bool,
@ -166,7 +152,7 @@ pub fn symbolReferences(
var builder = Builder{
.allocator = allocator,
.store = store,
.analyser = analyser,
.decl_handle = decl_handle,
.encoding = encoding,
};
@ -194,7 +180,7 @@ pub fn symbolReferences(
dependencies.deinit(allocator);
}
for (store.handles.values()) |handle| {
for (analyser.store.handles.values()) |handle| {
if (skip_std_references and std.mem.indexOf(u8, handle.uri, "std") != null) {
if (!include_decl or !std.mem.eql(u8, handle.uri, curr_handle.uri))
continue;
@ -207,7 +193,7 @@ pub fn symbolReferences(
}
handle_dependencies.deinit(allocator);
}
try store.collectDependencies(allocator, handle.*, &handle_dependencies);
try analyser.store.collectDependencies(allocator, handle.*, &handle_dependencies);
try dependencies.ensureUnusedCapacity(allocator, handle_dependencies.items.len);
for (handle_dependencies.items) |uri| {
@ -217,7 +203,7 @@ pub fn symbolReferences(
for (dependencies.keys()) |uri| {
if (std.mem.eql(u8, uri, curr_handle.uri)) continue;
const handle = store.getHandle(uri) orelse continue;
const handle = analyser.store.getHandle(uri) orelse continue;
try builder.collectReferences(handle, 0);
}

View File

@ -2,7 +2,7 @@ const std = @import("std");
const zig_builtin = @import("builtin");
const offsets = @import("offsets.zig");
const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig");
const Analyser = @import("analysis.zig");
const Ast = std.zig.Ast;
const ast = @import("ast.zig");
const types = @import("lsp.zig");
@ -40,7 +40,7 @@ pub const TokenModifiers = packed struct(u16) {
const Builder = struct {
arena: std.mem.Allocator,
store: *DocumentStore,
analyser: *Analyser,
handle: *const DocumentStore.Handle,
previous_source_index: usize = 0,
previous_token: ?Ast.TokenIndex = null,
@ -203,7 +203,7 @@ fn fieldTokenType(container_decl: Ast.Node.Index, handle: *const DocumentStore.H
});
}
fn colorIdentifierBasedOnType(builder: *Builder, type_node: analysis.TypeWithHandle, target_tok: Ast.TokenIndex, tok_mod: TokenModifiers) !void {
fn colorIdentifierBasedOnType(builder: *Builder, type_node: Analyser.TypeWithHandle, target_tok: Ast.TokenIndex, tok_mod: TokenModifiers) !void {
if (type_node.type.is_type_val) {
var new_tok_mod = tok_mod;
if (type_node.isNamespace())
@ -300,7 +300,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
.aligned_var_decl,
=> {
const var_decl = tree.fullVarDecl(node).?;
if (analysis.getDocCommentTokenIndex(token_tags, main_token)) |comment_idx|
if (Analyser.getDocCommentTokenIndex(token_tags, main_token)) |comment_idx|
try writeDocComments(builder, tree, comment_idx);
try writeToken(builder, var_decl.visib_token, .keyword);
@ -309,7 +309,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
try writeToken(builder, var_decl.comptime_token, .keyword);
try writeToken(builder, var_decl.ast.mut_token, .keyword);
if (try analysis.resolveTypeOfNode(allocator, builder.store, .{ .node = node, .handle = handle })) |decl_type| {
if (try builder.analyser.resolveTypeOfNode(.{ .node = node, .handle = handle })) |decl_type| {
try colorIdentifierBasedOnType(builder, decl_type, var_decl.ast.mut_token + 1, .{ .declaration = true });
} else {
try writeTokenMod(builder, var_decl.ast.mut_token + 1, .variable, .{ .declaration = true });
@ -393,15 +393,13 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
if (std.mem.eql(u8, name, "_")) {
return;
} else if (analysis.isValueIdent(name)) {
} else if (Analyser.isValueIdent(name)) {
return try writeToken(builder, main_token, .keywordLiteral);
} else if (analysis.isTypeIdent(name)) {
} else if (Analyser.isTypeIdent(name)) {
return try writeToken(builder, main_token, .type);
}
if (try analysis.lookupSymbolGlobal(
allocator,
builder.store,
if (try builder.analyser.lookupSymbolGlobal(
handle,
name,
tree.tokens.items(.start)[main_token],
@ -409,10 +407,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
if (child.decl.* == .param_payload) {
return try writeToken(builder, main_token, .parameter);
}
var bound_type_params = analysis.BoundTypeParams{};
defer bound_type_params.deinit(builder.store.allocator);
if (try child.resolveType(allocator, builder.store, &bound_type_params)) |decl_type| {
if (try child.resolveType(builder.analyser)) |decl_type| {
return try colorIdentifierBasedOnType(builder, decl_type, main_token, .{});
}
}
@ -426,7 +421,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
=> {
var buf: [1]Ast.Node.Index = undefined;
const fn_proto: Ast.full.FnProto = tree.fullFnProto(&buf, node).?;
if (analysis.getDocCommentTokenIndex(token_tags, main_token)) |docs|
if (Analyser.getDocCommentTokenIndex(token_tags, main_token)) |docs|
try writeDocComments(builder, tree, docs);
try writeToken(builder, fn_proto.visib_token, .keyword);
@ -434,14 +429,14 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
try writeToken(builder, fn_proto.lib_name, .string);
try writeToken(builder, fn_proto.ast.fn_token, .keyword);
const func_name_tok_type: TokenType = if (analysis.isTypeFunction(tree, fn_proto))
const func_name_tok_type: TokenType = if (Analyser.isTypeFunction(tree, fn_proto))
.type
else
.function;
const tok_mod = TokenModifiers{
.declaration = true,
.generic = analysis.isGenericFunction(tree, fn_proto),
.generic = Analyser.isGenericFunction(tree, fn_proto),
};
try writeTokenMod(builder, fn_proto.name_token, func_name_tok_type, tok_mod);
@ -477,7 +472,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
.@"comptime",
.@"nosuspend",
=> {
if (analysis.getDocCommentTokenIndex(token_tags, main_token)) |doc|
if (Analyser.getDocCommentTokenIndex(token_tags, main_token)) |doc|
try writeDocComments(builder, tree, doc);
try writeToken(builder, main_token, .keyword);
try callWriteNodeTokens(allocator, .{ builder, node_data[node].lhs });
@ -617,9 +612,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
if (struct_init.ast.type_expr != 0) {
try callWriteNodeTokens(allocator, .{ builder, struct_init.ast.type_expr });
field_token_type = if (try analysis.resolveTypeOfNode(
allocator,
builder.store,
field_token_type = if (try builder.analyser.resolveTypeOfNode(
.{ .node = struct_init.ast.type_expr, .handle = handle },
)) |struct_type| switch (struct_type.type.data) {
.other => |type_node| if (ast.isContainer(struct_type.handle.tree, type_node))
@ -775,7 +768,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
.asm_input,
=> unreachable,
.test_decl => {
if (analysis.getDocCommentTokenIndex(token_tags, main_token)) |doc|
if (Analyser.getDocCommentTokenIndex(token_tags, main_token)) |doc|
try writeDocComments(builder, tree, doc);
try writeToken(builder, main_token, .keyword);
@ -869,27 +862,14 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
// TODO This is basically exactly the same as what is done in analysis.resolveTypeOfNode, with the added
// writeToken code.
// Maybe we can hook into it instead? Also applies to Identifier and VarDecl
var bound_type_params = analysis.BoundTypeParams{};
defer bound_type_params.deinit(builder.store.allocator);
const lhs_type = try analysis.resolveFieldAccessLhsType(
allocator,
builder.store,
(try analysis.resolveTypeOfNodeInternal(
allocator,
builder.store,
.{ .node = data.lhs, .handle = handle },
&bound_type_params,
)) orelse return,
&bound_type_params,
const lhs_type = try builder.analyser.resolveFieldAccessLhsType(
(try builder.analyser.resolveTypeOfNode(.{ .node = data.lhs, .handle = handle })) orelse return,
);
const left_type_node = switch (lhs_type.type.data) {
.other => |n| n,
else => return,
};
if (try analysis.lookupSymbolContainer(
allocator,
builder.store,
if (try builder.analyser.lookupSymbolContainer(
.{ .node = left_type_node, .handle = lhs_type.handle },
tree.tokenSlice(data.rhs),
!lhs_type.type.is_type_val,
@ -913,7 +893,7 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
else => {},
}
if (try decl_type.resolveType(allocator, builder.store, &bound_type_params)) |resolved_type| {
if (try decl_type.resolveType(builder.analyser)) |resolved_type| {
try colorIdentifierBasedOnType(builder, resolved_type, data.rhs, .{});
}
}
@ -992,7 +972,7 @@ fn writeContainerField(builder: *Builder, node: Ast.Node.Index, field_token_type
var allocator = builder.arena;
if (analysis.getDocCommentTokenIndex(tokens, base)) |docs|
if (Analyser.getDocCommentTokenIndex(tokens, base)) |docs|
try writeDocComments(builder, tree, docs);
try writeToken(builder, container_field.comptime_token, .keyword);
@ -1026,14 +1006,14 @@ fn writeContainerField(builder: *Builder, node: Ast.Node.Index, field_token_type
/// TODO edit version.
pub fn writeSemanticTokens(
arena: std.mem.Allocator,
store: *DocumentStore,
analyser: *Analyser,
handle: *const DocumentStore.Handle,
loc: ?offsets.Loc,
encoding: offsets.Encoding,
) error{OutOfMemory}!types.SemanticTokens {
var builder = Builder{
.arena = arena,
.store = store,
.analyser = analyser,
.handle = handle,
.encoding = encoding,
};

View File

@ -1,5 +1,5 @@
const std = @import("std");
const analysis = @import("analysis.zig");
const Analyser = @import("analysis.zig");
const offsets = @import("offsets.zig");
const DocumentStore = @import("DocumentStore.zig");
const types = @import("lsp.zig");
@ -8,14 +8,14 @@ const Token = std.zig.Token;
const identifierFromPosition = @import("Server.zig").identifierFromPosition;
const ast = @import("ast.zig");
fn fnProtoToSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocator, commas: u32, skip_self_param: bool, handle: *const DocumentStore.Handle, fn_node: Ast.Node.Index, proto: Ast.full.FnProto) !types.SignatureInformation {
fn fnProtoToSignatureInfo(analyser: *Analyser, alloc: std.mem.Allocator, commas: u32, skip_self_param: bool, handle: *const DocumentStore.Handle, fn_node: Ast.Node.Index, proto: Ast.full.FnProto) !types.SignatureInformation {
const tree = handle.tree;
const token_starts = tree.tokens.items(.start);
const label = analysis.getFunctionSignature(tree, proto);
const proto_comments = (try analysis.getDocComments(alloc, tree, fn_node, .markdown)) orelse "";
const label = Analyser.getFunctionSignature(tree, proto);
const proto_comments = (try Analyser.getDocComments(alloc, tree, fn_node, .markdown)) orelse "";
const arg_idx = if (skip_self_param) blk: {
const has_self_param = try analysis.hasSelfParam(alloc, document_store, handle, proto);
const has_self_param = try analyser.hasSelfParam(handle, proto);
break :blk commas + @boolToInt(has_self_param);
} else commas;
@ -23,7 +23,7 @@ fn fnProtoToSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocat
var param_it = proto.iterate(&tree);
while (ast.nextFnParam(&param_it)) |param| {
const param_comments = if (param.first_doc_comment) |dc|
try analysis.collectDocComments(alloc, tree, dc, .markdown, false)
try Analyser.collectDocComments(alloc, tree, dc, .markdown, false)
else
"";
@ -70,8 +70,8 @@ fn fnProtoToSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocat
};
}
pub fn getSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocator, handle: *const DocumentStore.Handle, absolute_index: usize, comptime data: type) !?types.SignatureInformation {
const innermost_block = analysis.innermostBlockScope(handle.*, absolute_index);
pub fn getSignatureInfo(analyser: *Analyser, alloc: std.mem.Allocator, handle: *const DocumentStore.Handle, absolute_index: usize, comptime data: type) !?types.SignatureInformation {
const innermost_block = Analyser.innermostBlockScope(handle.*, absolute_index);
const tree = handle.tree;
const token_tags = tree.tokens.items(.tag);
const token_starts = tree.tokens.items(.start);
@ -256,9 +256,7 @@ pub fn getSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocator
// Resolve the expression.
var tokenizer = std.zig.Tokenizer.init(held_expr);
if (try analysis.getFieldAccessType(
alloc,
document_store,
if (try analyser.getFieldAccessType(
handle,
expr_start,
&tokenizer,
@ -275,7 +273,7 @@ pub fn getSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocator
var buf: [1]Ast.Node.Index = undefined;
if (type_handle.handle.tree.fullFnProto(&buf, node)) |proto| {
return try fnProtoToSignatureInfo(
document_store,
analyser,
alloc,
paren_commas,
false,
@ -292,9 +290,7 @@ pub fn getSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocator
}
const skip_self_param = !type_handle.type.is_type_val;
const decl_handle = (try analysis.lookupSymbolContainer(
alloc,
document_store,
const decl_handle = (try analyser.lookupSymbolContainer(
.{ .node = node, .handle = type_handle.handle },
name,
true,
@ -311,9 +307,7 @@ pub fn getSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocator
},
};
if (try analysis.resolveVarDeclAlias(
alloc,
document_store,
if (try analyser.resolveVarDeclAlias(
.{ .node = node, .handle = decl_handle.handle },
)) |resolved| {
switch (resolved.decl.*) {
@ -327,7 +321,7 @@ pub fn getSignatureInfo(document_store: *DocumentStore, alloc: std.mem.Allocator
if (res_handle.tree.fullFnProto(&buf, node)) |proto| {
return try fnProtoToSignatureInfo(
document_store,
analyser,
alloc,
paren_commas,
skip_self_param,

View File

@ -2,7 +2,7 @@
//! zigbot9001 to take advantage of zls' tools
pub const ast = @import("ast.zig");
pub const analysis = @import("analysis.zig");
pub const Analyser = @import("analysis.zig");
pub const Header = @import("Header.zig");
pub const debug = @import("debug.zig");
pub const offsets = @import("offsets.zig");

View File

@ -1,7 +1,7 @@
const std = @import("std");
const zls = @import("zls");
const analysis = zls.analysis;
const Analyser = zls.Analyser;
const types = zls.types;
const offsets = zls.offsets;
@ -510,12 +510,12 @@ test "position context - empty" {
);
}
fn testContext(line: []const u8, tag: std.meta.Tag(analysis.PositionContext), maybe_range: ?[]const u8) !void {
fn testContext(line: []const u8, tag: std.meta.Tag(Analyser.PositionContext), maybe_range: ?[]const u8) !void {
const cursor_idx = std.mem.indexOf(u8, line, "<cursor>").?;
const final_line = try std.mem.concat(allocator, u8, &.{ line[0..cursor_idx], line[cursor_idx + "<cursor>".len ..] });
defer allocator.free(final_line);
const ctx = try analysis.getPositionContext(allocator, final_line, cursor_idx, true);
const ctx = try Analyser.getPositionContext(allocator, final_line, cursor_idx, true);
if (std.meta.activeTag(ctx) != tag) {
std.debug.print("Expected tag `{s}`, got `{s}`\n", .{ @tagName(tag), @tagName(std.meta.activeTag(ctx)) });