Skip to content
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
33 changes: 33 additions & 0 deletions libnixf/src/Sema/VariableLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,39 @@ void VariableLookupAnalysis::emitEnvLivenessWarning(
Diagnostic &D = Diags.emplace_back(Kind, Def->syntax()->range());
D << Name;
D.tag(DiagnosticTag::Faded);

// Add fix for unused let bindings
if (Def->source() == Definition::DS_Let) {
const Node *LetNode = NewEnv->syntax();
if (LetNode && LetNode->kind() == Node::NK_ExprLet) {
const auto &Let = static_cast<const ExprLet &>(*LetNode);
if (Let.binds()) {
for (const auto &BindNode : Let.binds()->bindings()) {
// Skip non-Binding nodes (Inherit handled separately)
if (BindNode->kind() != Node::NK_Binding)
continue;

const auto &Bind = static_cast<const Binding &>(*BindNode);
const auto &PathNames = Bind.path().names();
if (PathNames.empty())
continue;

const auto &FirstName = PathNames[0];
if (!FirstName)
continue;

// Match by comparing first attrname range with Def->syntax()
// range
if (D.range().lCur() == FirstName->range().lCur() &&
D.range().rCur() == FirstName->range().rCur()) {
D.fix("remove unused binding")
.edit(TextEdit::mkRemoval(Bind.range()));
break;
}
}
}
}
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions nixd/lib/Controller/CodeAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ void Controller::onCodeAction(const lspserver::CodeActionParams &Params,
if (!Range.overlap(DRange))
continue;

// Determine if this diagnostic's fixes should be preferred
bool IsPreferred = false;
switch (D.kind()) {
case nixf::Diagnostic::DK_UnusedDefLet:
IsPreferred = true;
break;
default:
break;
}

// Add fixes.
for (const nixf::Fix &F : D.fixes()) {
std::vector<TextEdit> Edits;
Expand All @@ -64,6 +74,7 @@ void Controller::onCodeAction(const lspserver::CodeActionParams &Params,
Actions.emplace_back(CodeAction{
.title = F.message(),
.kind = std::string(CodeAction::QUICKFIX_KIND),
.isPreferred = IsPreferred,
.edit = std::move(WE),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ let "123" = 1; in x
}
```

Invalid identifiers (starting with digit) should return empty result
Invalid identifiers (starting with digit) should NOT offer unquote action.
However, a quickfix for unused binding may still be offered.

```
CHECK: "id": 2,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": []
CHECK-NEXT: "result": [
CHECK-NOT: "title": "unquote attribute name"
CHECK: "title": "remove unused binding"
```

```json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ let "true" = 1; in x
}
```

Keywords should return empty result (no unquote action available)
Keywords should NOT offer unquote action.
However, a quickfix for unused binding may still be offered.

```
CHECK: "id": 2,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": []
CHECK-NEXT: "result": [
CHECK-NOT: "title": "unquote attribute name"
CHECK: "title": "remove unused binding"
```

```json
Expand Down
78 changes: 78 additions & 0 deletions nixd/tools/nixd/test/code-action/unused-def/all-unused.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# RUN: nixd --lit-test < %s | FileCheck %s

Test `remove unused binding` action when all bindings are unused.

<-- initialize(0)

```json
{
"jsonrpc":"2.0",
"id":0,
"method":"initialize",
"params":{
"processId":123,
"rootPath":"",
"capabilities":{
},
"trace":"off"
}
}
```


<-- textDocument/didOpen

```nix file:///all-unused.nix
let x = 1; y = 2; in 3
```

<-- textDocument/codeAction(2)


```json
{
"jsonrpc":"2.0",
"id":2,
"method":"textDocument/codeAction",
"params":{
"textDocument":{
"uri":"file:///all-unused.nix"
},
"range":{
"start":{
"line": 0,
"character": 4
},
"end":{
"line":0,
"character": 5
}
},
"context":{
"diagnostics":[],
"triggerKind":2
}
}
}
```

The action should offer to remove `x = 1;`.

```
CHECK: "id": 2,
CHECK: "newText": "",
CHECK: "range":
CHECK: "end":
CHECK: "character": 10,
CHECK: "line": 0
CHECK: "start":
CHECK: "character": 4,
CHECK: "line": 0
CHECK: "isPreferred": true,
CHECK: "kind": "quickfix",
CHECK: "title": "remove unused binding"
```

```json
{"jsonrpc":"2.0","method":"exit"}
```
78 changes: 78 additions & 0 deletions nixd/tools/nixd/test/code-action/unused-def/basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# RUN: nixd --lit-test < %s | FileCheck %s

Test basic `remove unused binding` action for unused let binding.

<-- initialize(0)

```json
{
"jsonrpc":"2.0",
"id":0,
"method":"initialize",
"params":{
"processId":123,
"rootPath":"",
"capabilities":{
},
"trace":"off"
}
}
```


<-- textDocument/didOpen

```nix file:///unused-let.nix
let unused = 1; in 2
```

<-- textDocument/codeAction(2)


```json
{
"jsonrpc":"2.0",
"id":2,
"method":"textDocument/codeAction",
"params":{
"textDocument":{
"uri":"file:///unused-let.nix"
},
"range":{
"start":{
"line": 0,
"character": 4
},
"end":{
"line":0,
"character": 10
}
},
"context":{
"diagnostics":[],
"triggerKind":2
}
}
}
```

The action should remove `unused = 1;` from the let expression.

```
CHECK: "id": 2,
CHECK: "newText": "",
CHECK: "range":
CHECK: "end":
CHECK: "character": 15,
CHECK: "line": 0
CHECK: "start":
CHECK: "character": 4,
CHECK: "line": 0
CHECK: "isPreferred": true,
CHECK: "kind": "quickfix",
CHECK: "title": "remove unused binding"
```

```json
{"jsonrpc":"2.0","method":"exit"}
```
79 changes: 79 additions & 0 deletions nixd/tools/nixd/test/code-action/unused-def/multiline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# RUN: nixd --lit-test < %s | FileCheck %s

Test `remove unused binding` action with multiline let expression.

<-- initialize(0)

```json
{
"jsonrpc":"2.0",
"id":0,
"method":"initialize",
"params":{
"processId":123,
"rootPath":"",
"capabilities":{
},
"trace":"off"
}
}
```


<-- textDocument/didOpen

```nix file:///multiline.nix
let
unused = 1;
in
2
```

<-- textDocument/codeAction(2)


```json
{
"jsonrpc":"2.0",
"id":2,
"method":"textDocument/codeAction",
"params":{
"textDocument":{
"uri":"file:///multiline.nix"
},
"range":{
"start":{
"line": 1,
"character": 2
},
"end":{
"line":1,
"character": 8
}
},
"context":{
"diagnostics":[],
"triggerKind":2
}
}
}
```

The action should remove the entire binding line.

```
CHECK: "id": 2,
CHECK: "newText": "",
CHECK: "range":
CHECK: "end":
CHECK: "line": 1
CHECK: "start":
CHECK: "line": 1
CHECK: "isPreferred": true,
CHECK: "kind": "quickfix",
CHECK: "title": "remove unused binding"
```

```json
{"jsonrpc":"2.0","method":"exit"}
```
Loading