@flow-scanner/lightning-flow-scanner-core 6.10.6 → 6.11.0
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/README.md +19 -16
- package/index.d.ts +5 -2
- package/index.js +19 -0
- package/main/config/NodeIcons.d.ts +24 -0
- package/main/config/NodeIcons.js +122 -0
- package/main/config/VariableIcons.d.ts +25 -0
- package/main/config/VariableIcons.js +53 -0
- package/main/libs/Compiler.d.ts +1 -2
- package/main/libs/Compiler.js +10 -16
- package/main/libs/ExportDiagram.d.ts +41 -0
- package/main/libs/ExportDiagram.js +40 -0
- package/main/models/Flow.d.ts +8 -0
- package/main/models/Flow.js +57 -2
- package/main/models/FlowGraph.d.ts +85 -0
- package/main/models/FlowGraph.js +532 -0
- package/main/models/FlowNode.d.ts +58 -2
- package/main/models/FlowNode.js +161 -3
- package/main/models/FlowVariable.d.ts +59 -1
- package/main/models/FlowVariable.js +118 -1
- package/main/models/LoopRuleCommon.js +11 -12
- package/main/rules/DuplicateDMLOperation.d.ts +1 -1
- package/main/rules/DuplicateDMLOperation.js +36 -67
- package/main/rules/MissingFaultPath.js +4 -14
- package/main/rules/UnconnectedElement.js +2 -9
- package/package.json +2 -2
package/main/models/FlowNode.js
CHANGED
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "FlowNode", {
|
|
|
11
11
|
const _MetadataTypes = require("../enums/MetadataTypes");
|
|
12
12
|
const _FlowElement = require("./FlowElement");
|
|
13
13
|
const _FlowElementConnector = require("./FlowElementConnector");
|
|
14
|
+
const _NodeIcons = require("../config/NodeIcons");
|
|
14
15
|
function _define_property(obj, key, value) {
|
|
15
16
|
if (key in obj) {
|
|
16
17
|
Object.defineProperty(obj, key, {
|
|
@@ -25,6 +26,146 @@ function _define_property(obj, key, value) {
|
|
|
25
26
|
return obj;
|
|
26
27
|
}
|
|
27
28
|
let FlowNode = class FlowNode extends _FlowElement.FlowElement {
|
|
29
|
+
/**
|
|
30
|
+
* Set custom icon configuration for all FlowNodes
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // Use ASCII icons for old terminals
|
|
34
|
+
* FlowNode.setIconConfig(ASCII_ICONS);
|
|
35
|
+
*
|
|
36
|
+
* // Or provide custom icons
|
|
37
|
+
* FlowNode.setIconConfig({
|
|
38
|
+
* actionCalls: { default: '[ACTION]' },
|
|
39
|
+
* decisions: { default: '[IF]' }
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/ static setIconConfig(config) {
|
|
43
|
+
FlowNode.iconConfig = config;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Use ASCII icons instead of emoji (for older browsers/terminals)
|
|
47
|
+
*/ static useAsciiIcons() {
|
|
48
|
+
FlowNode.iconConfig = _NodeIcons.ASCII_ICONS;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reset to default emoji icons
|
|
52
|
+
*/ static useDefaultIcons() {
|
|
53
|
+
FlowNode.iconConfig = _NodeIcons.DEFAULT_ICONS;
|
|
54
|
+
}
|
|
55
|
+
extractTypeSpecificProperties(subtype, element) {
|
|
56
|
+
switch(subtype){
|
|
57
|
+
case "actionCalls":
|
|
58
|
+
this.actionType = element.actionType;
|
|
59
|
+
this.actionName = element.actionName;
|
|
60
|
+
break;
|
|
61
|
+
case "recordCreates":
|
|
62
|
+
case "recordUpdates":
|
|
63
|
+
case "recordDeletes":
|
|
64
|
+
case "recordLookups":
|
|
65
|
+
this.object = element.object;
|
|
66
|
+
this.inputReference = element.inputReference;
|
|
67
|
+
this.outputReference = element.outputReference;
|
|
68
|
+
break;
|
|
69
|
+
case "collectionProcessors":
|
|
70
|
+
this.elementSubtype = element.elementSubtype;
|
|
71
|
+
this.collectionReference = element.collectionReference;
|
|
72
|
+
break;
|
|
73
|
+
case "subflows":
|
|
74
|
+
this.flowName = element.flowName;
|
|
75
|
+
break;
|
|
76
|
+
case "decisions":
|
|
77
|
+
this.rules = Array.isArray(element.rules) ? element.rules : element.rules ? [
|
|
78
|
+
element.rules
|
|
79
|
+
] : [];
|
|
80
|
+
this.defaultConnectorLabel = element.defaultConnectorLabel;
|
|
81
|
+
break;
|
|
82
|
+
case "loops":
|
|
83
|
+
this.collectionReference = element.collectionReference;
|
|
84
|
+
this.iterationOrder = element.iterationOrder;
|
|
85
|
+
break;
|
|
86
|
+
case "screens":
|
|
87
|
+
this.fields = Array.isArray(element.fields) ? element.fields : element.fields ? [
|
|
88
|
+
element.fields
|
|
89
|
+
] : [];
|
|
90
|
+
this.allowPause = element.allowPause;
|
|
91
|
+
this.showFooter = element.showFooter;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get a human-readable summary of this node
|
|
97
|
+
*/ getSummary() {
|
|
98
|
+
const parts = [];
|
|
99
|
+
switch(this.subtype){
|
|
100
|
+
case "actionCalls":
|
|
101
|
+
if (this.actionType) parts.push(this.prettifyValue(this.actionType));
|
|
102
|
+
if (this.actionName) parts.push(this.actionName);
|
|
103
|
+
break;
|
|
104
|
+
case "recordCreates":
|
|
105
|
+
case "recordUpdates":
|
|
106
|
+
case "recordDeletes":
|
|
107
|
+
case "recordLookups":
|
|
108
|
+
if (this.object) parts.push(this.object);
|
|
109
|
+
break;
|
|
110
|
+
case "collectionProcessors":
|
|
111
|
+
if (this.elementSubtype) parts.push(this.prettifyValue(this.elementSubtype));
|
|
112
|
+
break;
|
|
113
|
+
case "decisions":
|
|
114
|
+
var _this_rules, _this_rules1;
|
|
115
|
+
parts.push(`${((_this_rules = this.rules) === null || _this_rules === void 0 ? void 0 : _this_rules.length) || 0} rule${((_this_rules1 = this.rules) === null || _this_rules1 === void 0 ? void 0 : _this_rules1.length) !== 1 ? 's' : ''}`);
|
|
116
|
+
break;
|
|
117
|
+
case "loops":
|
|
118
|
+
if (this.collectionReference) parts.push(`Loop: ${this.collectionReference}`);
|
|
119
|
+
break;
|
|
120
|
+
case "subflows":
|
|
121
|
+
if (this.flowName) parts.push(this.flowName);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
if (this.description) {
|
|
125
|
+
parts.push(this.description.substring(0, 50) + (this.description.length > 50 ? '...' : ''));
|
|
126
|
+
}
|
|
127
|
+
return parts.join(' • ');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get the icon for this node type
|
|
131
|
+
*/ getIcon() {
|
|
132
|
+
const typeIcons = FlowNode.iconConfig[this.subtype];
|
|
133
|
+
if (!typeIcons) {
|
|
134
|
+
// Fallback for unknown types
|
|
135
|
+
const fallback = FlowNode.iconConfig['default'];
|
|
136
|
+
return fallback && 'default' in fallback ? fallback.default : '\u2022'; // • BULLET
|
|
137
|
+
}
|
|
138
|
+
// For nodes with subtypes (like actionCalls or collectionProcessors)
|
|
139
|
+
const subtype = this.actionType || this.elementSubtype;
|
|
140
|
+
const icons = typeIcons;
|
|
141
|
+
if (subtype && icons[subtype]) {
|
|
142
|
+
return icons[subtype];
|
|
143
|
+
}
|
|
144
|
+
return icons.default || '\u2022'; // • BULLET fallback
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get the display name for this node type
|
|
148
|
+
*/ getTypeLabel() {
|
|
149
|
+
const labelMap = {
|
|
150
|
+
actionCalls: 'Action',
|
|
151
|
+
assignments: 'Assignment',
|
|
152
|
+
collectionProcessors: 'Collection',
|
|
153
|
+
customErrors: 'Error',
|
|
154
|
+
decisions: 'Decision',
|
|
155
|
+
loops: 'Loop',
|
|
156
|
+
recordCreates: 'Create',
|
|
157
|
+
recordDeletes: 'Delete',
|
|
158
|
+
recordLookups: 'Get Records',
|
|
159
|
+
recordUpdates: 'Update',
|
|
160
|
+
screens: 'Screen',
|
|
161
|
+
subflows: 'Subflow',
|
|
162
|
+
transforms: 'Transform'
|
|
163
|
+
};
|
|
164
|
+
return labelMap[this.subtype] || this.subtype;
|
|
165
|
+
}
|
|
166
|
+
prettifyValue(value) {
|
|
167
|
+
return value.replace(/([A-Z])/g, ' $1').replace(/^./, (str)=>str.toUpperCase()).trim();
|
|
168
|
+
}
|
|
28
169
|
getConnectors(subtype, element) {
|
|
29
170
|
const connectors = [];
|
|
30
171
|
if (subtype === "start") {
|
|
@@ -160,10 +301,27 @@ let FlowNode = class FlowNode extends _FlowElement.FlowElement {
|
|
|
160
301
|
}
|
|
161
302
|
constructor(provName, subtype, element){
|
|
162
303
|
const nodeName = subtype === "start" ? "flowstart" : provName;
|
|
163
|
-
super(_MetadataTypes.MetaType.NODE, subtype, nodeName, element), _define_property(this, "connectors", []), _define_property(this, "locationX", void 0), _define_property(this, "locationY", void 0)
|
|
164
|
-
|
|
165
|
-
this
|
|
304
|
+
super(_MetadataTypes.MetaType.NODE, subtype, nodeName, element), _define_property(this, "connectors", []), _define_property(this, "locationX", void 0), _define_property(this, "locationY", void 0), // Common properties across node types
|
|
305
|
+
_define_property(this, "label", void 0), _define_property(this, "description", void 0), // Action-specific properties
|
|
306
|
+
_define_property(this, "actionType", void 0), _define_property(this, "actionName", void 0), // Record operation properties
|
|
307
|
+
_define_property(this, "object", void 0), _define_property(this, "inputReference", void 0), _define_property(this, "outputReference", void 0), // Collection processor properties
|
|
308
|
+
_define_property(this, "elementSubtype", void 0), _define_property(this, "collectionReference", void 0), // Subflow properties
|
|
309
|
+
_define_property(this, "flowName", void 0), // Decision properties
|
|
310
|
+
_define_property(this, "rules", void 0), _define_property(this, "defaultConnectorLabel", void 0), // Loop properties
|
|
311
|
+
_define_property(this, "iterationOrder", void 0), // Screen properties
|
|
312
|
+
_define_property(this, "fields", void 0), _define_property(this, "allowPause", void 0), _define_property(this, "showFooter", void 0), // Fault handling
|
|
313
|
+
_define_property(this, "faultConnector", void 0);
|
|
314
|
+
// Extract common properties
|
|
315
|
+
this.label = element["label"];
|
|
316
|
+
this.description = element["description"];
|
|
166
317
|
this.locationX = element["locationX"];
|
|
167
318
|
this.locationY = element["locationY"];
|
|
319
|
+
// Extract type-specific properties
|
|
320
|
+
this.extractTypeSpecificProperties(subtype, element);
|
|
321
|
+
// Extract connectors
|
|
322
|
+
this.connectors = this.getConnectors(subtype, element);
|
|
323
|
+
this.faultConnector = this.connectors.find((c)=>c.type === "faultConnector");
|
|
168
324
|
}
|
|
169
325
|
};
|
|
326
|
+
// Static icon configuration (can be overridden)
|
|
327
|
+
_define_property(FlowNode, "iconConfig", _NodeIcons.DEFAULT_ICONS);
|
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
import { FlowElement } from "./FlowElement";
|
|
2
|
+
import { type VariableIconConfig } from "../config/VariableIcons";
|
|
2
3
|
export declare class FlowVariable extends FlowElement {
|
|
3
|
-
dataType
|
|
4
|
+
dataType?: string;
|
|
5
|
+
isCollection?: boolean;
|
|
6
|
+
isInput?: boolean;
|
|
7
|
+
isOutput?: boolean;
|
|
8
|
+
objectType?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
value?: any;
|
|
11
|
+
private static iconConfig;
|
|
12
|
+
/**
|
|
13
|
+
* Set custom icon configuration for all FlowVariables
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Use ASCII icons
|
|
17
|
+
* FlowVariable.setIconConfig(ASCII_VARIABLE_ICONS);
|
|
18
|
+
*
|
|
19
|
+
* // Or provide custom icons
|
|
20
|
+
* FlowVariable.setIconConfig({
|
|
21
|
+
* subtypes: {
|
|
22
|
+
* variables: '[VAR]',
|
|
23
|
+
* constants: '[CONST]'
|
|
24
|
+
* },
|
|
25
|
+
* boolean: {
|
|
26
|
+
* true: '[YES]',
|
|
27
|
+
* false: '[NO]'
|
|
28
|
+
* }
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
static setIconConfig(config: VariableIconConfig): void;
|
|
33
|
+
/**
|
|
34
|
+
* Use ASCII icons instead of emoji
|
|
35
|
+
*/
|
|
36
|
+
static useAsciiIcons(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Reset to default emoji icons
|
|
39
|
+
*/
|
|
40
|
+
static useDefaultIcons(): void;
|
|
4
41
|
constructor(name: string, subtype: string, element: object);
|
|
42
|
+
/**
|
|
43
|
+
* Get the icon for this variable subtype
|
|
44
|
+
*/
|
|
45
|
+
getIcon(): string;
|
|
46
|
+
/**
|
|
47
|
+
* Get icon for a boolean value
|
|
48
|
+
*/
|
|
49
|
+
private getBooleanIcon;
|
|
50
|
+
/**
|
|
51
|
+
* Get a human-readable type label
|
|
52
|
+
*/
|
|
53
|
+
getTypeLabel(): string;
|
|
54
|
+
/**
|
|
55
|
+
* Get a markdown table row for this variable
|
|
56
|
+
*/
|
|
57
|
+
toTableRow(): string;
|
|
58
|
+
/**
|
|
59
|
+
* Get a detailed markdown table for this variable
|
|
60
|
+
*/
|
|
61
|
+
toMarkdownTable(): string;
|
|
62
|
+
private formatValue;
|
|
5
63
|
}
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "FlowVariable", {
|
|
|
10
10
|
});
|
|
11
11
|
const _MetadataTypes = require("../enums/MetadataTypes");
|
|
12
12
|
const _FlowElement = require("./FlowElement");
|
|
13
|
+
const _VariableIcons = require("../config/VariableIcons");
|
|
13
14
|
function _define_property(obj, key, value) {
|
|
14
15
|
if (key in obj) {
|
|
15
16
|
Object.defineProperty(obj, key, {
|
|
@@ -24,8 +25,124 @@ function _define_property(obj, key, value) {
|
|
|
24
25
|
return obj;
|
|
25
26
|
}
|
|
26
27
|
let FlowVariable = class FlowVariable extends _FlowElement.FlowElement {
|
|
28
|
+
/**
|
|
29
|
+
* Set custom icon configuration for all FlowVariables
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Use ASCII icons
|
|
33
|
+
* FlowVariable.setIconConfig(ASCII_VARIABLE_ICONS);
|
|
34
|
+
*
|
|
35
|
+
* // Or provide custom icons
|
|
36
|
+
* FlowVariable.setIconConfig({
|
|
37
|
+
* subtypes: {
|
|
38
|
+
* variables: '[VAR]',
|
|
39
|
+
* constants: '[CONST]'
|
|
40
|
+
* },
|
|
41
|
+
* boolean: {
|
|
42
|
+
* true: '[YES]',
|
|
43
|
+
* false: '[NO]'
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/ static setIconConfig(config) {
|
|
48
|
+
FlowVariable.iconConfig = config;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Use ASCII icons instead of emoji
|
|
52
|
+
*/ static useAsciiIcons() {
|
|
53
|
+
FlowVariable.iconConfig = _VariableIcons.ASCII_VARIABLE_ICONS;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reset to default emoji icons
|
|
57
|
+
*/ static useDefaultIcons() {
|
|
58
|
+
FlowVariable.iconConfig = _VariableIcons.DEFAULT_VARIABLE_ICONS;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the icon for this variable subtype
|
|
62
|
+
*/ getIcon() {
|
|
63
|
+
return FlowVariable.iconConfig.subtypes[this.subtype] || '\uD83D\uDCCA'; // 📊 default
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get icon for a boolean value
|
|
67
|
+
*/ getBooleanIcon(value) {
|
|
68
|
+
if (value === true) {
|
|
69
|
+
return FlowVariable.iconConfig.boolean.true;
|
|
70
|
+
} else if (value === false) {
|
|
71
|
+
return FlowVariable.iconConfig.boolean.false;
|
|
72
|
+
}
|
|
73
|
+
return ''; // undefined/null
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get a human-readable type label
|
|
77
|
+
*/ getTypeLabel() {
|
|
78
|
+
const labelMap = {
|
|
79
|
+
variables: 'Variable',
|
|
80
|
+
constants: 'Constant',
|
|
81
|
+
formulas: 'Formula',
|
|
82
|
+
choices: 'Choice',
|
|
83
|
+
dynamicChoiceSets: 'Dynamic Choice'
|
|
84
|
+
};
|
|
85
|
+
return labelMap[this.subtype] || this.subtype;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get a markdown table row for this variable
|
|
89
|
+
*/ toTableRow() {
|
|
90
|
+
const parts = [
|
|
91
|
+
this.name,
|
|
92
|
+
this.dataType || '',
|
|
93
|
+
this.getBooleanIcon(this.isCollection),
|
|
94
|
+
this.getBooleanIcon(this.isInput),
|
|
95
|
+
this.getBooleanIcon(this.isOutput),
|
|
96
|
+
this.objectType || '',
|
|
97
|
+
this.description || ''
|
|
98
|
+
];
|
|
99
|
+
return `| ${parts.join(' | ')} |`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get a detailed markdown table for this variable
|
|
103
|
+
*/ toMarkdownTable() {
|
|
104
|
+
let table = '| Property | Value |\n|:---|:---|\n';
|
|
105
|
+
table += `| Name | ${this.name} |\n`;
|
|
106
|
+
table += `| Type | ${this.getIcon()} ${this.getTypeLabel()} |\n`;
|
|
107
|
+
if (this.dataType) table += `| Data Type | ${this.dataType} |\n`;
|
|
108
|
+
if (this.objectType) table += `| Object Type | ${this.objectType} |\n`;
|
|
109
|
+
if (this.isCollection !== undefined) {
|
|
110
|
+
table += `| Collection | ${this.getBooleanIcon(this.isCollection)} |\n`;
|
|
111
|
+
}
|
|
112
|
+
if (this.isInput !== undefined) {
|
|
113
|
+
table += `| Input | ${this.getBooleanIcon(this.isInput)} |\n`;
|
|
114
|
+
}
|
|
115
|
+
if (this.isOutput !== undefined) {
|
|
116
|
+
table += `| Output | ${this.getBooleanIcon(this.isOutput)} |\n`;
|
|
117
|
+
}
|
|
118
|
+
if (this.value !== undefined) {
|
|
119
|
+
table += `| Value | ${this.formatValue(this.value)} |\n`;
|
|
120
|
+
}
|
|
121
|
+
if (this.description) table += `| Description | ${this.description} |\n`;
|
|
122
|
+
return table;
|
|
123
|
+
}
|
|
124
|
+
formatValue(value) {
|
|
125
|
+
if (typeof value === 'object') {
|
|
126
|
+
return JSON.stringify(value, null, 2);
|
|
127
|
+
}
|
|
128
|
+
return String(value);
|
|
129
|
+
}
|
|
27
130
|
constructor(name, subtype, element){
|
|
28
|
-
super(_MetadataTypes.MetaType.VARIABLE, subtype, name, element), _define_property(this, "dataType", void 0);
|
|
131
|
+
super(_MetadataTypes.MetaType.VARIABLE, subtype, name, element), _define_property(this, "dataType", void 0), _define_property(this, "isCollection", void 0), _define_property(this, "isInput", void 0), _define_property(this, "isOutput", void 0), _define_property(this, "objectType", void 0), _define_property(this, "description", void 0), _define_property(this, "value", void 0);
|
|
132
|
+
// Extract properties based on variable subtype
|
|
29
133
|
this.dataType = element["dataType"];
|
|
134
|
+
this.isCollection = element["isCollection"];
|
|
135
|
+
this.isInput = element["isInput"];
|
|
136
|
+
this.isOutput = element["isOutput"];
|
|
137
|
+
this.objectType = element["objectType"];
|
|
138
|
+
this.description = element["description"];
|
|
139
|
+
// Different subtypes have different value properties
|
|
140
|
+
if (subtype === "constants") {
|
|
141
|
+
this.value = element["value"];
|
|
142
|
+
} else if (subtype === "formulas") {
|
|
143
|
+
this.value = element["expression"];
|
|
144
|
+
}
|
|
30
145
|
}
|
|
31
146
|
};
|
|
147
|
+
// Static icon configuration (can be overridden)
|
|
148
|
+
_define_property(FlowVariable, "iconConfig", _VariableIcons.DEFAULT_VARIABLE_ICONS);
|
|
@@ -12,7 +12,7 @@ const _internals = require("../internals/internals");
|
|
|
12
12
|
const _RuleCommon = require("./RuleCommon");
|
|
13
13
|
let LoopRuleCommon = class LoopRuleCommon extends _RuleCommon.RuleCommon {
|
|
14
14
|
check(flow, _options, suppressions) {
|
|
15
|
-
const loopElements =
|
|
15
|
+
const loopElements = flow.graph.getLoopNodes();
|
|
16
16
|
if (!loopElements.length) {
|
|
17
17
|
return [];
|
|
18
18
|
}
|
|
@@ -21,25 +21,24 @@ let LoopRuleCommon = class LoopRuleCommon extends _RuleCommon.RuleCommon {
|
|
|
21
21
|
return results;
|
|
22
22
|
}
|
|
23
23
|
findLoopElements(flow) {
|
|
24
|
-
|
|
25
|
-
return ((_flow_elements = flow.elements) === null || _flow_elements === void 0 ? void 0 : _flow_elements.filter((node)=>node.subtype === "loops")) || [];
|
|
24
|
+
return flow.graph.getLoopNodes();
|
|
26
25
|
}
|
|
27
26
|
findLoopEnd(element) {
|
|
28
|
-
var _element_element_noMoreValuesConnector;
|
|
27
|
+
var _element_element_noMoreValuesConnector, _element_element;
|
|
29
28
|
var _element_element_noMoreValuesConnector_targetReference;
|
|
30
|
-
return (_element_element_noMoreValuesConnector_targetReference = (
|
|
29
|
+
return (_element_element_noMoreValuesConnector_targetReference = (_element_element = element.element) === null || _element_element === void 0 ? void 0 : (_element_element_noMoreValuesConnector = _element_element.noMoreValuesConnector) === null || _element_element_noMoreValuesConnector === void 0 ? void 0 : _element_element_noMoreValuesConnector.targetReference) !== null && _element_element_noMoreValuesConnector_targetReference !== void 0 ? _element_element_noMoreValuesConnector_targetReference : element.name;
|
|
31
30
|
}
|
|
32
31
|
findStatementsInLoops(flow, loopElements) {
|
|
33
32
|
const statementsInLoops = [];
|
|
34
33
|
const statementTypes = this.getStatementTypes();
|
|
35
|
-
const findStatement = (element)=>{
|
|
36
|
-
if (statementTypes.includes(element.subtype)) {
|
|
37
|
-
statementsInLoops.push(element);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
34
|
for (const element of loopElements){
|
|
41
|
-
const
|
|
42
|
-
|
|
35
|
+
const loopElems = flow.graph.getLoopElements(element.name);
|
|
36
|
+
for (const elemName of loopElems){
|
|
37
|
+
const node = flow.graph.getNode(elemName);
|
|
38
|
+
if (node && statementTypes.includes(node.subtype)) {
|
|
39
|
+
statementsInLoops.push(node);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
43
42
|
}
|
|
44
43
|
return statementsInLoops;
|
|
45
44
|
}
|
|
@@ -4,5 +4,5 @@ import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
|
4
4
|
export declare class DuplicateDMLOperation extends RuleCommon implements IRuleDefinition {
|
|
5
5
|
constructor();
|
|
6
6
|
protected check(flow: core.Flow, _options: object | undefined, suppressions: Set<string>): core.Violation[];
|
|
7
|
-
private
|
|
7
|
+
private isDML;
|
|
8
8
|
}
|
|
@@ -53,81 +53,50 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
53
53
|
}
|
|
54
54
|
let DuplicateDMLOperation = class DuplicateDMLOperation extends _RuleCommon.RuleCommon {
|
|
55
55
|
check(flow, _options, suppressions) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
let dmlFlag = false;
|
|
66
|
-
let indexesToProcess = [
|
|
67
|
-
startingNode
|
|
68
|
-
];
|
|
69
|
-
do {
|
|
70
|
-
indexesToProcess = indexesToProcess.filter((index)=>!processedElementIndexes.includes(index));
|
|
71
|
-
if (indexesToProcess.length > 0) {
|
|
72
|
-
for (const [index, element] of flowElements.entries()){
|
|
73
|
-
if (indexesToProcess.includes(index)) {
|
|
74
|
-
const references = [];
|
|
75
|
-
if (element.connectors && element.connectors.length > 0) {
|
|
76
|
-
for (const connector of element.connectors){
|
|
77
|
-
if (connector.reference) {
|
|
78
|
-
references.push(connector.reference);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
dmlFlag = this.flagDML(element, dmlFlag);
|
|
83
|
-
if (references.length > 0) {
|
|
84
|
-
const elementsByReferences = flowElements.filter((el)=>references.includes(el.name));
|
|
85
|
-
for (const nextElement of elementsByReferences){
|
|
86
|
-
const nextIndex = flowElements.findIndex((el)=>nextElement.name === el.name);
|
|
87
|
-
if (nextElement.subtype === "screens") {
|
|
88
|
-
if (dmlFlag && nextElement.element["allowBack"] === "true" && nextElement.element["showFooter"] === "true") {
|
|
89
|
-
if (!suppressions.has(nextElement.name)) {
|
|
90
|
-
DuplicateDMLOperations.push(nextElement);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (!processedElementIndexes.includes(nextIndex)) {
|
|
95
|
-
indexesToProcess.push(nextIndex);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
processedElementIndexes.push(index);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
for (const index of flowElements.keys()){
|
|
104
|
-
if (!processedElementIndexes.includes(index)) {
|
|
105
|
-
unconnectedElementIndexes.push(index);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
56
|
+
const graph = flow.graph;
|
|
57
|
+
const start = flow.startReference;
|
|
58
|
+
if (!start) return [];
|
|
59
|
+
const violations = [];
|
|
60
|
+
const visited = new Set();
|
|
61
|
+
const stack = [
|
|
62
|
+
{
|
|
63
|
+
name: start,
|
|
64
|
+
seenDML: false
|
|
108
65
|
}
|
|
109
|
-
}while (processedElementIndexes.length + unconnectedElementIndexes.length < flowElements.length)
|
|
110
|
-
return DuplicateDMLOperations.map((det)=>new _internals.Violation(det));
|
|
111
|
-
}
|
|
112
|
-
flagDML(element, dmlFlag) {
|
|
113
|
-
const dmlStatementTypes = [
|
|
114
|
-
"recordDeletes",
|
|
115
|
-
"recordUpdates",
|
|
116
|
-
"recordCreates"
|
|
117
66
|
];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
67
|
+
while(stack.length > 0){
|
|
68
|
+
const { name, seenDML } = stack.pop();
|
|
69
|
+
const stateKey = `${name}:${seenDML}`;
|
|
70
|
+
if (visited.has(stateKey)) continue;
|
|
71
|
+
visited.add(stateKey);
|
|
72
|
+
const node = graph.getNode(name);
|
|
73
|
+
if (!node) continue;
|
|
74
|
+
let nextSeenDML = seenDML || this.isDML(node);
|
|
75
|
+
if (nextSeenDML && node.subtype === "screens" && node.element["allowBack"] === "true" && node.element["showFooter"] === "true" && !suppressions.has(node.name)) {
|
|
76
|
+
violations.push(new _internals.Violation(node));
|
|
77
|
+
// Note: do NOT return early; multiple violations possible
|
|
78
|
+
}
|
|
79
|
+
// Reset DML flag after screen with back disabled
|
|
80
|
+
if (nextSeenDML && node.subtype === "screens" && node.element["allowBack"] !== "true") {
|
|
81
|
+
nextSeenDML = false;
|
|
82
|
+
}
|
|
83
|
+
for (const next of graph.getNextElements(name)){
|
|
84
|
+
stack.push({
|
|
85
|
+
name: next,
|
|
86
|
+
seenDML: nextSeenDML
|
|
87
|
+
});
|
|
88
|
+
}
|
|
124
89
|
}
|
|
90
|
+
return violations;
|
|
91
|
+
}
|
|
92
|
+
isDML(node) {
|
|
93
|
+
return node.subtype === "recordCreates" || node.subtype === "recordUpdates" || node.subtype === "recordDeletes";
|
|
125
94
|
}
|
|
126
95
|
constructor(){
|
|
127
96
|
super({
|
|
128
97
|
name: "DuplicateDMLOperation",
|
|
129
98
|
label: "Duplicate DML Operation",
|
|
130
|
-
description: "When the flow executes database changes
|
|
99
|
+
description: "When the flow executes database changes between screens, users must not be allowed to navigate back, or duplicate DML operations may occur.",
|
|
131
100
|
supportedTypes: _internals.FlowType.visualTypes,
|
|
132
101
|
docRefs: []
|
|
133
102
|
});
|
|
@@ -82,7 +82,7 @@ let MissingFaultPath = class MissingFaultPath extends _RuleCommon.RuleCommon {
|
|
|
82
82
|
return true;
|
|
83
83
|
}
|
|
84
84
|
check(flow, _options, suppressions) {
|
|
85
|
-
|
|
85
|
+
var _flow_graph;
|
|
86
86
|
const results = [];
|
|
87
87
|
const elementsWhereFaultPathIsApplicable = flow.elements.filter((node)=>{
|
|
88
88
|
const proxyNode = node;
|
|
@@ -104,9 +104,7 @@ let MissingFaultPath = class MissingFaultPath extends _RuleCommon.RuleCommon {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
|
-
|
|
108
|
-
compiler.traverseFlow(flow, flow.startReference, visitCallback);
|
|
109
|
-
}
|
|
107
|
+
(_flow_graph = flow.graph) === null || _flow_graph === void 0 ? void 0 : _flow_graph.forEachReachable(visitCallback);
|
|
110
108
|
return results;
|
|
111
109
|
}
|
|
112
110
|
/**
|
|
@@ -123,16 +121,8 @@ let MissingFaultPath = class MissingFaultPath extends _RuleCommon.RuleCommon {
|
|
|
123
121
|
return false;
|
|
124
122
|
}
|
|
125
123
|
isPartOfFaultHandlingFlow(element, flow) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (otherElement !== element) {
|
|
129
|
-
var _otherElement_connectors;
|
|
130
|
-
if ((_otherElement_connectors = otherElement.connectors) === null || _otherElement_connectors === void 0 ? void 0 : _otherElement_connectors.find((connector)=>connector.type === "faultConnector" && connector.reference && connector.reference === element.name)) {
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return false;
|
|
124
|
+
var _flow_graph;
|
|
125
|
+
return ((_flow_graph = flow.graph) === null || _flow_graph === void 0 ? void 0 : _flow_graph.isPartOfFaultHandling(element.name)) || false;
|
|
136
126
|
}
|
|
137
127
|
constructor(){
|
|
138
128
|
super({
|
|
@@ -53,16 +53,9 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
53
53
|
}
|
|
54
54
|
let UnconnectedElement = class UnconnectedElement extends _RuleCommon.RuleCommon {
|
|
55
55
|
check(flow, _options, suppressions) {
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
connectedElements.add(element.name);
|
|
59
|
-
};
|
|
56
|
+
var _flow_graph;
|
|
57
|
+
const connectedElements = ((_flow_graph = flow.graph) === null || _flow_graph === void 0 ? void 0 : _flow_graph.getReachableElements()) || new Set();
|
|
60
58
|
const flowElements = flow.elements.filter((node)=>node instanceof _internals.FlowNode);
|
|
61
|
-
// Use the helper to get the start reference
|
|
62
|
-
const startRef = this.getStartReference(flow);
|
|
63
|
-
if (startRef) {
|
|
64
|
-
new _internals.Compiler().traverseFlow(flow, startRef, logConnected);
|
|
65
|
-
}
|
|
66
59
|
const unconnectedElements = flowElements.filter((element)=>!connectedElements.has(element.name) && !suppressions.has(element.name));
|
|
67
60
|
return unconnectedElements.map((det)=>new _internals.Violation(det));
|
|
68
61
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flow-scanner/lightning-flow-scanner-core",
|
|
3
3
|
"description": "A lightweight engine for Flow metadata in Node.js, and browser environments. Assess and enhance Salesforce Flow automations for best practices, security, governor limits, and performance issues.",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.11.0",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"fast-xml-parser": "^5.3.0"
|
|
24
24
|
},
|
|
25
|
-
"homepage": "https://
|
|
25
|
+
"homepage": "https://flow-scanner.github.io/lightning-flow-scanner/",
|
|
26
26
|
"author": {
|
|
27
27
|
"name": "Ruben Halman",
|
|
28
28
|
"url": "https://github.com/RubenHalman"
|