Skip to content

Commit 4938f22

Browse files
authored
Configure expressions (#9)
* adds severity and fixes rule configuration * Bring back configure rules(with legacy support)
1 parent a08d451 commit 4938f22

File tree

10 files changed

+282
-136
lines changed

10 files changed

+282
-136
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
Use our side bar or the **Command Palette** and type `Flow Scanner` to see the list of all available commands.
1313

14+
* `Configure Rules` Allows to define rules and expressions as per defined in the [core documentation](https://github.com/Flow-Scanner/lightning-flow-scanner-core).
1415
* `Scan Flows` allows choosing either a directory or a selection of flows to run the analysis against.
15-
*More information on the default rules can be found in the [core documentation](https://github.com/Flow-Scanner/lightning-flow-scanner-core).*
1616
* `Fix Flows` will apply available fixes automatically.
1717
* `Open Documentation` can be used to reference the documentation.
1818

@@ -23,7 +23,6 @@ Use our side bar or the **Command Palette** and type `Flow Scanner` to see the l
2323
| `lightningFlowScanner.SpecifyFiles` | Specify flow file paths instead of a root directory. | `false` |
2424
| `lightningFlowScanner.NamingConvention` | Specify a REGEX expression to use as Flow Naming convention. | `"[A-Za-z0-9]+_[A-Za-z0-9]+"` |
2525
| `lightningFlowScanner.APIVersion` | Specify an expression to validate the API version, i.e. '===50'(use at least 50). | `">50"` |
26-
| `lightningFlowScanner.Reset` | Reset all configurations on every scan | `false` |
2726

2827
## Development Setup
2928

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"icon": "media/lightningflow.png",
1111
"description": "A VS Code Extension for analysis and optimization of Salesforce Flows. Scans metadata for 20+ issues such as hardcoded IDs, unsafe contexts, inefficient SOQL/DML operations, recursion risks, and missing fault handling. Supports auto-fixes, rule configurations, and tests integration.",
12-
"version": "1.7.2",
12+
"version": "1.8.0",
1313
"engines": {
1414
"vscode": "^1.99.1"
1515
},
@@ -37,11 +37,6 @@
3737
"type": "string",
3838
"default": ">50",
3939
"description": "Specify an expression to validate the API version, i.e. '===50'(use at least 50)."
40-
},
41-
"lightningFlowScanner.Reset": {
42-
"type": "boolean",
43-
"default": false,
44-
"description": "Reset all configurations on every scan"
4540
}
4641
}
4742
},
@@ -180,7 +175,7 @@
180175
},
181176
"dependencies": {
182177
"convert-array-to-csv": "^2.0.0",
183-
"lightning-flow-scanner-core": "^5.9.0",
178+
"lightning-flow-scanner-core": "5.9.4",
184179
"tabulator-tables": "^6.3.1",
185180
"uuid": "^11.0.5",
186181
"xml2js": "^0.6.2",
@@ -195,6 +190,7 @@
195190
"flow scanner",
196191
"salesforce flow",
197192
"best practices",
198-
"code quality"
193+
"code quality",
194+
"salesforce automation"
199195
]
200196
}

src/commands/handlers.ts

Lines changed: 127 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CacheProvider } from '../providers/cache-provider';
99
import { testdata } from '../store/testdata';
1010
import { OutputChannel } from '../providers/outputChannel';
1111
import { ConfigProvider } from '../providers/config-provider';
12+
import * as YAML from 'yaml';
1213

1314
const { USE_NEW_CONFIG: isUseNewConfig } = process.env;
1415

@@ -40,83 +41,151 @@ export default class Commands {
4041
vscode.env.openExternal(url);
4142
}
4243

43-
private async configRules() {
44-
if (isUseNewConfig === 'true') {
45-
await this.ruleConfiguration();
44+
private async configRules() {
45+
type RuleWithExpression = { severity: string; expression?: string };
46+
type RuleConfig = Record<string, RuleWithExpression>;
47+
48+
// If new YAML config flow is enabled
49+
if (isUseNewConfig === 'true') {
50+
const workspaceFolders = vscode.workspace.workspaceFolders;
51+
if (!workspaceFolders || workspaceFolders.length === 0) {
52+
vscode.window.showErrorMessage('No workspace folder found.');
4653
return;
4754
}
48-
const allRules: core.IRuleDefinition[] = [
49-
...core.getRules()
50-
];
51-
const ruleConfig = { rules: {} };
5255

53-
let items = allRules.map((rule) => {
54-
return { label: rule.label, value: rule.name, picked: true };
55-
});
56+
const workspacePath = workspaceFolders[0].uri.fsPath;
57+
const configProvider = new ConfigProvider();
58+
59+
try {
60+
const configResult = await configProvider.discover(workspacePath);
61+
62+
// Type assertion for safe access
63+
const configTyped = configResult.config as { rules?: RuleConfig } | undefined;
64+
const rules: RuleConfig = configTyped?.rules ?? {};
5665

57-
const selectedRules = (await vscode.window.showQuickPick(items, {
58-
canPickMany: true,
59-
})) as { label: string; value: string }[];
66+
// Prompt for missing expressions
67+
if (!rules.FlowName?.expression) {
68+
const naming = await vscode.window.showInputBox({
69+
prompt: 'Define a naming convention for Flow Names (REGEX format).',
70+
placeHolder: '[A-Za-z0-9]+_[A-Za-z0-9]+',
71+
value: '[A-Za-z0-9]+_[A-Za-z0-9]+',
72+
});
73+
if (naming) rules.FlowName = { severity: 'error', expression: naming };
74+
}
6075

61-
for (const rule of allRules) {
62-
if (selectedRules.map((r) => r.value).includes(rule.name)) {
63-
ruleConfig.rules[rule.name] = { severity: 'error' };
76+
if (!rules.APIVersion?.expression) {
77+
const apiVersion = await vscode.window.showInputBox({
78+
prompt: 'Set an API Version rule (e.g. ">50" or ">=60").',
79+
placeHolder: '>50',
80+
value: '>50',
81+
});
82+
if (apiVersion) rules.APIVersion = { severity: 'error', expression: apiVersion };
6483
}
84+
85+
// Persist updated YAML config using yaml.stringify
86+
const yamlString = YAML.stringify(configResult.config);
87+
await vscode.workspace.fs.writeFile(
88+
vscode.Uri.file(configResult.fspath),
89+
new TextEncoder().encode(yamlString)
90+
);
91+
92+
// Store rules in cache
93+
await CacheProvider.instance.set('ruleconfig', rules);
94+
OutputChannel.getInstance().logChannel.debug('Stored YAML rule config', rules);
95+
96+
// Open config file in editor
97+
const document = await vscode.workspace.openTextDocument(configResult.fspath);
98+
await vscode.window.showTextDocument(document);
99+
vscode.window.showInformationMessage(`Loaded configuration from ${configResult.fspath}`);
100+
return;
101+
} catch (err: any) {
102+
vscode.window.showErrorMessage(`Error loading configuration: ${err?.message || err}`);
103+
return;
104+
}
105+
}
106+
107+
// ----- Legacy QuickPick flow -----
108+
const allRules: core.IRuleDefinition[] = [...core.getRules()];
109+
const ruleConfig: RuleConfig = {};
110+
111+
const items = allRules.map((rule) => ({
112+
label: rule.label,
113+
value: rule.name,
114+
picked: true,
115+
}));
116+
117+
const selectedRules = (await vscode.window.showQuickPick(items, {
118+
canPickMany: true,
119+
})) as { label: string; value: string }[];
120+
121+
if (!selectedRules) {
122+
vscode.window.showInformationMessage('No rules selected.');
123+
return;
124+
}
125+
126+
for (const rule of allRules) {
127+
if (selectedRules.map((r) => r.value).includes(rule.name)) {
128+
ruleConfig[rule.name] = { severity: 'error' };
65129
}
66-
if (selectedRules.map((r) => r.value).includes('FlowName')) {
67-
const namingConventionString = await vscode.window.showInputBox({
68-
prompt:
69-
'Readability of a flow is very important. Setting a naming convention for the Flow Name will improve the findability/searchability and overall consistency. You can define your default naming convention using REGEX.',
70-
placeHolder: '[A-Za-z0-9]+_[A-Za-z0-9]+',
71-
value: '[A-Za-z0-9]+_[A-Za-z0-9]+',
72-
});
73-
ruleConfig.rules['FlowName'] = {
74-
severity: 'error',
75-
expression: namingConventionString,
76-
};
130+
}
131+
132+
// Prompt for FlowName expression if selected
133+
if (selectedRules.some((r) => r.value === 'FlowName')) {
134+
const naming = await vscode.window.showInputBox({
135+
prompt: 'Define a naming convention for Flow Names (REGEX format).',
136+
placeHolder: '[A-Za-z0-9]+_[A-Za-z0-9]+',
137+
value: '[A-Za-z0-9]+_[A-Za-z0-9]+',
138+
});
139+
if (naming) {
140+
ruleConfig['FlowName'] = { severity: 'error', expression: naming };
77141
await vscode.workspace
78142
.getConfiguration()
79-
.update(
80-
'lightningFlowScanner.NamingConvention',
81-
namingConventionString,
82-
true
83-
);
143+
.update('lightningFlowScanner.NamingConvention', naming, true);
84144
}
85-
if (selectedRules.map((r) => r.value).includes('APIVersion')) {
86-
const apiVersionEvalExpressionString = await vscode.window.showInputBox({
87-
prompt:
88-
' The Api Version has been available as an attribute on the Flow since API v50.0 and it is recommended to limit variation and to update them on a regular basis. Set an expression to set a valid range of API versions(Minimum 50).',
89-
placeHolder: '>50',
90-
value: '>50',
91-
});
92-
ruleConfig.rules['APIVersion'] = {
93-
severity: 'error',
94-
expression: apiVersionEvalExpressionString,
95-
};
145+
}
146+
147+
// Prompt for APIVersion expression if selected
148+
if (selectedRules.some((r) => r.value === 'APIVersion')) {
149+
const apiVersion = await vscode.window.showInputBox({
150+
prompt: 'Set an API Version rule (e.g. ">50" or ">=60").',
151+
placeHolder: '>50',
152+
value: '>50',
153+
});
154+
if (apiVersion) {
155+
ruleConfig['APIVersion'] = { severity: 'error', expression: apiVersion };
96156
await vscode.workspace
97157
.getConfiguration()
98-
.update(
99-
'lightningFlowScanner.APIVersion',
100-
apiVersionEvalExpressionString,
101-
true
102-
);
158+
.update('lightningFlowScanner.APIVersion', apiVersion, true);
103159
}
104-
await CacheProvider.instance.set('ruleconfig', ruleConfig);
105-
OutputChannel.getInstance().logChannel.debug(
106-
'Stored rule configurations',
107-
ruleConfig
108-
);
109160
}
110161

162+
// Store legacy rules in cache
163+
await CacheProvider.instance.set('ruleconfig', ruleConfig);
164+
OutputChannel.getInstance().logChannel.debug('Stored legacy rule config', ruleConfig);
165+
}
166+
167+
111168
private async ruleConfiguration() {
169+
const workspaceFolders = vscode.workspace.workspaceFolders;
170+
if (!workspaceFolders || workspaceFolders.length === 0) {
171+
vscode.window.showErrorMessage('No workspace folder found.');
172+
return;
173+
}
174+
175+
const workspacePath = workspaceFolders[0].uri.fsPath;
112176
const configProvider = new ConfigProvider();
113-
const config = await configProvider.discover(
114-
vscode.workspace.workspaceFolders?.[0].uri.path
115-
);
116-
const document = await vscode.workspace.openTextDocument(config.fspath);
117-
await vscode.window.showTextDocument(document);
177+
178+
try {
179+
const config = await configProvider.discover(workspacePath);
180+
const document = await vscode.workspace.openTextDocument(config.fspath);
181+
await vscode.window.showTextDocument(document);
182+
vscode.window.showInformationMessage(`Loaded configuration from ${config.fspath}`);
183+
} catch (err: any) {
184+
vscode.window.showErrorMessage(`Error loading configuration: ${err?.message || err}`);
185+
}
118186
}
119187

188+
120189
private async debugView() {
121190
let results = testdata as unknown as core.ScanResult[];
122191
await CacheProvider.instance.set('results', results);

0 commit comments

Comments
 (0)