diff --git a/README.md b/README.md index 6e8d0611b..3a33a9d19 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 | @@ -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 { @@ -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 { diff --git a/src/Config.zig b/src/Config.zig index 4d6083c4a..ce7e7f827 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -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, diff --git a/src/main.zig b/src/main.zig index ada5ae583..128a1e338 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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, @@ -255,7 +312,7 @@ 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", }); @@ -263,7 +320,7 @@ 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 = "Type functions should be PascalCase", }); @@ -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()); } } } @@ -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(); @@ -1396,25 +1452,24 @@ 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_", }; } @@ -1422,10 +1477,10 @@ fn sortCompletionItems(completions: []types.CompletionItem, _: *const Config, al // 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 |_| {} } @@ -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, diff --git a/src/requests.zig b/src/requests.zig index 2d08466cd..f2cd13d36 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -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, diff --git a/src/types.zig b/src/types.zig index 9dcca6ed8..38a65d45e 100644 --- a/src/types.zig +++ b/src/types.zig @@ -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 { diff --git a/tests/sessions.zig b/tests/sessions.zig index 5f4d84aeb..303912eb4 100644 --- a/tests/sessions.zig +++ b/tests/sessions.zig @@ -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}]} ); } @@ -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}]} ); } @@ -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(); }