const std = @import("std"); const zls = @import("zls"); const analysis = zls.analysis; const types = zls.types; const offsets = zls.offsets; const allocator = std.testing.allocator; test "position context - var access" { try testContext( \\const a_var = identifier; , .empty, null, ); try testContext( \\const a_var = identifier; , .var_access, "i", ); try testContext( \\const a_var = identifier; , .var_access, "ident", ); try testContext( \\const a_var = identifier; , .var_access, "identifier", ); try testContext( \\const a_var = identifier; , .empty, null, ); try testContext( \\ fn foo() !Str { , .var_access, "S", ); try testContext( \\ fn foo() !Str { , .var_access, "Str", ); try testContext( \\ fn foo() !Str { , .var_access, "Str", ); try testContext( \\ fn foo() !Str { , .var_access, "Str", ); // TODO fix failing tests // try testContext( // \\ fn foo() Err!void { // , // .var_access, // "E", // ); // try testContext( // \\ fn foo() Err!void { // , // .var_access, // "Err", // ); // try testContext( // \\ fn foo() Err!void { // , // .var_access, // "Err", // ); // try testContext( // \\ fn foo() Err!void { // , // .var_access, // "v", // ); try testContext( \\if (bar.field == foo) { , .var_access, "b", ); try testContext( \\if (bar.field == foo) { , .var_access, "bar", ); try testContext( \\if (bar.field == foo) { , .var_access, "bar", ); try testContext( \\if (bar[0].field == foo) { , .var_access, "bar", ); } test "position context - field access" { try testContext( \\if (bar.field == foo) { , .field_access, "bar.f", ); try testContext( \\if (bar.field == foo) { , .field_access, "bar.fiel", ); try testContext( \\if (bar.field == foo) { , .field_access, "bar.field", ); try testContext( \\if (bar.member.field == foo) { , .field_access, "bar.member", ); try testContext( \\if (bar.member.field == foo) { , .field_access, "bar.member.f", ); try testContext( \\if (bar.member.field == foo) { , .field_access, "bar.member.fiel", ); try testContext( \\if (bar.member.field == foo) { , .field_access, "bar.member.field", ); try testContext( \\if (bar.*.?.field == foo) { , .field_access, "bar.*.?", ); try testContext( \\if (bar.*.?.field == foo) { , .field_access, "bar.*.?.f", ); try testContext( \\if (bar[0].field == foo) { , .field_access, "bar[0].f", ); try testContext( \\if (bar.@"field" == foo) { , .field_access, "bar.@\"", ); try testContext( \\if (bar.@"field" == foo) { , .field_access, "bar.@\"fiel", ); try testContext( \\if (bar.@"field" == foo) { , .field_access, "bar.@\"field\"", ); try testContext( \\const arr = std.ArrayList(SomeStruct(a, b, c, d)).init(allocator); , .field_access, "std.ArrayList(SomeStruct(a, b, c, d)).i", ); try testContext( \\const arr = std.ArrayList(SomeStruct(a, b, c, d)).init(allocator); , .field_access, "std.ArrayList(SomeStruct(a, b, c, d)).ini", ); try testContext( \\const arr = std.ArrayList(SomeStruct(a, b, c, d)).init(allocator); , .field_access, "std.ArrayList(SomeStruct(a, b, c, d)).init", ); try testContext( \\fn foo() !Foo.bar { , .field_access, "Foo.b", ); try testContext( \\fn foo() !Foo.bar { , .field_access, "Foo.bar", ); try testContext( \\fn foo() !Foo.bar { , .field_access, "Foo.bar", ); // TODO fix failing tests // try testContext( // \\fn foo() Foo.bar!void { // , // .field_access, // "Foo.b", // ); // try testContext( // \\fn foo() Foo.bar!void { // , // .field_access, // "Foo.bar", // ); // try testContext( // \\fn foo() Foo.bar!void { // , // .field_access, // "Foo.bar", // ); } test "position context - builtin" { try testContext( \\var foo = @ , .empty, null, ); try testContext( \\var foo = @intC(u32, 5); , .builtin, "@i", ); try testContext( \\var foo = @intC(u32, 5); , .builtin, "@i", ); try testContext( \\var foo = @intC(u32, 5); , .builtin, "@intC", ); try testContext( \\var foo = @intC(u32, 5); , .builtin, "@intC", ); try testContext( \\fn foo() void { @setRuntime(false); }; , .builtin, "@s", ); try testContext( \\fn foo() void { @setRuntime(false); }; , .builtin, "@s", ); try testContext( \\fn foo() void { @setRuntime(false); }; , .builtin, "@setR", ); try testContext( \\fn foo() void { @setRuntime(false); }; , .builtin, "@setRuntime", ); } test "position context - comment" { try testContext( \\// i am a test , .comment, null, // report "// i am a test" ); try testContext( \\/// i am a test , .comment, null, // report /// i am a test ); } test "position context - import/embedfile string literal" { try testContext( \\const std = @import("st"); , .import_string_literal, "\"st", // maybe report just "st" ); try testContext( \\const std = @import("st"); , .import_string_literal, "\"st", // maybe report just "st" ); try testContext( \\const std = @embedFile("file."); , .embedfile_string_literal, "\"file.", // maybe report just "file." ); try testContext( \\const std = @embedFile("file."); , .embedfile_string_literal, "\"file", // maybe report just "file." ); } test "position context - string literal" { try testContext( \\var foo = "hello world!"; , .string_literal, "\"hel", // maybe report just "he" ); try testContext( \\var foo = \\hello; , .string_literal, "\\\\hello", // maybe report just "hello;" ); } test "position context - global error set" { // TODO why is this a .var_access instead of a .global_error_set? // try testContext( // \\fn foo() error!void { // , // .global_error_set, // null, // ); try testContext( \\fn foo() error!void { , .global_error_set, null, ); try testContext( \\fn foo() error!void { , .global_error_set, null, ); try testContext( \\fn foo() error.!void { , .global_error_set, null, ); try testContext( \\fn foo() error.!void { , .global_error_set, null, ); // TODO this should probably also be .global_error_set // try testContext( // \\fn foo() error{}!void { // , // .global_error_set, // null, // ); // try testContext( // \\fn foo() error{OutOfMemory, }!void { // , // .global_error_set, // null, // ); } test "position context - enum literal" { try testContext( \\var foo = .tag; , .enum_literal, null, ); try testContext( \\var foo = .tag; , .enum_literal, null, ); try testContext( \\var foo = .tag; , .enum_literal, null, ); try testContext( \\var foo = .; , .empty, null, ); try testContext( \\var foo = .; , .enum_literal, null, ); } test "position context - label" { try testContext( \\var foo = blk: { break :blk null }; , .pre_label, null, ); try testContext( \\var foo = blk: { break :blk null }; , .label, null, ); try testContext( \\var foo = blk: { break :blk null }; , .label, null, ); try testContext( \\var foo = blk: { break :blk null }; , .label, null, ); } test "position context - empty" { try testContext( \\ , .empty, null, ); try testContext( \\try foo(arg, slice[]); , .empty, null, ); try testContext( \\try foo(arg, slice[..3]); , .empty, null, ); try testContext( \\try foo(arg, slice[0..]); , .empty, null, ); } fn testContext(line: []const u8, tag: std.meta.Tag(analysis.PositionContext), maybe_range: ?[]const u8) !void { const cursor_idx = std.mem.indexOf(u8, line, "").?; const final_line = try std.mem.concat(allocator, u8, &.{ line[0..cursor_idx], line[cursor_idx + "".len ..] }); defer allocator.free(final_line); const ctx = try analysis.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)) }); return error.DifferentTag; } const actual_loc = ctx.loc() orelse if (maybe_range) |expected_range| { std.debug.print("Expected `{s}`, got null range\n", .{ expected_range, }); return error.DifferentRange; } else return; const expected_range = maybe_range orelse { std.debug.print("Expected null range, got `{s}`\n", .{ final_line[actual_loc.start..actual_loc.end], }); return error.DifferentRange; }; const expected_range_start = std.mem.indexOf(u8, final_line, expected_range).?; const expected_range_end = expected_range_start + expected_range.len; if (expected_range_start != actual_loc.start or expected_range_end != actual_loc.end) { std.debug.print("Expected range `{s}` ({}..{}), got `{s}` ({}..{})\n", .{ final_line[expected_range_start..expected_range_end], expected_range_start, expected_range_end, final_line[actual_loc.start..actual_loc.end], actual_loc.start, actual_loc.end, }); return error.DifferentRange; } }