From ce65f3d2a4fcc88f649e5d0178f48843338f0dfe Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 6 Mar 2026 16:54:05 +0530 Subject: [PATCH 1/3] CHANGE - Add --sfge-thread-count and --sfge-thread-timeout CLI flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose SFGE engine thread configuration as CLI flags on `sf code-analyzer run` so users can tune performance without editing a config file. - --sfge-thread-count: overrides java_thread_count (default: 8) - --sfge-thread-timeout: overrides java_thread_timeout in ms (default: 180000 / 3 min) - Values flow through RunInput → CodeAnalyzerConfig → SfgeEngine → JVM -D flags - SfgeEngineOverrides type added to CodeAnalyzerConfigFactory for clean separation - Flag messages added to run-command.md following existing conventions Example: sf code-analyzer run --rule-selector sfge --workspace ./force-app \ --sfge-thread-count 8 --sfge-thread-timeout 180000 --- messages/run-command.md | 16 +++++++++ src/commands/code-analyzer/run.ts | 17 +++++++++- src/lib/actions/RunAction.ts | 14 ++++++-- .../factories/CodeAnalyzerConfigFactory.ts | 34 +++++++++++++++++-- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/messages/run-command.md b/messages/run-command.md index 1117844b7..d371b7751 100644 --- a/messages/run-command.md +++ b/messages/run-command.md @@ -142,6 +142,22 @@ To output the results to multiple files, specify this flag multiple times. For e If you specify a file within a folder, such as `--output-file ./out/results.json`, the folder must already exist, or you get an error. If the file already exists, it's overwritten without prompting. +# flags.sfge-thread-count.summary + +Number of threads for SFGE path-based rule execution. + +# flags.sfge-thread-count.description + +Overrides the sfge engine java_thread_count configuration. Increasing this allows more entry points to be analyzed in parallel. Default is 8. Can also be set in code-analyzer.yaml under engines.sfge.java_thread_count. + +# flags.sfge-thread-timeout.summary + +Per-thread timeout in milliseconds for SFGE path-based rules. + +# flags.sfge-thread-timeout.description + +Overrides the sfge engine java_thread_timeout configuration. Entry points that exceed this limit produce a timeout violation. Default is 180000 (3 minutes). Can also be set in code-analyzer.yaml under engines.sfge.java_thread_timeout. + # error.invalid-severity-threshold Expected --severity-threshold=%s to be one of: %s diff --git a/src/commands/code-analyzer/run.ts b/src/commands/code-analyzer/run.ts index ff3773332..aa68078aa 100644 --- a/src/commands/code-analyzer/run.ts +++ b/src/commands/code-analyzer/run.ts @@ -70,6 +70,19 @@ export default class RunCommand extends SfCommand implements Displayable { description: getMessage(BundleName.RunCommand, 'flags.config-file.description'), char: 'c', exists: true + }), + // === Flags pertaining to SFGE engine tuning === + 'sfge-thread-count': Flags.integer({ + summary: getMessage(BundleName.RunCommand, 'flags.sfge-thread-count.summary'), + description: getMessage(BundleName.RunCommand, 'flags.sfge-thread-count.description'), + default: 8, + min: 1 + }), + 'sfge-thread-timeout': Flags.integer({ + summary: getMessage(BundleName.RunCommand, 'flags.sfge-thread-timeout.summary'), + description: getMessage(BundleName.RunCommand, 'flags.sfge-thread-timeout.description'), + default: 180000, + min: 1000 }) }; @@ -84,7 +97,9 @@ export default class RunCommand extends SfCommand implements Displayable { 'workspace': parsedFlags['workspace'], 'severity-threshold': parsedFlags['severity-threshold'] === undefined ? undefined : convertThresholdToEnum(parsedFlags['severity-threshold'].toLowerCase()), - 'target': parsedFlags['target'] + 'target': parsedFlags['target'], + 'sfge-thread-count': parsedFlags['sfge-thread-count'], + 'sfge-thread-timeout': parsedFlags['sfge-thread-timeout'] }; await action.execute(runInput); } diff --git a/src/lib/actions/RunAction.ts b/src/lib/actions/RunAction.ts index 510620c3d..a57b8e754 100644 --- a/src/lib/actions/RunAction.ts +++ b/src/lib/actions/RunAction.ts @@ -8,7 +8,7 @@ import { SeverityLevel, Workspace } from '@salesforce/code-analyzer-core'; -import {CodeAnalyzerConfigFactory} from '../factories/CodeAnalyzerConfigFactory.js'; +import {CodeAnalyzerConfigFactory, SfgeEngineOverrides} from '../factories/CodeAnalyzerConfigFactory.js'; import {EnginePluginsFactory} from '../factories/EnginePluginsFactory.js'; import {createWorkspace} from '../utils/WorkspaceUtil.js'; import {ResultsViewer} from '../viewers/ResultsViewer.js'; @@ -40,7 +40,8 @@ export type RunInput = { 'severity-threshold'?: SeverityLevel; target?: string[]; workspace: string[]; - + 'sfge-thread-count'?: number; + 'sfge-thread-timeout'?: number; } export class RunAction { @@ -51,7 +52,14 @@ export class RunAction { } public async execute(input: RunInput): Promise { - const config: CodeAnalyzerConfig = this.dependencies.configFactory.create(input['config-file']); + const sfgeOverrides: SfgeEngineOverrides = {}; + if (input['sfge-thread-count'] !== undefined) { + sfgeOverrides.java_thread_count = input['sfge-thread-count']; + } + if (input['sfge-thread-timeout'] !== undefined) { + sfgeOverrides.java_thread_timeout = input['sfge-thread-timeout']; + } + const config: CodeAnalyzerConfig = this.dependencies.configFactory.create(input['config-file'], sfgeOverrides); const logWriter: LogFileWriter = await LogFileWriter.fromConfig(config); this.dependencies.actionSummaryViewer.viewPreExecutionSummary(logWriter.getLogDestination()); // We always add a Logger Listener to the appropriate listeners list, because we should Always Be Logging. diff --git a/src/lib/factories/CodeAnalyzerConfigFactory.ts b/src/lib/factories/CodeAnalyzerConfigFactory.ts index 6264d554e..160ff6342 100644 --- a/src/lib/factories/CodeAnalyzerConfigFactory.ts +++ b/src/lib/factories/CodeAnalyzerConfigFactory.ts @@ -2,18 +2,46 @@ import * as path from 'node:path'; import * as fs from 'node:fs'; import {CodeAnalyzerConfig} from '@salesforce/code-analyzer-core'; +export type SfgeEngineOverrides = { + java_thread_count?: number; + java_thread_timeout?: number; +}; + export interface CodeAnalyzerConfigFactory { - create(configPath?: string): CodeAnalyzerConfig; + create(configPath?: string, sfgeOverrides?: SfgeEngineOverrides): CodeAnalyzerConfig; } export class CodeAnalyzerConfigFactoryImpl implements CodeAnalyzerConfigFactory { private static readonly CONFIG_FILE_NAME: string = 'code-analyzer'; private static readonly CONFIG_FILE_EXTENSIONS: string[] = ['yaml', 'yml']; - public create(configPath?: string): CodeAnalyzerConfig { - return this.getConfigFromProvidedPath(configPath) + public create(configPath?: string, sfgeOverrides?: SfgeEngineOverrides): CodeAnalyzerConfig { + const baseConfig: CodeAnalyzerConfig = + this.getConfigFromProvidedPath(configPath) || this.seekConfigInCurrentDirectory() || CodeAnalyzerConfig.withDefaults(); + + if (!sfgeOverrides || Object.keys(sfgeOverrides).length === 0) { + return baseConfig; + } + + // Merge CLI sfge overrides on top of whatever the base config provides. + // We build a fresh config object containing only the overridden sfge fields. + // All other engine settings (java_command, java_max_heap_size, etc.) continue + // to come from the base config via their own defaults. + const overrideConfig: CodeAnalyzerConfig = CodeAnalyzerConfig.fromObject({ + engines: { + sfge: sfgeOverrides + } + }); + + // The override config wins for sfge-specific fields; the base config is used + // for everything else. We achieve this by returning the override config when + // sfge values are explicitly set, since all other sfge fields fall back to + // their defaults inside SfgeEngine.init() → validateAndNormalizeConfig(). + // Non-sfge engine config from the base config is unaffected because the + // override object only specifies the sfge engine section. + return overrideConfig; } private getConfigFromProvidedPath(configPath?: string): CodeAnalyzerConfig|undefined { From 489ce1358a343be5881b0f0fd1cb0965b89a3790 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Mon, 9 Mar 2026 12:52:41 +0530 Subject: [PATCH 2/3] revert cli flags --- messages/run-command.md | 21 ++++++--------------- src/commands/code-analyzer/run.ts | 23 ++++++++--------------- src/lib/actions/RunAction.ts | 17 ++++++----------- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/messages/run-command.md b/messages/run-command.md index d371b7751..443acea81 100644 --- a/messages/run-command.md +++ b/messages/run-command.md @@ -142,22 +142,13 @@ To output the results to multiple files, specify this flag multiple times. For e If you specify a file within a folder, such as `--output-file ./out/results.json`, the folder must already exist, or you get an error. If the file already exists, it's overwritten without prompting. -# flags.sfge-thread-count.summary - -Number of threads for SFGE path-based rule execution. - -# flags.sfge-thread-count.description - -Overrides the sfge engine java_thread_count configuration. Increasing this allows more entry points to be analyzed in parallel. Default is 8. Can also be set in code-analyzer.yaml under engines.sfge.java_thread_count. - -# flags.sfge-thread-timeout.summary - -Per-thread timeout in milliseconds for SFGE path-based rules. - -# flags.sfge-thread-timeout.description - -Overrides the sfge engine java_thread_timeout configuration. Entry points that exceed this limit produce a timeout violation. Default is 180000 (3 minutes). Can also be set in code-analyzer.yaml under engines.sfge.java_thread_timeout. + +> **Note:** SFGE engine threading settings are no longer exposed as CLI +> flags. Please configure `engines.sfge.java_thread_count` and +> `engines.sfge.java_thread_timeout` in your `code-analyzer.yml`/`.yaml` +> file. The CLI will automatically pick up values from that file when it is +> present. # error.invalid-severity-threshold Expected --severity-threshold=%s to be one of: %s diff --git a/src/commands/code-analyzer/run.ts b/src/commands/code-analyzer/run.ts index aa68078aa..bc228c8b1 100644 --- a/src/commands/code-analyzer/run.ts +++ b/src/commands/code-analyzer/run.ts @@ -72,18 +72,10 @@ export default class RunCommand extends SfCommand implements Displayable { exists: true }), // === Flags pertaining to SFGE engine tuning === - 'sfge-thread-count': Flags.integer({ - summary: getMessage(BundleName.RunCommand, 'flags.sfge-thread-count.summary'), - description: getMessage(BundleName.RunCommand, 'flags.sfge-thread-count.description'), - default: 8, - min: 1 - }), - 'sfge-thread-timeout': Flags.integer({ - summary: getMessage(BundleName.RunCommand, 'flags.sfge-thread-timeout.summary'), - description: getMessage(BundleName.RunCommand, 'flags.sfge-thread-timeout.description'), - default: 180000, - min: 1000 - }) + // NOTE: thread count/timeout are now configured via the YAML file + // (engines.sfge.java_thread_count / engines.sfge.java_thread_timeout) and + // are no longer exposed as CLI flags. Keeping the section around so the + // comment about configuration is still surfaced in help text. }; public async run(): Promise { @@ -97,10 +89,11 @@ export default class RunCommand extends SfCommand implements Displayable { 'workspace': parsedFlags['workspace'], 'severity-threshold': parsedFlags['severity-threshold'] === undefined ? undefined : convertThresholdToEnum(parsedFlags['severity-threshold'].toLowerCase()), - 'target': parsedFlags['target'], - 'sfge-thread-count': parsedFlags['sfge-thread-count'], - 'sfge-thread-timeout': parsedFlags['sfge-thread-timeout'] +'target': parsedFlags['target'] }; + // sfge threading parameters are controlled entirely through the config + // file now; the CLI used to accept overrides but those flags have been + // removed. await action.execute(runInput); } diff --git a/src/lib/actions/RunAction.ts b/src/lib/actions/RunAction.ts index a57b8e754..8248eb3cf 100644 --- a/src/lib/actions/RunAction.ts +++ b/src/lib/actions/RunAction.ts @@ -8,7 +8,7 @@ import { SeverityLevel, Workspace } from '@salesforce/code-analyzer-core'; -import {CodeAnalyzerConfigFactory, SfgeEngineOverrides} from '../factories/CodeAnalyzerConfigFactory.js'; +import {CodeAnalyzerConfigFactory} from '../factories/CodeAnalyzerConfigFactory.js'; import {EnginePluginsFactory} from '../factories/EnginePluginsFactory.js'; import {createWorkspace} from '../utils/WorkspaceUtil.js'; import {ResultsViewer} from '../viewers/ResultsViewer.js'; @@ -40,8 +40,6 @@ export type RunInput = { 'severity-threshold'?: SeverityLevel; target?: string[]; workspace: string[]; - 'sfge-thread-count'?: number; - 'sfge-thread-timeout'?: number; } export class RunAction { @@ -52,14 +50,11 @@ export class RunAction { } public async execute(input: RunInput): Promise { - const sfgeOverrides: SfgeEngineOverrides = {}; - if (input['sfge-thread-count'] !== undefined) { - sfgeOverrides.java_thread_count = input['sfge-thread-count']; - } - if (input['sfge-thread-timeout'] !== undefined) { - sfgeOverrides.java_thread_timeout = input['sfge-thread-timeout']; - } - const config: CodeAnalyzerConfig = this.dependencies.configFactory.create(input['config-file'], sfgeOverrides); + // Threading configuration for the SFGE engine is now solely managed via the + // user’s YAML configuration file (engines.sfge.java_thread_count and + // engines.sfge.java_thread_timeout). CLI overrides were removed, so we + // simply forward the config file path to the factory. + const config: CodeAnalyzerConfig = this.dependencies.configFactory.create(input['config-file']); const logWriter: LogFileWriter = await LogFileWriter.fromConfig(config); this.dependencies.actionSummaryViewer.viewPreExecutionSummary(logWriter.getLogDestination()); // We always add a Logger Listener to the appropriate listeners list, because we should Always Be Logging. From fa9685d08ced236af6f390ea18146ea9b38f8f35 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Mon, 9 Mar 2026 13:35:16 +0530 Subject: [PATCH 3/3] cleanup --- .../factories/CodeAnalyzerConfigFactory.ts | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/src/lib/factories/CodeAnalyzerConfigFactory.ts b/src/lib/factories/CodeAnalyzerConfigFactory.ts index 160ff6342..6264d554e 100644 --- a/src/lib/factories/CodeAnalyzerConfigFactory.ts +++ b/src/lib/factories/CodeAnalyzerConfigFactory.ts @@ -2,46 +2,18 @@ import * as path from 'node:path'; import * as fs from 'node:fs'; import {CodeAnalyzerConfig} from '@salesforce/code-analyzer-core'; -export type SfgeEngineOverrides = { - java_thread_count?: number; - java_thread_timeout?: number; -}; - export interface CodeAnalyzerConfigFactory { - create(configPath?: string, sfgeOverrides?: SfgeEngineOverrides): CodeAnalyzerConfig; + create(configPath?: string): CodeAnalyzerConfig; } export class CodeAnalyzerConfigFactoryImpl implements CodeAnalyzerConfigFactory { private static readonly CONFIG_FILE_NAME: string = 'code-analyzer'; private static readonly CONFIG_FILE_EXTENSIONS: string[] = ['yaml', 'yml']; - public create(configPath?: string, sfgeOverrides?: SfgeEngineOverrides): CodeAnalyzerConfig { - const baseConfig: CodeAnalyzerConfig = - this.getConfigFromProvidedPath(configPath) + public create(configPath?: string): CodeAnalyzerConfig { + return this.getConfigFromProvidedPath(configPath) || this.seekConfigInCurrentDirectory() || CodeAnalyzerConfig.withDefaults(); - - if (!sfgeOverrides || Object.keys(sfgeOverrides).length === 0) { - return baseConfig; - } - - // Merge CLI sfge overrides on top of whatever the base config provides. - // We build a fresh config object containing only the overridden sfge fields. - // All other engine settings (java_command, java_max_heap_size, etc.) continue - // to come from the base config via their own defaults. - const overrideConfig: CodeAnalyzerConfig = CodeAnalyzerConfig.fromObject({ - engines: { - sfge: sfgeOverrides - } - }); - - // The override config wins for sfge-specific fields; the base config is used - // for everything else. We achieve this by returning the override config when - // sfge values are explicitly set, since all other sfge fields fall back to - // their defaults inside SfgeEngine.init() → validateAndNormalizeConfig(). - // Non-sfge engine config from the base config is unaffected because the - // override object only specifies the sfge engine section. - return overrideConfig; } private getConfigFromProvidedPath(configPath?: string): CodeAnalyzerConfig|undefined {