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..6654d8d338 100644
--- a/src/Tools/GenerateDocumentationAndConfigFiles/Program.cs
+++ b/src/Tools/GenerateDocumentationAndConfigFiles/Program.cs
@@ -666,7 +666,7 @@ async Task checkHelpLinkAsync(string helpLink)
async Task createGlobalConfigFilesAsync()
{
- using var shippedFilesDataBuilder = ArrayBuilder.GetInstance();
+ using var releaseTrackingFilesDataBuilder = ArrayBuilder.GetInstance();
using var versionsBuilder = PooledHashSet.GetInstance();
// Validate all assemblies exist on disk and can be loaded.
@@ -705,7 +705,19 @@ 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);
+ var shippedFileExists = File.Exists(shippedFile);
+ var unshippedFileExists = File.Exists(unshippedFile);
+
+ if (shippedFileExists ^ unshippedFileExists)
+ {
+ var existingFile = shippedFileExists ? shippedFile : unshippedFile;
+ var nonExistingFile = shippedFileExists ? unshippedFile : shippedFile;
+ await Console.Error.WriteLineAsync($"Expected both '{shippedFile}' and '{unshippedFile}' to exist or not exist, but '{existingFile}' exists and '{nonExistingFile}' does not exist.").ConfigureAwait(false);
+ return false;
+ }
+
+ if (shippedFileExists)
{
sawShippedFile = true;
@@ -717,14 +729,24 @@ async Task createGlobalConfigFilesAsync()
try
{
+ // Read shipped file
using var fileStream = File.OpenRead(shippedFile);
var sourceText = SourceText.From(fileStream);
var releaseTrackingData = ReleaseTrackingHelper.ReadReleaseTrackingData(shippedFile, sourceText,
- onDuplicateEntryInRelease: (_1, _2, _3, _4, line) => throw new Exception($"Duplicate entry in {shippedFile} at {line.LineNumber}: '{line}'"),
- onInvalidEntry: (line, _2, _3, _4) => throw new Exception($"Invalid entry in {shippedFile} at {line.LineNumber}: '{line}'"),
+ onDuplicateEntryInRelease: (_1, _2, _3, _4, line) => throw new InvalidOperationException($"Duplicate entry in {shippedFile} at {line.LineNumber}: '{line}'"),
+ onInvalidEntry: (line, _2, _3, _4) => throw new InvalidOperationException($"Invalid entry in {shippedFile} at {line.LineNumber}: '{line}'"),
isShippedFile: true);
- shippedFilesDataBuilder.Add(releaseTrackingData);
+ releaseTrackingFilesDataBuilder.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 InvalidOperationException($"Duplicate entry in {unshippedFile} at {line.LineNumber}: '{line}'"),
+ onInvalidEntry: (line, _2, _3, _4) => throw new InvalidOperationException($"Invalid entry in {unshippedFile} at {line.LineNumber}: '{line}'"),
+ isShippedFile: false);
+ releaseTrackingFilesDataBuilder.Add(releaseTrackingDataUnshipped);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
@@ -744,33 +766,49 @@ async Task createGlobalConfigFilesAsync()
if (versionsBuilder.Count > 0)
{
- var shippedFilesData = shippedFilesDataBuilder.ToImmutable();
+ var releaseTrackingData = releaseTrackingFilesDataBuilder.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, 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);
+ CreateGlobalConfigsForVersion(unshippedVersion, isShippedVersion: false, releaseTrackingData);
}
return true;
// Local functions.
+ void CreateGlobalConfigsForVersion(
+ Version version,
+ bool isShippedVersion,
+ ImmutableArray releaseTrackingData)
+ {
+ var analysisLevelVersionString = GetNormalizedVersionStringForEditorconfigFileNameSuffix(version);
+
+ 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 +831,7 @@ void CreateGlobalConfig(
analysisMode,
category,
allRulesById,
- (shippedFilesData, version));
+ (releaseTrackingData, version, isShippedVersion));
}
static string GetNormalizedVersionStringForEditorconfigFileNameSuffix(Version version)
@@ -1125,7 +1163,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 +1173,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 +1186,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 +1295,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;