Skip to content

Commit ca7f9d2

Browse files
authored
Merge pull request #609 from bash-lsp/shellcheck-when-opening
ShellCheck: handle linting file when opened and respond to config changes
2 parents c38da48 + d210c24 commit ca7f9d2

File tree

4 files changed

+57
-35
lines changed

4 files changed

+57
-35
lines changed

server/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Bash Language Server
22

3-
## 4.0.0
3+
## 4.0.1
44

55
- **Breaking**: Drop support for Node 12, which reached its official end of life on April 30th 2022. Doing so enables new features. https://github.com/bash-lsp/bash-language-server/pull/584
66
- ShellCheck: support code actions, remove duplicated error codes, add URLs and tags, support parsing dialects (sh, bash, dash, ksh) but still fallback to bash, enable configuring ShellCheck arguments using the `shellcheckArguments` configuration parameter (legacy environment variable: `SHELLCHECK_ARGUMENTS`)

server/package.json

Lines changed: 1 addition & 1 deletion
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.0.0",
6+
"version": "4.0.1",
77
"publisher": "mads-hartmann",
88
"main": "./out/server.js",
99
"typings": "./out/server.d.ts",

server/src/__tests__/server.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ describe('server', () => {
517517
const { connection, server } = await initializeServer()
518518
const document = FIXTURE_DOCUMENT.COMMENT_DOC
519519

520-
await server.onDocumentContentChange(document)
520+
await server.analyzeAndLintDocument(document)
521521

522522
expect(connection.sendDiagnostics).toHaveBeenCalledTimes(1)
523523
const { diagnostics } = connection.sendDiagnostics.mock.calls[0][0]

server/src/server.ts

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default class BashServer {
3232
private documents: LSP.TextDocuments<TextDocument> = new LSP.TextDocuments(TextDocument)
3333
private executables: Executables
3434
private linter?: Linter
35-
private rootPath: string | null | undefined
35+
private workspaceFolder: string | null | undefined
3636
private uriToCodeActions: { [uri: string]: CodeAction[] | undefined } = {}
3737

3838
private constructor({
@@ -41,33 +41,35 @@ export default class BashServer {
4141
connection,
4242
executables,
4343
linter,
44-
rootPath,
44+
workspaceFolder,
4545
}: {
4646
analyzer: Analyzer
4747
capabilities: LSP.ClientCapabilities
4848
connection: LSP.Connection
4949
executables: Executables
5050
linter?: Linter
51-
rootPath: string | null | undefined
51+
workspaceFolder: string | null | undefined
5252
}) {
5353
this.analyzer = analyzer
5454
this.clientCapabilities = capabilities
55-
this.config = config.getDefaultConfiguration()
5655
this.connection = connection
5756
this.executables = executables
5857
this.linter = linter
59-
this.rootPath = rootPath
58+
this.workspaceFolder = workspaceFolder
59+
this.config = {} as any // NOTE: configured in updateConfiguration
60+
this.updateConfiguration(config.getDefaultConfiguration())
6061
}
6162
/**
6263
* Initialize the server based on a connection to the client and the protocols
6364
* initialization parameters.
6465
*/
6566
public static async initialize(
6667
connection: LSP.Connection,
67-
{ rootPath, capabilities }: LSP.InitializeParams,
68+
{ rootPath, rootUri, capabilities }: LSP.InitializeParams,
6869
): // TODO: use workspaceFolders instead of rootPath
6970
Promise<BashServer> {
7071
const { PATH } = process.env
72+
const workspaceFolder = rootUri || rootPath
7173

7274
if (!PATH) {
7375
throw new Error('Expected PATH environment variable to be set')
@@ -83,7 +85,7 @@ export default class BashServer {
8385
capabilities,
8486
connection,
8587
executables,
86-
rootPath,
88+
workspaceFolder,
8789
})
8890
}
8991

@@ -92,11 +94,20 @@ export default class BashServer {
9294
* care about.
9395
*/
9496
public register(connection: LSP.Connection): void {
97+
const hasConfigurationCapability = !!this.clientCapabilities?.workspace?.configuration
98+
99+
let currentDocument: TextDocument | null = null
100+
let initialized = false // Whether the client finished initializing
101+
95102
this.documents.listen(this.connection)
96-
this.documents.onDidChangeContent(async ({ document }) => {
103+
104+
this.documents.onDidChangeContent(({ document }) => {
97105
// The content of a text document has changed. This event is emitted
98106
// when the text document first opened or when its content has changed.
99-
await this.onDocumentContentChange(document)
107+
currentDocument = document
108+
if (initialized) {
109+
this.analyzeAndLintDocument(document)
110+
}
100111
})
101112

102113
this.documents.onDidClose((event) => {
@@ -114,9 +125,6 @@ export default class BashServer {
114125
connection.onCompletionResolve(this.onCompletionResolve.bind(this))
115126
connection.onCodeAction(this.onCodeAction.bind(this))
116127

117-
const hasConfigurationCapability = !!this.clientCapabilities?.workspace?.configuration
118-
let initialized = false
119-
120128
/**
121129
* The initialized notification is sent from the client to the server after
122130
* the client received the result of the initialize request but before the
@@ -125,11 +133,11 @@ export default class BashServer {
125133
* register capabilities. The initialized notification may only be sent once.
126134
*/
127135
connection.onInitialized(async () => {
128-
const { config: newConfig, environmentVariablesUsed } =
136+
const { config: environmentConfig, environmentVariablesUsed } =
129137
config.getConfigFromEnvironmentVariables()
130-
this.config = newConfig
131138

132139
if (environmentVariablesUsed.length > 0) {
140+
this.updateConfiguration(environmentConfig)
133141
connection.console.warn(
134142
`Environment variable configuration is being deprecated, please use workspace configuration. The following environment variables were used: ${environmentVariablesUsed.join(
135143
', ',
@@ -151,17 +159,12 @@ export default class BashServer {
151159
this.connection.console.log('Configuration loaded from client')
152160
}
153161

154-
const { shellcheckPath } = this.config
155-
if (!shellcheckPath) {
156-
connection.console.info('ShellCheck linting is disabled.')
157-
} else {
158-
this.linter = new Linter({
159-
console: connection.console,
160-
executablePath: shellcheckPath,
161-
})
162-
}
163-
164162
initialized = true
163+
if (currentDocument) {
164+
// If we already have a document, analyze it now that we're initialized
165+
// and the linter is ready.
166+
this.analyzeAndLintDocument(currentDocument)
167+
}
165168

166169
// NOTE: we do not block the server initialization on this background analysis.
167170
return { backgroundAnalysisCompleted: this.startBackgroundAnalysis() }
@@ -173,20 +176,23 @@ export default class BashServer {
173176
if (configChanged && initialized) {
174177
this.connection.console.log('Configuration changed')
175178
this.startBackgroundAnalysis()
176-
// TODO: we should trigger the linter again
179+
if (currentDocument) {
180+
this.uriToCodeActions[currentDocument.uri] = undefined
181+
this.analyzeAndLintDocument(currentDocument)
182+
}
177183
}
178184
})
179185

180186
// FIXME: re-lint on workspace folder change
181187
}
182188

183189
private async startBackgroundAnalysis(): Promise<{ filesParsed: number }> {
184-
const { rootPath } = this
185-
if (rootPath) {
190+
const { workspaceFolder } = this
191+
if (workspaceFolder) {
186192
return this.analyzer.initiateBackgroundAnalysis({
187193
globPattern: this.config.globPattern,
188194
backgroundAnalysisMaxFiles: this.config.backgroundAnalysisMaxFiles,
189-
rootPath,
195+
rootPath: workspaceFolder,
190196
})
191197
}
192198

@@ -200,6 +206,24 @@ export default class BashServer {
200206

201207
if (!isDeepStrictEqual(this.config, newConfig)) {
202208
this.config = newConfig
209+
210+
// NOTE: I don't really like this... An alternative would be to pass in the
211+
// shellcheck executable path when linting. We would need to handle
212+
// resetting the canLint flag though.
213+
214+
const { shellcheckPath } = this.config
215+
if (!shellcheckPath) {
216+
this.connection.console.log(
217+
'ShellCheck linting is disabled as "shellcheckPath" was not set',
218+
)
219+
this.linter = undefined
220+
} else {
221+
this.linter = new Linter({
222+
console: this.connection.console,
223+
executablePath: shellcheckPath,
224+
})
225+
}
226+
203227
return true
204228
}
205229
} catch (err) {
@@ -211,10 +235,9 @@ export default class BashServer {
211235
}
212236

213237
/**
214-
* The content of a text document has changed. This event is emitted
215-
* when the text document first opened or when its content has changed.
238+
* Analyze and lint the given document.
216239
*/
217-
public async onDocumentContentChange(document: TextDocument) {
240+
public async analyzeAndLintDocument(document: TextDocument) {
218241
const { uri } = document
219242

220243
let diagnostics: LSP.Diagnostic[] = []
@@ -399,7 +422,6 @@ export default class BashServer {
399422
) {
400423
const shellDocumentation = await getShellDocumentation({ word })
401424
if (shellDocumentation) {
402-
// eslint-disable-next-line no-console
403425
return { contents: getMarkdownContent(shellDocumentation) }
404426
}
405427
} else {

0 commit comments

Comments
 (0)