@flow-scanner/lightning-flow-scanner-core 6.10.6 → 6.11.1

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.
@@ -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
- const connectors = this.getConnectors(subtype, element);
165
- this.connectors = connectors;
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: string;
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 = this.findLoopElements(flow);
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
- var _flow_elements;
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 = (_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;
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 loopEnd = this.findLoopEnd(element);
42
- new _internals.Compiler().traverseFlow(flow, element.name, findStatement, loopEnd);
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 flagDML;
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 flowElements = flow.elements.filter((node)=>node instanceof _internals.FlowNode);
57
- // Use the helper to find the starting element index
58
- const startingNode = this.findStartIndex(flow, flowElements);
59
- if (startingNode === -1) {
60
- return [];
61
- }
62
- const processedElementIndexes = [];
63
- const unconnectedElementIndexes = [];
64
- const DuplicateDMLOperations = [];
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
- if (dmlStatementTypes.includes(element.subtype)) {
119
- return true;
120
- } else if (dmlFlag === true && element.subtype === "screens" && element.element["allowBack"] === "true") {
121
- return false;
122
- } else {
123
- return dmlFlag;
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 or actions between two screens, it's important to prevent users from navigating back between screens. Failure to do so may result in duplicate database operations being performed within the flow.",
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
- const compiler = new _internals.Compiler();
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
- if (flow.startReference) {
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
- const flowelements = flow.elements.filter((el)=>el instanceof _internals.FlowNode);
127
- for (const otherElement of flowelements){
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,23 +53,22 @@ function _interop_require_wildcard(obj, nodeInterop) {
53
53
  }
54
54
  let TransformInsteadOfLoop = class TransformInsteadOfLoop extends _RuleCommon.RuleCommon {
55
55
  check(flow, _options, _suppressions) {
56
- var _flow_elements;
57
56
  const violations = [];
58
- var _flow_elements_filter;
59
- // Get all loop elements
60
- const loops = (_flow_elements_filter = (_flow_elements = flow.elements) === null || _flow_elements === void 0 ? void 0 : _flow_elements.filter((e)=>e.subtype === "loops")) !== null && _flow_elements_filter !== void 0 ? _flow_elements_filter : [];
61
- for (const loop of loops){
62
- var _loopNode_connectors;
63
- const loopNode = loop;
64
- // Check if the loop's nextValueConnector (the iterative path) leads to an assignment
65
- const nextValueConnector = (_loopNode_connectors = loopNode.connectors) === null || _loopNode_connectors === void 0 ? void 0 : _loopNode_connectors.find((connector)=>connector.type === "nextValueConnector");
66
- if (nextValueConnector === null || nextValueConnector === void 0 ? void 0 : nextValueConnector.reference) {
67
- var _flow_elements1;
68
- // Find the element that the nextValueConnector points to
69
- const targetElement = (_flow_elements1 = flow.elements) === null || _flow_elements1 === void 0 ? void 0 : _flow_elements1.find((e)=>e.name === nextValueConnector.reference);
70
- // Check if the target is an assignment
71
- if ((targetElement === null || targetElement === void 0 ? void 0 : targetElement.subtype) === "assignments") {
57
+ const triggerType = this.getStartProperty(flow, 'triggerType');
58
+ const isRecordBeforeSave = triggerType === "RecordBeforeSave";
59
+ if (isRecordBeforeSave) {
60
+ return violations;
61
+ }
62
+ const loops = flow.graph.getLoopNodes();
63
+ for (const loopNode of loops){
64
+ // Get elements that the loop connects to (includes nextValueConnector)
65
+ const nextElements = flow.graph.getNextElements(loopNode.name);
66
+ // Check if any directly connected element is an assignment
67
+ for (const nextElementName of nextElements){
68
+ const nextElement = flow.graph.getNode(nextElementName);
69
+ if ((nextElement === null || nextElement === void 0 ? void 0 : nextElement.subtype) === "assignments") {
72
70
  violations.push(new _internals.Violation(loopNode));
71
+ break; // Only report once per loop
73
72
  }
74
73
  }
75
74
  }
@@ -88,7 +87,7 @@ let TransformInsteadOfLoop = class TransformInsteadOfLoop extends _RuleCommon.Ru
88
87
  }
89
88
  ]
90
89
  }, {
91
- severity: "error"
90
+ severity: "note"
92
91
  });
93
92
  }
94
93
  };
@@ -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
- const connectedElements = new Set();
57
- const logConnected = (element)=>{
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
  }