22// ReSharper disable ReturnTypeCanBeEnumerable.Local
33// ReSharper disable SuggestBaseTypeForParameter
44// ReSharper disable ReplaceSubstringWithRangeIndexer
5+ // ReSharper disable ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
56namespace Atc . CodingRules . Updater ;
67
78public static class EditorConfigHelper
89{
910 public const string FileName = ".editorconfig" ;
1011 public const string SectionDivider = "##########################################" ;
11- public const string CustomSectionHeader = "# Custom - Code Analyzers Rules" ;
12+ public const string CustomSectionHeaderPrefix = "# Custom - " ;
13+ public const string CustomSectionHeaderCodeAnalyzersRulesSuffix = "Code Analyzers Rules" ;
14+ public const string CustomSectionFirstLine = "[*.{cs,csx,cake}]" ;
1215 public const string AutogeneratedCustomSectionHeaderPrefix = "# ATC temporary suppressions" ;
1316
1417 public static void HandleFile (
@@ -40,6 +43,27 @@ public static void HandleFile(
4043 var contentGit = HttpClientHelper . GetAsString ( logger , rawGitUrl ) ;
4144 var contentFile = FileHelper . ReadAllText ( file ) ;
4245
46+ HandleFile ( logger , area , contentGit , contentFile , descriptionPart , file ) ;
47+ }
48+ catch ( Exception ex )
49+ {
50+ logger . LogError ( $ "{ EmojisConstants . Error } { area } - { ex . Message } ") ;
51+ }
52+ }
53+
54+ public static void HandleFile (
55+ ILogger logger ,
56+ string area ,
57+ string contentGit ,
58+ string contentFile ,
59+ string descriptionPart ,
60+ FileInfo file )
61+ {
62+ ArgumentNullException . ThrowIfNull ( contentGit ) ;
63+ ArgumentNullException . ThrowIfNull ( file ) ;
64+
65+ try
66+ {
4367 if ( FileHelper . IsFileDataLengthEqual ( contentGit , contentFile ) )
4468 {
4569 logger . LogInformation ( $ "{ EmojisConstants . FileNotUpdated } { descriptionPart } nothing to update") ;
@@ -52,15 +76,16 @@ public static void HandleFile(
5276 return ;
5377 }
5478
55- var rawFileAtcData = ExtractDataAndCutAfterCustomRulesHeader ( contentFile ) ;
79+ var contentGitBasePart = ExtractContentBasePart ( contentGit ) ;
80+ var contentFileBasePart = ExtractContentBasePart ( contentFile ) ;
5681
57- if ( FileHelper . IsFileDataLengthEqual ( contentGit , rawFileAtcData ) )
82+ if ( FileHelper . IsFileDataLengthEqual ( contentGitBasePart , contentFileBasePart ) )
5883 {
5984 logger . LogInformation ( $ "{ EmojisConstants . FileNotUpdated } { descriptionPart } nothing to update") ;
6085 return ;
6186 }
6287
63- UpdateFile ( logger , contentFile , contentGit , file , descriptionPart , rawFileAtcData ) ;
88+ UpdateFile ( logger , contentGit , contentFile , file , descriptionPart , contentGitBasePart ) ;
6489 }
6590 catch ( Exception ex )
6691 {
@@ -110,7 +135,7 @@ public static Task UpdateRootFileRemoveCustomAtcAutogeneratedRuleSuppressions(
110135 linesToWrite . AddRange ( linesBefore ) ;
111136 linesToWrite . AddRange ( linesAfter ) ;
112137
113- var contentToWrite = LinesToString ( linesToWrite ) ;
138+ var contentToWrite = linesToWrite . TrimEndForEmptyValuesToString ( ) ;
114139 return File . WriteAllTextAsync ( rootEditorConfigFile . FullName , contentToWrite , Encoding . UTF8 ) ;
115140 }
116141
@@ -140,89 +165,183 @@ public static Task UpdateRootFileAddCustomAtcAutogeneratedRuleSuppressions(
140165 lines . AddRange ( suppressionLines ) ;
141166 }
142167
143- var contentToWrite = LinesToString ( lines ) ;
168+ var contentToWrite = lines . TrimEndForEmptyValuesToString ( ) ;
144169 return File . WriteAllTextAsync ( rootEditorConfigFile . FullName , contentToWrite , Encoding . UTF8 ) ;
145170 }
146171
147172 private static void UpdateFile (
148173 ILogger logger ,
149- string rawFileData ,
150- string rawGitData ,
174+ string contentGit ,
175+ string contentFile ,
151176 FileInfo file ,
152177 string descriptionPart ,
153- string rawFileAtcData )
178+ string contentGitBasePart )
154179 {
155- var rawFileCustomData = ExtractCustomDataWithoutCustomRulesHeader ( rawFileData ) ;
156- var data = rawGitData + rawFileCustomData ;
157- if ( data . EndsWith ( Environment . NewLine , StringComparison . Ordinal ) )
158- {
159- data = data . Substring ( 0 , data . Length - Environment . NewLine . Length ) ;
160- }
180+ var contentGitCustomParts = ExtractContentCustomParts ( contentGit ) ;
181+ var contentFileCustomParts = ExtractContentCustomParts ( contentFile ) ;
182+
183+ MergeCustomPartsToFileCustomParts ( contentGitCustomParts , contentFileCustomParts ) ;
184+
185+ EnsureCustomSectionCodeAnalyzersRulesFirstLine ( contentGit , contentFileCustomParts ) ;
186+
187+ var newContentFile = BuildNewContentFile ( contentGitBasePart , contentFileCustomParts ) ;
161188
162- File . WriteAllText ( file . FullName , data ) ;
189+ File . WriteAllText ( file . FullName , newContentFile ) ;
163190 logger . LogInformation ( $ "{ EmojisConstants . FileUpdated } { descriptionPart } files merged") ;
164191
165- var rawGitDataKeyValues = GetKeyValues ( rawGitData ) ;
166- var rawFileDataKeyValues = GetKeyValues ( rawFileAtcData ) ;
167- var rawFileCustomDataKeyValues = GetKeyValues ( rawFileCustomData ) ;
168- LogSeverityDiffs ( logger , rawGitDataKeyValues , rawFileDataKeyValues , rawFileCustomDataKeyValues , rawGitData , data ) ;
192+ var customLines = contentFileCustomParts
193+ . FirstOrDefault ( x => x . Item1 . Equals ( CustomSectionHeaderCodeAnalyzersRulesSuffix , StringComparison . Ordinal ) )
194+ ? . Item2 ;
195+
196+ if ( customLines is not null )
197+ {
198+ var gitKeyValues = contentGit . GetDotnetDiagnosticSeverityKeyValues ( ) ;
199+ var fileKeyValues = contentFile . GetDotnetDiagnosticSeverityKeyValues ( ) ;
200+ var fileCustomKeyValues = customLines . ToArray ( ) . GetDotnetDiagnosticSeverityKeyValues ( ) ;
201+ LogSeverityDiffs ( logger , gitKeyValues , fileKeyValues , fileCustomKeyValues , contentGit , newContentFile ) ;
202+ }
169203 }
170204
171- private static string ExtractDataAndCutAfterCustomRulesHeader (
172- string rawFileData )
205+ private static void MergeCustomPartsToFileCustomParts (
206+ List < Tuple < string , List < string > > > contentGitCustomParts ,
207+ List < Tuple < string , List < string > > > contentFileCustomParts )
173208 {
174- var lines = rawFileData . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
175- var sb = new StringBuilder ( ) ;
209+ foreach ( var contentGitCustomPart in contentGitCustomParts )
210+ {
211+ if ( ! contentFileCustomParts . Any ( x => x . Item1 . Equals ( contentGitCustomPart . Item1 , StringComparison . Ordinal ) ) )
212+ {
213+ if ( contentFileCustomParts . Any ( x =>
214+ x . Item1 . Equals ( CustomSectionHeaderCodeAnalyzersRulesSuffix , StringComparison . Ordinal ) ) )
215+ {
216+ contentFileCustomParts . Insert ( 0 , contentGitCustomPart ) ;
217+ }
218+ else
219+ {
220+ contentFileCustomParts . Add ( contentGitCustomPart ) ;
221+ }
222+ }
223+ }
224+ }
176225
177- foreach ( var line in lines )
226+ private static void EnsureCustomSectionCodeAnalyzersRulesFirstLine (
227+ string contentGit ,
228+ List < Tuple < string , List < string > > > contentFileCustomParts )
229+ {
230+ if ( ! contentGit . Contains ( "root = true" , StringComparison . Ordinal ) )
178231 {
179- sb . AppendLine ( line ) ;
180- if ( ! CustomSectionHeader . Equals ( line , StringComparison . Ordinal ) )
232+ return ;
233+ }
234+
235+ foreach ( var ( header , lines ) in contentFileCustomParts )
236+ {
237+ if ( header . Equals ( CustomSectionHeaderCodeAnalyzersRulesSuffix , StringComparison . Ordinal ) &&
238+ ! lines . Any ( ) )
181239 {
182- continue ;
240+ lines . Insert ( 0 , CustomSectionFirstLine ) ;
183241 }
242+ }
243+ }
244+
245+ private static string BuildNewContentFile ( string contentGitBasePart , List < Tuple < string , List < string > > > contentFileCustomParts )
246+ {
247+ var sbNewContentFile = new StringBuilder ( ) ;
248+ sbNewContentFile . Append ( contentGitBasePart ) ;
249+ if ( contentFileCustomParts . Count > 0 )
250+ {
251+ sbNewContentFile . AppendLine ( ) ;
252+ }
184253
185- sb . Append ( SectionDivider ) ;
186- return sb . ToString ( ) ;
254+ foreach ( var ( header , lines ) in contentFileCustomParts )
255+ {
256+ sbNewContentFile . AppendLine ( ) ;
257+ sbNewContentFile . AppendLine ( ) ;
258+ sbNewContentFile . AppendLine ( SectionDivider ) ;
259+ sbNewContentFile . AppendLine ( CustomSectionHeaderPrefix + header ) ;
260+ sbNewContentFile . AppendLine ( SectionDivider ) ;
261+ foreach ( var line in lines )
262+ {
263+ sbNewContentFile . AppendLine ( line ) ;
264+ }
187265 }
188266
189- return sb . ToString ( ) ;
267+ var newContentFile = sbNewContentFile . ToString ( ) ;
268+ return newContentFile ;
190269 }
191270
192- private static string ExtractCustomDataWithoutCustomRulesHeader (
193- string rawFileData )
271+ private static string ExtractContentBasePart (
272+ string content )
194273 {
195- var lines = rawFileData . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
274+ var lines = content . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
275+
196276 var sb = new StringBuilder ( ) ;
197- var addLines = false ;
277+ for ( int i = 0 ; i < lines . Length ; i ++ )
278+ {
279+ var line = lines [ i ] ;
280+ if ( line . Equals ( SectionDivider , StringComparison . Ordinal ) &&
281+ i + 1 < lines . Length )
282+ {
283+ var nextLine = lines [ i + 1 ] ;
284+ if ( nextLine . StartsWith ( CustomSectionHeaderPrefix , StringComparison . Ordinal ) )
285+ {
286+ return sb
287+ . ToString ( )
288+ . TrimEndForEmptyLines ( ) ;
289+ }
290+ }
291+
292+ sb . AppendLine ( line ) ;
293+ }
294+
295+ return sb
296+ . ToString ( )
297+ . TrimEndForEmptyLines ( ) ;
298+ }
299+
300+ private static List < Tuple < string , List < string > > > ExtractContentCustomParts (
301+ string content )
302+ {
303+ var customParts = new List < Tuple < string , List < string > > > ( ) ;
198304
199- for ( var index = 0 ; index < lines . Length ; index ++ )
305+ var lines = content . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
306+
307+ var workingOnCustomHeader = string . Empty ;
308+ var workingOnCustomLines = new List < string > ( ) ;
309+ for ( int i = 0 ; i < lines . Length ; i ++ )
200310 {
201- var line = lines [ index ] ;
202- if ( addLines )
311+ var line = lines [ i ] ;
312+ if ( line . Equals ( SectionDivider , StringComparison . Ordinal ) &&
313+ i + 1 < lines . Length )
203314 {
204- if ( SectionDivider . Equals ( line , StringComparison . Ordinal ) )
315+ var nextLine = lines [ i + 1 ] ;
316+ if ( nextLine . StartsWith ( CustomSectionHeaderPrefix , StringComparison . Ordinal ) )
205317 {
206- if ( index == 0 ||
207- index + 1 == lines . Length ||
208- ( ! CustomSectionHeader . Equals ( lines [ index + 1 ] , StringComparison . Ordinal ) &&
209- ! CustomSectionHeader . Equals ( lines [ index - 1 ] , StringComparison . Ordinal ) ) )
318+ if ( workingOnCustomHeader . Length != 0 )
210319 {
211- sb . AppendLine ( line ) ;
320+ workingOnCustomLines . TrimEndForEmptyValues ( ) ;
321+ customParts . Add ( new Tuple < string , List < string > > ( workingOnCustomHeader , workingOnCustomLines ) ) ;
212322 }
213- }
214- else
215- {
216- sb . AppendLine ( line ) ;
323+
324+ workingOnCustomHeader = nextLine . Substring ( CustomSectionHeaderPrefix . Length ) . Trim ( ) ;
325+ workingOnCustomLines = new List < string > ( ) ;
217326 }
218327 }
219- else if ( CustomSectionHeader . Equals ( line , StringComparison . Ordinal ) )
328+
329+ if ( workingOnCustomHeader . Length > 0 &&
330+ ! ( line . Equals ( SectionDivider , StringComparison . Ordinal ) ||
331+ line . StartsWith ( CustomSectionHeaderPrefix , StringComparison . Ordinal ) ) )
220332 {
221- addLines = true ;
333+ workingOnCustomLines . Add ( line ) ;
222334 }
223335 }
224336
225- return sb . ToString ( ) ;
337+ if ( workingOnCustomHeader . Length > 0 &&
338+ ! customParts . Any ( x => x . Item1 . Equals ( workingOnCustomHeader , StringComparison . Ordinal ) ) )
339+ {
340+ workingOnCustomLines . TrimEndForEmptyValues ( ) ;
341+ customParts . Add ( new Tuple < string , List < string > > ( workingOnCustomHeader , workingOnCustomLines ) ) ;
342+ }
343+
344+ return customParts ;
226345 }
227346
228347 [ SuppressMessage ( "Performance" , "MA0098:Use indexer instead of LINQ methods" , Justification = "OK." ) ]
@@ -282,81 +401,35 @@ private static string[] ExtractAfterCustomAutogeneratedRulesContent(
282401 return result . ToArray ( ) ;
283402 }
284403
285- private static string LinesToString (
286- IList < string > lines )
287- {
288- while ( string . IsNullOrEmpty ( lines . Last ( ) ) )
289- {
290- lines = lines . Take ( lines . Count - 1 ) . ToList ( ) ;
291- }
292-
293- var sb = new StringBuilder ( ) ;
294- foreach ( var line in lines )
295- {
296- sb . AppendLine ( line ) ;
297- }
298-
299- return sb . ToString ( ) ;
300- }
301-
302- private static List < KeyValueItem > GetKeyValues (
303- string data )
304- {
305- var list = new List < KeyValueItem > ( ) ;
306-
307- var lines = data . Split ( FileHelper . LineBreaks , StringSplitOptions . RemoveEmptyEntries ) ;
308- foreach ( var line in lines )
309- {
310- if ( string . IsNullOrEmpty ( line ) || line . StartsWith ( '#' ) )
311- {
312- continue ;
313- }
314-
315- var keyValueLine = line . Split ( "=" , StringSplitOptions . RemoveEmptyEntries ) ;
316- if ( keyValueLine . Length == 2 )
317- {
318- list . Add ( new KeyValueItem ( keyValueLine [ 0 ] , keyValueLine [ 1 ] ) ) ;
319- }
320- }
321-
322- return list ;
323- }
324-
325404 private static void LogSeverityDiffs (
326405 ILogger logger ,
327- IEnumerable < KeyValueItem > rawGitDataKeyValues ,
328- IReadOnlyCollection < KeyValueItem > rawFileDataKeyValues ,
329- IReadOnlyCollection < KeyValueItem > rawFileCustomDataKeyValues ,
330- string rawGitData ,
331- string rawFileData )
406+ IEnumerable < KeyValueItem > gitKeyValues ,
407+ IReadOnlyCollection < KeyValueItem > fileKeyValues ,
408+ IReadOnlyCollection < KeyValueItem > fileCustomKeyValues ,
409+ string contentGit ,
410+ string contentFile )
332411 {
333- var gitLines = rawGitData . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
334- var fileLines = rawFileData . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
412+ var gitLines = contentGit . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
413+ var fileLines = contentFile . Split ( FileHelper . LineBreaks , StringSplitOptions . None ) ;
335414
336- foreach ( var rawGitDataKeyValue in rawGitDataKeyValues )
415+ foreach ( var gitKeyValue in gitKeyValues )
337416 {
338- var key = rawGitDataKeyValue . Key ;
339- if ( ! key . StartsWith ( "dotnet_diagnostic." , StringComparison . Ordinal ) ||
340- ! key . Contains ( ".severity" , StringComparison . Ordinal ) )
341- {
342- continue ;
343- }
344-
345- var item = rawFileCustomDataKeyValues . FirstOrDefault ( x => x . Key . Equals ( key , StringComparison . Ordinal ) ) ;
417+ var key = gitKeyValue . Key ;
418+ var item = fileCustomKeyValues . FirstOrDefault ( x => x . Key . Equals ( key , StringComparison . Ordinal ) ) ;
346419 if ( item != null )
347420 {
348421 // Duplicate
349422 var gitLineNumber = GetLineNumberForwardSearch ( gitLines , key ) ;
350423 var fileLineNumber = GetLineNumberReverseSearch ( fileLines , item ) ;
351424
352425 logger . LogWarning ( $ "{ EmojisConstants . DuplicateKey } Duplicate key: { key } ") ;
353- logger . LogWarning ( $ "{ FormattableString . Invariant ( $ " -- GitHub section (line { gitLineNumber : 0000} ): ") } { rawGitDataKeyValue . Value . Trim ( ) } ") ;
426+ logger . LogWarning ( $ "{ FormattableString . Invariant ( $ " -- GitHub section (line { gitLineNumber : 0000} ): ") } { gitKeyValue . Value . Trim ( ) } ") ;
354427 logger . LogWarning ( $ "{ FormattableString . Invariant ( $ " -- Custom section (line { fileLineNumber : 0000} ): ") } { item . Value . Trim ( ) } ") ;
355428 }
356- else if ( ! rawFileDataKeyValues . Any ( x => x . Key . Equals ( key , StringComparison . Ordinal ) ) )
429+ else if ( ! fileKeyValues . Any ( x => x . Key . Equals ( key , StringComparison . Ordinal ) ) )
357430 {
358431 // New
359- logger . LogDebug ( $ " - New key/value - { key } ={ rawGitDataKeyValue . Value } ") ;
432+ logger . LogDebug ( $ " - New key/value - { key } ={ gitKeyValue . Value } ") ;
360433 }
361434 }
362435 }
0 commit comments