From 00c98ef669bbb97ed10ad5783c2aa963bb332676 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 11 Nov 2022 17:27:28 +0530 Subject: [PATCH] Enhance GenerateDocumentationAndConfigFiles tool to generate vNext globalconfig files Fixes #6247 Our generated globalconfig files that we ship in the analyzer NuGet packages and .NET SDK should likely include a vNext set of globalconfig files so that when the analysis level is set to latest or explicitly to vNext for upcoming .NET Release, we still find a proper mapped globalconfig file. This will avoid regressions such as #6245 in future. Verified that locally built Microsoft.CodeAnalysis.NetAnalyzers package with this change includes globalconfig files specific to 8.0 release version. --- src/Directory.Build.targets | 6 ++ .../Program.cs | 73 +++++++++++++------ 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index c7c28fd9f4..87650f1f43 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -24,6 +24,12 @@ + + + + PreserveNewest + AnalyzerReleases\$(AssemblyName)\AnalyzerReleases.Unshipped.md + diff --git a/src/Tools/GenerateDocumentationAndConfigFiles/Program.cs b/src/Tools/GenerateDocumentationAndConfigFiles/Program.cs index b9606e17ba..f0a7c4f0f5 100644 --- a/src/Tools/GenerateDocumentationAndConfigFiles/Program.cs +++ b/src/Tools/GenerateDocumentationAndConfigFiles/Program.cs @@ -667,6 +667,7 @@ async Task checkHelpLinkAsync(string helpLink) async Task createGlobalConfigFilesAsync() { using var shippedFilesDataBuilder = ArrayBuilder.GetInstance(); + using var unshippedFilesDataBuilder = ArrayBuilder.GetInstance(); using var versionsBuilder = PooledHashSet.GetInstance(); // Validate all assemblies exist on disk and can be loaded. @@ -705,7 +706,8 @@ async Task createGlobalConfigFilesAsync() var assemblyName = Path.GetFileNameWithoutExtension(assembly); var shippedFile = Path.Combine(assemblyDir, "AnalyzerReleases", assemblyName, ReleaseTrackingHelper.ShippedFileName); - if (File.Exists(shippedFile)) + var unshippedFile = Path.Combine(assemblyDir, "AnalyzerReleases", assemblyName, ReleaseTrackingHelper.UnshippedFileName); + if (File.Exists(shippedFile) && File.Exists(unshippedFile)) { sawShippedFile = true; @@ -717,6 +719,7 @@ async Task createGlobalConfigFilesAsync() try { + // Read shipped file using var fileStream = File.OpenRead(shippedFile); var sourceText = SourceText.From(fileStream); var releaseTrackingData = ReleaseTrackingHelper.ReadReleaseTrackingData(shippedFile, sourceText, @@ -725,6 +728,15 @@ async Task createGlobalConfigFilesAsync() isShippedFile: true); shippedFilesDataBuilder.Add(releaseTrackingData); versionsBuilder.AddRange(releaseTrackingData.Versions); + + // Read unshipped file + using var fileStreamUnshipped = File.OpenRead(unshippedFile); + var sourceTextUnshipped = SourceText.From(fileStreamUnshipped); + var releaseTrackingDataUnshipped = ReleaseTrackingHelper.ReadReleaseTrackingData(unshippedFile, sourceTextUnshipped, + onDuplicateEntryInRelease: (_1, _2, _3, _4, line) => throw new Exception($"Duplicate entry in {unshippedFile} at {line.LineNumber}: '{line}'"), + onInvalidEntry: (line, _2, _3, _4) => throw new Exception($"Invalid entry in {unshippedFile} at {line.LineNumber}: '{line}'"), + isShippedFile: false); + unshippedFilesDataBuilder.Add(releaseTrackingDataUnshipped); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -744,33 +756,50 @@ async Task createGlobalConfigFilesAsync() if (versionsBuilder.Count > 0) { - var shippedFilesData = shippedFilesDataBuilder.ToImmutable(); + var releaseTrackingData = shippedFilesDataBuilder.Concat(unshippedFilesDataBuilder).ToImmutableArray(); - // Generate global analyzer config files for each shipped version, if required. + // Generate global analyzer config files for each shipped version. foreach (var version in versionsBuilder) { var analysisLevelVersionString = GetNormalizedVersionStringForEditorconfigFileNameSuffix(version); - - foreach (var analysisMode in Enum.GetValues(typeof(AnalysisMode))) - { - CreateGlobalConfig(version, analysisLevelVersionString, (AnalysisMode)analysisMode!, shippedFilesData, category: null); - foreach (var category in categories) - { - CreateGlobalConfig(version, analysisLevelVersionString, (AnalysisMode)analysisMode!, shippedFilesData, category); - } - } + CreateGlobalConfigsForVersion(version, isShippedVersion: true, analysisLevelVersionString, releaseTrackingData); } + + // Generate global analyzer config files for unshipped version. + // See https://github.com/dotnet/roslyn-analyzers/issues/6247 for details. + + // Use 'unshippedVersion = maxShippedVersion + 1' for unshipped data. + var maxShippedVersion = versionsBuilder.Max(); + var unshippedVersion = new Version(maxShippedVersion!.Major + 1, maxShippedVersion.Minor); + var unshippedAnalysisLevelVersionString = GetNormalizedVersionStringForEditorconfigFileNameSuffix(unshippedVersion); + CreateGlobalConfigsForVersion(unshippedVersion, isShippedVersion: false, unshippedAnalysisLevelVersionString, releaseTrackingData); } return true; // Local functions. + void CreateGlobalConfigsForVersion( + Version version, + bool isShippedVersion, + string analysisLevelVersionString, + ImmutableArray releaseTrackingData) + { + foreach (var analysisMode in Enum.GetValues(typeof(AnalysisMode))) + { + CreateGlobalConfig(version, isShippedVersion, analysisLevelVersionString, (AnalysisMode)analysisMode!, releaseTrackingData, category: null); + foreach (var category in categories!) + { + CreateGlobalConfig(version, isShippedVersion, analysisLevelVersionString, (AnalysisMode)analysisMode!, releaseTrackingData, category); + } + } + } void CreateGlobalConfig( Version version, + bool isShippedVersion, string analysisLevelVersionString, AnalysisMode analysisMode, - ImmutableArray shippedFilesData, + ImmutableArray releaseTrackingData, string? category) { var analysisLevelPropName = "AnalysisLevel"; @@ -793,7 +822,7 @@ void CreateGlobalConfig( analysisMode, category, allRulesById, - (shippedFilesData, version)); + (releaseTrackingData, version, isShippedVersion)); } static string GetNormalizedVersionStringForEditorconfigFileNameSuffix(Version version) @@ -1125,7 +1154,7 @@ private static void CreateGlobalconfig( AnalysisMode analysisMode, string? category, SortedList sortedRulesById, - (ImmutableArray shippedFiles, Version version) shippedReleaseData) + (ImmutableArray releaseTrackingData, Version version, bool isShippedVersion) releaseTrackingDataAndVersion) { Debug.Assert(editorconfigFileName.EndsWith(".editorconfig", StringComparison.Ordinal)); @@ -1135,7 +1164,7 @@ private static void CreateGlobalconfig( analysisMode, category, sortedRulesById, - shippedReleaseData); + releaseTrackingDataAndVersion); var directory = Directory.CreateDirectory(folder); var editorconfigFilePath = Path.Combine(directory.FullName, editorconfigFileName.ToLowerInvariant()); File.WriteAllText(editorconfigFilePath, text); @@ -1148,7 +1177,7 @@ static string GetGlobalconfigText( AnalysisMode analysisMode, string? category, SortedList sortedRulesById, - (ImmutableArray shippedFiles, Version version)? shippedReleaseData) + (ImmutableArray releaseTrackingData, Version version, bool isShippedVersion)? releaseTrackingDataAndVersion) { var result = new StringBuilder(); StartGlobalconfig(); @@ -1257,14 +1286,16 @@ bool AddRule(DiagnosticDescriptor rule, string? category) effectiveSeverity = DiagnosticSeverity.Warning; } - if (shippedReleaseData != null) + if (releaseTrackingDataAndVersion != null) { isEnabledByDefault = isEnabledRuleForNonDefaultAnalysisMode; - var maxVersion = shippedReleaseData.Value.version; + var maxVersion = releaseTrackingDataAndVersion.Value.isShippedVersion ? + releaseTrackingDataAndVersion.Value.version : + ReleaseTrackingHelper.UnshippedVersion; var foundReleaseTrackingEntry = false; - foreach (var shippedFile in shippedReleaseData.Value.shippedFiles) + foreach (var releaseTrackingData in releaseTrackingDataAndVersion.Value.releaseTrackingData) { - if (shippedFile.TryGetLatestReleaseTrackingLine(rule.Id, maxVersion, out _, out var releaseTrackingLine)) + if (releaseTrackingData.TryGetLatestReleaseTrackingLine(rule.Id, maxVersion, out _, out var releaseTrackingLine)) { foundReleaseTrackingEntry = true;