@flow-scanner/lightning-flow-scanner-core 6.10.5 → 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.
Files changed (38) hide show
  1. package/README.md +19 -16
  2. package/index.d.ts +5 -2
  3. package/index.js +19 -0
  4. package/main/config/NodeIcons.d.ts +24 -0
  5. package/main/config/NodeIcons.js +122 -0
  6. package/main/config/VariableIcons.d.ts +25 -0
  7. package/main/config/VariableIcons.js +53 -0
  8. package/main/libs/Compiler.d.ts +1 -2
  9. package/main/libs/Compiler.js +10 -16
  10. package/main/libs/ExportDiagram.d.ts +41 -0
  11. package/main/libs/ExportDiagram.js +40 -0
  12. package/main/libs/ExportSarif.js +1 -1
  13. package/main/models/Flow.d.ts +42 -11
  14. package/main/models/Flow.js +164 -76
  15. package/main/models/FlowGraph.d.ts +85 -0
  16. package/main/models/FlowGraph.js +532 -0
  17. package/main/models/FlowNode.d.ts +58 -2
  18. package/main/models/FlowNode.js +161 -3
  19. package/main/models/FlowVariable.d.ts +59 -1
  20. package/main/models/FlowVariable.js +118 -1
  21. package/main/models/LoopRuleCommon.js +11 -12
  22. package/main/models/ParsedFlow.d.ts +1 -1
  23. package/main/models/RuleCommon.d.ts +30 -7
  24. package/main/models/RuleCommon.js +49 -11
  25. package/main/rules/APIVersion.js +31 -1
  26. package/main/rules/DuplicateDMLOperation.d.ts +1 -2
  27. package/main/rules/DuplicateDMLOperation.js +35 -73
  28. package/main/rules/MissingFaultPath.d.ts +4 -0
  29. package/main/rules/MissingFaultPath.js +19 -15
  30. package/main/rules/MissingFilterRecordTrigger.js +4 -4
  31. package/main/rules/RecordIdAsString.js +3 -2
  32. package/main/rules/RecursiveAfterUpdate.js +7 -4
  33. package/main/rules/SameRecordFieldUpdates.js +5 -3
  34. package/main/rules/TriggerOrder.d.ts +0 -1
  35. package/main/rules/TriggerOrder.js +8 -19
  36. package/main/rules/UnconnectedElement.d.ts +0 -1
  37. package/main/rules/UnconnectedElement.js +2 -13
  38. package/package.json +2 -2
@@ -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
  }
@@ -3,5 +3,5 @@ export declare class ParsedFlow {
3
3
  uri: string;
4
4
  flow: Flow | undefined;
5
5
  errorMessage?: string;
6
- constructor(uri: string, flow: Flow, errorMessage?: string);
6
+ constructor(uri: string, flow?: Flow, errorMessage?: string);
7
7
  }
@@ -16,17 +16,40 @@ export declare abstract class RuleCommon {
16
16
  constructor(info: RuleInfo, optional?: {
17
17
  severity?: string;
18
18
  });
19
+ execute(flow: core.Flow, options?: object, suppressions?: string[]): core.RuleResult;
20
+ protected abstract check(flow: core.Flow, options: object | undefined, suppressions: Set<string>): core.Violation[];
21
+ protected isSuppressed(name: string, suppressions: Set<string>): boolean;
19
22
  /**
20
- * execute() automatic suppression
23
+ * Get the start node (the special <start> element).
24
+ * This is now stored separately in flow.startNode, not in flow.elements.
25
+ *
26
+ * @param flow - The Flow instance
27
+ * @returns The start FlowNode or undefined if not found
21
28
  */
22
- execute(flow: core.Flow, options?: object, suppressions?: string[]): core.RuleResult;
29
+ protected getStartNode(flow: core.Flow): core.FlowNode | undefined;
23
30
  /**
24
- * Rules implement this. They should return *all* violations,
25
- * NOT pre-filter suppressed ones (unless they need early-exit performance).
31
+ * Get the reference name of the first actual element (what the flow starts at).
32
+ * This is the element that comes AFTER the start node.
33
+ *
34
+ * @param flow - The Flow instance
35
+ * @returns The start reference name or undefined
26
36
  */
27
- protected abstract check(flow: core.Flow, options: object | undefined, suppressions: Set<string>): core.Violation[];
37
+ protected getStartReference(flow: core.Flow): string | undefined;
28
38
  /**
29
- * Legacy/manual suppression helper (still available for early exits)
39
+ * Find the INDEX of the first actual element in a FlowNode array.
40
+ * Useful for rules that need to iterate by index.
41
+ *
42
+ * @param flow - The Flow instance
43
+ * @param flowElements - Array of FlowNodes (typically from flow.elements)
44
+ * @returns The index of the starting element, or -1 if not found
30
45
  */
31
- protected isSuppressed(name: string, suppressions: Set<string>): boolean;
46
+ protected findStartIndex(flow: core.Flow, flowElements: core.FlowNode[]): number;
47
+ /**
48
+ * Safely get a property from the start element.
49
+ *
50
+ * @param flow - The Flow instance
51
+ * @param propertyName - The property to retrieve (e.g., 'triggerType', 'object')
52
+ * @returns The property value or undefined
53
+ */
54
+ protected getStartProperty(flow: core.Flow, propertyName: string): any;
32
55
  }
@@ -64,31 +64,69 @@ function _interop_require_wildcard(obj, nodeInterop) {
64
64
  return newObj;
65
65
  }
66
66
  let RuleCommon = class RuleCommon {
67
- /**
68
- * execute() – automatic suppression
69
- */ execute(flow, options, suppressions = []) {
67
+ execute(flow, options, suppressions = []) {
70
68
  // Wildcard suppression disables entire rule
71
69
  if (suppressions.includes("*")) {
72
70
  return new _internals.RuleResult(this, []);
73
71
  }
74
- // Convert to Set for fast lookup
75
72
  const suppSet = new Set(suppressions);
76
- // Raw violations from rule
77
73
  let violations = this.check(flow, options, suppSet);
78
- // Automatically filter suppressed violations by their .name
79
74
  violations = violations.filter((v)=>!suppSet.has(v.name));
80
- // Wrap into RuleResult
81
75
  return new _internals.RuleResult(this, violations);
82
76
  }
83
- /**
84
- * Legacy/manual suppression helper (still available for early exits)
85
- */ isSuppressed(name, suppressions) {
77
+ isSuppressed(name, suppressions) {
86
78
  return suppressions.has(name);
87
79
  }
80
+ /**
81
+ * Get the start node (the special <start> element).
82
+ * This is now stored separately in flow.startNode, not in flow.elements.
83
+ *
84
+ * @param flow - The Flow instance
85
+ * @returns The start FlowNode or undefined if not found
86
+ */ getStartNode(flow) {
87
+ return flow.startNode;
88
+ }
89
+ /**
90
+ * Get the reference name of the first actual element (what the flow starts at).
91
+ * This is the element that comes AFTER the start node.
92
+ *
93
+ * @param flow - The Flow instance
94
+ * @returns The start reference name or undefined
95
+ */ getStartReference(flow) {
96
+ return flow.startReference || undefined;
97
+ }
98
+ /**
99
+ * Find the INDEX of the first actual element in a FlowNode array.
100
+ * Useful for rules that need to iterate by index.
101
+ *
102
+ * @param flow - The Flow instance
103
+ * @param flowElements - Array of FlowNodes (typically from flow.elements)
104
+ * @returns The index of the starting element, or -1 if not found
105
+ */ findStartIndex(flow, flowElements) {
106
+ const startRef = this.getStartReference(flow);
107
+ if (!startRef) {
108
+ return -1;
109
+ }
110
+ return flowElements.findIndex((n)=>n.name === startRef);
111
+ }
112
+ /**
113
+ * Safely get a property from the start element.
114
+ *
115
+ * @param flow - The Flow instance
116
+ * @param propertyName - The property to retrieve (e.g., 'triggerType', 'object')
117
+ * @returns The property value or undefined
118
+ */ getStartProperty(flow, propertyName) {
119
+ var _flow_startNode;
120
+ if ((_flow_startNode = flow.startNode) === null || _flow_startNode === void 0 ? void 0 : _flow_startNode.element) {
121
+ var _flow_startNode_element;
122
+ return (_flow_startNode_element = flow.startNode.element) === null || _flow_startNode_element === void 0 ? void 0 : _flow_startNode_element[propertyName];
123
+ }
124
+ return undefined;
125
+ }
88
126
  constructor(info, optional){
89
127
  _define_property(this, "description", void 0);
90
128
  _define_property(this, "docRefs", []);
91
- _define_property(this, "isConfigurable", void 0); // Auto-detected by checking if the implemented check() method actually uses "options."
129
+ _define_property(this, "isConfigurable", void 0);
92
130
  _define_property(this, "label", void 0);
93
131
  _define_property(this, "name", void 0);
94
132
  _define_property(this, "severity", void 0);
@@ -65,7 +65,37 @@ let APIVersion = class APIVersion extends _RuleCommon.RuleCommon {
65
65
  }
66
66
  // Custom logic
67
67
  if (options === null || options === void 0 ? void 0 : options.expression) {
68
- const isValid = new Function(`return ${flowAPIVersionNumber}${options.expression};`)();
68
+ // Match something like: >= 58
69
+ const match = options.expression.match(/^\s*(>=|<=|>|<|===|!==)\s*(\d+)\s*$/);
70
+ if (!match) {
71
+ // Invalid expression format
72
+ return [
73
+ new _internals.Violation(new _internals.FlowAttribute("Invalid API rule expression", "apiVersion", options.expression))
74
+ ];
75
+ }
76
+ const [, operator, versionStr] = match;
77
+ const target = parseFloat(versionStr);
78
+ let isValid = true;
79
+ switch(operator){
80
+ case ">":
81
+ isValid = flowAPIVersionNumber > target;
82
+ break;
83
+ case "<":
84
+ isValid = flowAPIVersionNumber < target;
85
+ break;
86
+ case ">=":
87
+ isValid = flowAPIVersionNumber >= target;
88
+ break;
89
+ case "<=":
90
+ isValid = flowAPIVersionNumber <= target;
91
+ break;
92
+ case "===":
93
+ isValid = flowAPIVersionNumber === target;
94
+ break;
95
+ case "!==":
96
+ isValid = flowAPIVersionNumber !== target;
97
+ break;
98
+ }
69
99
  if (!isValid) {
70
100
  return [
71
101
  new _internals.Violation(new _internals.FlowAttribute(`${flowAPIVersionNumber}`, "apiVersion", options.expression))
@@ -4,6 +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 findStart;
8
- private flagDML;
7
+ private isDML;
9
8
  }