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
13 changes: 13 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"options": {
"typeAware": true,
"typeCheck": true
},
"categories": {
"correctness": "error"
},
Expand Down Expand Up @@ -45,5 +49,14 @@
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unsafe-function-type": "error"
},
"overrides": [
{
"files": ["**/*.test.ts"],
"rules": {
// Vitest assertions inspect mock methods without calling them.
"typescript/unbound-method": "off"
}
}
],
"ignorePatterns": ["**/out/**", "**/node_modules/**", "coverage/**", ".context/**"]
}
10 changes: 8 additions & 2 deletions docs/development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ Development uses [Oxlint][oxlint], [Prettier][prettier], and integration tests u

pnpm verify # fixes lint/formatting, compiles, type-checks, and runs tests
pnpm verify:bail # checks lint/formatting, compiles, and runs tests with coverage
pnpm lint # fixes lint and formatting
pnpm lint:bail # checks lint and formatting without rewriting files
pnpm lint # fixes lint and formatting, and checks types
pnpm lint:bail # checks lint, formatting, and types without rewriting files
pnpm test
pnpm test:coverage
pnpm test:watch
Expand All @@ -64,6 +64,11 @@ pnpm test server/src/__tests__/input-declarations.test.ts
pnpm test server/src/__tests__/server.test.ts -t 'rename'
```

The lint commands run [type-aware rules and TypeScript compiler diagnostics
through Oxlint][oxlint-types]. Test commands run Vitest without repeating lint or
type checks. `pnpm compile` and `pnpm watch` still use `tsc` to emit JavaScript and
declarations.

Tests and test helpers are type-checked using `tsconfig.test.json`. Vitest runs
files sequentially so subprocess and filesystem integration tests stay isolated.
Coverage reports are written to `coverage/` in HTML and LCOV formats.
Expand Down Expand Up @@ -146,5 +151,6 @@ To analyze the performance of the extension or server using the Chrome inspector
[vitest]: https://vitest.dev/
[prettier]: https://prettier.io/
[oxlint]: https://oxc.rs/docs/guide/usage/linter/
[oxlint-types]: https://oxc.rs/docs/guide/usage/linter/type-aware.html
[pnpm]: https://pnpm.io/installation
[node]: https://nodejs.org/en/download/
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@
"watch": "tsc -b -w",
"lint": "oxlint --fix --deny-warnings && pnpm format",
"lint:bail": "oxlint --deny-warnings && pnpm format:check",
"test": "pnpm typecheck && vitest run",
"test": "vitest run",
"test:coverage": "pnpm run test --coverage",
"test:watch": "pnpm typecheck && vitest --watch",
"test:watch": "vitest --watch",
"verify": "pnpm lint && pnpm compile && pnpm run test",
"verify:bail": "pnpm lint:bail && pnpm compile && pnpm test:coverage",
"reinstall-server": "npm uninstall -g bash-language-server && pnpm compile && npm i -g ./server",
"link-server": "pnpm compile && node scripts/link-server.mjs",
"postinstall": "pnpm --dir=vscode-client install",
"format": "prettier --write \"**/*.{js,ts,tsx,mts}\"",
"format:check": "prettier --check \"**/*.{js,ts,tsx,mts}\"",
"typecheck": "tsc --noEmit -p tsconfig.test.json"
"format:check": "prettier --check \"**/*.{js,ts,tsx,mts}\""
},
"devDependencies": {
"@types/node": "22.20.4",
"@vitest/coverage-v8": "5.0.1",
"cross-spawn": "7.0.6",
"oxlint": "1.83.0",
"oxlint-tsgolint": "7.0.2002",
"prettier": "2.8.8",
"typescript": "6.0.3",
"vitest": "5.0.1",
Expand Down
69 changes: 67 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/src/__tests__/background-lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ it.each(['shutdown', 'configuration change'])(
if (event === 'shutdown') {
await connection.onShutdown.mock.calls[0][0]({} as any)
} else {
connection.onDidChangeConfiguration.mock.calls[0][0]({
await connection.onDidChangeConfiguration.mock.calls[0][0]({
settings: { bashIde: { backgroundAnalysisMaxFiles: 0 } },
})
}
Expand Down
4 changes: 3 additions & 1 deletion server/src/__tests__/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@ describe('server', () => {

const onDidChangeConfiguration = connection.onDidChangeConfiguration.mock.calls[0][0]

onDidChangeConfiguration({ settings: { bashIde: { explainshellEndpoint: 42 } } })
await onDidChangeConfiguration({
settings: { bashIde: { explainshellEndpoint: 42 } },
})

expect(connection.workspace.getConfiguration).toHaveBeenCalled()
expect(Logger.prototype.log).toHaveBeenCalledWith(expect.any(Number), [
Expand Down
10 changes: 7 additions & 3 deletions server/src/analyser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@ export default class Analyzer {
} catch (error) {
if (!signal.aborted)
logger.warn(
`BackgroundAnalysis: failed resolving glob "${globPattern}". The experience across files will be degraded. Error: ${error}`,
`BackgroundAnalysis: failed resolving glob "${globPattern}". The experience across files will be degraded. Error: ${String(
error,
)}`,
)
return { filesParsed }
}
Expand Down Expand Up @@ -293,7 +295,9 @@ export default class Analyzer {
filesParsed++
} catch (error) {
if (stopped()) break
logger.warn(`BackgroundAnalysis: Failed analyzing ${uri}. Error: ${error}`)
logger.warn(
`BackgroundAnalysis: Failed analyzing ${uri}. Error: ${String(error)}`,
)
}
}
// Rereading background files can remove source relationships. Recompute
Expand Down Expand Up @@ -1067,7 +1071,7 @@ export default class Analyzer {
uri,
})
} catch (err) {
logger.warn(`Error while analyzing file ${uri}: ${err}`)
logger.warn(`Error while analyzing file ${uri}: ${String(err)}`)
return false
}
}
Expand Down
22 changes: 11 additions & 11 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export default class BashServer {
// when the text document first opened or when its content has changed.
currentDocument = document
if (initialized) {
this.analyzeAndLintDocument(document)
void this.analyzeAndLintDocument(document)
}
})

Expand All @@ -170,7 +170,7 @@ export default class BashServer {
if (currentDocument?.uri === event.document.uri) {
currentDocument = null
}
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] })
void connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] })
delete this.uriToCodeActions[event.document.uri]
})

Expand Down Expand Up @@ -230,7 +230,7 @@ export default class BashServer {
if (hasConfigurationCapability) {
// Register event for all configuration changes.
if (canDynamicallyRegisterConfigurationChangeNotification) {
connection.client.register(LSP.DidChangeConfigurationNotification.type, {
void connection.client.register(LSP.DidChangeConfigurationNotification.type, {
section: CONFIGURATION_SECTION,
})
}
Expand All @@ -247,7 +247,7 @@ export default class BashServer {
if (currentDocument) {
// If we already have a document, analyze it now that we're initialized
// and the linter is ready.
this.analyzeAndLintDocument(currentDocument)
void this.analyzeAndLintDocument(currentDocument)
}

// NOTE: we do not block the server initialization on this background analysis.
Expand All @@ -259,9 +259,9 @@ export default class BashServer {
const configChanged = this.updateConfiguration(settings[CONFIGURATION_SECTION])
if (configChanged && initialized) {
logger.debug('Configuration changed')
this.startBackgroundAnalysis()
void this.startBackgroundAnalysis()
for (const document of this.documents.all()) {
this.analyzeAndLintDocument(document)
void this.analyzeAndLintDocument(document)
}
}
})
Expand Down Expand Up @@ -335,7 +335,7 @@ export default class BashServer {
return true
}
} catch (err) {
logger.warn(`updateConfiguration: failed with ${err}`)
logger.warn(`updateConfiguration: failed with ${String(err)}`)
}
}

Expand Down Expand Up @@ -373,11 +373,11 @@ export default class BashServer {
result,
})
} catch (err) {
logger.error(`Error while linting: ${err}`)
logger.error(`Error while linting: ${String(err)}`)
}
}

this.connection.sendDiagnostics({ uri, version, diagnostics })
await this.connection.sendDiagnostics({ uri, version, diagnostics })
}

private logRequest({
Expand Down Expand Up @@ -724,7 +724,7 @@ export default class BashServer {
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : error
logger.warn(`getExplainshellDocumentation exception: ${errorMessage}`)
logger.warn(`getExplainshellDocumentation exception: ${String(errorMessage)}`)
}
}

Expand Down Expand Up @@ -886,7 +886,7 @@ export default class BashServer {

return await this.formatter.format(document, params.options, this.config.shfmt)
} catch (err) {
logger.error(`Error while formatting: ${err}`)
logger.error(`Error while formatting: ${String(err)}`)
}
}

Expand Down
16 changes: 8 additions & 8 deletions server/src/shellcheck/__tests__/lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async function initializeServer() {
workspaceFolders: null,
})
server.register(connection)
connection.onDidChangeConfiguration.mock.calls[0][0]({
await connection.onDidChangeConfiguration.mock.calls[0][0]({
settings: { bashIde: { shellcheckPath: 'controlled-shellcheck' } },
})
await connection.onInitialized.mock.calls[0][0]({})
Expand Down Expand Up @@ -186,18 +186,18 @@ describe('lint process lifecycle', () => {
it('cancels on close without republishing diagnostics or relinting a closed document', async () => {
const { connection, server } = await initializeServer()
const analyze = vi.spyOn(server, 'analyzeAndLintDocument')
connection.onDidOpenTextDocument.mock.calls[0][0]({
await connection.onDidOpenTextDocument.mock.calls[0][0]({
textDocument: { uri, languageId: 'shellscript', version: 1, text: 'echo stale' },
})
vi.advanceTimersByTime(500)

connection.onDidCloseTextDocument.mock.calls[0][0]({ textDocument: { uri } })
await connection.onDidCloseTextDocument.mock.calls[0][0]({ textDocument: { uri } })
await analyze.mock.results[0].value
await exits[0]
expect(children[0].signalCode).toBe('SIGTERM')
expect(connection.sendDiagnostics.mock.calls).toEqual([[{ uri, diagnostics: [] }]])

connection.onDidChangeConfiguration.mock.calls[0][0]({
await connection.onDidChangeConfiguration.mock.calls[0][0]({
settings: { bashIde: { shellcheckPath: 'another-shellcheck' } },
})
vi.advanceTimersByTime(500)
Expand All @@ -208,12 +208,12 @@ describe('lint process lifecycle', () => {
it('cancels the old checker when configuration disables linting', async () => {
const { connection, server } = await initializeServer()
const analyze = vi.spyOn(server, 'analyzeAndLintDocument')
connection.onDidOpenTextDocument.mock.calls[0][0]({
await connection.onDidOpenTextDocument.mock.calls[0][0]({
textDocument: { uri, languageId: 'shellscript', version: 1, text: 'echo stale' },
})
vi.advanceTimersByTime(500)

connection.onDidChangeConfiguration.mock.calls[0][0]({
await connection.onDidChangeConfiguration.mock.calls[0][0]({
settings: { bashIde: { shellcheckPath: '' } },
})
await Promise.all(analyze.mock.results.map(({ value }) => value))
Expand All @@ -240,12 +240,12 @@ describe('lint process lifecycle', () => {
},
]
for (const textDocument of openDocuments) {
connection.onDidOpenTextDocument.mock.calls[0][0]({ textDocument })
await connection.onDidOpenTextDocument.mock.calls[0][0]({ textDocument })
}
vi.advanceTimersByTime(500)
expect(children).toHaveLength(2)

connection.onDidChangeConfiguration.mock.calls[0][0]({
await connection.onDidChangeConfiguration.mock.calls[0][0]({
settings: { bashIde: { shellcheckPath } },
})
await Promise.all(exits)
Expand Down
Loading
Loading