@flow-scanner/lightning-flow-scanner-core 6.6.4 → 6.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +31 -0
- package/README.md +426 -0
- package/SECURITY.md +54 -0
- package/package.json +7 -57
- package/.husky/pre-commit +0 -1
- package/.husky/pre-push +0 -1
- package/.prettierignore +0 -5
- package/.swcrc +0 -26
- package/eslint.config.mjs +0 -36
- package/jest.config.cjs +0 -32
- package/jest.env-setup.js +0 -101
- package/lint-staged.config.mjs +0 -8
- package/out/assets/media/banner.png +0 -0
- package/prettier.config.mjs +0 -5
- package/src/index.ts +0 -44
- package/src/main/interfaces/IExceptions.ts +0 -6
- package/src/main/interfaces/IRuleConfig.ts +0 -3
- package/src/main/interfaces/IRuleDefinition.ts +0 -12
- package/src/main/interfaces/IRuleOptions.ts +0 -5
- package/src/main/interfaces/IRulesConfig.ts +0 -15
- package/src/main/internals/internals.ts +0 -35
- package/src/main/libs/BuildFlow.ts +0 -11
- package/src/main/libs/Compiler.ts +0 -69
- package/src/main/libs/ConvertFlowNodes.ts +0 -4
- package/src/main/libs/DynamicRule.ts +0 -11
- package/src/main/libs/FixFlows.ts +0 -61
- package/src/main/libs/GetRuleDefinitions.ts +0 -65
- package/src/main/libs/ParseFlows.ts +0 -34
- package/src/main/libs/ScanFlows.ts +0 -112
- package/src/main/libs/exportAsDetails.ts +0 -26
- package/src/main/libs/exportAsSarif.ts +0 -88
- package/src/main/models/FlatViolation.ts +0 -8
- package/src/main/models/Flow.ts +0 -214
- package/src/main/models/FlowAttribute.ts +0 -12
- package/src/main/models/FlowElement.ts +0 -15
- package/src/main/models/FlowElementConnector.ts +0 -28
- package/src/main/models/FlowMetadata.ts +0 -7
- package/src/main/models/FlowNode.ts +0 -171
- package/src/main/models/FlowResource.ts +0 -10
- package/src/main/models/FlowType.ts +0 -52
- package/src/main/models/FlowVariable.ts +0 -12
- package/src/main/models/LoopRuleCommon.ts +0 -55
- package/src/main/models/ParsedFlow.ts +0 -15
- package/src/main/models/RuleCommon.ts +0 -68
- package/src/main/models/RuleInfo.ts +0 -43
- package/src/main/models/RuleResult.ts +0 -25
- package/src/main/models/ScanResult.ts +0 -12
- package/src/main/models/Violation.ts +0 -78
- package/src/main/rules/APIVersion.ts +0 -59
- package/src/main/rules/ActionCallsInLoop.ts +0 -24
- package/src/main/rules/AutoLayout.ts +0 -44
- package/src/main/rules/CopyAPIName.ts +0 -29
- package/src/main/rules/CyclomaticComplexity.ts +0 -67
- package/src/main/rules/DMLStatementInLoop.ts +0 -24
- package/src/main/rules/DuplicateDMLOperation.ts +0 -114
- package/src/main/rules/FlowDescription.ts +0 -32
- package/src/main/rules/FlowName.ts +0 -40
- package/src/main/rules/GetRecordAllFields.ts +0 -59
- package/src/main/rules/HardcodedId.ts +0 -37
- package/src/main/rules/HardcodedUrl.ts +0 -42
- package/src/main/rules/InactiveFlow.ts +0 -31
- package/src/main/rules/MissingFaultPath.ts +0 -89
- package/src/main/rules/MissingMetadataDescription.ts +0 -39
- package/src/main/rules/MissingNullHandler.ts +0 -95
- package/src/main/rules/ProcessBuilder.ts +0 -33
- package/src/main/rules/RecursiveAfterUpdate.ts +0 -88
- package/src/main/rules/SOQLQueryInLoop.ts +0 -24
- package/src/main/rules/SameRecordFieldUpdates.ts +0 -64
- package/src/main/rules/TriggerOrder.ts +0 -44
- package/src/main/rules/UnconnectedElement.ts +0 -48
- package/src/main/rules/UnsafeRunningContext.ts +0 -44
- package/src/main/rules/UnusedVariable.ts +0 -64
- package/src/main/store/DefaultRuleStore.ts +0 -54
- package/stryker.config.mjs +0 -23
- package/tests/APIVersion.test.ts +0 -83
- package/tests/AutoLayout.test.ts +0 -39
- package/tests/Config.test.ts +0 -119
- package/tests/ConfigBetaMode.test.ts +0 -26
- package/tests/CopyAPIName.test.ts +0 -43
- package/tests/CyclomaticComplexity.test.ts +0 -123
- package/tests/DMLStatementInLoop.test.ts +0 -31
- package/tests/DuplicateDMLOperation.test.ts +0 -41
- package/tests/Exceptions.test.ts +0 -813
- package/tests/ExportSarif.test.ts +0 -61
- package/tests/FlowDescription.test.ts +0 -42
- package/tests/FlowName.test.ts +0 -62
- package/tests/GetRecordElementAllFields.test.ts +0 -180
- package/tests/HardcodedId.test.ts +0 -16
- package/tests/HardcodedUrl.test.ts +0 -252
- package/tests/InactiveFlow.test.ts +0 -99
- package/tests/MissingFaultPath.test.ts +0 -50
- package/tests/MissingMetadataDescription.test.ts +0 -24
- package/tests/MissingNullHandler.test.ts +0 -43
- package/tests/No_Missing_Null_Handler.test.ts +0 -30
- package/tests/RecursiveAfterUpdate.test.ts +0 -160
- package/tests/SOQLQueryInLoop.test.ts +0 -32
- package/tests/SameRecordFieldUpdates.test.ts +0 -241
- package/tests/SanityTest.test.ts +0 -15
- package/tests/TriggerOrder.test.ts +0 -92
- package/tests/UnconnectedElement.test.ts +0 -74
- package/tests/UnsafeRunningContext.test.ts +0 -46
- package/tests/UnusedVariable.test.ts +0 -56
- package/tests/jsonfiles/MissingFaultPath_BeforeSave_Bypass.json +0 -128
- package/tests/jsonfiles/MissingFaultPath_WaitConditions.json +0 -102
- package/tests/jsonfiles/MissingFaultPath_WaitDate.json +0 -88
- package/tests/jsonfiles/MissingFaultPath_WaitDuration.json +0 -90
- package/tests/models/Flow.test.ts +0 -107
- package/tests/models/LoopRuleCommon.test.ts +0 -253
- package/tests/models/RuleCommon.test.ts +0 -47
- package/tsconfig.json +0 -19
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.types.json +0 -25
- package/vite.config.ts +0 -28
- /package/{out/index.d.ts → index.d.ts} +0 -0
- /package/{out/index.js → index.js} +0 -0
- /package/{out/main → main}/interfaces/IExceptions.d.ts +0 -0
- /package/{out/main → main}/interfaces/IExceptions.js +0 -0
- /package/{out/main → main}/interfaces/IRuleConfig.d.ts +0 -0
- /package/{out/main → main}/interfaces/IRuleConfig.js +0 -0
- /package/{out/main → main}/interfaces/IRuleDefinition.d.ts +0 -0
- /package/{out/main → main}/interfaces/IRuleDefinition.js +0 -0
- /package/{out/main → main}/interfaces/IRuleOptions.d.ts +0 -0
- /package/{out/main → main}/interfaces/IRuleOptions.js +0 -0
- /package/{out/main → main}/interfaces/IRulesConfig.d.ts +0 -0
- /package/{out/main → main}/interfaces/IRulesConfig.js +0 -0
- /package/{out/main → main}/internals/internals.d.ts +0 -0
- /package/{out/main → main}/internals/internals.js +0 -0
- /package/{out/main → main}/libs/BuildFlow.d.ts +0 -0
- /package/{out/main → main}/libs/BuildFlow.js +0 -0
- /package/{out/main → main}/libs/Compiler.d.ts +0 -0
- /package/{out/main → main}/libs/Compiler.js +0 -0
- /package/{out/main → main}/libs/ConvertFlowNodes.d.ts +0 -0
- /package/{out/main → main}/libs/ConvertFlowNodes.js +0 -0
- /package/{out/main → main}/libs/DynamicRule.d.ts +0 -0
- /package/{out/main → main}/libs/DynamicRule.js +0 -0
- /package/{out/main → main}/libs/FixFlows.d.ts +0 -0
- /package/{out/main → main}/libs/FixFlows.js +0 -0
- /package/{out/main → main}/libs/GetRuleDefinitions.d.ts +0 -0
- /package/{out/main → main}/libs/GetRuleDefinitions.js +0 -0
- /package/{out/main → main}/libs/ParseFlows.d.ts +0 -0
- /package/{out/main → main}/libs/ParseFlows.js +0 -0
- /package/{out/main → main}/libs/ScanFlows.d.ts +0 -0
- /package/{out/main → main}/libs/ScanFlows.js +0 -0
- /package/{out/main → main}/libs/exportAsDetails.d.ts +0 -0
- /package/{out/main → main}/libs/exportAsDetails.js +0 -0
- /package/{out/main → main}/libs/exportAsSarif.d.ts +0 -0
- /package/{out/main → main}/libs/exportAsSarif.js +0 -0
- /package/{out/main → main}/models/FlatViolation.d.ts +0 -0
- /package/{out/main → main}/models/FlatViolation.js +0 -0
- /package/{out/main → main}/models/Flow.d.ts +0 -0
- /package/{out/main → main}/models/Flow.js +0 -0
- /package/{out/main → main}/models/FlowAttribute.d.ts +0 -0
- /package/{out/main → main}/models/FlowAttribute.js +0 -0
- /package/{out/main → main}/models/FlowElement.d.ts +0 -0
- /package/{out/main → main}/models/FlowElement.js +0 -0
- /package/{out/main → main}/models/FlowElementConnector.d.ts +0 -0
- /package/{out/main → main}/models/FlowElementConnector.js +0 -0
- /package/{out/main → main}/models/FlowMetadata.d.ts +0 -0
- /package/{out/main → main}/models/FlowMetadata.js +0 -0
- /package/{out/main → main}/models/FlowNode.d.ts +0 -0
- /package/{out/main → main}/models/FlowNode.js +0 -0
- /package/{out/main → main}/models/FlowResource.d.ts +0 -0
- /package/{out/main → main}/models/FlowResource.js +0 -0
- /package/{out/main → main}/models/FlowType.d.ts +0 -0
- /package/{out/main → main}/models/FlowType.js +0 -0
- /package/{out/main → main}/models/FlowVariable.d.ts +0 -0
- /package/{out/main → main}/models/FlowVariable.js +0 -0
- /package/{out/main → main}/models/LoopRuleCommon.d.ts +0 -0
- /package/{out/main → main}/models/LoopRuleCommon.js +0 -0
- /package/{out/main → main}/models/ParsedFlow.d.ts +0 -0
- /package/{out/main → main}/models/ParsedFlow.js +0 -0
- /package/{out/main → main}/models/RuleCommon.d.ts +0 -0
- /package/{out/main → main}/models/RuleCommon.js +0 -0
- /package/{out/main → main}/models/RuleInfo.d.ts +0 -0
- /package/{out/main → main}/models/RuleInfo.js +0 -0
- /package/{out/main → main}/models/RuleResult.d.ts +0 -0
- /package/{out/main → main}/models/RuleResult.js +0 -0
- /package/{out/main → main}/models/ScanResult.d.ts +0 -0
- /package/{out/main → main}/models/ScanResult.js +0 -0
- /package/{out/main → main}/models/Violation.d.ts +0 -0
- /package/{out/main → main}/models/Violation.js +0 -0
- /package/{out/main → main}/rules/APIVersion.d.ts +0 -0
- /package/{out/main → main}/rules/APIVersion.js +0 -0
- /package/{out/main → main}/rules/ActionCallsInLoop.d.ts +0 -0
- /package/{out/main → main}/rules/ActionCallsInLoop.js +0 -0
- /package/{out/main → main}/rules/AutoLayout.d.ts +0 -0
- /package/{out/main → main}/rules/AutoLayout.js +0 -0
- /package/{out/main → main}/rules/CopyAPIName.d.ts +0 -0
- /package/{out/main → main}/rules/CopyAPIName.js +0 -0
- /package/{out/main → main}/rules/CyclomaticComplexity.d.ts +0 -0
- /package/{out/main → main}/rules/CyclomaticComplexity.js +0 -0
- /package/{out/main → main}/rules/DMLStatementInLoop.d.ts +0 -0
- /package/{out/main → main}/rules/DMLStatementInLoop.js +0 -0
- /package/{out/main → main}/rules/DuplicateDMLOperation.d.ts +0 -0
- /package/{out/main → main}/rules/DuplicateDMLOperation.js +0 -0
- /package/{out/main → main}/rules/FlowDescription.d.ts +0 -0
- /package/{out/main → main}/rules/FlowDescription.js +0 -0
- /package/{out/main → main}/rules/FlowName.d.ts +0 -0
- /package/{out/main → main}/rules/FlowName.js +0 -0
- /package/{out/main → main}/rules/GetRecordAllFields.d.ts +0 -0
- /package/{out/main → main}/rules/GetRecordAllFields.js +0 -0
- /package/{out/main → main}/rules/HardcodedId.d.ts +0 -0
- /package/{out/main → main}/rules/HardcodedId.js +0 -0
- /package/{out/main → main}/rules/HardcodedUrl.d.ts +0 -0
- /package/{out/main → main}/rules/HardcodedUrl.js +0 -0
- /package/{out/main → main}/rules/InactiveFlow.d.ts +0 -0
- /package/{out/main → main}/rules/InactiveFlow.js +0 -0
- /package/{out/main → main}/rules/MissingFaultPath.d.ts +0 -0
- /package/{out/main → main}/rules/MissingFaultPath.js +0 -0
- /package/{out/main → main}/rules/MissingMetadataDescription.d.ts +0 -0
- /package/{out/main → main}/rules/MissingMetadataDescription.js +0 -0
- /package/{out/main → main}/rules/MissingNullHandler.d.ts +0 -0
- /package/{out/main → main}/rules/MissingNullHandler.js +0 -0
- /package/{out/main → main}/rules/ProcessBuilder.d.ts +0 -0
- /package/{out/main → main}/rules/ProcessBuilder.js +0 -0
- /package/{out/main → main}/rules/RecursiveAfterUpdate.d.ts +0 -0
- /package/{out/main → main}/rules/RecursiveAfterUpdate.js +0 -0
- /package/{out/main → main}/rules/SOQLQueryInLoop.d.ts +0 -0
- /package/{out/main → main}/rules/SOQLQueryInLoop.js +0 -0
- /package/{out/main → main}/rules/SameRecordFieldUpdates.d.ts +0 -0
- /package/{out/main → main}/rules/SameRecordFieldUpdates.js +0 -0
- /package/{out/main → main}/rules/TriggerOrder.d.ts +0 -0
- /package/{out/main → main}/rules/TriggerOrder.js +0 -0
- /package/{out/main → main}/rules/UnconnectedElement.d.ts +0 -0
- /package/{out/main → main}/rules/UnconnectedElement.js +0 -0
- /package/{out/main → main}/rules/UnsafeRunningContext.d.ts +0 -0
- /package/{out/main → main}/rules/UnsafeRunningContext.js +0 -0
- /package/{out/main → main}/rules/UnusedVariable.d.ts +0 -0
- /package/{out/main → main}/rules/UnusedVariable.js +0 -0
- /package/{out/main → main}/store/DefaultRuleStore.d.ts +0 -0
- /package/{out/main → main}/store/DefaultRuleStore.js +0 -0
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import * as core from "../internals/internals";
|
|
2
|
-
import { RuleCommon } from "../models/RuleCommon";
|
|
3
|
-
import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
4
|
-
export class RecursiveAfterUpdate extends RuleCommon implements IRuleDefinition {
|
|
5
|
-
protected qualifiedRecordTriggerTypes: Set<string> = new Set<string>([
|
|
6
|
-
"Create",
|
|
7
|
-
"CreateAndUpdate",
|
|
8
|
-
"Update",
|
|
9
|
-
]);
|
|
10
|
-
constructor() {
|
|
11
|
-
super(
|
|
12
|
-
{
|
|
13
|
-
description:
|
|
14
|
-
"After updates are meant to be used for record modifications that are not the same record that triggered the flow. Using after updates on the same record can lead to recursion and unexpected behavior. Consider using before save flows for same record updates.",
|
|
15
|
-
docRefs: [
|
|
16
|
-
{
|
|
17
|
-
label: "Learn about same record field updates",
|
|
18
|
-
path: "https://architect.salesforce.com/decision-guides/trigger-automation#Same_Record_Field_Updates",
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
label: "Recursive After Update",
|
|
22
|
-
name: "RecursiveAfterUpdate",
|
|
23
|
-
supportedTypes: [...core.FlowType.backEndTypes],
|
|
24
|
-
},
|
|
25
|
-
{ severity: "warning" }
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
protected check(
|
|
29
|
-
flow: core.Flow,
|
|
30
|
-
_options: object | undefined,
|
|
31
|
-
suppressions: Set<string>
|
|
32
|
-
): core.Violation[] {
|
|
33
|
-
const results: core.Violation[] = [];
|
|
34
|
-
const isAfterSave = flow.start?.triggerType === "RecordAfterSave";
|
|
35
|
-
const isQualifiedTriggerTypes = this.qualifiedRecordTriggerTypes.has(
|
|
36
|
-
flow.start?.recordTriggerType
|
|
37
|
-
);
|
|
38
|
-
if (!isAfterSave || !isQualifiedTriggerTypes) {
|
|
39
|
-
return results;
|
|
40
|
-
}
|
|
41
|
-
const potentialElements = flow.elements?.filter(
|
|
42
|
-
(node) => node.subtype === "recordUpdates"
|
|
43
|
-
) as core.FlowNode[];
|
|
44
|
-
if (potentialElements == null || typeof potentialElements[Symbol.iterator] !== "function") {
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
// === $Record updates ===
|
|
48
|
-
for (const node of potentialElements) {
|
|
49
|
-
if (
|
|
50
|
-
typeof node.element === "object" &&
|
|
51
|
-
"inputReference" in node.element &&
|
|
52
|
-
node.element.inputReference === "$Record"
|
|
53
|
-
) {
|
|
54
|
-
if (!suppressions.has(node.name)) {
|
|
55
|
-
results.push(new core.Violation(node));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// === Lookup → same object type updates ===
|
|
60
|
-
const lookupElementsWithTheSameObjectType = flow.elements
|
|
61
|
-
?.filter(
|
|
62
|
-
(node) =>
|
|
63
|
-
node.subtype === "recordLookups" &&
|
|
64
|
-
typeof node.element === "object" &&
|
|
65
|
-
"object" in node.element &&
|
|
66
|
-
flow.start.object === node.element["object"]
|
|
67
|
-
)
|
|
68
|
-
?.map((node) => node.name);
|
|
69
|
-
if (
|
|
70
|
-
lookupElementsWithTheSameObjectType == null ||
|
|
71
|
-
typeof lookupElementsWithTheSameObjectType[Symbol.iterator] !== "function"
|
|
72
|
-
) {
|
|
73
|
-
return results;
|
|
74
|
-
}
|
|
75
|
-
for (const node of potentialElements) {
|
|
76
|
-
if (
|
|
77
|
-
typeof node.element === "object" &&
|
|
78
|
-
"inputReference" in node.element &&
|
|
79
|
-
lookupElementsWithTheSameObjectType.includes(node.element.inputReference as string)
|
|
80
|
-
) {
|
|
81
|
-
if (!suppressions.has(node.name)) {
|
|
82
|
-
results.push(new core.Violation(node));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return results;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { FlowType, IRuleDefinition } from "../internals/internals";
|
|
2
|
-
import { LoopRuleCommon } from "../models/LoopRuleCommon";
|
|
3
|
-
|
|
4
|
-
export class SOQLQueryInLoop extends LoopRuleCommon implements IRuleDefinition {
|
|
5
|
-
constructor() {
|
|
6
|
-
super({
|
|
7
|
-
description:
|
|
8
|
-
"To prevent exceeding Apex governor limits, it is advisable to consolidate all your SOQL queries at the conclusion of the flow.",
|
|
9
|
-
docRefs: [
|
|
10
|
-
{
|
|
11
|
-
label: "Flow Best Practices",
|
|
12
|
-
path: "https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5",
|
|
13
|
-
},
|
|
14
|
-
],
|
|
15
|
-
label: "SOQL Query In A Loop",
|
|
16
|
-
name: "SOQLQueryInLoop",
|
|
17
|
-
supportedTypes: FlowType.backEndTypes,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
protected getStatementTypes(): string[] {
|
|
22
|
-
return ["recordLookups"];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import * as core from "../internals/internals";
|
|
2
|
-
import { RuleCommon } from "../models/RuleCommon";
|
|
3
|
-
import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
4
|
-
|
|
5
|
-
export class SameRecordFieldUpdates extends RuleCommon implements IRuleDefinition {
|
|
6
|
-
protected qualifiedRecordTriggerTypes: Set<string> = new Set<string>([
|
|
7
|
-
"Create",
|
|
8
|
-
"Update",
|
|
9
|
-
"CreateAndUpdate",
|
|
10
|
-
]);
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
super(
|
|
14
|
-
{
|
|
15
|
-
name: "SameRecordFieldUpdates",
|
|
16
|
-
label: "Same Record Field Updates",
|
|
17
|
-
description:
|
|
18
|
-
"Before-save same-record field updates allows you to update the record using variable assignments to `$Record`. This is significantly faster than doing another DML on the same-record that triggered the flow",
|
|
19
|
-
supportedTypes: [...core.FlowType.backEndTypes],
|
|
20
|
-
docRefs: [
|
|
21
|
-
{
|
|
22
|
-
label: "Learn about same record field updates",
|
|
23
|
-
path: "https://architect.salesforce.com/decision-guides/trigger-automation#Same_Record_Field_Updates",
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
{ severity: "warning" }
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
protected check(
|
|
32
|
-
flow: core.Flow,
|
|
33
|
-
_options: object | undefined,
|
|
34
|
-
_suppressions: Set<string>
|
|
35
|
-
): core.Violation[] {
|
|
36
|
-
const results: core.Violation[] = [];
|
|
37
|
-
const isBeforeSaveType = flow.start?.triggerType === "RecordBeforeSave";
|
|
38
|
-
const isQualifiedTriggerTypes = this.qualifiedRecordTriggerTypes.has(
|
|
39
|
-
flow.start?.recordTriggerType
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
if (!isBeforeSaveType || !isQualifiedTriggerTypes) {
|
|
43
|
-
return results;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const potentialElements = flow.elements?.filter(
|
|
47
|
-
(node) => node.subtype === "recordUpdates"
|
|
48
|
-
) as core.FlowNode[];
|
|
49
|
-
|
|
50
|
-
if (!potentialElements) return results;
|
|
51
|
-
|
|
52
|
-
for (const node of potentialElements) {
|
|
53
|
-
if (
|
|
54
|
-
typeof node.element === "object" &&
|
|
55
|
-
"inputReference" in node.element &&
|
|
56
|
-
node.element.inputReference === "$Record"
|
|
57
|
-
) {
|
|
58
|
-
results.push(new core.Violation(node));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return results;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import * as core from "../internals/internals";
|
|
2
|
-
import { RuleCommon } from "../models/RuleCommon";
|
|
3
|
-
import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
4
|
-
|
|
5
|
-
export class TriggerOrder extends RuleCommon implements IRuleDefinition {
|
|
6
|
-
protected qualifiedRecordTriggerTypes: Set<string> = new Set<string>(["Create", "Update"]);
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
super(
|
|
10
|
-
{
|
|
11
|
-
name: "TriggerOrder",
|
|
12
|
-
label: "Trigger Order",
|
|
13
|
-
description:
|
|
14
|
-
"With flow trigger ordering, introduced in Spring '22, admins can now assign a priority value to their flows and guarantee their execution order. This priority value is not an absolute value, so the values need not be sequentially numbered as 1, 2, 3, and so on.",
|
|
15
|
-
supportedTypes: [core.FlowType.autolaunchedType],
|
|
16
|
-
docRefs: [
|
|
17
|
-
{
|
|
18
|
-
label: "Learn more about flow ordering orchestration",
|
|
19
|
-
path: "https://architect.salesforce.com/decision-guides/trigger-automation#Ordering___Orchestration",
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
},
|
|
23
|
-
{ severity: "note" }
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
protected check(
|
|
28
|
-
flow: core.Flow,
|
|
29
|
-
_options: object | undefined,
|
|
30
|
-
_suppressions: Set<string>
|
|
31
|
-
): core.Violation[] {
|
|
32
|
-
if (!("object" in flow.start)) return [];
|
|
33
|
-
|
|
34
|
-
if (!flow.triggerOrder) {
|
|
35
|
-
return [
|
|
36
|
-
new core.Violation(
|
|
37
|
-
new core.FlowAttribute("TriggerOrder", "TriggerOrder", "10, 20, 30 ...")
|
|
38
|
-
),
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as core from "../internals/internals";
|
|
2
|
-
import { RuleCommon } from "../models/RuleCommon";
|
|
3
|
-
import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
4
|
-
|
|
5
|
-
export class UnconnectedElement extends RuleCommon implements IRuleDefinition {
|
|
6
|
-
constructor() {
|
|
7
|
-
super({
|
|
8
|
-
description:
|
|
9
|
-
"To maintain the efficiency and manageability of your Flow, it's best to avoid including unconnected elements that are not in use.",
|
|
10
|
-
docRefs: [],
|
|
11
|
-
label: "Unconnected Element",
|
|
12
|
-
name: "UnconnectedElement",
|
|
13
|
-
supportedTypes: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes],
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
protected check(
|
|
18
|
-
flow: core.Flow,
|
|
19
|
-
_options: object | undefined,
|
|
20
|
-
suppressions: Set<string>
|
|
21
|
-
): core.Violation[] {
|
|
22
|
-
const connectedElements: Set<string> = new Set<string>();
|
|
23
|
-
const logConnected = (element: core.FlowNode) => {
|
|
24
|
-
connectedElements.add(element.name);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const flowElements: core.FlowNode[] = flow.elements!.filter(
|
|
28
|
-
(node) => node instanceof core.FlowNode
|
|
29
|
-
) as core.FlowNode[];
|
|
30
|
-
|
|
31
|
-
const startIndex = this.findStart(flowElements);
|
|
32
|
-
if (startIndex !== -1) {
|
|
33
|
-
new core.Compiler().traverseFlow(flow, flowElements[startIndex].name, logConnected);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const unconnectedElements: core.FlowNode[] = flowElements.filter(
|
|
37
|
-
(element) => !connectedElements.has(element.name) && !suppressions.has(element.name)
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
return unconnectedElements.map((det) => new core.Violation(det));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private findStart(nodes: core.FlowNode[]) {
|
|
44
|
-
return nodes.findIndex((n) => {
|
|
45
|
-
return n.subtype === "start";
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import * as core from "../internals/internals";
|
|
2
|
-
import { RuleCommon } from "../models/RuleCommon";
|
|
3
|
-
import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
4
|
-
|
|
5
|
-
export class UnsafeRunningContext extends RuleCommon implements IRuleDefinition {
|
|
6
|
-
constructor() {
|
|
7
|
-
super({
|
|
8
|
-
name: "UnsafeRunningContext",
|
|
9
|
-
label: "Unsafe Running Context",
|
|
10
|
-
description: `This flow is configured to run in System Mode without Sharing. This system context grants all running users the permission to view and edit all data in your org. Running a flow in System Mode without Sharing can lead to unsafe data access.`,
|
|
11
|
-
supportedTypes: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes],
|
|
12
|
-
docRefs: [
|
|
13
|
-
{
|
|
14
|
-
label:
|
|
15
|
-
"Learn about data safety when running flows in system context in Salesforce Help",
|
|
16
|
-
path: "https://help.salesforce.com/s/articleView?id=sf.flow_distribute_context_data_safety_system_context.htm&type=5",
|
|
17
|
-
},
|
|
18
|
-
],
|
|
19
|
-
}, { severity: "warning" });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
protected check(
|
|
23
|
-
flow: core.Flow,
|
|
24
|
-
_options: object | undefined,
|
|
25
|
-
_suppressions: Set<string>
|
|
26
|
-
): core.Violation[] {
|
|
27
|
-
if (!("runInMode" in flow.xmldata)) {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const runInMode: string = flow.xmldata.runInMode;
|
|
32
|
-
const riskyMode: string = "SystemModeWithoutSharing";
|
|
33
|
-
|
|
34
|
-
if (runInMode === riskyMode) {
|
|
35
|
-
return [
|
|
36
|
-
new core.Violation(
|
|
37
|
-
new core.FlowAttribute(runInMode, "runInMode", `== ${riskyMode}`)
|
|
38
|
-
)
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import * as core from "../internals/internals";
|
|
2
|
-
import { RuleCommon } from "../models/RuleCommon";
|
|
3
|
-
import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
4
|
-
|
|
5
|
-
export class UnusedVariable extends RuleCommon implements IRuleDefinition {
|
|
6
|
-
constructor() {
|
|
7
|
-
super({
|
|
8
|
-
name: "UnusedVariable",
|
|
9
|
-
label: "Unused Variable",
|
|
10
|
-
description:
|
|
11
|
-
"To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use.",
|
|
12
|
-
supportedTypes: [...core.FlowType.backEndTypes, ...core.FlowType.visualTypes],
|
|
13
|
-
docRefs: [],
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
protected check(
|
|
18
|
-
flow: core.Flow,
|
|
19
|
-
_options: object | undefined,
|
|
20
|
-
_suppressions: Set<string>
|
|
21
|
-
): core.Violation[] {
|
|
22
|
-
const variables = flow.elements.filter(
|
|
23
|
-
(node) => node instanceof core.FlowVariable
|
|
24
|
-
) as core.FlowVariable[];
|
|
25
|
-
|
|
26
|
-
const unusedVariables: core.FlowVariable[] = [];
|
|
27
|
-
|
|
28
|
-
for (const variable of variables) {
|
|
29
|
-
const variableName = variable.name;
|
|
30
|
-
|
|
31
|
-
const nodeMatches = [
|
|
32
|
-
...JSON.stringify(flow.elements.filter((node) => node instanceof core.FlowNode)).matchAll(
|
|
33
|
-
new RegExp(variableName, "gi")
|
|
34
|
-
),
|
|
35
|
-
].map((a) => a.index);
|
|
36
|
-
|
|
37
|
-
if (nodeMatches.length > 0) continue;
|
|
38
|
-
|
|
39
|
-
const resourceMatches = [
|
|
40
|
-
...JSON.stringify(flow.elements.filter((node) => node instanceof core.FlowResource)).matchAll(
|
|
41
|
-
new RegExp(variableName, "gi")
|
|
42
|
-
),
|
|
43
|
-
].map((a) => a.index);
|
|
44
|
-
|
|
45
|
-
if (resourceMatches.length > 0) continue;
|
|
46
|
-
|
|
47
|
-
const insideCounter = [
|
|
48
|
-
...JSON.stringify(variable).matchAll(new RegExp(variable.name, "gi")),
|
|
49
|
-
].map((a) => a.index);
|
|
50
|
-
|
|
51
|
-
const variableUsage = [
|
|
52
|
-
...JSON.stringify(flow.elements.filter((node) => node instanceof core.FlowVariable)).matchAll(
|
|
53
|
-
new RegExp(variableName, "gi")
|
|
54
|
-
),
|
|
55
|
-
].map((a) => a.index);
|
|
56
|
-
|
|
57
|
-
if (variableUsage.length === insideCounter.length) {
|
|
58
|
-
unusedVariables.push(variable);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return unusedVariables.map((variable) => new core.Violation(variable));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { ActionCallsInLoop } from "../rules/ActionCallsInLoop";
|
|
2
|
-
import { APIVersion } from "../rules/APIVersion";
|
|
3
|
-
import { AutoLayout } from "../rules/AutoLayout";
|
|
4
|
-
import { CopyAPIName } from "../rules/CopyAPIName";
|
|
5
|
-
import { CyclomaticComplexity } from "../rules/CyclomaticComplexity";
|
|
6
|
-
import { DMLStatementInLoop } from "../rules/DMLStatementInLoop";
|
|
7
|
-
import { DuplicateDMLOperation } from "../rules/DuplicateDMLOperation";
|
|
8
|
-
import { FlowDescription } from "../rules/FlowDescription";
|
|
9
|
-
import { FlowName } from "../rules/FlowName";
|
|
10
|
-
import { GetRecordAllFields } from "../rules/GetRecordAllFields";
|
|
11
|
-
import { HardcodedId } from "../rules/HardcodedId";
|
|
12
|
-
import { HardcodedUrl } from "../rules/HardcodedUrl";
|
|
13
|
-
import { InactiveFlow } from "../rules/InactiveFlow";
|
|
14
|
-
import { MissingFaultPath } from "../rules/MissingFaultPath";
|
|
15
|
-
import { MissingNullHandler } from "../rules/MissingNullHandler";
|
|
16
|
-
import { ProcessBuilder } from "../rules/ProcessBuilder";
|
|
17
|
-
import { RecursiveAfterUpdate } from "../rules/RecursiveAfterUpdate";
|
|
18
|
-
import { SameRecordFieldUpdates } from "../rules/SameRecordFieldUpdates";
|
|
19
|
-
import { SOQLQueryInLoop } from "../rules/SOQLQueryInLoop";
|
|
20
|
-
import { TriggerOrder } from "../rules/TriggerOrder";
|
|
21
|
-
import { UnconnectedElement } from "../rules/UnconnectedElement";
|
|
22
|
-
import { UnsafeRunningContext } from "../rules/UnsafeRunningContext";
|
|
23
|
-
import { UnusedVariable } from "../rules/UnusedVariable";
|
|
24
|
-
import { MissingMetadataDescription } from "../rules/MissingMetadataDescription";
|
|
25
|
-
|
|
26
|
-
export const DefaultRuleStore: object = {
|
|
27
|
-
ActionCallsInLoop,
|
|
28
|
-
APIVersion,
|
|
29
|
-
AutoLayout,
|
|
30
|
-
CopyAPIName,
|
|
31
|
-
CyclomaticComplexity,
|
|
32
|
-
DMLStatementInLoop,
|
|
33
|
-
DuplicateDMLOperation,
|
|
34
|
-
FlowDescription,
|
|
35
|
-
FlowName,
|
|
36
|
-
GetRecordAllFields,
|
|
37
|
-
HardcodedId,
|
|
38
|
-
HardcodedUrl,
|
|
39
|
-
InactiveFlow,
|
|
40
|
-
MissingFaultPath,
|
|
41
|
-
MissingNullHandler,
|
|
42
|
-
ProcessBuilder,
|
|
43
|
-
RecursiveAfterUpdate,
|
|
44
|
-
SameRecordFieldUpdates,
|
|
45
|
-
SOQLQueryInLoop,
|
|
46
|
-
TriggerOrder,
|
|
47
|
-
UnconnectedElement,
|
|
48
|
-
UnsafeRunningContext,
|
|
49
|
-
UnusedVariable,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const BetaRuleStore: object = {
|
|
53
|
-
MissingMetadataDescription,
|
|
54
|
-
};
|
package/stryker.config.mjs
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
/** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
|
|
3
|
-
const config = {
|
|
4
|
-
_comment:
|
|
5
|
-
"This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.",
|
|
6
|
-
packageManager: "npm",
|
|
7
|
-
reporters: ["html", "clear-text", "progress", "dashboard"],
|
|
8
|
-
commandRunner: { command: "npm test" },
|
|
9
|
-
testRunner: "jest",
|
|
10
|
-
jest: {
|
|
11
|
-
config: {
|
|
12
|
-
testEnvironment: "node",
|
|
13
|
-
},
|
|
14
|
-
enableFindRelatedTests: true,
|
|
15
|
-
},
|
|
16
|
-
testRunnerNodeArgs: ["--experimental-vm-modules"],
|
|
17
|
-
testRunner_comment:
|
|
18
|
-
"Take a look at https://stryker-mutator.io/docs/stryker-js/jest-runner for information about the jest plugin.",
|
|
19
|
-
coverageAnalysis: "perTest",
|
|
20
|
-
mutate: ["src/**/*.ts", "!tests/**/*.test.ts"],
|
|
21
|
-
ignoreStatic: true,
|
|
22
|
-
};
|
|
23
|
-
export default config;
|
package/tests/APIVersion.test.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import * as core from "../src";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect } from "@jest/globals";
|
|
5
|
-
|
|
6
|
-
describe("APIVersion", () => {
|
|
7
|
-
const example_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/demo/Outdated_API_Version.flow-meta.xml");
|
|
8
|
-
const fixed_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/testing/Outdated_API_Version_Fixed.flow-meta.xml");
|
|
9
|
-
|
|
10
|
-
it("should have a result when attribute is missing", async () => {
|
|
11
|
-
const flows = await core.parse([example_uri]);
|
|
12
|
-
const ruleConfig = {
|
|
13
|
-
rules: {
|
|
14
|
-
APIVersion: {
|
|
15
|
-
severity: "error",
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
21
|
-
expect(results[0].ruleResults).toHaveLength(1);
|
|
22
|
-
expect(results[0].ruleResults[0].ruleName).toBe("APIVersion");
|
|
23
|
-
expect(results[0].ruleResults[0].occurs).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("should have a result when below configured threshold", async () => {
|
|
27
|
-
const flows = await core.parse([example_uri]);
|
|
28
|
-
const ruleConfig = {
|
|
29
|
-
rules: {
|
|
30
|
-
APIVersion: {
|
|
31
|
-
severity: "error",
|
|
32
|
-
expression: ">55",
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
38
|
-
expect(results[0].ruleResults).toHaveLength(1);
|
|
39
|
-
expect(results[0].ruleResults[0].ruleName).toBe("APIVersion");
|
|
40
|
-
expect(results[0].ruleResults[0].occurs).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("should have no result when version is meeting threshold", async () => {
|
|
44
|
-
const flows = await core.parse([fixed_uri]);
|
|
45
|
-
const ruleConfig = {
|
|
46
|
-
rules: {
|
|
47
|
-
APIVersion: {
|
|
48
|
-
severity: "error",
|
|
49
|
-
expression: ">55",
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
55
|
-
expect(results[0].ruleResults).toHaveLength(1);
|
|
56
|
-
expect(results[0].ruleResults[0].ruleName).toBe("APIVersion");
|
|
57
|
-
expect(results[0].ruleResults[0].occurs).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("should have a result when configured is more than what the file has", async () => {
|
|
61
|
-
const flows = await core.parse([fixed_uri]);
|
|
62
|
-
const ruleConfig = {
|
|
63
|
-
rules: {
|
|
64
|
-
APIVersion: {
|
|
65
|
-
severity: "error",
|
|
66
|
-
expression: ">=60",
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
72
|
-
expect(results[0].ruleResults).toHaveLength(1);
|
|
73
|
-
expect(results[0].ruleResults[0].ruleName).toBe("APIVersion");
|
|
74
|
-
expect(results[0].ruleResults[0].occurs).toBe(true);
|
|
75
|
-
|
|
76
|
-
const violations = results[0].ruleResults[0].details;
|
|
77
|
-
if (violations.length > 0) {
|
|
78
|
-
// ensure tag level line numbers are correct
|
|
79
|
-
expect(violations[0].lineNumber).toBeDefined();
|
|
80
|
-
expect(violations[0].lineNumber).toBeGreaterThan(1);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
});
|
package/tests/AutoLayout.test.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as core from "../src";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect } from "@jest/globals";
|
|
5
|
-
|
|
6
|
-
describe("Autolayout", () => {
|
|
7
|
-
const example_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/demo/Unconnected_Element.flow-meta.xml");
|
|
8
|
-
const fixed_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/testing/Outdated_API_Version_Fixed.flow-meta.xml");
|
|
9
|
-
|
|
10
|
-
it("should have a result when CanvasMode is set to FREE_FORM_CANVAS", async () => {
|
|
11
|
-
const flows = await core.parse([example_uri]);
|
|
12
|
-
const ruleConfig = {
|
|
13
|
-
rules: {
|
|
14
|
-
AutoLayout: {
|
|
15
|
-
severity: "error",
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
21
|
-
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
|
|
22
|
-
expect(occurringResults).toHaveLength(1);
|
|
23
|
-
expect(occurringResults.find((res) => res.ruleName === "AutoLayout")).toBeTruthy();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("should not have result when autolayout is configured", async () => {
|
|
27
|
-
const flows = await core.parse([fixed_uri]);
|
|
28
|
-
const ruleConfig = {
|
|
29
|
-
rules: {
|
|
30
|
-
AutoLayout: {
|
|
31
|
-
severity: "error",
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
36
|
-
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
|
|
37
|
-
expect(occurringResults).toHaveLength(0);
|
|
38
|
-
});
|
|
39
|
-
});
|