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

Add super basic unused variable support #511

Merged
merged 3 commits into from
Jul 8, 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
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ Zig Language Server, or `zls`, is a language server for Zig. The Zig wiki states
## Table Of Contents

- [Installation](#installation)
- [Build Options](#build-options)
- [Installing binaries](#installing-binaries)
- [MacOS](#macos)
- [Linux](#linux)
- [From Source](#from-source)
- [Build Options](#build-options)
- [Updating Data Files](#updating-data-files)
- [Configuration Options](#configuration-options)
- [Usage](#usage)
- [Features](#features)
- [VSCode](#vscode)
- [Sublime Text](#sublime-text)
- [Sublime Text 3](#sublime-text-3)
- [Sublime Text 4](#sublime-text-4)
- [Kate](#kate)
- [Neovim/Vim8](#neovimvim8)
- [CoC](#coc)
- [YouCompleteMe](#youcompleteme)
- [nvim-lspconfig](#nvim-lspconfig)
- [LanguageClient-neovim](#languageclient-neovim)
- [Emacs](#emacs)
- [Doom Emacs](#doom-emacs)
- [Spacemacs](#spacemacs)
- [Related Projects](#related-projects)
- [License](#license)

Expand Down Expand Up @@ -88,6 +100,7 @@ The following options are currently available.
| Option | Type | Default value | What it Does |
| --- | --- | --- | --- |
| `enable_snippets` | `bool` | `false` | Enables snippet completions when the client also supports them. |
| `enable_unused_variable_warnings` | `bool` | `false`| Enables warnings for local variables that aren't used. |
| `zig_lib_path` | `?[]const u8` | `null` | zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports. |
| `zig_exe_path` | `?[]const u8` | `null` | zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided. |
| `warn_style` | `bool` | `false` | Enables warnings for style *guideline* mismatches |
Expand Down Expand Up @@ -125,7 +138,7 @@ Install the `zls-vscode` extension from [here](https://github.com/zigtools/zls-v
- Install the `LSP` package from [here](https://github.com/sublimelsp/LSP/releases) or via Package Control.
- Add this snippet to `LSP's` user settings:

#### For Sublime Text 3:
#### Sublime Text 3

```json
{
Expand All @@ -141,7 +154,7 @@ Install the `zls-vscode` extension from [here](https://github.com/zigtools/zls-v
}
```

#### For Sublime Text 4:
#### Sublime Text 4

```json
{
Expand Down
3 changes: 3 additions & 0 deletions src/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
/// Whether to enable snippet completions
enable_snippets: bool = false,

/// Whether to enable unused variable warnings
enable_unused_variable_warnings: bool = false,

/// zig library path
zig_lib_path: ?[]const u8 = null,

Expand Down
95 changes: 72 additions & 23 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,68 @@ fn publishDiagnostics(arena: *std.heap.ArenaAllocator, handle: DocumentStore.Han
});
}

if (config.enable_unused_variable_warnings) {
for (handle.document_scope.scopes) |scope| {
const scope_data = switch (scope.data) {
.function => |f| f,
.block => |b| b,
else => continue,
};

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

var 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,
}
},
.param_decl => |param| param.name_token orelse continue,
else => continue,
};

const pit_start = tree.firstToken(scope_data);
const pit_end = ast.lastToken(tree, scope_data);

for (tree.tokens.items(.tag)[pit_start..pit_end]) |tag, index| {
if (tag == .identifier and std.mem.eql(u8, tree.tokenSlice(pit_start + @intCast(u32, index)), tree.tokenSlice(name_token_index))) identifier_count += 1;
}

if (identifier_count <= 1)
try diagnostics.append(.{
.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.",
});
}
}
}

// TODO: style warnings for types, values and declarations below root scope
if (tree.errors.len == 0) {
for (tree.rootDecls()) |decl_idx| {
const decl = tree.nodes.items(.tag)[decl_idx];
switch (decl) {
// .simple_var_decl => {
// // var d = ast.varDecl(tree, decl_idx).?;
// const loc = tree.tokenLocation(0, decl_idx);

// try diagnostics.append(.{
// .range = astLocationToRange(loc),
// .severity = .Information,
// .code = "unused_variable",
// .source = "zls",
// .message = "Unused variable",
// });
// },
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
Expand All @@ -255,15 +312,15 @@ fn publishDiagnostics(arena: *std.heap.ArenaAllocator, handle: DocumentStore.Han
try diagnostics.append(.{
.range = astLocationToRange(loc),
.severity = .Information,
.code = "BadStyle",
.code = "bad-style",
.source = "zls",
.message = "Functions should be camelCase",
});
} else if (is_type_function and !analysis.isPascalCase(func_name)) {
try diagnostics.append(.{
.range = astLocationToRange(loc),
.severity = .Information,
.code = "BadStyle",
.code = "bad-style",
.source = "zls",
.message = "Type functions should be PascalCase",
});
Expand Down Expand Up @@ -1192,7 +1249,7 @@ fn completeFieldAccess(arena: *std.heap.ArenaAllocator, id: types.RequestId, han
truncateCompletions(completions.items, config.max_detail_length);
if (client_capabilities.label_details_support) {
for (completions.items) |*item| {
try formatDetailledLabel(item, arena.allocator());
try formatDetailledLabel(item, arena.allocator());
}
}
}
Expand Down Expand Up @@ -1375,7 +1432,6 @@ fn formatDetailledLabel(item: *types.CompletionItem, alloc: std.mem.Allocator) !
// logger.info("labelDetails: {s} :: {s}", .{item.labelDetails.?.detail, item.labelDetails.?.description});
}


fn completeError(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, config: *const Config) !void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
Expand All @@ -1396,36 +1452,35 @@ fn completeError(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *
});
}

fn kindToSortScore(kind: types.CompletionItem.Kind) [] const u8 {
return switch (kind)
{
fn kindToSortScore(kind: types.CompletionItem.Kind) []const u8 {
return switch (kind) {
.Constant => "1_",

.Variable => "2_",
.Field => "3_",
.Function => "4_",

.Keyword,
.EnumMember => "5_",


.Keyword, .EnumMember => "5_",

.Class,
.Interface,
.Struct,
// Union?
.TypeParameter => "6_",

else => "9_"
.TypeParameter,
=> "6_",

else => "9_",
};
}

fn sortCompletionItems(completions: []types.CompletionItem, _: *const Config, alloc: std.mem.Allocator) void {
// TODO: config for sorting rule?
for (completions) |*c| {
c.sortText = kindToSortScore(c.kind);

if (alloc.alloc(u8, 2 + c.label.len)) |it| {
std.mem.copy(u8, it, c.sortText.?);
std.mem.copy(u8, it[2 .. ], c.label);
std.mem.copy(u8, it[2..], c.label);
c.sortText = it;
} else |_| {}
}
Expand Down Expand Up @@ -1554,13 +1609,7 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
},
.textDocumentSync = .Full,
.renameProvider = true,
.completionProvider = .{
.resolveProvider = false,
.triggerCharacters = &[_][]const u8{ ".", ":", "@" },
.completionItem = .{
.labelDetailsSupport = true
}
},
.completionProvider = .{ .resolveProvider = false, .triggerCharacters = &[_][]const u8{ ".", ":", "@" }, .completionItem = .{ .labelDetailsSupport = true } },
.documentHighlightProvider = false,
.hoverProvider = true,
.codeActionProvider = false,
Expand Down
1 change: 1 addition & 0 deletions src/requests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ pub const Configuration = struct {
params: struct {
settings: struct {
enable_snippets: ?bool,
enable_unused_variable_warnings: ?bool,
zig_lib_path: ?[]const u8,
zig_exe_path: ?[]const u8,
warn_style: ?bool,
Expand Down
4 changes: 2 additions & 2 deletions src/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,11 @@ pub const CompletionItem = struct {

insertTextFormat: ?InsertTextFormat = .PlainText,
documentation: ?MarkupContent = null,

// FIXME: i commented this out, because otherwise the vscode client complains about *ranges*
// and breaks code completion entirely
// see: https://github.com/zigtools/zls-vscode/pull/33
// textEdit: ?TextEdit = null,
// textEdit: ?TextEdit = null,
};

pub const CompletionItemLabelDetails = struct {
Expand Down
8 changes: 4 additions & 4 deletions tests/sessions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ test "Request completion with no trailing whitespace" {
try server.request("textDocument/completion",
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
,
\\{"isIncomplete":false,"items":[{"label":"std","kind":21,"textEdit":null,"filterText":null,"insertText":"std","insertTextFormat":1,"detail":"const std = @import(\"std\")","documentation":null,"sortText":"1_std"}]}
\\{"isIncomplete":false,"items":[{"label":"std","labelDetails":{"detail":"","description":"@import(\"std\")","sortText":null},"kind":21,"detail":"std","sortText":"1_std","filterText":null,"insertText":"std","insertTextFormat":1,"documentation":null}]}
);
}

Expand Down Expand Up @@ -214,7 +214,7 @@ test "Self-referential definition" {
try server.request("textDocument/completion",
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
,
\\{"isIncomplete":false,"items":[{"label":"h","kind":21,"textEdit":null,"filterText":null,"insertText":"h","insertTextFormat":1,"detail":"const h = h(0)","documentation":null}]}
\\{"isIncomplete":false,"items":[{"label":"h","labelDetails":{"detail":"","description":"h(0)","sortText":null},"kind":21,"detail":"h","sortText":"1_h","filterText":null,"insertText":"h","insertTextFormat":1,"documentation":null}]}
);
}

Expand Down Expand Up @@ -245,13 +245,13 @@ test "Pointer and optional deref" {
try server.request("textDocument/completion",
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":18}}
,
\\{"isIncomplete":false,"items":[{"label":"data","kind":5,"textEdit":null,"filterText":null,"insertText":"data","insertTextFormat":1,"detail":"data: i32 = 5","documentation":null}]}
\\{"isIncomplete":false,"items":[{"label":"data","labelDetails":{"detail":"","description":"i32 ","sortText":null},"kind":5,"detail":"data","sortText":"3_data","filterText":null,"insertText":"data","insertTextFormat":1,"documentation":null}]}
);
}

test "Request utf-8 offset encoding" {
var server = try Server.start(initialize_msg_offs,
\\{"offsetEncoding":"utf-8","capabilities":{"signatureHelpProvider":{"triggerCharacters":["("],"retriggerCharacters":[","]},"textDocumentSync":1,"renameProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":","@"]},"documentHighlightProvider":false,"hoverProvider":true,"codeActionProvider":false,"declarationProvider":true,"definitionProvider":true,"typeDefinitionProvider":true,"implementationProvider":false,"referencesProvider":true,"documentSymbolProvider":true,"colorProvider":false,"documentFormattingProvider":true,"documentRangeFormattingProvider":false,"foldingRangeProvider":false,"selectionRangeProvider":false,"workspaceSymbolProvider":false,"rangeProvider":false,"documentProvider":true,"workspace":{"workspaceFolders":{"supported":false,"changeNotifications":false}},"semanticTokensProvider":{"full":true,"range":false,"legend":{"tokenTypes":["type","parameter","variable","enumMember","field","errorTag","function","keyword","comment","string","number","operator","builtin","label","keywordLiteral"],"tokenModifiers":["namespace","struct","enum","union","opaque","declaration","async","documentation","generic"]}}},"serverInfo":{"name":"zls","version":"0.1.0"}}
\\{"offsetEncoding":"utf-8","capabilities":{"signatureHelpProvider":{"triggerCharacters":["("],"retriggerCharacters":[","]},"textDocumentSync":1,"renameProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":","@"],"completionItem":{"labelDetailsSupport":true}},"documentHighlightProvider":false,"hoverProvider":true,"codeActionProvider":false,"declarationProvider":true,"definitionProvider":true,"typeDefinitionProvider":true,"implementationProvider":false,"referencesProvider":true,"documentSymbolProvider":true,"colorProvider":false,"documentFormattingProvider":true,"documentRangeFormattingProvider":false,"foldingRangeProvider":false,"selectionRangeProvider":false,"workspaceSymbolProvider":false,"rangeProvider":false,"documentProvider":true,"workspace":{"workspaceFolders":{"supported":false,"changeNotifications":false}},"semanticTokensProvider":{"full":true,"range":false,"legend":{"tokenTypes":["type","parameter","variable","enumMember","field","errorTag","function","keyword","comment","string","number","operator","builtin","label","keywordLiteral"],"tokenModifiers":["namespace","struct","enum","union","opaque","declaration","async","documentation","generic"]}}},"serverInfo":{"name":"zls","version":"0.1.0"}}
);
defer server.shutdown();
}
Expand Down