Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ast-check for zls! #617

Merged
merged 2 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"type": "boolean",
"default": "false"
},
"enable_unused_variable_warnings": {
"description": "Enables warnings for local variables that aren't used",
"enable_ast_check_diagnostics": {
"description": "Whether to enable ast-check diagnostics",
"type": "boolean",
"default": "false"
},
Expand Down
4 changes: 2 additions & 2 deletions src/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const logger = std.log.scoped(.config);
/// Whether to enable snippet completions
enable_snippets: bool = false,

/// Whether to enable unused variable warnings
enable_unused_variable_warnings: bool = false,
/// Whether to enable ast-check diagnostics
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)
enable_import_embedfile_argument_completions: bool = false,
Expand Down
138 changes: 74 additions & 64 deletions src/Server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -237,72 +237,82 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: DocumentStore.Ha
});
}

if (server.config.enable_unused_variable_warnings) {
scopes: for (handle.document_scope.scopes.items) |scope| {
const scope_data = switch (scope.data) {
.function => |f| b: {
if (!ast.fnProtoHasBody(tree, f).?) continue :scopes;
break :b f;
},
.block => |b| b,
else => continue,
if (server.config.enable_ast_check_diagnostics and tree.errors.len == 0) diag: {
if (server.config.zig_exe_path) |zig_exe_path| {
var process = std.ChildProcess.init(&[_][]const u8{ zig_exe_path, "ast-check", "--color", "off" }, server.allocator);
process.stdin_behavior = .Pipe;
process.stderr_behavior = .Pipe;

process.spawn() catch |err| {
Logger.warn(server, writer, "Failed to spawn zig ast-check process, error: {}", .{err});
break :diag;
};

var decl_iterator = scope.decls.iterator();
while (decl_iterator.next()) |decl| {
var identifier_count: usize = 0;

const name_token_index = switch (decl.value_ptr.*) {
.ast_node => |an| s: {
const an_tag = tree.nodes.items(.tag)[an];
switch (an_tag) {
.simple_var_decl => {
break :s tree.nodes.items(.main_token)[an] + 1;
},
else => continue,
try process.stdin.?.writeAll(handle.document.text);
process.stdin.?.close();

process.stdin = null;

const stderr_bytes = try process.stderr.?.reader().readAllAlloc(server.allocator, std.math.maxInt(usize));
defer server.allocator.free(stderr_bytes);

switch (try process.wait()) {
.Exited => {
// 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");

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,
};

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",
});
}
},
else => {},
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/requests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ pub const Configuration = struct {
params: struct {
settings: struct {
enable_snippets: ?bool,
enable_unused_variable_warnings: ?bool,
enable_ast_check_diagnostics: ?bool,
enable_import_embedfile_argument_completions: ?bool,
zig_lib_path: ?[]const u8,
zig_exe_path: ?[]const u8,
Expand Down
4 changes: 2 additions & 2 deletions src/setup.zig
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ pub fn wizard(allocator: std.mem.Allocator) !void {

const editor = try askSelectOne("Which code editor do you use?", enum { VSCode, Sublime, Kate, Neovim, Vim8, Emacs, Doom, Spacemacs, Helix, Other });
const snippets = try askBool("Do you want to enable snippets?");
const unused_variables = try askBool("Do you want to enable unused variable warnings?");
const ast_check = try askBool("Do you want to enable ast-check diagnostics?");
const ief_apc = try askBool("Do you want to enable @import/@embedFile argument path completion?");
const style = try askBool("Do you want to enable style warnings?");
const semantic_tokens = try askBool("Do you want to enable semantic highlighting?");
Expand All @@ -192,7 +192,7 @@ pub fn wizard(allocator: std.mem.Allocator) !void {
.@"$schema" = "https://raw.githubusercontent.com/zigtools/zls/master/schema.json",
.zig_exe_path = zig_exe_path,
.enable_snippets = snippets,
.enable_unused_variable_warnings = unused_variables,
.enable_ast_check_diagnostics = ast_check,
.enable_import_embedfile_argument_completions = ief_apc,
.warn_style = style,
.enable_semantic_tokens = semantic_tokens,
Expand Down
6 changes: 6 additions & 0 deletions src/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,18 @@ pub const DiagnosticSeverity = enum(i64) {
}
};

pub const DiagnosticRelatedInformation = struct {
location: Location,
message: string,
};

pub const Diagnostic = struct {
range: Range,
severity: DiagnosticSeverity,
code: string,
source: string,
message: string,
relatedInformation: []const DiagnosticRelatedInformation = &.{},
};

pub const TextDocument = struct {
Expand Down