Skip to content

Commit

Permalink
ast-check for zls!
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperAuguste committed Sep 1, 2022
1 parent da03c81 commit 1fd9dd0
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 69 deletions.
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) 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 fmt 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 stdout_bytes = try process.stderr.?.reader().readAllAlloc(server.allocator, std.math.maxInt(usize));
defer server.allocator.free(stdout_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, stdout_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
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 = &[0]DiagnosticRelatedInformation{},
};

pub const TextDocument = struct {
Expand Down

0 comments on commit 1fd9dd0

Please sign in to comment.