Skip to content

Commit 3bc1640

Browse files
committed
Improve logic to handle the small changes as newlines, multi custom-sections in atc-coding-rules distribution
1 parent 42b0ea1 commit 3bc1640

File tree

3 files changed

+186
-112
lines changed

3 files changed

+186
-112
lines changed

src/Atc.CodingRules.Updater/EditorConfigHelper.cs

Lines changed: 183 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
// ReSharper disable ReturnTypeCanBeEnumerable.Local
33
// ReSharper disable SuggestBaseTypeForParameter
44
// ReSharper disable ReplaceSubstringWithRangeIndexer
5+
// ReSharper disable ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
56
namespace Atc.CodingRules.Updater;
67

78
public 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

Comments
 (0)