ast-check for zls!

This commit is contained in:
Auguste Rame 2022-08-31 20:48:42 -04:00
parent da03c81992
commit 57a35a7bc8
No known key found for this signature in database
GPG Key ID: 3A5E3F90DF2AAEFE
5 changed files with 83 additions and 67 deletions

View File

@ -9,8 +9,8 @@
"type": "boolean", "type": "boolean",
"default": "false" "default": "false"
}, },
"enable_unused_variable_warnings": { "enable_ast_check_diagnostics": {
"description": "Enables warnings for local variables that aren't used", "description": "Whether to enable ast-check diagnostics",
"type": "boolean", "type": "boolean",
"default": "false" "default": "false"
}, },

View File

@ -13,8 +13,8 @@ const logger = std.log.scoped(.config);
/// Whether to enable snippet completions /// Whether to enable snippet completions
enable_snippets: bool = false, enable_snippets: bool = false,
/// Whether to enable unused variable warnings /// Whether to enable ast-check diagnostics
enable_unused_variable_warnings: bool = false, enable_ast_check_diagnostics: bool = false,
/// Whether to enable import/embedFile argument completions (NOTE: these are triggered manually as updating the autotrigger characters may cause issues) /// Whether to enable import/embedFile argument completions (NOTE: these are triggered manually as updating the autotrigger characters may cause issues)
enable_import_embedfile_argument_completions: bool = false, enable_import_embedfile_argument_completions: bool = false,

View File

@ -237,72 +237,82 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: DocumentStore.Ha
}); });
} }
if (server.config.enable_unused_variable_warnings) { if (server.config.enable_ast_check_diagnostics) diag: {
scopes: for (handle.document_scope.scopes.items) |scope| { if (server.config.zig_exe_path) |zig_exe_path| {
const scope_data = switch (scope.data) { var process = std.ChildProcess.init(&[_][]const u8{ zig_exe_path, "ast-check", "--color", "off" }, server.allocator);
.function => |f| b: { process.stdin_behavior = .Pipe;
if (!ast.fnProtoHasBody(tree, f).?) continue :scopes; process.stderr_behavior = .Pipe;
break :b f;
}, process.spawn() catch |err| {
.block => |b| b, Logger.warn(server, writer, "Failed to spawn zig fmt process, error: {}", .{err});
else => continue, break :diag;
}; };
try process.stdin.?.writeAll(handle.document.text);
process.stdin.?.close();
var decl_iterator = scope.decls.iterator(); process.stdin = null;
while (decl_iterator.next()) |decl| {
var identifier_count: usize = 0;
const name_token_index = switch (decl.value_ptr.*) { const stderr_bytes = try process.stderr.?.reader().readAllAlloc(server.allocator, std.math.maxInt(usize));
.ast_node => |an| s: { defer server.allocator.free(stderr_bytes);
const an_tag = tree.nodes.items(.tag)[an];
switch (an_tag) { switch (try process.wait()) {
.simple_var_decl => { .Exited => {
break :s tree.nodes.items(.main_token)[an] + 1; // NOTE: I believe that with color off it's one diag per line; is this correct?
}, var line_iterator = std.mem.split(u8, stderr_bytes, "\n");
else => continue,
while (line_iterator.next()) |line| lin: {
var pos_and_diag_iterator = std.mem.split(u8, line, ":");
const maybe_first = pos_and_diag_iterator.next();
if (maybe_first) |first| {
if (first.len <= 1) break :lin;
} else break;
const pos = types.Position{
.line = (try std.fmt.parseInt(i64, pos_and_diag_iterator.next().?, 10)) - 1,
.character = (try std.fmt.parseInt(i64, pos_and_diag_iterator.next().?, 10)) - 1,
};
const msg = pos_and_diag_iterator.rest()[1..];
if (std.mem.startsWith(u8, msg, "error: ")) {
try diagnostics.append(allocator, .{
.range = .{ .start = pos, .end = pos },
.severity = .Error,
.code = "ast_check",
.source = "zls",
.message = try server.arena.allocator().dupe(u8, msg["error: ".len..]),
});
} else if (std.mem.startsWith(u8, msg, "note: ")) {
var latestDiag = &diagnostics.items[diagnostics.items.len - 1];
var fresh = if (latestDiag.relatedInformation.len == 0)
try server.arena.allocator().alloc(types.DiagnosticRelatedInformation, 1)
else
try server.arena.allocator().realloc(@ptrCast([]types.DiagnosticRelatedInformation, latestDiag.relatedInformation), latestDiag.relatedInformation.len + 1);
const location = types.Location{
.uri = handle.uri(),
.range = .{ .start = pos, .end = pos },
};
fresh[fresh.len - 1] = .{
.location = location,
.message = try server.arena.allocator().dupe(u8, msg["note: ".len..]),
};
latestDiag.relatedInformation = fresh;
} else {
try diagnostics.append(allocator, .{
.range = .{ .start = pos, .end = pos },
.severity = .Error,
.code = "ast_check",
.source = "zls",
.message = try server.arena.allocator().dupe(u8, msg),
});
} }
}, }
.param_decl => |param| param.name_token orelse continue, },
else => continue, else => {},
};
if (std.mem.eql(u8, tree.tokenSlice(name_token_index), "_"))
continue;
const pit_start = tree.firstToken(scope_data);
const pit_end = ast.lastToken(tree, scope_data);
const tags = tree.tokens.items(.tag)[pit_start..pit_end];
for (tags) |tag, index| {
if (tag != .identifier) continue;
if (!std.mem.eql(u8, tree.tokenSlice(pit_start + @intCast(u32, index)), tree.tokenSlice(name_token_index))) continue;
if (index -| 1 > 0 and tags[index - 1] == .period) continue;
if (index +| 2 < tags.len and tags[index + 1] == .colon) switch (tags[index + 2]) {
.l_brace,
.keyword_inline,
.keyword_while,
.keyword_for,
.keyword_switch,
=> continue,
else => {},
};
if (index -| 2 > 0 and tags[index - 1] == .colon) switch (tags[index - 2]) {
.keyword_break,
.keyword_continue,
=> continue,
else => {},
};
identifier_count += 1;
}
if (identifier_count <= 1)
try diagnostics.append(allocator, .{
.range = astLocationToRange(tree.tokenLocation(0, name_token_index)),
.severity = .Error,
.code = "unused_variable",
.source = "zls",
.message = "Unused variable; either remove the variable or use '_ = ' on the variable to bypass this error",
});
} }
} }
} }

View File

@ -276,7 +276,7 @@ pub const Configuration = struct {
params: struct { params: struct {
settings: struct { settings: struct {
enable_snippets: ?bool, enable_snippets: ?bool,
enable_unused_variable_warnings: ?bool, enable_ast_check_diagnostics: ?bool,
enable_import_embedfile_argument_completions: ?bool, enable_import_embedfile_argument_completions: ?bool,
zig_lib_path: ?[]const u8, zig_lib_path: ?[]const u8,
zig_exe_path: ?[]const u8, zig_exe_path: ?[]const u8,

View File

@ -108,12 +108,18 @@ pub const DiagnosticSeverity = enum(i64) {
} }
}; };
pub const DiagnosticRelatedInformation = struct {
location: Location,
message: string,
};
pub const Diagnostic = struct { pub const Diagnostic = struct {
range: Range, range: Range,
severity: DiagnosticSeverity, severity: DiagnosticSeverity,
code: string, code: string,
source: string, source: string,
message: string, message: string,
relatedInformation: []const DiagnosticRelatedInformation = &[0]DiagnosticRelatedInformation{},
}; };
pub const TextDocument = struct { pub const TextDocument = struct {