From b5b7818d9e20436dad3ce206b8a503f15a336d9c Mon Sep 17 00:00:00 2001 From: junners Date: Sun, 8 Jun 2025 01:11:56 -0400 Subject: [PATCH 1/4] feat(#230): introduce new concept for advanced suppression --- src/main/interfaces/AdvancedRuleConfig.ts | 10 ++ src/main/interfaces/AdvancedRuleDefintion.ts | 7 ++ src/main/interfaces/AdvancedSuppression.ts | 6 ++ src/main/interfaces/IRulesConfig.ts | 3 +- src/main/libs/ScanFlows.ts | 98 +++++++++++++++----- src/main/models/AdvancedRule.ts | 44 +++++++++ src/main/models/RuleCommon.ts | 10 +- src/main/rules/MissingFaultPath.ts | 22 ++++- src/main/store/DefaultRuleStore.ts | 6 ++ 9 files changed, 175 insertions(+), 31 deletions(-) create mode 100644 src/main/interfaces/AdvancedRuleConfig.ts create mode 100644 src/main/interfaces/AdvancedRuleDefintion.ts create mode 100644 src/main/interfaces/AdvancedSuppression.ts create mode 100644 src/main/models/AdvancedRule.ts diff --git a/src/main/interfaces/AdvancedRuleConfig.ts b/src/main/interfaces/AdvancedRuleConfig.ts new file mode 100644 index 00000000..33d5c27a --- /dev/null +++ b/src/main/interfaces/AdvancedRuleConfig.ts @@ -0,0 +1,10 @@ +export type AdvancedConfig = { + disabled?: boolean; + path?: string; + severity?: string; + suppressions?: string[]; +}; + +export type AdvancedRuleConfig = { + [ruleName: string]: AdvancedConfig; +}; diff --git a/src/main/interfaces/AdvancedRuleDefintion.ts b/src/main/interfaces/AdvancedRuleDefintion.ts new file mode 100644 index 00000000..bf9fa738 --- /dev/null +++ b/src/main/interfaces/AdvancedRuleDefintion.ts @@ -0,0 +1,7 @@ +import type { AdvancedRuleConfig } from "./AdvancedRuleConfig"; + +import { Flow, RuleResult } from "../internals/internals"; + +export interface AdvancedRuleDefinition { + execute(flow: Flow, ruleConfiguration?: AdvancedRuleConfig): RuleResult; +} diff --git a/src/main/interfaces/AdvancedSuppression.ts b/src/main/interfaces/AdvancedSuppression.ts new file mode 100644 index 00000000..ab39d54d --- /dev/null +++ b/src/main/interfaces/AdvancedSuppression.ts @@ -0,0 +1,6 @@ +import { RuleResult } from "../internals/internals"; +import { AdvancedRuleConfig } from "./AdvancedRuleConfig"; + +export interface AdvancedSuppression { + suppress(scanResult: RuleResult, ruleConfiguration?: AdvancedRuleConfig): RuleResult; +} diff --git a/src/main/interfaces/IRulesConfig.ts b/src/main/interfaces/IRulesConfig.ts index 5f08e0e2..c02d45f8 100644 --- a/src/main/interfaces/IRulesConfig.ts +++ b/src/main/interfaces/IRulesConfig.ts @@ -1,7 +1,8 @@ +import { AdvancedRuleConfig } from "./AdvancedRuleConfig"; import { IExceptions } from "./IExceptions"; import { IRuleOptions } from "./IRuleOptions"; export interface IRulesConfig { - rules?: IRuleOptions; exceptions?: IExceptions; + rules?: AdvancedRuleConfig | IRuleOptions; } diff --git a/src/main/libs/ScanFlows.ts b/src/main/libs/ScanFlows.ts index e770c56e..a2120575 100644 --- a/src/main/libs/ScanFlows.ts +++ b/src/main/libs/ScanFlows.ts @@ -1,18 +1,31 @@ -import { GetRuleDefinitions } from "./GetRuleDefinitions"; -import * as core from "../../main/internals/internals"; +import type { IRuleDefinition } from "../interfaces/IRuleDefinition"; + +import { + Flow, + IRulesConfig, + ResultDetails, + RuleResult, + ScanResult, +} from "../../main/internals/internals"; +import { AdvancedRuleConfig } from "../interfaces/AdvancedRuleConfig"; +import { AdvancedRule } from "../models/AdvancedRule"; import { ParsedFlow } from "../models/ParsedFlow"; +import { BetaRuleStore, DefaultRuleStore } from "../store/DefaultRuleStore"; +import { GetRuleDefinitions } from "./GetRuleDefinitions"; -export function scan( - parsedFlows: ParsedFlow[], - ruleOptions?: core.IRulesConfig -): core.ScanResult[] { - const flows: core.Flow[] = []; +const { IS_NEW_SCAN_ENABLED: isNewScanEnabled } = process.env; + +export function scan(parsedFlows: ParsedFlow[], ruleOptions?: IRulesConfig): ScanResult[] { + if (isNewScanEnabled === "true") { + return scanInternal(parsedFlows, ruleOptions); + } + const flows: Flow[] = []; for (const flow of parsedFlows) { if (!flow.errorMessage && flow.flow) { flows.push(flow.flow); } } - let scanResults: core.ScanResult[]; + let scanResults: ScanResult[]; if (ruleOptions?.rules && Object.entries(ruleOptions.rules).length > 0) { scanResults = ScanFlows(flows, ruleOptions); } else { @@ -23,14 +36,12 @@ export function scan( for (const [exceptionName, exceptionElements] of Object.entries(ruleOptions.exceptions)) { for (const scanResult of scanResults) { if (scanResult.flow.name === exceptionName) { - for (const ruleResult of scanResult.ruleResults as core.RuleResult[]) { + for (const ruleResult of scanResult.ruleResults as RuleResult[]) { if (exceptionElements[ruleResult.ruleName]) { const exceptions = exceptionElements[ruleResult.ruleName]; - const filteredDetails = (ruleResult.details as core.ResultDetails[]).filter( - (detail) => { - return !exceptions.includes(detail.name); - } - ); + const filteredDetails = (ruleResult.details as ResultDetails[]).filter((detail) => { + return !exceptions.includes(detail.name); + }); ruleResult.details = filteredDetails; ruleResult.occurs = filteredDetails.length > 0; } @@ -43,10 +54,10 @@ export function scan( return scanResults; } -export function ScanFlows(flows: core.Flow[], ruleOptions?: core.IRulesConfig): core.ScanResult[] { - const flowResults: core.ScanResult[] = []; +export function ScanFlows(flows: Flow[], ruleOptions?: IRulesConfig): ScanResult[] { + const flowResults: ScanResult[] = []; - let selectedRules: core.IRuleDefinition[] = []; + let selectedRules: IRuleDefinition[] = []; if (ruleOptions && ruleOptions.rules) { const ruleMap = new Map(); for (const [ruleName, rule] of Object.entries(ruleOptions.rules)) { @@ -58,7 +69,7 @@ export function ScanFlows(flows: core.Flow[], ruleOptions?: core.IRulesConfig): } for (const flow of flows) { - const ruleResults: core.RuleResult[] = []; + const ruleResults: RuleResult[] = []; for (const rule of selectedRules) { try { if (rule.supportedTypes.includes(flow.type)) { @@ -75,17 +86,58 @@ export function ScanFlows(flows: core.Flow[], ruleOptions?: core.IRulesConfig): } ruleResults.push(result); } else { - ruleResults.push(new core.RuleResult(rule, [])); + ruleResults.push(new RuleResult(rule, [])); } // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { - const message = - "Something went wrong while executing " + rule.name + " in the Flow: '" + flow.name; - ruleResults.push(new core.RuleResult(rule, [], message)); + const message = `Something went wrong while executing ${rule.name} in the Flow: ${flow.name} with error ${error}`; + ruleResults.push(new RuleResult(rule, [], message)); } } - flowResults.push(new core.ScanResult(flow, ruleResults)); + flowResults.push(new ScanResult(flow, ruleResults)); } return flowResults; } + +export function scanInternal(parsedFlows: ParsedFlow[], ruleOptions?: IRulesConfig): ScanResult[] { + const flows: Flow[] = parsedFlows.map((parsedFlow) => parsedFlow.flow as Flow); + const ruleConfiguration = unifiedRuleConfig(ruleOptions); + const allRules = { ...DefaultRuleStore, ...BetaRuleStore }; + const scanResults: ScanResult[] = []; + for (const flow of flows) { + scanResults.push(scanFlowWithConfig(flow, allRules, ruleConfiguration)); + } + return scanResults; +} + +function scanFlowWithConfig( + flow: Flow, + allRules: Record, + ruleConfiguration: Record +): ScanResult { + const ruleResults: RuleResult[] = []; + for (const [ruleName, ruleAction] of Object.entries(allRules)) { + ruleResults.push(ruleAction.executeRule(flow, ruleConfiguration[ruleName] ?? {})); + } + return new ScanResult(flow, ruleResults); +} + +function unifiedRuleConfig( + ruleOptions: IRulesConfig | undefined +): Record { + const configuredRules: AdvancedRuleConfig = ruleOptions?.rules ?? {}; + const activeConfiguredRules: Record = Object.entries(configuredRules) + .filter(([, configuration]) => { + if (!("disabled" in configuration)) { + return true; + } + + return configuration.disabled !== true; + }) + .reduce>((accumulator, [ruleName, config]) => { + return { ...accumulator, [ruleName]: config as AdvancedRuleConfig }; + }, {}); + + return activeConfiguredRules; +} diff --git a/src/main/models/AdvancedRule.ts b/src/main/models/AdvancedRule.ts new file mode 100644 index 00000000..a2b970bb --- /dev/null +++ b/src/main/models/AdvancedRule.ts @@ -0,0 +1,44 @@ +import { AdvancedRuleConfig } from "../interfaces/AdvancedRuleConfig"; +import { AdvancedRuleDefinition } from "../interfaces/AdvancedRuleDefintion"; +import { AdvancedSuppression } from "../interfaces/AdvancedSuppression"; +import { IRuleDefinition } from "../internals/internals"; +import { Flow } from "./Flow"; +import { RuleCommon } from "./RuleCommon"; +import { RuleInfo } from "./RuleInfo"; +import { RuleResult } from "./RuleResult"; + +export abstract class AdvancedRule extends RuleCommon { + constructor( + info: RuleInfo, + optional?: { + severity?: string; + } + ) { + super(info, optional); + } + + public executeRule(flow: Flow, ruleConfiguration?: AdvancedRuleConfig): RuleResult { + if (!hasAdvancedRuleDefinition(this)) { + return new RuleResult(this as unknown as IRuleDefinition, []); + } + + let ruleResult = this.execute(flow, ruleConfiguration); + + if (hasAdvancedSuppression(this)) { + ruleResult = this.suppress(ruleResult, ruleConfiguration); + } + + return ruleResult; + } +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +const isFunction = (val: unknown): val is Function => typeof val === "function"; + +function hasAdvancedRuleDefinition(instance: unknown): instance is AdvancedRuleDefinition { + return isFunction((instance as AdvancedRuleDefinition).execute); +} + +function hasAdvancedSuppression(instance: unknown): instance is AdvancedSuppression { + return isFunction((instance as AdvancedSuppression).suppress); +} diff --git a/src/main/models/RuleCommon.ts b/src/main/models/RuleCommon.ts index 598b506d..faf4b9e6 100644 --- a/src/main/models/RuleCommon.ts +++ b/src/main/models/RuleCommon.ts @@ -1,15 +1,15 @@ import { RuleInfo } from "./RuleInfo"; export class RuleCommon { + public autoFixable: boolean; + public description: string; + public docRefs: Array<{ label: string; path: string }> = []; + public isConfigurable: boolean; public label; public name; public severity?; - public uri; - public docRefs: { label: string; path: string }[] = []; - public description: string; public supportedTypes: string[]; - public isConfigurable: boolean; - public autoFixable: boolean; + public uri; constructor( info: RuleInfo, diff --git a/src/main/rules/MissingFaultPath.ts b/src/main/rules/MissingFaultPath.ts index a88249d8..e8adf9ba 100644 --- a/src/main/rules/MissingFaultPath.ts +++ b/src/main/rules/MissingFaultPath.ts @@ -1,7 +1,12 @@ +import { AdvancedConfig } from "../interfaces/AdvancedRuleConfig"; +import { AdvancedSuppression } from "../interfaces/AdvancedSuppression"; import * as core from "../internals/internals"; import { RuleCommon } from "../models/RuleCommon"; -export class MissingFaultPath extends RuleCommon implements core.IRuleDefinition { +export class MissingFaultPath + extends RuleCommon + implements AdvancedSuppression, core.IRuleDefinition +{ protected applicableElements: string[] = [ "recordLookups", "recordDeletes", @@ -28,7 +33,6 @@ export class MissingFaultPath extends RuleCommon implements core.IRuleDefinition supportedTypes: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes], }); } - public execute(flow: core.Flow): core.RuleResult { const compiler = new core.Compiler(); const results: core.ResultDetails[] = []; @@ -63,6 +67,20 @@ export class MissingFaultPath extends RuleCommon implements core.IRuleDefinition return new core.RuleResult(this, results); } + public suppress( + scanResult: core.RuleResult, + ruleConfiguration?: AdvancedConfig + ): core.RuleResult { + const suppressedResults: core.ResultDetails[] = []; + for (const resultDetails of scanResult.details) { + if (ruleConfiguration?.suppressions?.includes(resultDetails.name)) { + continue; + } + suppressedResults.push(resultDetails); + } + return new core.RuleResult(this, suppressedResults); + } + private isPartOfFaultHandlingFlow(element: core.FlowNode, flow: core.Flow): boolean { const flowelements = flow.elements?.filter( (el) => el instanceof core.FlowNode diff --git a/src/main/store/DefaultRuleStore.ts b/src/main/store/DefaultRuleStore.ts index 9d626028..eef0e88f 100644 --- a/src/main/store/DefaultRuleStore.ts +++ b/src/main/store/DefaultRuleStore.ts @@ -50,3 +50,9 @@ export const DefaultRuleStore: object = { export const BetaRuleStore: object = { ActionCallsInLoop, }; + +export type AllRules = KeysOfIntersection; + +type DefinedRules = typeof BetaRuleStore & typeof DefaultRuleStore; + +type KeysOfIntersection = T extends T ? keyof T : never; From 03da63a3febbdd650b17a2ed3c9f7b316c34f5da Mon Sep 17 00:00:00 2001 From: junners Date: Sun, 8 Jun 2025 01:14:36 -0400 Subject: [PATCH 2/4] chore: remove extra all rules on default rule store --- src/main/store/DefaultRuleStore.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/store/DefaultRuleStore.ts b/src/main/store/DefaultRuleStore.ts index eef0e88f..9d626028 100644 --- a/src/main/store/DefaultRuleStore.ts +++ b/src/main/store/DefaultRuleStore.ts @@ -50,9 +50,3 @@ export const DefaultRuleStore: object = { export const BetaRuleStore: object = { ActionCallsInLoop, }; - -export type AllRules = KeysOfIntersection; - -type DefinedRules = typeof BetaRuleStore & typeof DefaultRuleStore; - -type KeysOfIntersection = T extends T ? keyof T : never; From 3a1b3b4b824086eb5d3c384ff00314b4162a553a Mon Sep 17 00:00:00 2001 From: junners Date: Sun, 8 Jun 2025 01:20:18 -0400 Subject: [PATCH 3/4] chore(#230): backward compatibility to the old api --- src/main/libs/ScanFlows.ts | 10 ++-- src/main/models/AdvancedRule.ts | 88 ++++++++++++++++----------------- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/main/libs/ScanFlows.ts b/src/main/libs/ScanFlows.ts index a2120575..81dc380b 100644 --- a/src/main/libs/ScanFlows.ts +++ b/src/main/libs/ScanFlows.ts @@ -8,13 +8,14 @@ import { ScanResult, } from "../../main/internals/internals"; import { AdvancedRuleConfig } from "../interfaces/AdvancedRuleConfig"; -import { AdvancedRule } from "../models/AdvancedRule"; import { ParsedFlow } from "../models/ParsedFlow"; import { BetaRuleStore, DefaultRuleStore } from "../store/DefaultRuleStore"; import { GetRuleDefinitions } from "./GetRuleDefinitions"; const { IS_NEW_SCAN_ENABLED: isNewScanEnabled } = process.env; +// Will be replaced by scanInternal in the future +// eslint-disable-next-line sonarjs/cognitive-complexity export function scan(parsedFlows: ParsedFlow[], ruleOptions?: IRulesConfig): ScanResult[] { if (isNewScanEnabled === "true") { return scanInternal(parsedFlows, ruleOptions); @@ -54,6 +55,8 @@ export function scan(parsedFlows: ParsedFlow[], ruleOptions?: IRulesConfig): Sca return scanResults; } +// Will be removed once scanInternal is fully enabled +// eslint-disable-next-line sonarjs/cognitive-complexity export function ScanFlows(flows: Flow[], ruleOptions?: IRulesConfig): ScanResult[] { const flowResults: ScanResult[] = []; @@ -88,7 +91,6 @@ export function ScanFlows(flows: Flow[], ruleOptions?: IRulesConfig): ScanResult } else { ruleResults.push(new RuleResult(rule, [])); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { const message = `Something went wrong while executing ${rule.name} in the Flow: ${flow.name} with error ${error}`; ruleResults.push(new RuleResult(rule, [], message)); @@ -113,12 +115,12 @@ export function scanInternal(parsedFlows: ParsedFlow[], ruleOptions?: IRulesConf function scanFlowWithConfig( flow: Flow, - allRules: Record, + allRules: Record, ruleConfiguration: Record ): ScanResult { const ruleResults: RuleResult[] = []; for (const [ruleName, ruleAction] of Object.entries(allRules)) { - ruleResults.push(ruleAction.executeRule(flow, ruleConfiguration[ruleName] ?? {})); + ruleResults.push(ruleAction.execute(flow, ruleConfiguration[ruleName] ?? {})); } return new ScanResult(flow, ruleResults); } diff --git a/src/main/models/AdvancedRule.ts b/src/main/models/AdvancedRule.ts index a2b970bb..a2486666 100644 --- a/src/main/models/AdvancedRule.ts +++ b/src/main/models/AdvancedRule.ts @@ -1,44 +1,44 @@ -import { AdvancedRuleConfig } from "../interfaces/AdvancedRuleConfig"; -import { AdvancedRuleDefinition } from "../interfaces/AdvancedRuleDefintion"; -import { AdvancedSuppression } from "../interfaces/AdvancedSuppression"; -import { IRuleDefinition } from "../internals/internals"; -import { Flow } from "./Flow"; -import { RuleCommon } from "./RuleCommon"; -import { RuleInfo } from "./RuleInfo"; -import { RuleResult } from "./RuleResult"; - -export abstract class AdvancedRule extends RuleCommon { - constructor( - info: RuleInfo, - optional?: { - severity?: string; - } - ) { - super(info, optional); - } - - public executeRule(flow: Flow, ruleConfiguration?: AdvancedRuleConfig): RuleResult { - if (!hasAdvancedRuleDefinition(this)) { - return new RuleResult(this as unknown as IRuleDefinition, []); - } - - let ruleResult = this.execute(flow, ruleConfiguration); - - if (hasAdvancedSuppression(this)) { - ruleResult = this.suppress(ruleResult, ruleConfiguration); - } - - return ruleResult; - } -} - -// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -const isFunction = (val: unknown): val is Function => typeof val === "function"; - -function hasAdvancedRuleDefinition(instance: unknown): instance is AdvancedRuleDefinition { - return isFunction((instance as AdvancedRuleDefinition).execute); -} - -function hasAdvancedSuppression(instance: unknown): instance is AdvancedSuppression { - return isFunction((instance as AdvancedSuppression).suppress); -} +import { AdvancedRuleConfig } from "../interfaces/AdvancedRuleConfig"; +import { AdvancedRuleDefinition } from "../interfaces/AdvancedRuleDefintion"; +import { AdvancedSuppression } from "../interfaces/AdvancedSuppression"; +import { IRuleDefinition } from "../internals/internals"; +import { Flow } from "./Flow"; +import { RuleCommon } from "./RuleCommon"; +import { RuleInfo } from "./RuleInfo"; +import { RuleResult } from "./RuleResult"; + +export abstract class AdvancedRule extends RuleCommon { + constructor( + info: RuleInfo, + optional?: { + severity?: string; + } + ) { + super(info, optional); + } + + public execute(flow: Flow, ruleConfiguration?: AdvancedRuleConfig): RuleResult { + if (!hasAdvancedRuleDefinition(this)) { + return new RuleResult(this as unknown as IRuleDefinition, []); + } + + let ruleResult = this.execute(flow, ruleConfiguration); + + if (hasAdvancedSuppression(this)) { + ruleResult = this.suppress(ruleResult, ruleConfiguration); + } + + return ruleResult; + } +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +const isFunction = (val: unknown): val is Function => typeof val === "function"; + +function hasAdvancedRuleDefinition(instance: unknown): instance is AdvancedRuleDefinition { + return isFunction((instance as AdvancedRuleDefinition).execute); +} + +function hasAdvancedSuppression(instance: unknown): instance is AdvancedSuppression { + return isFunction((instance as AdvancedSuppression).suppress); +} From 534dea124c252f68aa49e7ed8d0b950cee813851 Mon Sep 17 00:00:00 2001 From: junners Date: Wed, 11 Jun 2025 09:08:49 -0700 Subject: [PATCH 4/4] feat: rule suppression refactoring, individual exception in general --- .vscode/launch.json | 24 +++++++++++++++++++ src/main/libs/DynamicRule.ts | 7 +++--- src/main/libs/ScanFlows.ts | 42 ++++++++++++++++++++++++--------- src/main/models/AdvancedRule.ts | 2 +- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 445a4629..bc9f7341 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,30 @@ "name": "Run npm test", "request": "launch", "type": "node-terminal" + }, + { + "type": "node", + "name": "vscode-jest-tests.v2.lightning-flow-scanner-core", + "request": "launch", + "args": [ + "test", + "--", + "--runInBand", + "--watchAll=false", + "--testNamePattern", + "${jest.testNamePattern}", + "--runTestsByPath", + "${jest.testFile}" + ], + "cwd": "/Users/jun/Development/github/lightning-flow-scanner-core", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "runtimeExecutable": "npm", + "env": { + "OVERRIDE_CONFIG": "true", + "IS_NEW_SCAN_ENABLED": "true" + } } ] } diff --git a/src/main/libs/DynamicRule.ts b/src/main/libs/DynamicRule.ts index 7052eb67..e6b9f942 100644 --- a/src/main/libs/DynamicRule.ts +++ b/src/main/libs/DynamicRule.ts @@ -1,11 +1,12 @@ import { IRuleDefinition } from "../interfaces/IRuleDefinition"; +import { AdvancedRule } from "../models/AdvancedRule"; import { BetaRuleStore, DefaultRuleStore } from "../store/DefaultRuleStore"; -export class DynamicRule { +export class DynamicRule { constructor(className: string) { if (!DefaultRuleStore.hasOwnProperty(className) && BetaRuleStore.hasOwnProperty(className)) { - return new BetaRuleStore[className]() as IRuleDefinition; + return new BetaRuleStore[className]() as T; } - return new DefaultRuleStore[className]() as IRuleDefinition; + return new DefaultRuleStore[className]() as T; } } diff --git a/src/main/libs/ScanFlows.ts b/src/main/libs/ScanFlows.ts index 81dc380b..7d27684d 100644 --- a/src/main/libs/ScanFlows.ts +++ b/src/main/libs/ScanFlows.ts @@ -8,11 +8,13 @@ import { ScanResult, } from "../../main/internals/internals"; import { AdvancedRuleConfig } from "../interfaces/AdvancedRuleConfig"; +import { AdvancedRule } from "../models/AdvancedRule"; import { ParsedFlow } from "../models/ParsedFlow"; import { BetaRuleStore, DefaultRuleStore } from "../store/DefaultRuleStore"; +import { DynamicRule } from "./DynamicRule"; import { GetRuleDefinitions } from "./GetRuleDefinitions"; -const { IS_NEW_SCAN_ENABLED: isNewScanEnabled } = process.env; +const { IS_NEW_SCAN_ENABLED: isNewScanEnabled, OVERRIDE_CONFIG: overrideConfig } = process.env; // Will be replaced by scanInternal in the future // eslint-disable-next-line sonarjs/cognitive-complexity @@ -104,23 +106,41 @@ export function ScanFlows(flows: Flow[], ruleOptions?: IRulesConfig): ScanResult export function scanInternal(parsedFlows: ParsedFlow[], ruleOptions?: IRulesConfig): ScanResult[] { const flows: Flow[] = parsedFlows.map((parsedFlow) => parsedFlow.flow as Flow); - const ruleConfiguration = unifiedRuleConfig(ruleOptions); - const allRules = { ...DefaultRuleStore, ...BetaRuleStore }; const scanResults: ScanResult[] = []; for (const flow of flows) { - scanResults.push(scanFlowWithConfig(flow, allRules, ruleConfiguration)); + scanResults.push(scanFlowWithConfig(flow, ruleOptions)); } return scanResults; } -function scanFlowWithConfig( - flow: Flow, - allRules: Record, - ruleConfiguration: Record -): ScanResult { +function ruleAndConfig( + ruleOptions?: IRulesConfig +): [Record, Record] { + // for unit tests, use a small set of rules + const ruleConfiguration = unifiedRuleConfig(ruleOptions); + let allRules: Record = { ...DefaultRuleStore, ...BetaRuleStore }; + if (overrideConfig === "true" && ruleOptions?.rules) { + allRules = Object.entries(allRules).reduce>( + (accumulator, [ruleName, rule]) => { + if (ruleOptions?.rules?.[ruleName]) { + accumulator[ruleName] = rule; + } + return accumulator; + }, + {} + ); + } + return [allRules, ruleConfiguration]; +} + +function scanFlowWithConfig(flow: Flow, ruleOptions?: IRulesConfig): ScanResult { + const [allRules, ruleConfiguration] = ruleAndConfig(ruleOptions); const ruleResults: RuleResult[] = []; - for (const [ruleName, ruleAction] of Object.entries(allRules)) { - ruleResults.push(ruleAction.execute(flow, ruleConfiguration[ruleName] ?? {})); + for (const [ruleName] of Object.entries(allRules)) { + const advancedRule = new DynamicRule(ruleName); + ruleResults.push( + (advancedRule as AdvancedRule).execute(flow, ruleConfiguration[ruleName] ?? {}) + ); } return new ScanResult(flow, ruleResults); } diff --git a/src/main/models/AdvancedRule.ts b/src/main/models/AdvancedRule.ts index a2486666..455dd176 100644 --- a/src/main/models/AdvancedRule.ts +++ b/src/main/models/AdvancedRule.ts @@ -22,7 +22,7 @@ export abstract class AdvancedRule extends RuleCommon { return new RuleResult(this as unknown as IRuleDefinition, []); } - let ruleResult = this.execute(flow, ruleConfiguration); + let ruleResult = (this as AdvancedRuleDefinition).execute(flow, ruleConfiguration); if (hasAdvancedSuppression(this)) { ruleResult = this.suppress(ruleResult, ruleConfiguration);