@@ -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
223239class 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
233249class 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+ }
0 commit comments