diff --git a/SourceGeneratorToolkit.Test/DiagnosticCatalogTests.cs b/SourceGeneratorToolkit.Test/DiagnosticCatalogTests.cs index 102489c..65e293a 100644 --- a/SourceGeneratorToolkit.Test/DiagnosticCatalogTests.cs +++ b/SourceGeneratorToolkit.Test/DiagnosticCatalogTests.cs @@ -66,6 +66,37 @@ public void DescriptorsAreRecordedInAllocationOrder() CollectionAssert.AreEqual(ExpectedAllocationOrder, ids); } + [TestMethod] + public void ReusingANumberAcrossSeveritiesThrows() + { + DiagnosticCatalog catalog = new("X", "c"); + catalog.Warning(1, "a", "a"); + + ArgumentException exception = Assert.ThrowsExactly(() => catalog.Error(1, "b", "b")); + + Assert.AreEqual("number", exception.ParamName); + Assert.AreEqual(1, catalog.Descriptors.Count); + } + + [TestMethod] + public void ReusingANumberWithTheSameSeverityThrows() + { + DiagnosticCatalog catalog = new("X", "c"); + catalog.Warning(1, "a", "a"); + + Assert.ThrowsExactly(() => catalog.Warning(1, "a", "a")); + } + + [TestMethod] + public void TheSameNumberIsAllowedInSeparateCatalogues() + { + DiagnosticCatalog first = new("X", "c"); + DiagnosticCatalog second = new("Y", "c"); + + Assert.AreEqual("X001", first.Warning(1, "a", "a").Id); + Assert.AreEqual("Y001", second.Warning(1, "a", "a").Id); + } + [TestMethod] public void DescriptorsIsEmptyBeforeAnythingIsAllocated() { diff --git a/SourceGeneratorToolkit/DiagnosticCatalog.cs b/SourceGeneratorToolkit/DiagnosticCatalog.cs index be738f5..357fb0b 100644 --- a/SourceGeneratorToolkit/DiagnosticCatalog.cs +++ b/SourceGeneratorToolkit/DiagnosticCatalog.cs @@ -24,6 +24,7 @@ namespace ktsu.SourceGeneratorToolkit; public sealed class DiagnosticCatalog(string idPrefix, string category) { private readonly List descriptors = []; + private readonly HashSet numbers = []; /// Gets the category every descriptor in this catalogue is reported under. public string Category { get; } = category; @@ -40,6 +41,9 @@ public sealed class DiagnosticCatalog(string idPrefix, string category) /// The diagnostic title. /// The message format string. /// The descriptor, also recorded in . + /// + /// has already been allocated from this catalogue. + /// public DiagnosticDescriptor Warning(int number, string title, string messageFormat) => Add(number, title, messageFormat, DiagnosticSeverity.Warning); @@ -50,13 +54,25 @@ public DiagnosticDescriptor Warning(int number, string title, string messageForm /// The diagnostic title. /// The message format string. /// The descriptor, also recorded in . + /// + /// has already been allocated from this catalogue. + /// public DiagnosticDescriptor Error(int number, string title, string messageFormat) => Add(number, title, messageFormat, DiagnosticSeverity.Error); private DiagnosticDescriptor Add(int number, string title, string messageFormat, DiagnosticSeverity severity) { + string id = idPrefix + number.ToString("D3", CultureInfo.InvariantCulture); + + // Two descriptors under one identifier would share every .editorconfig severity override and + // #pragma suppression meant for either, so a reused number is a mistake to catch here. + if (!numbers.Add(number)) + { + throw new ArgumentException($"Diagnostic number {number} is already allocated as {id}.", nameof(number)); + } + DiagnosticDescriptor descriptor = new( - id: idPrefix + number.ToString("D3", CultureInfo.InvariantCulture), + id: id, title: title, messageFormat: messageFormat, category: Category,