Skip to content

Commit 29ea396

Browse files
authored
FIX @W-20434655@ Typo in hardcoded Id (#386)
Adding fix for the issue reported by customer here: forcedotcom/code-analyzer#1947
1 parent a779954 commit 29ea396

File tree

6 files changed

+75
-33
lines changed

6 files changed

+75
-33
lines changed

packages/code-analyzer-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@salesforce/code-analyzer-core",
33
"description": "Core Package for the Salesforce Code Analyzer",
4-
"version": "0.40.0",
4+
"version": "0.41.0-SNAPSHOT",
55
"author": "The Salesforce Code Analyzer Team",
66
"license": "BSD-3-Clause",
77
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",

packages/code-analyzer-flow-engine/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@salesforce/code-analyzer-flow-engine",
33
"description": "Plugin package that adds 'Flow Scanner' as an engine into Salesforce Code Analyzer",
4-
"version": "0.31.0",
4+
"version": "0.32.0-SNAPSHOT",
55
"author": "The Salesforce Code Analyzer Team",
66
"license": "BSD-3-Clause",
77
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",

packages/code-analyzer-flow-engine/src/engine.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import {Clock, RealClock} from '@salesforce/code-analyzer-engine-api/utils';
1414
import {getMessage} from './messages';
1515
import {FlowNodeDescriptor, FlowScannerCommandWrapper, FlowScannerExecutionResult, FlowScannerRuleResult} from "./python/FlowScannerCommandWrapper";
16-
import {getDescriptionForRule, getRuleNameFromQueryId, getAllRuleNames, getQueryIdsForRule} from "./hardcoded-catalog";
16+
import {getDescriptionForRule, getAllRuleNames} from "./hardcoded-catalog";
1717

1818
/**
1919
* An arbitrarily chosen value for how close the engine is to completion before the underlying Flow tool is invoked,
@@ -80,7 +80,8 @@ export class FlowScannerEngine extends Engine {
8080
this.emitRunRulesProgressEvent(normalizeRelativeCompletionPercentage(percentage));
8181
}
8282

83-
const queryIds: string[] = ruleNames.flatMap(getQueryIdsForRule);
83+
// Query IDs are the same as rule names (1:1 mapping)
84+
const queryIds: string[] = ruleNames;
8485

8586
const executionResults: FlowScannerExecutionResult = await this.commandWrapper.runFlowScannerRules(
8687
runOptions.workingFolder,
@@ -142,7 +143,7 @@ function toEngineRunResults(flowScannerExecutionResult: FlowScannerExecutionResu
142143
for (const queryId of Object.keys(flowScannerExecutionResult.results)) {
143144
const flowScannerRuleResults: FlowScannerRuleResult[] = flowScannerExecutionResult.results[queryId];
144145
for (const flowScannerRuleResult of flowScannerRuleResults) {
145-
const ruleName = getRuleNameFromQueryId(flowScannerRuleResult.query_id);
146+
const ruleName = flowScannerRuleResult.query_id; // Query IDs are the same as rule names
146147
const flowNodes: FlowNodeDescriptor[] | undefined = flowScannerRuleResult.flow;
147148
if (flowNodes) { // If flow based violation
148149
results.violations.push({

packages/code-analyzer-flow-engine/src/hardcoded-catalog.ts

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import {COMMON_TAGS, RuleDescription, SeverityLevel} from '@salesforce/code-analyzer-engine-api';
22
import {getMessage} from './messages';
33

4-
// Code Analyzer rule names
5-
// Good news: The python flow scanner query ids now happen to be the exact same names as our code analyzer rule names
6-
// so we no longer need to keep a map between the two.
4+
// Code Analyzer rule names (these match the Python flow scanner query IDs 1:1)
75
enum RuleName {
86
CyclicSubflow = 'CyclicSubflow',
97
DbInLoop = 'DbInLoop',
108
DefaultCopy = 'DefaultCopy',
11-
HardcodedId = 'HardCodedId',
9+
HardcodedId = 'HardcodedId',
1210
MissingDescription = 'MissingDescription',
1311
MissingFaultHandler = 'MissingFaultHandler',
1412
MissingNextValueConnector = 'MissingNextValueConnector',
@@ -136,32 +134,11 @@ export function getAllRuleNames(): string[] {
136134
return Object.values(RuleName);
137135
}
138136

139-
export function getRuleNameFromQueryId(queryId: string): string {
140-
// Good news: The python flow scanner query ids now happen to be the exact same names as our code analyzer rule names
141-
// so we no longer need to keep a map between the two. But leaving this helper just in case we need it again in the
142-
// future.
143-
144-
// istanbul ignore else
145-
if (Object.values(RuleName).includes(queryId as RuleName)) {
146-
return queryId;
147-
} else {
148-
throw new Error(`Developer error: invalid query id ${queryId}`);
149-
}
150-
}
151-
152-
export function getQueryIdsForRule(ruleName: string): string[] {
153-
// It used to be that a single Code Analyzer rule could map to multiple flow scanner query ids. But now
154-
// they are mapped 1-to-1 and happen to be the exact same names. But keeping the output as a string array
155-
// just in case things change in the future.
156-
const queryIds: string[] = [ruleName];
157-
return queryIds;
158-
}
159-
160137
export function getDescriptionForRule(ruleName: string): RuleDescription {
161138
// istanbul ignore else
162139
if (RULE_DESCRIPTIONS_BY_NAME.has(ruleName)) {
163140
return RULE_DESCRIPTIONS_BY_NAME.get(ruleName)!;
164141
} else {
165-
throw new Error(`Developer rule: No rule with name ${ruleName}`);
142+
throw new Error(`Developer error: No rule with name ${ruleName}`);
166143
}
167-
}
144+
}

packages/code-analyzer-flow-engine/test/engine.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,49 @@ import {FlowScannerEngine} from "../src/engine";
1717
import {RunTimeFlowScannerCommandWrapper} from "../src/python/FlowScannerCommandWrapper";
1818
import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers";
1919
import {getMessage} from "../src/messages";
20+
import {getAllRuleNames} from "../src/hardcoded-catalog";
2021
import * as fs from "node:fs";
2122

2223
changeWorkingDirectoryToPackageRoot();
2324

25+
/**
26+
* Extracts query IDs from a Python file by parsing the QUERIES dictionary.
27+
* Looks for patterns like: QUERIES = { "QueryId": "Description", ... }
28+
*/
29+
function extractQueryIdsFromPythonFile(filePath: string): string[] {
30+
const content = fs.readFileSync(filePath, 'utf-8');
31+
32+
// Match QUERIES = { ... } block
33+
const queriesMatch = content.match(/QUERIES\s*=\s*\{([^}]+)\}/s);
34+
if (!queriesMatch) {
35+
return [];
36+
}
37+
38+
// Extract keys from the dictionary (supports both single and double quotes)
39+
const queryIds: string[] = [];
40+
const keyPattern = /['"]([^'"]+)['"]\s*:/g;
41+
let match;
42+
while ((match = keyPattern.exec(queriesMatch[1])) !== null) {
43+
queryIds.push(match[1]);
44+
}
45+
return queryIds;
46+
}
47+
48+
/**
49+
* Gets all Python query IDs by reading the actual Python source files.
50+
* This ensures TypeScript stays in sync with Python automatically.
51+
*/
52+
function getPythonQueryIds(): string[] {
53+
const flowScannerPath = path.resolve(__dirname, '..', 'FlowScanner', 'queries');
54+
const defaultQueryPath = path.join(flowScannerPath, 'default_query.py');
55+
const optionalQueryPath = path.join(flowScannerPath, 'optional_query.py');
56+
57+
const defaultQueryIds = extractQueryIdsFromPythonFile(defaultQueryPath);
58+
const optionalQueryIds = extractQueryIdsFromPythonFile(optionalQueryPath);
59+
60+
return [...defaultQueryIds, ...optionalQueryIds].sort();
61+
}
62+
2463
//the space in the "example workspaces" path is important for testing purposes. do not remove.
2564
const TEST_DATA_FOLDER: string = path.resolve(__dirname, 'test-data');
2665
const PATH_TO_NO_FLOWS_WORKSPACE = path.resolve(TEST_DATA_FOLDER, 'example workspaces', 'contains-no-flows');
@@ -688,4 +727,29 @@ describe('Tests for the FlowScannerEngine', () => {
688727
});
689728
});
690729
});
730+
});
731+
732+
describe('TypeScript and Python rule name validation', () => {
733+
it('All TypeScript rule names must match Python query IDs exactly (including case)', () => {
734+
const tsRuleNames = getAllRuleNames().sort();
735+
const pythonQueryIds = getPythonQueryIds();
736+
737+
// Validate same count
738+
expect(tsRuleNames).toHaveLength(pythonQueryIds.length);
739+
740+
// Validate exact match (case-sensitive)
741+
expect(tsRuleNames).toEqual(pythonQueryIds);
742+
});
743+
744+
it('No duplicate rule names in TypeScript', () => {
745+
const tsRuleNames = getAllRuleNames();
746+
const uniqueNames = new Set(tsRuleNames);
747+
expect(tsRuleNames.length).toEqual(uniqueNames.size);
748+
});
749+
750+
it('No duplicate query IDs in Python', () => {
751+
const pythonQueryIds = getPythonQueryIds();
752+
const uniqueIds = new Set(pythonQueryIds);
753+
expect(pythonQueryIds.length).toEqual(uniqueIds.size);
754+
});
691755
});

packages/code-analyzer-flow-engine/test/test-data/goldfiles/all_rules.goldfile.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"resourceUrls": []
3434
},
3535
{
36-
"name": "HardCodedId",
36+
"name": "HardcodedId",
3737
"description": "This rule detects hardcoded IDs within a flow. Hardcoded Ids are a bad practice, and such flows are not appropriate for distribution.",
3838
"severityLevel": 3,
3939
"tags": [

0 commit comments

Comments
 (0)