Skip to content

Commit e5a4ec9

Browse files
committed
feat: add shellcheckExternalSources config to make --external-sources optional
The --external-sources flag is currently hardcoded in every ShellCheck invocation. On projects with many shell scripts that cross-source each other (e.g. 100+ scripts with source-path=SCRIPTDIR in .shellcheckrc), this causes exponential AST expansion — individual shellcheck processes grow to 3-5 GB RSS and never terminate. Adds a shellcheckExternalSources boolean config option (default: true for backward compatibility) and SHELLCHECK_EXTERNAL_SOURCES env var. When set to false, --external-sources is omitted from the ShellCheck invocation, preventing the unbounded memory growth. Fixes #874 Fixes #1375
1 parent c6b9bda commit e5a4ec9

File tree

4 files changed

+22
-5
lines changed

4 files changed

+22
-5
lines changed

server/src/config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ export const ConfigSchema = z.object({
2424
// If true, then all symbols from the workspace are included.
2525
includeAllWorkspaceSymbols: z.boolean().default(false),
2626

27-
// Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, --external-sources."
27+
// Controls whether ShellCheck is invoked with --external-sources. When enabled (default),
28+
// ShellCheck follows source directives to lint referenced files. On projects with many
29+
// cross-sourcing scripts this can cause unbounded memory growth. Set to false to disable.
30+
shellcheckExternalSources: z.boolean().default(true),
31+
32+
// Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, and --external-sources (if shellcheckExternalSources is true).
2833
shellcheckArguments: z
2934
.preprocess((arg) => {
3035
let argsList: string[] = []
@@ -87,6 +92,7 @@ export function getConfigFromEnvironmentVariables(): {
8792
includeAllWorkspaceSymbols: toBoolean(process.env.INCLUDE_ALL_WORKSPACE_SYMBOLS),
8893
logLevel: process.env[LOG_LEVEL_ENV_VAR],
8994
shellcheckArguments: process.env.SHELLCHECK_ARGUMENTS,
95+
shellcheckExternalSources: toBoolean(process.env.SHELLCHECK_EXTERNAL_SOURCES),
9096
shellcheckPath: process.env.SHELLCHECK_PATH,
9197
shfmt: {
9298
path: process.env.SHFMT_PATH,

server/src/server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ export default class BashServer {
277277
logger.info('ShellCheck linting is disabled as "shellcheckPath" was not set')
278278
this.linter = undefined
279279
} else {
280-
this.linter = new Linter({ executablePath: shellcheckPath })
280+
this.linter = new Linter({
281+
executablePath: shellcheckPath,
282+
externalSources: this.config.shellcheckExternalSources,
283+
})
281284
}
282285

283286
const shfmtPath = this.config.shfmt?.path

server/src/shellcheck/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function safeFileURLToPath(uri: string): string | null {
3333
type LinterOptions = {
3434
executablePath: string
3535
cwd?: string
36+
externalSources?: boolean
3637
}
3738

3839
export type LintingResult = {
@@ -43,15 +44,17 @@ export type LintingResult = {
4344
export class Linter {
4445
private cwd: string
4546
public executablePath: string
47+
private externalSources: boolean
4648
private uriToDebouncedExecuteLint: {
4749
[uri: string]: InstanceType<typeof Linter>['executeLint']
4850
}
4951
private _canLint: boolean
5052

51-
constructor({ cwd, executablePath }: LinterOptions) {
53+
constructor({ cwd, executablePath, externalSources = true }: LinterOptions) {
5254
this._canLint = true
5355
this.cwd = cwd || process.cwd()
5456
this.executablePath = executablePath
57+
this.externalSources = externalSources
5558
this.uriToDebouncedExecuteLint = Object.create(null)
5659
}
5760

@@ -139,7 +142,7 @@ export class Linter {
139142

140143
const args = [
141144
'--format=json1',
142-
'--external-sources',
145+
...(this.externalSources ? ['--external-sources'] : []),
143146
...sourcePathsArgs,
144147
...additionalArgs,
145148
]

vscode-client/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,15 @@
7373
"default": "shellcheck",
7474
"description": "Controls the executable used for ShellCheck linting information. An empty string will disable linting."
7575
},
76+
"bashIde.shellcheckExternalSources": {
77+
"type": "boolean",
78+
"default": true,
79+
"description": "Controls whether ShellCheck is invoked with --external-sources. When enabled (default), ShellCheck follows source directives to lint referenced files. On projects with many cross-sourcing scripts this can cause unbounded memory growth. Set to false to disable."
80+
},
7681
"bashIde.shellcheckArguments": {
7782
"type": "string",
7883
"default": "",
79-
"description": "Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, --external-sources."
84+
"description": "Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, and --external-sources (if shellcheckExternalSources is true)."
8085
},
8186
"bashIde.shfmt.path": {
8287
"type": "string",

0 commit comments

Comments
 (0)