Skip to content

Commit 4be625e

Browse files
authored
Merge pull request #700 from bash-lsp/fix-codeactions
Fix ShellCheck code actions not working on some clients
2 parents e4aef79 + 3d9759f commit 4be625e

File tree

7 files changed

+103
-97
lines changed

7 files changed

+103
-97
lines changed

Diff for: docs/development-guide.md

+16-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ convenience - it proxies to the `package.json` files in the `vscode-client` and
1616
This guide presumes you have the following dependencies installed:
1717

1818
- [`yarn`][yarn].
19-
- [`node`][node] (v6 or newer)
19+
- [`node`][node] (v14 or newer)
2020
- `g++`
21-
- `make`
21+
- `make` (optional)
2222

2323
## Initial setup
2424

@@ -85,6 +85,20 @@ reload your vscode window to re-launch the server.
8585
yarn run reinstall-server
8686
```
8787

88+
If you for some reason cannot get access to logs through the client,
89+
then you can hack the `server/util/logger` with:
90+
91+
```typescript
92+
const fs = require('fs')
93+
const util = require('util')
94+
const log_file = fs.createWriteStream(`/tmp/bash-language-server-debug.log`, {
95+
flags: 'w',
96+
})
97+
98+
// inside log function
99+
log_file.write(`${severity} ${util.format(message)}\n`)
100+
```
101+
88102
## Performance
89103

90104
To analyze the performance of the extension or server using the Chrome inspector:

Diff for: server/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Bash Language Server
22

3+
## 4.6.1
4+
5+
- Fix the ShellCheck code action feature that for some clients did not return any code actions. https://github.com/bash-lsp/bash-language-server/pull/700
6+
37
## 4.6.0
48

59
- Support parsing `: "${VARIABLE:="default"}"` as a variable definition https://github.com/bash-lsp/bash-language-server/pull/693

Diff for: server/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "A language server for Bash",
44
"author": "Mads Hartmann",
55
"license": "MIT",
6-
"version": "4.6.0",
6+
"version": "4.6.1",
77
"main": "./out/server.js",
88
"typings": "./out/server.d.ts",
99
"bin": {

Diff for: server/src/__tests__/server.test.ts

+24-23
Original file line numberDiff line numberDiff line change
@@ -177,29 +177,30 @@ describe('server', () => {
177177
const fixableDiagnostic = diagnostics.filter(({ code }) => code === 'SC2086')[0]
178178

179179
expect(fixableDiagnostic).toMatchInlineSnapshot(`
180-
Object {
181-
"code": "SC2086",
182-
"codeDescription": Object {
183-
"href": "https://www.shellcheck.net/wiki/SC2086",
184-
},
185-
"message": "Double quote to prevent globbing and word splitting.",
186-
"range": Object {
187-
"end": Object {
188-
"character": 13,
189-
"line": 55,
190-
},
191-
"start": Object {
192-
"character": 5,
193-
"line": 55,
194-
},
195-
},
196-
"severity": 3,
197-
"source": "shellcheck",
198-
"tags": undefined,
199-
}
200-
`)
201-
202-
// TODO: we could find the diagnostics and then use the range to test the code action
180+
Object {
181+
"code": "SC2086",
182+
"codeDescription": Object {
183+
"href": "https://www.shellcheck.net/wiki/SC2086",
184+
},
185+
"data": Object {
186+
"id": "shellcheck|2086|55:5-55:13",
187+
},
188+
"message": "Double quote to prevent globbing and word splitting.",
189+
"range": Object {
190+
"end": Object {
191+
"character": 13,
192+
"line": 55,
193+
},
194+
"start": Object {
195+
"character": 5,
196+
"line": 55,
197+
},
198+
},
199+
"severity": 3,
200+
"source": "shellcheck",
201+
"tags": undefined,
202+
}
203+
`)
203204

204205
const onCodeAction = connection.onCodeAction.mock.calls[0][0]
205206

Diff for: server/src/server.ts

+10-35
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { isDeepStrictEqual } from 'node:util'
44

55
import * as TurndownService from 'turndown'
66
import * as LSP from 'vscode-languageserver/node'
7-
import { CodeAction } from 'vscode-languageserver/node'
87
import { TextDocument } from 'vscode-languageserver-textdocument'
98

109
import Analyzer from './analyser'
@@ -13,7 +12,7 @@ import * as config from './config'
1312
import Executables from './executables'
1413
import { initializeParser } from './parser'
1514
import * as ReservedWords from './reserved-words'
16-
import { Linter } from './shellcheck'
15+
import { Linter, LintingResult } from './shellcheck'
1716
import { SNIPPETS } from './snippets'
1817
import { BashCompletionItem, CompletionItemDataType } from './types'
1918
import { uniqueBasedOnHash } from './util/array'
@@ -37,7 +36,9 @@ export default class BashServer {
3736
private executables: Executables
3837
private linter?: Linter
3938
private workspaceFolder: string | null
40-
private uriToCodeActions: { [uri: string]: CodeAction[] | undefined } = {}
39+
private uriToCodeActions: {
40+
[uri: string]: LintingResult['codeActions'] | undefined
41+
} = {}
4142

4243
private constructor({
4344
analyzer,
@@ -385,41 +386,15 @@ export default class BashServer {
385386
// ==============================
386387

387388
private async onCodeAction(params: LSP.CodeActionParams): Promise<LSP.CodeAction[]> {
388-
const codeActions = this.uriToCodeActions[params.textDocument.uri]
389+
const codeActionsForUri = this.uriToCodeActions[params.textDocument.uri] || {}
389390

390-
if (!codeActions) {
391-
return []
392-
}
393-
394-
const getDiagnosticsFingerPrint = (diagnostics?: LSP.Diagnostic[]): string[] =>
395-
diagnostics
396-
?.map(({ code, source, range }) =>
397-
code !== undefined && source && range
398-
? JSON.stringify({
399-
code,
400-
source,
401-
range,
402-
})
403-
: null,
404-
)
405-
.filter((fingerPrint): fingerPrint is string => fingerPrint != null) || []
391+
const codeActions = params.context.diagnostics
392+
.map(({ data }) => codeActionsForUri[data?.id])
393+
.filter((action): action is LSP.CodeAction => action != null)
406394

407-
const paramsDiagnosticsKeys = getDiagnosticsFingerPrint(params.context.diagnostics)
408-
409-
// find actions that match the paramsDiagnosticsKeys
410-
const actions = codeActions.filter((action) => {
411-
const actionDiagnosticsKeys = getDiagnosticsFingerPrint(action.diagnostics)
412-
// actions without diagnostics are always returned
413-
if (actionDiagnosticsKeys.length === 0) {
414-
return true
415-
}
416-
417-
return actionDiagnosticsKeys.some((actionDiagnosticKey) =>
418-
paramsDiagnosticsKeys.includes(actionDiagnosticKey),
419-
)
420-
})
395+
logger.debug(`onCodeAction: found ${codeActions.length} code action(s)`)
421396

422-
return actions
397+
return codeActions
423398
}
424399

425400
private onCompletion(params: LSP.TextDocumentPositionParams): BashCompletionItem[] {

Diff for: server/src/shellcheck/__tests__/index.test.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('linter', () => {
5454
})
5555

5656
expect(result).toEqual({
57-
codeActions: [],
57+
codeActions: {},
5858
diagnostics: [],
5959
})
6060

@@ -76,14 +76,17 @@ describe('linter', () => {
7676
const [result] = await getLintingResult({ document: textToDoc(shell) })
7777
expect(result).toMatchInlineSnapshot(`
7878
Object {
79-
"codeActions": Array [
80-
Object {
79+
"codeActions": Object {
80+
"shellcheck|2086|1:5-1:9": Object {
8181
"diagnostics": Array [
8282
Object {
8383
"code": "SC2086",
8484
"codeDescription": Object {
8585
"href": "https://www.shellcheck.net/wiki/SC2086",
8686
},
87+
"data": Object {
88+
"id": "shellcheck|2086|1:5-1:9",
89+
},
8790
"message": "Double quote to prevent globbing and word splitting.",
8891
"range": Object {
8992
"end": Object {
@@ -135,13 +138,16 @@ describe('linter', () => {
135138
"kind": "quickfix",
136139
"title": "Apply fix for SC2086",
137140
},
138-
],
141+
},
139142
"diagnostics": Array [
140143
Object {
141144
"code": "SC2154",
142145
"codeDescription": Object {
143146
"href": "https://www.shellcheck.net/wiki/SC2154",
144147
},
148+
"data": Object {
149+
"id": "shellcheck|2154|1:5-1:9",
150+
},
145151
"message": "foo is referenced but not assigned.",
146152
"range": Object {
147153
"end": Object {
@@ -162,6 +168,9 @@ describe('linter', () => {
162168
"codeDescription": Object {
163169
"href": "https://www.shellcheck.net/wiki/SC2086",
164170
},
171+
"data": Object {
172+
"id": "shellcheck|2086|1:5-1:9",
173+
},
165174
"message": "Double quote to prevent globbing and word splitting.",
166175
"range": Object {
167176
"end": Object {
@@ -197,7 +206,7 @@ describe('linter', () => {
197206

198207
const result = await promises[promises.length - 1]
199208
expect(result).toEqual({
200-
codeActions: [],
209+
codeActions: {},
201210
diagnostics: [],
202211
})
203212
})
@@ -209,7 +218,7 @@ describe('linter', () => {
209218
})
210219

211220
expect(result).toEqual({
212-
codeActions: [],
221+
codeActions: {},
213222
diagnostics: [],
214223
})
215224
})
@@ -222,13 +231,16 @@ describe('linter', () => {
222231

223232
expect(result).toMatchInlineSnapshot(`
224233
Object {
225-
"codeActions": Array [],
234+
"codeActions": Object {},
226235
"diagnostics": Array [
227236
Object {
228237
"code": "SC1091",
229238
"codeDescription": Object {
230239
"href": "https://www.shellcheck.net/wiki/SC1091",
231240
},
241+
"data": Object {
242+
"id": "shellcheck|1091|3:7-3:19",
243+
},
232244
"message": "Not following: shellcheck/sourced.sh: openBinaryFile: does not exist (No such file or directory)",
233245
"range": Object {
234246
"end": Object {
@@ -249,6 +261,9 @@ describe('linter', () => {
249261
"codeDescription": Object {
250262
"href": "https://www.shellcheck.net/wiki/SC2154",
251263
},
264+
"data": Object {
265+
"id": "shellcheck|2154|5:6-5:10",
266+
},
252267
"message": "foo is referenced but not assigned.",
253268
"range": Object {
254269
"end": Object {
@@ -276,7 +291,7 @@ describe('linter', () => {
276291
sourcePaths: [path.resolve(FIXTURE_FOLDER)],
277292
})
278293
expect(result).toEqual({
279-
codeActions: [],
294+
codeActions: {},
280295
diagnostics: [],
281296
})
282297
})

0 commit comments

Comments
 (0)