Skip to content
Open
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
16 changes: 16 additions & 0 deletions messages/run-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion src/commands/code-analyzer/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ export default class RunCommand extends SfCommand<void> 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
})
};

Expand All @@ -84,7 +97,9 @@ export default class RunCommand extends SfCommand<void> 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);
}
Expand Down
14 changes: 11 additions & 3 deletions src/lib/actions/RunAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {
Expand All @@ -51,7 +52,14 @@ export class RunAction {
}

public async execute(input: RunInput): Promise<void> {
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.
Expand Down
34 changes: 31 additions & 3 deletions src/lib/factories/CodeAnalyzerConfigFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down