diff --git a/server/src/__tests__/config.test.ts b/server/src/__tests__/config.test.ts index edfeab43e..64f6d6b31 100644 --- a/server/src/__tests__/config.test.ts +++ b/server/src/__tests__/config.test.ts @@ -12,6 +12,7 @@ describe('ConfigSchema', () => { "includeAllWorkspaceSymbols": false, "logLevel": "info", "shellcheckArguments": [], + "shellcheckExternalSources": true, "shellcheckPath": "shellcheck", "shfmt": { "binaryNextLine": false, @@ -62,6 +63,7 @@ describe('ConfigSchema', () => { "-e", "SC2002", ], + "shellcheckExternalSources": true, "shellcheckPath": "", "shfmt": { "binaryNextLine": true, @@ -99,6 +101,7 @@ describe('getConfigFromEnvironmentVariables', () => { "includeAllWorkspaceSymbols": false, "logLevel": "info", "shellcheckArguments": [], + "shellcheckExternalSources": true, "shellcheckPath": "shellcheck", "shfmt": { "binaryNextLine": false, @@ -130,6 +133,7 @@ describe('getConfigFromEnvironmentVariables', () => { "includeAllWorkspaceSymbols": false, "logLevel": "info", "shellcheckArguments": [], + "shellcheckExternalSources": true, "shellcheckPath": "", "shfmt": { "binaryNextLine": false, @@ -170,6 +174,7 @@ describe('getConfigFromEnvironmentVariables', () => { "-e", "SC2001", ], + "shellcheckExternalSources": true, "shellcheckPath": "/path/to/shellcheck", "shfmt": { "binaryNextLine": false, diff --git a/server/src/config.ts b/server/src/config.ts index cf6fd99f9..408b4f667 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -24,7 +24,12 @@ export const ConfigSchema = z.object({ // If true, then all symbols from the workspace are included. includeAllWorkspaceSymbols: z.boolean().default(false), - // Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, --external-sources." + // 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. + shellcheckExternalSources: z.boolean().default(true), + + // Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, and --external-sources (if shellcheckExternalSources is true). shellcheckArguments: z .preprocess((arg) => { let argsList: string[] = [] @@ -87,6 +92,7 @@ export function getConfigFromEnvironmentVariables(): { includeAllWorkspaceSymbols: toBoolean(process.env.INCLUDE_ALL_WORKSPACE_SYMBOLS), logLevel: process.env[LOG_LEVEL_ENV_VAR], shellcheckArguments: process.env.SHELLCHECK_ARGUMENTS, + shellcheckExternalSources: toBoolean(process.env.SHELLCHECK_EXTERNAL_SOURCES), shellcheckPath: process.env.SHELLCHECK_PATH, shfmt: { path: process.env.SHFMT_PATH, diff --git a/server/src/server.ts b/server/src/server.ts index 33e9ae81d..800b633dd 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -277,7 +277,10 @@ export default class BashServer { logger.info('ShellCheck linting is disabled as "shellcheckPath" was not set') this.linter = undefined } else { - this.linter = new Linter({ executablePath: shellcheckPath }) + this.linter = new Linter({ + executablePath: shellcheckPath, + externalSources: this.config.shellcheckExternalSources, + }) } const shfmtPath = this.config.shfmt?.path diff --git a/server/src/shellcheck/index.ts b/server/src/shellcheck/index.ts index 4aa7ee15b..c55bce636 100644 --- a/server/src/shellcheck/index.ts +++ b/server/src/shellcheck/index.ts @@ -33,6 +33,7 @@ function safeFileURLToPath(uri: string): string | null { type LinterOptions = { executablePath: string cwd?: string + externalSources?: boolean } export type LintingResult = { @@ -43,15 +44,17 @@ export type LintingResult = { export class Linter { private cwd: string public executablePath: string + private externalSources: boolean private uriToDebouncedExecuteLint: { [uri: string]: InstanceType['executeLint'] } private _canLint: boolean - constructor({ cwd, executablePath }: LinterOptions) { + constructor({ cwd, executablePath, externalSources = true }: LinterOptions) { this._canLint = true this.cwd = cwd || process.cwd() this.executablePath = executablePath + this.externalSources = externalSources this.uriToDebouncedExecuteLint = Object.create(null) } @@ -139,7 +142,7 @@ export class Linter { const args = [ '--format=json1', - '--external-sources', + ...(this.externalSources ? ['--external-sources'] : []), ...sourcePathsArgs, ...additionalArgs, ] diff --git a/vscode-client/package.json b/vscode-client/package.json index fd3a934f6..dd47393a9 100644 --- a/vscode-client/package.json +++ b/vscode-client/package.json @@ -73,10 +73,15 @@ "default": "shellcheck", "description": "Controls the executable used for ShellCheck linting information. An empty string will disable linting." }, + "bashIde.shellcheckExternalSources": { + "type": "boolean", + "default": true, + "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." + }, "bashIde.shellcheckArguments": { "type": "string", "default": "", - "description": "Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, --external-sources." + "description": "Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, and --external-sources (if shellcheckExternalSources is true)." }, "bashIde.shfmt.path": { "type": "string",