From 52916972427b9212adac89d6e29b9eb897c332a5 Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Mon, 19 May 2025 16:49:26 -0400 Subject: [PATCH] NEW(config): @W-18541853@: Stop displaying unmodified rules and add in --include-unmodified-rules flag --- messages/config-command.md | 14 +++- messages/config-model.md | 4 +- src/commands/code-analyzer/config.ts | 4 + src/lib/actions/ConfigAction.ts | 4 +- src/lib/models/ConfigModel.ts | 74 ++++++++++++------- ...lesSectionWithNoModifications.yml.goldfile | 11 +++ .../Stub1Rule1.yml.goldfile | 9 +++ .../Stub1Rule2.yml.goldfile | 2 + .../Stub1Rule3.yml.goldfile | 5 ++ .../Stub1Rule4.yml.goldfile | 9 +-- test/lib/actions/ConfigAction.test.ts | 56 ++++++++++---- 11 files changed, 141 insertions(+), 51 deletions(-) create mode 100644 test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/default-configurations/rulesSectionWithNoModifications.yml.goldfile create mode 100644 test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule1.yml.goldfile create mode 100644 test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule2.yml.goldfile create mode 100644 test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule3.yml.goldfile diff --git a/messages/config-command.md b/messages/config-command.md index b49e35adf..eb906b518 100644 --- a/messages/config-command.md +++ b/messages/config-command.md @@ -1,6 +1,6 @@ # command.summary -Display the current state of configuration for Code Analyzer. +Output the current state of configuration for Code Analyzer. # command.description @@ -28,6 +28,10 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi <%= config.bin %> <%= command.id %> --rule-selector Recommended +- 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: + + <%= config.bin %> <%= command.id %> --rule-selector Recommended --include-unmodified-rules + - Display the configuration state associated with all the rules that are applicable to the files targeted within the folder `./src`: <%= 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 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. If you don't specify this flag, the command outputs the configuration state to the terminal. + +# flags.include-unmodified-rules.summary + +Includes unmodified rules in the rule override settings. + +# flags.include-unmodified-rules.description + +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. diff --git a/messages/config-model.md b/messages/config-model.md index 414a20c34..f80dfff12 100644 --- a/messages/config-model.md +++ b/messages/config-model.md @@ -7,10 +7,10 @@ Modified from: %s # template.rule-overrides-section %s ENGINE RULE OVERRIDES -# template.yaml.no-engines-selected +# template.yaml.no-rules-selected Empty object used because rule selection returned no rules -# template.yaml.no-rules-selected +# template.yaml.no-rule-overrides Remove this empty object {} when you are ready to specify your first rule override # template.common.end-of-config diff --git a/src/commands/code-analyzer/config.ts b/src/commands/code-analyzer/config.ts index b193ac045..dad994b00 100644 --- a/src/commands/code-analyzer/config.ts +++ b/src/commands/code-analyzer/config.ts @@ -50,6 +50,10 @@ export default class ConfigCommand extends SfCommand implements Displayabl summary: getMessage(BundleName.ConfigCommand, 'flags.output-file.summary'), description: getMessage(BundleName.ConfigCommand, 'flags.output-file.description'), char: 'f' + }), + 'include-unmodified-rules': Flags.boolean({ + summary: getMessage(BundleName.ConfigCommand, 'flags.include-unmodified-rules.summary'), + description: getMessage(BundleName.ConfigCommand, 'flags.include-unmodified-rules.description') }) }; diff --git a/src/lib/actions/ConfigAction.ts b/src/lib/actions/ConfigAction.ts index be04be858..f63669270 100644 --- a/src/lib/actions/ConfigAction.ts +++ b/src/lib/actions/ConfigAction.ts @@ -27,6 +27,7 @@ export type ConfigInput = { 'rule-selector': string[]; workspace?: string[]; target?: string[]; + 'include-unmodified-rules'?: boolean }; export class ConfigAction { @@ -137,7 +138,8 @@ export class ConfigAction { ...userRules.getEngineNames(), ...selectedDefaultRules.getEngineNames()]); - const configModel: ConfigModel = new AnnotatedConfigModel(userCore, userRules, allDefaultRules, relevantEngines); + const includeUnmodifiedRules: boolean = input["include-unmodified-rules"] ?? false; + const configModel: ConfigModel = new AnnotatedConfigModel(userCore, userRules, allDefaultRules, relevantEngines, includeUnmodifiedRules); const fileWritten: boolean = this.dependencies.writer ? await this.dependencies.writer.write(configModel) diff --git a/src/lib/models/ConfigModel.ts b/src/lib/models/ConfigModel.ts index fb1b57202..c7792bd1e 100644 --- a/src/lib/models/ConfigModel.ts +++ b/src/lib/models/ConfigModel.ts @@ -33,19 +33,22 @@ export class AnnotatedConfigModel implements ConfigModel { // configs not associated with the user's rule selection, thus we can't use the engines from allDefaultRules. private readonly relevantEngines: Set; - constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set) { + private readonly includeUnmodifiedRules: boolean; + + constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set, includeUnmodifiedRules: boolean) { this.codeAnalyzer = codeAnalyzer; this.userRules = userRules; this.allDefaultRules = allDefaultRules; this.relevantEngines = relevantEngines; + this.includeUnmodifiedRules = includeUnmodifiedRules; } toFormattedOutput(format: OutputFormat): string { // istanbul ignore else: Should be impossible if (format === OutputFormat.STYLED_YAML) { - return new StyledYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines).toYaml(); + return new StyledYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines, this.includeUnmodifiedRules).toYaml(); } else if (format === OutputFormat.RAW_YAML) { - return new PlainYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines).toYaml(); + return new PlainYamlFormatter(this.codeAnalyzer, this.userRules, this.allDefaultRules, this.relevantEngines, this.includeUnmodifiedRules).toYaml(); } else { throw new Error(`Unsupported`) } @@ -58,13 +61,15 @@ abstract class YamlFormatter { private readonly userRules: RuleSelection; private readonly allDefaultRules: RuleSelection; private readonly relevantEngines: Set; + private readonly includeUnmodifiedRules: boolean; - protected constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set) { + protected constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set, includeUnmodifiedRules: boolean) { this.config = codeAnalyzer.getConfig(); this.codeAnalyzer = codeAnalyzer; this.userRules = userRules; this.allDefaultRules = allDefaultRules; this.relevantEngines = relevantEngines; + this.includeUnmodifiedRules = includeUnmodifiedRules; } protected abstract toYamlComment(commentText: string): string @@ -85,16 +90,13 @@ abstract class YamlFormatter { } private toYamlFieldWithFieldDescription(fieldName: string, resolvedValue: unknown, fieldDescription: ConfigFieldDescription): string { - const resolvedValueJson: string = JSON.stringify(resolvedValue); - const defaultValueJson: string = JSON.stringify(fieldDescription.defaultValue); - let yamlField: string; - if (!fieldDescription.wasSuppliedByUser && resolvedValueJson !== defaultValueJson) { + if (!fieldDescription.wasSuppliedByUser && !isSame(resolvedValue, fieldDescription.defaultValue)) { // Whenever the user did not supply the value themselves but the resolved value is different from the // default value, this means the value was not a "fixed" value but a value "calculated" at runtime. // Since "calculated" values often depend on the specific environment, we do not want to actually hard code // this value into the config since checking in the config to CI/CD system may create a different value. - const commentText: string = getMessage(BundleName.ConfigModel, 'template.last-calculated-as', [resolvedValueJson]); + const commentText: string = getMessage(BundleName.ConfigModel, 'template.last-calculated-as', [JSON.stringify(resolvedValue)]); yamlField = this.toYamlUncheckedFieldWithInlineComment(fieldName, fieldDescription.defaultValue, commentText); } else { yamlField = this.toYamlField(fieldName, resolvedValue, fieldDescription.defaultValue); @@ -104,14 +106,10 @@ abstract class YamlFormatter { } private toYamlField(fieldName: string, resolvedValue: unknown, defaultValue: unknown): string { - const resolvedValueJson: string = JSON.stringify(resolvedValue); - const defaultValueJson: string = JSON.stringify(defaultValue); - - if (resolvedValueJson === defaultValueJson) { + if (isSame(resolvedValue, defaultValue)) { return this.toYamlUncheckedField(fieldName, resolvedValue); } - - const commentText: string = getMessage(BundleName.ConfigModel, 'template.modified-from', [defaultValueJson]); + const commentText: string = getMessage(BundleName.ConfigModel, 'template.modified-from', [JSON.stringify(defaultValue)]); resolvedValue = replaceAbsolutePathsWithRelativePathsWherePossible(resolvedValue, this.config.getConfigRoot() + path.sep); return this.toYamlUncheckedFieldWithInlineComment(fieldName, resolvedValue, commentText); } @@ -155,13 +153,23 @@ abstract class YamlFormatter { private toYamlRuleOverridesForEngine(engineName: string): string { const engineConfigHeader: string = getMessage(BundleName.ConfigModel, 'template.rule-overrides-section', [engineName.toUpperCase()]); - let yamlCode: string = this.toYamlSectionHeadingComment(engineConfigHeader) + '\n'; - yamlCode += `${engineName}:\n`; + const ruleOverrideYamlStrings: string[] = []; for (const userRule of this.userRules.getRulesFor(engineName)) { const defaultRule: Rule|null = this.getDefaultRuleFor(engineName, userRule.getName()); - yamlCode += indent(this.toYamlRuleOverridesForRule(userRule, defaultRule), 2) + '\n'; + const ruleOverrideYaml: string = this.toYamlRuleOverridesForRule(userRule, defaultRule); + if (ruleOverrideYaml) { + ruleOverrideYamlStrings.push(indent(ruleOverrideYaml, 2)); + } } - return yamlCode.trimEnd(); + + let yamlCode: string = this.toYamlSectionHeadingComment(engineConfigHeader) + '\n'; + if (ruleOverrideYamlStrings.length > 0) { + yamlCode += `${engineName}:\n` + ruleOverrideYamlStrings.join('\n'); + } else { + const commentText: string = getMessage(BundleName.ConfigModel, 'template.yaml.no-rule-overrides'); + yamlCode += `${engineName}: {} ${this.toYamlComment(commentText)}`; + } + return yamlCode; } private getDefaultRuleFor(engineName: string, ruleName: string): Rule|null { @@ -175,15 +183,23 @@ abstract class YamlFormatter { private toYamlRuleOverridesForRule(userRule: Rule, defaultRule: Rule|null): string { const userSeverity: SeverityLevel = userRule.getSeverityLevel(); + const defaultSeverity: SeverityLevel = defaultRule !== null ? defaultRule.getSeverityLevel() : userSeverity; const userTags: string[] = userRule.getTags(); - return `"${userRule.getName()}":\n` + - indent(this.toYamlField('severity', userSeverity, defaultRule !== null ? defaultRule.getSeverityLevel() : userSeverity), 2) + '\n' + - indent(this.toYamlField('tags', userTags, defaultRule !== null ? defaultRule.getTags() : userTags), 2); + const defaultTags: string[] = defaultRule !== null ? defaultRule.getTags() : userTags; + + let yamlCode: string = ''; + if (this.includeUnmodifiedRules || !isSame(userSeverity, defaultSeverity)) { + yamlCode += indent(this.toYamlField('severity', userSeverity, defaultSeverity), 2) + '\n'; + } + if (this.includeUnmodifiedRules || !isSame(userTags, defaultTags)) { + yamlCode += indent(this.toYamlField('tags', userTags, defaultTags), 2); + } + return yamlCode.length === 0 ? '' : `"${userRule.getName()}":\n${yamlCode.trimEnd()}`; } private toYamlEngineOverrides(): string { if (this.relevantEngines.size === 0) { - const commentText: string = getMessage(BundleName.ConfigModel, 'template.yaml.no-engines-selected'); + const commentText: string = getMessage(BundleName.ConfigModel, 'template.yaml.no-rules-selected'); return `engines: {} ${this.toYamlComment(commentText)}`; } @@ -221,8 +237,8 @@ abstract class YamlFormatter { } class PlainYamlFormatter extends YamlFormatter { - constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set) { - super(codeAnalyzer, userRules, allDefaultRules, relevantEngines); + constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set, includeUnmodifiedRules: boolean) { + super(codeAnalyzer, userRules, allDefaultRules, relevantEngines, includeUnmodifiedRules); } protected toYamlComment(commentText: string): string { @@ -231,8 +247,8 @@ class PlainYamlFormatter extends YamlFormatter { } class StyledYamlFormatter extends YamlFormatter { - constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set) { - super(codeAnalyzer, userRules, allDefaultRules, relevantEngines); + constructor(codeAnalyzer: CodeAnalyzer, userRules: RuleSelection, allDefaultRules: RuleSelection, relevantEngines: Set, includeUnmodifiedRules: boolean) { + super(codeAnalyzer, userRules, allDefaultRules, relevantEngines, includeUnmodifiedRules); } protected toYamlComment(commentText: string): string { @@ -262,3 +278,7 @@ function replaceAbsolutePathsWithRelativePathsWherePossible(value: unknown, pare // Return the value unchanged if it's a number, boolean, or null return value; } + +function isSame(resolvedValue: unknown, defaultValue: unknown): boolean { + return JSON.stringify(resolvedValue) === JSON.stringify(defaultValue); +} diff --git a/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/default-configurations/rulesSectionWithNoModifications.yml.goldfile b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/default-configurations/rulesSectionWithNoModifications.yml.goldfile new file mode 100644 index 000000000..12ecba936 --- /dev/null +++ b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/default-configurations/rulesSectionWithNoModifications.yml.goldfile @@ -0,0 +1,11 @@ +rules: + + # ====================================================================== + # STUBENGINE1 ENGINE RULE OVERRIDES + # ====================================================================== + StubEngine1: {} # Remove this empty object {} when you are ready to specify your first rule override + + # ====================================================================== + # STUBENGINE3 ENGINE RULE OVERRIDES + # ====================================================================== + StubEngine3: {} # Remove this empty object {} when you are ready to specify your first rule override \ No newline at end of file diff --git a/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule1.yml.goldfile b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule1.yml.goldfile new file mode 100644 index 000000000..e35677bb6 --- /dev/null +++ b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule1.yml.goldfile @@ -0,0 +1,9 @@ + # ====================================================================== + # STUBENGINE1 ENGINE RULE OVERRIDES + # ====================================================================== + StubEngine1: + "Stub1Rule1": + tags: # Modified from: ["Recommended","CodeStyle"] + - Recommended + - CodeStyle + - Beep diff --git a/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule2.yml.goldfile b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule2.yml.goldfile new file mode 100644 index 000000000..45b68b484 --- /dev/null +++ b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule2.yml.goldfile @@ -0,0 +1,2 @@ + "Stub1Rule2": + severity: 2 # Modified from: 3 \ No newline at end of file diff --git a/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule3.yml.goldfile b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule3.yml.goldfile new file mode 100644 index 000000000..468484486 --- /dev/null +++ b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations-only-modifications/Stub1Rule3.yml.goldfile @@ -0,0 +1,5 @@ + "Stub1Rule3": + severity: 3 # Modified from: 4 + tags: # Modified from: ["BestPractices"] + - CodeStyle + - BestPractices diff --git a/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations/Stub1Rule4.yml.goldfile b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations/Stub1Rule4.yml.goldfile index 468484486..1125b9258 100644 --- a/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations/Stub1Rule4.yml.goldfile +++ b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/override-configurations/Stub1Rule4.yml.goldfile @@ -1,5 +1,4 @@ - "Stub1Rule3": - severity: 3 # Modified from: 4 - tags: # Modified from: ["BestPractices"] - - CodeStyle - - BestPractices + "Stub1Rule4": + severity: 2 + tags: + - CodeStyle \ No newline at end of file diff --git a/test/lib/actions/ConfigAction.test.ts b/test/lib/actions/ConfigAction.test.ts index 5a0fdb229..2eaf8e143 100644 --- a/test/lib/actions/ConfigAction.test.ts +++ b/test/lib/actions/ConfigAction.test.ts @@ -87,11 +87,23 @@ describe('ConfigAction tests', () => { expect(output).toContain(goldFileContents); }); - it('Selected rules are present and uncommented', async () => { + it('When not including unmodified rules, then selected rules are not present', async () => { // ==== TESTED BEHAVIOR ==== // Select the rules with the CodeStyle tag const output = await runActionAndGetDisplayedConfig(dependencies, ['CodeStyle']); + // ==== ASSERTIONS ==== + const unExpectedStub1RuleOverrideText: string = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'default-configurations', 'Stub1Rule1.yml.goldfile')); + expect(output).not.toContain(unExpectedStub1RuleOverrideText); + const expectedRuleOverrideText: string = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'default-configurations', 'rulesSectionWithNoModifications.yml.goldfile')); + expect(output).toContain(expectedRuleOverrideText); + }); + + it('When including unmodified rules, then selected rules are present and uncommented', async () => { + // ==== TESTED BEHAVIOR ==== + // Select the rules with the CodeStyle tag + const output = await runActionAndGetDisplayedConfig(dependencies, ['CodeStyle'], undefined, undefined, undefined, true); + // ==== ASSERTIONS ==== // Rather than exhaustively check every rule, we'll just check one, because if that one is correct then // we can reasonably assume that the other rules are also present and correct. @@ -137,14 +149,14 @@ describe('ConfigAction tests', () => { const output = await runActionAndGetDisplayedConfig(dependencies, ['NoRuleHasThisTag']); // ==== ASSERTIONS ==== - expect(output).toContain('rules: {} # Remove this empty object {} when you are ready to specify your first rule override'); + expect(output).toContain('rules: {} # Empty object used because rule selection returned no rules'); expect(output).toContain('engines: {} # Empty object used because rule selection returned no rules'); }); - it('Edge case: When a selected rule has no tags by default, `tags` is an empty array with no comment', async () => { + it('Edge case: When including unmodified rules and a selected rule has no tags by default, `tags` is an empty array with no comment', async () => { // ==== TESTED BEHAVIOR ==== // Select Stub1Rule7, which has no tags, by its name directly. - const output = await runActionAndGetDisplayedConfig(dependencies, ['Stub1Rule7']); + const output = await runActionAndGetDisplayedConfig(dependencies, ['Stub1Rule7'], undefined, undefined, undefined, true); // ==== ASSERTIONS ==== const goldFileContents = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'default-configurations', 'Stub1Rule7.yml.goldfile')); @@ -263,9 +275,9 @@ describe('ConfigAction tests', () => { dependencies.configFactory = new StubCodeAnalyzerConfigFactory(CodeAnalyzerConfig.fromObject({ log_level: "warn" })); - + const output = await runActionAndGetDisplayedConfig(dependencies, ['all']); - + expect(output).toContain('log_level: 2 # Modified from: 4'); }); @@ -274,15 +286,28 @@ describe('ConfigAction tests', () => { {overrideStatus: 'overridden severity', commentStatus: 'comment indicating original values', ruleName: 'Stub1Rule2'}, {overrideStatus: 'overridden tags and severity', commentStatus: 'comment indicating original values', ruleName: 'Stub1Rule3'}, {overrideStatus: 'no overrides', commentStatus: 'no comment', ruleName: 'Stub1Rule4'}, - ])('Selected and enabled rules with $overrideStatus are present with $commentStatus', async ({ruleName}) => { + ])('When including unmodified rule settings, selected and enabled rules with $overrideStatus are present with $commentStatus', async ({ruleName}) => { // ==== TESTED BEHAVIOR ==== - const output = await runActionAndGetDisplayedConfig(dependencies, ['CodeStyle']); + const output = await runActionAndGetDisplayedConfig(dependencies, ['CodeStyle'], undefined, undefined, undefined, true); // ==== ASSERTIONS ==== const goldFileContents = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'override-configurations', `${ruleName}.yml.goldfile`)); expect(output).toContain(goldFileContents); }); + it.each([ + {overrideStatus: 'overridden tags', commentStatus: 'comment indicating original values', ruleName: 'Stub1Rule1'}, + {overrideStatus: 'overridden severity', commentStatus: 'comment indicating original values', ruleName: 'Stub1Rule2'}, + {overrideStatus: 'overridden tags and severity', commentStatus: 'comment indicating original values', ruleName: 'Stub1Rule3'} + ])('When not including unmodified rule settings, selected and enabled rules with $overrideStatus are present with $commentStatus', async ({ruleName}) => { + // ==== TESTED BEHAVIOR ==== + const output = await runActionAndGetDisplayedConfig(dependencies, ['CodeStyle']); + + // ==== ASSERTIONS ==== + const goldFileContents = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'override-configurations-only-modifications', `${ruleName}.yml.goldfile`)); + expect(output).toContain(goldFileContents); + }); + it.each([ {ruleType: 'Overridden and unselected rules', ruleName: 'Stub1Rule5'}, {ruleType: 'Non-overridden and unselected rules', ruleName: 'Stub1Rule6'}, @@ -329,7 +354,7 @@ describe('ConfigAction tests', () => { const output = await runActionAndGetDisplayedConfig(dependencies, ['NoRuleHasThisTag']); // ==== ASSERTIONS ==== - expect(output).toContain('rules: {} # Remove this empty object {} when you are ready to specify your first rule override'); + expect(output).toContain('rules: {} # Empty object used because rule selection returned no rules'); expect(output).toContain('engines: {} # Empty object used because rule selection returned no rules'); }); @@ -342,7 +367,7 @@ describe('ConfigAction tests', () => { const output = await runActionAndGetDisplayedConfig(dependencies, ['NoRuleHasThisTag']); // ==== ASSERTIONS ==== - expect(output).toContain('rules: {} # Remove this empty object {} when you are ready to specify your first rule override'); + expect(output).toContain('rules: {} # Empty object used because rule selection returned no rules'); expect(output).toContain('disable_engine: true # Modified from: false'); }); @@ -389,9 +414,9 @@ describe('ConfigAction tests', () => { it.each([ {overrideStatus: 'via override', commentStatus: 'there is a comment', ruleName: 'Stub1Rule7'}, {overrideStatus: 'by default', commentStatus: 'there is no comment', ruleName: 'Stub1Rule8'} - ])('Edge Case: When selected rule has no tags $overrideStatus, `tags` is an empty array and $commentStatus', async ({ruleName}) => { + ])('Edge Case: When including unmodified rule settings and selected rule has no tags $overrideStatus, `tags` is an empty array and $commentStatus', async ({ruleName}) => { // ==== TESTED BEHAVIOR ==== - const output = await runActionAndGetDisplayedConfig(dependencies, [ruleName]); + const output = await runActionAndGetDisplayedConfig(dependencies, [ruleName], undefined, undefined, undefined, true); // ==== ASSERTIONS ==== const goldFileContents = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'override-configurations', `${ruleName}.yml.goldfile`)); @@ -452,7 +477,7 @@ describe('ConfigAction tests', () => { }; // ==== TESTED BEHAVIOR ==== - const output: string = await runActionAndGetDisplayedConfig(dependencies, ['all'], undefined, workspace, target); + const output: string = await runActionAndGetDisplayedConfig(dependencies, ['all'], undefined, workspace, target, true); // ==== ASSERTIONS ==== const goldFileContents: string = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'workspace-resolution', 'workspaceAwareRules.yml.goldfile')); @@ -584,14 +609,15 @@ describe('ConfigAction tests', () => { return fsp.readFile(goldFilePath, {encoding: 'utf-8'}); } - async function runActionAndGetDisplayedConfig(dependencies: ConfigDependencies, ruleSelectors: string[], configFile?: string, workspace?: string[], target?: string[]): Promise { + async function runActionAndGetDisplayedConfig(dependencies: ConfigDependencies, ruleSelectors: string[], configFile?: string, workspace?: string[], target?: string[], includeUnmodifiedRules?: boolean): Promise { // ==== SETUP ==== const action = ConfigAction.createAction(dependencies); const input: ConfigInput = { 'rule-selector': ruleSelectors, 'config-file': configFile, workspace, - target + target, + 'include-unmodified-rules': includeUnmodifiedRules }; // ==== TESTED BEHAVIOR ====