ast-check for zls!
This commit is contained in:
parent
da03c81992
commit
57a35a7bc8
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
134
src/Server.zig
134
src/Server.zig
@ -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",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user