Skip to content

Commit fd5900c

Browse files
NEW(config): @W-18541853@: Stop displaying unmodified rules and add in --include-unmodified-rules flag (#1812)
1 parent e37d56b commit fd5900c

File tree

11 files changed

+141
-51
lines changed

11 files changed

+141
-51
lines changed

messages/config-command.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# command.summary
22

3-
Display the current state of configuration for Code Analyzer.
3+
Output the current state of configuration for Code Analyzer.
44

55
# command.description
66

@@ -28,6 +28,10 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi
2828

2929
<%= config.bin %> <%= command.id %> --rule-selector Recommended
3030

31+
- By default, only rule override values that you have specified in your `code-analyzer.yml` file that are not default values are displayed. To display the default rule values, in addition to the modified values, for the recommended rules:
32+
33+
<%= config.bin %> <%= command.id %> --rule-selector Recommended --include-unmodified-rules
34+
3135
- Display the configuration state associated with all the rules that are applicable to the files targeted within the folder `./src`:
3236

3337
<%= config.bin %> <%= command.id %> --target ./src
@@ -101,3 +105,11 @@ Output file to write the configuration state to. The file is written in YAML for
101105
If you specify a file within folder, such as `--output-file ./config/code-analyzer.yml`, the folder must already exist, or you get an error. If the file already exists, a prompt asks if you want to overwrite it.
102106

103107
If you don't specify this flag, the command outputs the configuration state to the terminal.
108+
109+
# flags.include-unmodified-rules.summary
110+
111+
Includes unmodified rules in the rule override settings.
112+
113+
# flags.include-unmodified-rules.description
114+
115+
The default behavior of the config command is to not include the unmodified rules with their default values in the rule override settings (for the rules selected via the `–-rule-selector` flag). This helps prevent your configuration file from being unnecessarily large. If you wish to instead include the unmodified rules, in addition to the modified rules, then specify this flag.

messages/config-model.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ Modified from: %s
77
# template.rule-overrides-section
88
%s ENGINE RULE OVERRIDES
99

10-
# template.yaml.no-engines-selected
10+
# template.yaml.no-rules-selected
1111
Empty object used because rule selection returned no rules
1212

13-
# template.yaml.no-rules-selected
13+
# template.yaml.no-rule-overrides
1414
Remove this empty object {} when you are ready to specify your first rule override
1515

1616
# template.common.end-of-config

src/commands/code-analyzer/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ export default class ConfigCommand extends SfCommand<void> implements Displayabl
5050
summary: getMessage(BundleName.ConfigCommand, 'flags.output-file.summary'),
5151
description: getMessage(BundleName.ConfigCommand, 'flags.output-file.description'),
5252
char: 'f'
53+
}),
54+
'include-unmodified-rules': Flags.boolean({
55+
summary: getMessage(BundleName.ConfigCommand, 'flags.include-unmodified-rules.summary'),
56+
description: getMessage(BundleName.ConfigCommand, 'flags.include-unmodified-rules.description')
5357
})
5458
};
5559

src/lib/actions/ConfigAction.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type ConfigInput = {
2727
'rule-selector': string[];
2828
workspace?: string[];
2929
target?: string[];
30+
'include-unmodified-rules'?: boolean
3031
};
3132

3233
export class ConfigAction {
@@ -137,7 +138,8 @@ export class ConfigAction {
137138
...userRules.getEngineNames(),
138139
...selectedDefaultRules.getEngineNames()]);
139140

140-
const configModel: ConfigModel = new AnnotatedConfigModel(userCore, userRules, allDefaultRules, relevantEngines);
141+
const includeUnmodifiedRules: boolean = input["include-unmodified-rules"] ?? false;
142+
const configModel: ConfigModel = new AnnotatedConfigModel(userCore, userRules, allDefaultRules, relevantEngines, includeUnmodifiedRules);
141143

142144
const fileWritten: boolean = this.dependencies.writer
143145
? await this.dependencies.writer.write(configModel)

src/lib/models/ConfigModel.ts

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,22 @@ export class AnnotatedConfigModel implements ConfigModel {
3333
// configs not associated with the user's rule selection, thus we can't use the engines from allDefaultRules.
3434
private readonly relevantEngines: Set<string>;
3535

36-
constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>) {
36+
private readonly includeUnmodifiedRules: boolean;
37+
38+
constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>, includeUnmodifiedRules: boolean) {
3739
this.codeAnalyzer = codeAnalyzer;
3840
this.userRules = userRules;
3941
this.allDefaultRules = allDefaultRules;
4042
this.relevantEngines = relevantEngines;
43+
this.includeUnmodifiedRules = includeUnmodifiedRules;
4144
}
4245

4346
toFormattedOutput(format: OutputFormat): string {
4447
// istanbul ignore else: Should be impossible
4548
if (format === OutputFormat.STYLED_YAML) {
46-
return new StyledYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines).toYaml();
49+
return new StyledYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines, this.includeUnmodifiedRules).toYaml();
4750
} else if (format === OutputFormat.RAW_YAML) {
48-
return new PlainYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines).toYaml();
51+
return new PlainYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines, this.includeUnmodifiedRules).toYaml();
4952
} else {
5053
throw new Error(`Unsupported`)
5154
}
@@ -58,13 +61,15 @@ abstract class YamlFormatter {
5861
private readonly userRules: RuleSelection;
5962
private readonly allDefaultRules: RuleSelection;
6063
private readonly relevantEngines: Set<string>;
64+
private readonly includeUnmodifiedRules: boolean;
6165

62-
protected constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>) {
66+
protected constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>, includeUnmodifiedRules: boolean) {
6367
this.config = codeAnalyzer.getConfig();
6468
this.codeAnalyzer = codeAnalyzer;
6569
this.userRules = userRules;
6670
this.allDefaultRules = allDefaultRules;
6771
this.relevantEngines = relevantEngines;
72+
this.includeUnmodifiedRules = includeUnmodifiedRules;
6873
}
6974

7075
protected abstract toYamlComment(commentText: string): string
@@ -85,16 +90,13 @@ abstract class YamlFormatter {
8590
}
8691

8792
private toYamlFieldWithFieldDescription(fieldName: string, resolvedValue: unknown, fieldDescription: ConfigFieldDescription): string {
88-
const resolvedValueJson: string = JSON.stringify(resolvedValue);
89-
const defaultValueJson: string = JSON.stringify(fieldDescription.defaultValue);
90-
9193
let yamlField: string;
92-
if (!fieldDescription.wasSuppliedByUser && resolvedValueJson !== defaultValueJson) {
94+
if (!fieldDescription.wasSuppliedByUser && !isSame(resolvedValue, fieldDescription.defaultValue)) {
9395
// Whenever the user did not supply the value themselves but the resolved value is different from the
9496
// default value, this means the value was not a "fixed" value but a value "calculated" at runtime.
9597
// Since "calculated" values often depend on the specific environment, we do not want to actually hard code
9698
// this value into the config since checking in the config to CI/CD system may create a different value.
97-
const commentText: string = getMessage(BundleName.ConfigModel, 'template.last-calculated-as', [resolvedValueJson]);
99+
const commentText: string = getMessage(BundleName.ConfigModel, 'template.last-calculated-as', [JSON.stringify(resolvedValue)]);
98100
yamlField = this.toYamlUncheckedFieldWithInlineComment(fieldName, fieldDescription.defaultValue, commentText);
99101
} else {
100102
yamlField = this.toYamlField(fieldName, resolvedValue, fieldDescription.defaultValue);
@@ -104,14 +106,10 @@ abstract class YamlFormatter {
104106
}
105107

106108
private toYamlField(fieldName: string, resolvedValue: unknown, defaultValue: unknown): string {
107-
const resolvedValueJson: string = JSON.stringify(resolvedValue);
108-
const defaultValueJson: string = JSON.stringify(defaultValue);
109-
110-
if (resolvedValueJson === defaultValueJson) {
109+
if (isSame(resolvedValue, defaultValue)) {
111110
return this.toYamlUncheckedField(fieldName, resolvedValue);
112111
}
113-
114-
const commentText: string = getMessage(BundleName.ConfigModel, 'template.modified-from', [defaultValueJson]);
112+
const commentText: string = getMessage(BundleName.ConfigModel, 'template.modified-from', [JSON.stringify(defaultValue)]);
115113
resolvedValue = replaceAbsolutePathsWithRelativePathsWherePossible(resolvedValue, this.config.getConfigRoot() + path.sep);
116114
return this.toYamlUncheckedFieldWithInlineComment(fieldName, resolvedValue, commentText);
117115
}
@@ -155,13 +153,23 @@ abstract class YamlFormatter {
155153
private toYamlRuleOverridesForEngine(engineName: string): string {
156154
const engineConfigHeader: string = getMessage(BundleName.ConfigModel, 'template.rule-overrides-section',
157155
[engineName.toUpperCase()]);
158-
let yamlCode: string = this.toYamlSectionHeadingComment(engineConfigHeader) + '\n';
159-
yamlCode += `${engineName}:\n`;
156+
const ruleOverrideYamlStrings: string[] = [];
160157
for (const userRule of this.userRules.getRulesFor(engineName)) {
161158
const defaultRule: Rule|null = this.getDefaultRuleFor(engineName, userRule.getName());
162-
yamlCode += indent(this.toYamlRuleOverridesForRule(userRule, defaultRule), 2) + '\n';
159+
const ruleOverrideYaml: string = this.toYamlRuleOverridesForRule(userRule, defaultRule);
160+
if (ruleOverrideYaml) {
161+
ruleOverrideYamlStrings.push(indent(ruleOverrideYaml, 2));
162+
}
163163
}
164-
return yamlCode.trimEnd();
164+
165+
let yamlCode: string = this.toYamlSectionHeadingComment(engineConfigHeader) + '\n';
166+
if (ruleOverrideYamlStrings.length > 0) {
167+
yamlCode += `${engineName}:\n` + ruleOverrideYamlStrings.join('\n');
168+
} else {
169+
const commentText: string = getMessage(BundleName.ConfigModel, 'template.yaml.no-rule-overrides');
170+
yamlCode += `${engineName}: {} ${this.toYamlComment(commentText)}`;
171+
}
172+
return yamlCode;
165173
}
166174

167175
private getDefaultRuleFor(engineName: string, ruleName: string): Rule|null {
@@ -175,15 +183,23 @@ abstract class YamlFormatter {
175183

176184
private toYamlRuleOverridesForRule(userRule: Rule, defaultRule: Rule|null): string {
177185
const userSeverity: SeverityLevel = userRule.getSeverityLevel();
186+
const defaultSeverity: SeverityLevel = defaultRule !== null ? defaultRule.getSeverityLevel() : userSeverity;
178187
const userTags: string[] = userRule.getTags();
179-
return `"${userRule.getName()}":\n` +
180-
indent(this.toYamlField('severity', userSeverity, defaultRule !== null ? defaultRule.getSeverityLevel() : userSeverity), 2) + '\n' +
181-
indent(this.toYamlField('tags', userTags, defaultRule !== null ? defaultRule.getTags() : userTags), 2);
188+
const defaultTags: string[] = defaultRule !== null ? defaultRule.getTags() : userTags;
189+
190+
let yamlCode: string = '';
191+
if (this.includeUnmodifiedRules || !isSame(userSeverity, defaultSeverity)) {
192+
yamlCode += indent(this.toYamlField('severity', userSeverity, defaultSeverity), 2) + '\n';
193+
}
194+
if (this.includeUnmodifiedRules || !isSame(userTags, defaultTags)) {
195+
yamlCode += indent(this.toYamlField('tags', userTags, defaultTags), 2);
196+
}
197+
return yamlCode.length === 0 ? '' : `"${userRule.getName()}":\n${yamlCode.trimEnd()}`;
182198
}
183199

184200
private toYamlEngineOverrides(): string {
185201
if (this.relevantEngines.size === 0) {
186-
const commentText: string = getMessage(BundleName.ConfigModel, 'template.yaml.no-engines-selected');
202+
const commentText: string = getMessage(BundleName.ConfigModel, 'template.yaml.no-rules-selected');
187203
return `engines: {} ${this.toYamlComment(commentText)}`;
188204
}
189205

@@ -221,8 +237,8 @@ abstract class YamlFormatter {
221237
}
222238

223239
class PlainYamlFormatter extends YamlFormatter {
224-
constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>) {
225-
super(codeAnalyzer, userRules, allDefaultRules, relevantEngines);
240+
constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>, includeUnmodifiedRules: boolean) {
241+
super(codeAnalyzer, userRules, allDefaultRules, relevantEngines, includeUnmodifiedRules);
226242
}
227243

228244
protected toYamlComment(commentText: string): string {
@@ -231,8 +247,8 @@ class PlainYamlFormatter extends YamlFormatter {
231247
}
232248

233249
class StyledYamlFormatter extends YamlFormatter {
234-
constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>) {
235-
super(codeAnalyzer, userRules, allDefaultRules, relevantEngines);
250+
constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set<string>, includeUnmodifiedRules: boolean) {
251+
super(codeAnalyzer, userRules, allDefaultRules, relevantEngines, includeUnmodifiedRules);
236252
}
237253

238254
protected toYamlComment(commentText: string): string {
@@ -262,3 +278,7 @@ function replaceAbsolutePathsWithRelativePathsWherePossible(value: unknown, pare
262278
// Return the value unchanged if it's a number, boolean, or null
263279
return value;
264280
}
281+
282+
function isSame(resolvedValue: unknown, defaultValue: unknown): boolean {
283+
return JSON.stringify(resolvedValue) === JSON.stringify(defaultValue);
284+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
rules:
2+
3+
# ======================================================================
4+
# STUBENGINE1 ENGINE RULE OVERRIDES
5+
# ======================================================================
6+
StubEngine1: {} # Remove this empty object {} when you are ready to specify your first rule override
7+
8+
# ======================================================================
9+
# STUBENGINE3 ENGINE RULE OVERRIDES
10+
# ======================================================================
11+
StubEngine3: {} # Remove this empty object {} when you are ready to specify your first rule override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ======================================================================
2+
# STUBENGINE1 ENGINE RULE OVERRIDES
3+
# ======================================================================
4+
StubEngine1:
5+
"Stub1Rule1":
6+
tags: # Modified from: ["Recommended","CodeStyle"]
7+
- Recommended
8+
- CodeStyle
9+
- Beep
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"Stub1Rule2":
2+
severity: 2 # Modified from: 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"Stub1Rule3":
2+
severity: 3 # Modified from: 4
3+
tags: # Modified from: ["BestPractices"]
4+
- CodeStyle
5+
- BestPractices
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
"Stub1Rule3":
2-
severity: 3 # Modified from: 4
3-
tags: # Modified from: ["BestPractices"]
4-
- CodeStyle
5-
- BestPractices
1+
"Stub1Rule4":
2+
severity: 2
3+
tags:
4+
- CodeStyle

0 commit comments

Comments
 (0)