From 65627bb02596ff2c514a997f47a21cc4ee5f24d3 Mon Sep 17 00:00:00 2001 From: tpodolak Date: Fri, 5 Oct 2018 01:38:16 +0200 Subject: [PATCH 1/3] [GH-40] - Adding basic convention tests for diagnostic identifiers categories --- .../DiagnosticIdentifiers.cs | 38 ++++---- .../DiagnosticIdentifierTests.cs | 89 +++++++++++++++++++ 2 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs index 4c041442..1903d04c 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs @@ -2,24 +2,24 @@ { internal class DiagnosticIdentifiers { - public static readonly string NonVirtualSetupSpecification = "NS001"; - public static readonly string UnusedReceived = "NS002"; - public static readonly string PartialSubstituteForUnsupportedType = "NS003"; - public static readonly string SubstituteForWithoutAccessibleConstructor = "NS004"; - public static readonly string SubstituteForConstructorParametersMismatch = "NS005"; - public static readonly string SubstituteForInternalMember = "NS006"; - public static readonly string SubstituteConstructorMismatch = "NS007"; - public static readonly string SubstituteMultipleClasses = "NS008"; - public static readonly string SubstituteConstructorArgumentsForInterface = "NS009"; - public static readonly string SubstituteConstructorArgumentsForDelegate = "NS010"; - public static readonly string NonVirtualReceivedSetupSpecification = "NS011"; - public static readonly string NonVirtualWhenSetupSpecification = "NS012"; - public static readonly string ReEntrantSubstituteCall = "NS013"; - public static readonly string CallInfoArgumentOutOfRange = "NS014"; - public static readonly string CallInfoCouldNotConvertParameterAtPosition = "NS015"; - public static readonly string CallInfoCouldNotFindArgumentToThisCall = "NS016"; - public static readonly string CallInfoMoreThanOneArgumentOfType = "NS017"; - public static readonly string CallInfoArgumentSetWithIncompatibleValue = "NS018"; - public static readonly string CallInfoArgumentIsNotOutOrRef = "NS019"; + public const string NonVirtualSetupSpecification = "NS1001"; + public const string UnusedReceived = "NS1002"; + public const string PartialSubstituteForUnsupportedType = "NS1003"; + public const string SubstituteForWithoutAccessibleConstructor = "NS1004"; + public const string SubstituteForConstructorParametersMismatch = "NS1005"; + public const string SubstituteForInternalMember = "NS1006"; + public const string SubstituteConstructorMismatch = "NS1007"; + public const string SubstituteMultipleClasses = "NS1008"; + public const string SubstituteConstructorArgumentsForInterface = "NS1009"; + public const string SubstituteConstructorArgumentsForDelegate = "NS1010"; + public const string NonVirtualReceivedSetupSpecification = "NS1011"; + public const string NonVirtualWhenSetupSpecification = "NS1012"; + public const string ReEntrantSubstituteCall = "NS1013"; + public const string CallInfoArgumentOutOfRange = "NS1014"; + public const string CallInfoCouldNotConvertParameterAtPosition = "NS1015"; + public const string CallInfoCouldNotFindArgumentToThisCall = "NS1016"; + public const string CallInfoMoreThanOneArgumentOfType = "NS1017"; + public const string CallInfoArgumentSetWithIncompatibleValue = "NS1018"; + public const string CallInfoArgumentIsNotOutOrRef = "NS1019"; } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs new file mode 100644 index 00000000..8ee125e4 --- /dev/null +++ b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs @@ -0,0 +1,89 @@ +using System.Linq; +using System.Reflection; +using FluentAssertions; +using NSubstitute.Analyzers.Shared; +using Xunit; + +namespace NSubstitute.Analyzers.Tests.Shared.DiagnosticIdentifierTests +{ + public class DiagnosticIdentifierTests + { + private const string IdentifierPrefix = "NS"; + + private readonly FieldInfo[] _diagnosticIdentifiers = typeof(DiagnosticIdentifiers) + .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + .Where(fieldInfo => fieldInfo.IsLiteral && fieldInfo.IsInitOnly == false) + .ToArray(); + + [Fact] + public void DiagnosticIdentifiers_HasConstantValue() + { + _diagnosticIdentifiers.Should().BeEquivalentTo(typeof(DiagnosticIdentifiers).GetFields()); + } + + [Fact] + public void DiagnosticIdentifiers_StartsWithProperPrefix() + { + var invalidDiagnosticNames = _diagnosticIdentifiers.Where(info => ((string)info.GetRawConstantValue()).StartsWith(IdentifierPrefix) == false); + + invalidDiagnosticNames.Should().BeEmpty($"because all diagnostics should start with {IdentifierPrefix} prefix"); + } + + [Fact] + public void DiagnosticIdentifiers_BelongToSpecificCategory() + { + var invalidCategories = _diagnosticIdentifiers.Where(info => GetCategoryId(info) == 0); + + invalidCategories.Should().BeEmpty("because all diagnostics should belong to specific category"); + } + + [Fact] + public void DiagnosticIdentifiersCategories_ShouldHaveConsecutiveNumbers() + { + var groupedCategories = _diagnosticIdentifiers.Select(GetCategoryId) + .GroupBy(category => category) + .Select(group => group.Key) + .OrderBy(category => category) + .ToList(); + + var expectedCategories = Enumerable.Range(1, groupedCategories.Count); + + groupedCategories.Should().BeEquivalentTo(expectedCategories, "because category numbers should be consecutive"); + } + + [Fact] + public void DiagnosticIdentifiersWithinCategory_ShouldHaveConsecutiveNumbers() + { + var groupedIdentifiers = _diagnosticIdentifiers + .GroupBy(GetCategoryId) + .Select(group => group.Select(GetDiagnosticId).OrderBy(diagnostic => diagnostic).ToList()) + .ToList(); + + var expectedGroupedIdentifiers = groupedIdentifiers.Select(group => Enumerable.Range(1, group.Count)); + + groupedIdentifiers.Should().BeEquivalentTo(expectedGroupedIdentifiers); + } + + private int GetCategoryId(FieldInfo fieldInfo) + { + return GetCategoryId((string)fieldInfo.GetRawConstantValue()); + } + + private int GetCategoryId(string diagnosticId) + { + var substringIndex = diagnosticId.IndexOf(IdentifierPrefix) + IdentifierPrefix.Length; + return int.Parse(diagnosticId.Substring(substringIndex, 1)); + } + + private int GetDiagnosticId(FieldInfo fieldInfo) + { + return GetDiagnosticId((string)fieldInfo.GetRawConstantValue()); + } + + private int GetDiagnosticId(string diagnosticId) + { + var substringIndex = diagnosticId.IndexOf(IdentifierPrefix) + IdentifierPrefix.Length + 1; + return int.Parse(diagnosticId.Substring(substringIndex)); + } + } +} \ No newline at end of file From 357b820f57a1f87e5123c138369ef7e2dd8aa5e5 Mon Sep 17 00:00:00 2001 From: tpodolak Date: Sat, 6 Oct 2018 00:26:14 +0200 Subject: [PATCH 2/3] [GH-40] - Grouping diagnostics into categories --- README.md | 4 +- .../DiagnosticCategories.cs | 17 +++- .../DiagnosticDescriptors.cs | 95 ++++++++++--------- .../DiagnosticIdentifiers.cs | 42 ++++---- .../DisplayNameAttribute.cs | 15 +++ .../Extensions/EnumExtensions.cs | 14 +++ ...pSuppressDiagnosticsCodeFixActionsTests.cs | 18 ++-- .../DiagnosticIdentifierTests.cs | 40 +++++--- ...pSuppressDiagnosticsCodeFixActionsTests.cs | 18 ++-- 9 files changed, 164 insertions(+), 99 deletions(-) create mode 100644 src/NSubstitute.Analyzers.Shared/DisplayNameAttribute.cs create mode 100644 src/NSubstitute.Analyzers.Shared/Extensions/EnumExtensions.cs diff --git a/README.md b/README.md index b172ec72..b591ca34 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Analyzers to detect possible NSubstitute usage problems, such as attempts to set ## Diagnostics -* Non-virtual member setup detected (`NonVirtualSetupSpecification`, `NS001`) -* `Received` used without a following method call. (`UnusedReceived`, `NS002`) +* Non-virtual member setup detected (`NonVirtualSetupSpecification`, `NS1000`) +* `Received` used without a following method call. (`UnusedReceived`, `NS5000`) ## Support diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticCategories.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticCategories.cs index c35b909e..b92df322 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticCategories.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticCategories.cs @@ -1,7 +1,20 @@ namespace NSubstitute.Analyzers.Shared { - internal class DiagnosticCategories + internal enum DiagnosticCategory { - public static readonly string Usage = "NSubstitute.Usage"; + [DisplayName("Non virtual substitution")] + NonVirtualSubstitution = 1, + + [DisplayName("Substitute creation")] + SubstituteCreation, + + [DisplayName("Argument specification")] + ArgumentSpecification, + + [DisplayName("Call configuration")] + CallConfiguration, + + [DisplayName("Usage")] + Usage } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs index 71fe7bc3..1da05740 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticDescriptors.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Resources; using Microsoft.CodeAnalysis; +using NSubstitute.Analyzers.Shared.Extensions; namespace NSubstitute.Analyzers.Shared { @@ -15,23 +16,23 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(NonVirtualSetupSpecification), id: DiagnosticIdentifiers.NonVirtualSetupSpecification, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.NonVirtualSubstitution.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - public static DiagnosticDescriptor UnusedReceived { get; } = + public static DiagnosticDescriptor NonVirtualReceivedSetupSpecification { get; } = CreateDiagnosticDescriptor( - name: nameof(UnusedReceived), - id: DiagnosticIdentifiers.UnusedReceived, - category: DiagnosticCategories.Usage, + name: nameof(NonVirtualReceivedSetupSpecification), + id: DiagnosticIdentifiers.NonVirtualReceivedSetupSpecification, + category: DiagnosticCategory.NonVirtualSubstitution.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - public static DiagnosticDescriptor UnusedReceivedForOrdinaryMethod { get; } = + public static DiagnosticDescriptor NonVirtualWhenSetupSpecification { get; } = CreateDiagnosticDescriptor( - name: nameof(UnusedReceivedForOrdinaryMethod), - id: DiagnosticIdentifiers.UnusedReceived, - category: DiagnosticCategories.Usage, + name: nameof(NonVirtualWhenSetupSpecification), + id: DiagnosticIdentifiers.NonVirtualWhenSetupSpecification, + category: DiagnosticCategory.NonVirtualSubstitution.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -39,7 +40,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(PartialSubstituteForUnsupportedType), id: DiagnosticIdentifiers.PartialSubstituteForUnsupportedType, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -47,7 +48,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(SubstituteForWithoutAccessibleConstructor), id: DiagnosticIdentifiers.SubstituteForWithoutAccessibleConstructor, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -55,7 +56,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(SubstituteForConstructorParametersMismatch), id: DiagnosticIdentifiers.SubstituteForConstructorParametersMismatch, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -63,7 +64,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(SubstituteForInternalMember), id: DiagnosticIdentifiers.SubstituteForInternalMember, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -71,7 +72,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(SubstituteConstructorMismatch), id: DiagnosticIdentifiers.SubstituteConstructorMismatch, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -79,7 +80,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(SubstituteMultipleClasses), id: DiagnosticIdentifiers.SubstituteMultipleClasses, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -87,7 +88,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(SubstituteConstructorArgumentsForInterface), id: DiagnosticIdentifiers.SubstituteConstructorArgumentsForInterface, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -95,31 +96,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(SubstituteConstructorArgumentsForDelegate), id: DiagnosticIdentifiers.SubstituteConstructorArgumentsForDelegate, - category: DiagnosticCategories.Usage, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static DiagnosticDescriptor NonVirtualReceivedSetupSpecification { get; } = - CreateDiagnosticDescriptor( - name: nameof(NonVirtualReceivedSetupSpecification), - id: DiagnosticIdentifiers.NonVirtualReceivedSetupSpecification, - category: DiagnosticCategories.Usage, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static DiagnosticDescriptor NonVirtualWhenSetupSpecification { get; } = - CreateDiagnosticDescriptor( - name: nameof(NonVirtualWhenSetupSpecification), - id: DiagnosticIdentifiers.NonVirtualWhenSetupSpecification, - category: DiagnosticCategories.Usage, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static DiagnosticDescriptor ReEntrantSubstituteCall { get; } = - CreateDiagnosticDescriptor( - name: nameof(ReEntrantSubstituteCall), - id: DiagnosticIdentifiers.ReEntrantSubstituteCall, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.SubstituteCreation.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -127,7 +104,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(CallInfoArgumentOutOfRange), id: DiagnosticIdentifiers.CallInfoArgumentOutOfRange, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.ArgumentSpecification.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -135,7 +112,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(CallInfoCouldNotConvertParameterAtPosition), id: DiagnosticIdentifiers.CallInfoCouldNotConvertParameterAtPosition, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.ArgumentSpecification.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -143,7 +120,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(CallInfoCouldNotFindArgumentToThisCall), id: DiagnosticIdentifiers.CallInfoCouldNotFindArgumentToThisCall, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.ArgumentSpecification.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -151,7 +128,7 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(CallInfoMoreThanOneArgumentOfType), id: DiagnosticIdentifiers.CallInfoMoreThanOneArgumentOfType, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.ArgumentSpecification.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -159,17 +136,41 @@ internal class DiagnosticDescriptors CreateDiagnosticDescriptor( name: nameof(CallInfoArgumentSetWithIncompatibleValue), id: DiagnosticIdentifiers.CallInfoArgumentSetWithIncompatibleValue, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.ArgumentSpecification.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); public static DiagnosticDescriptor CallInfoArgumentIsNotOutOrRef { get; } = CreateDiagnosticDescriptor( name: nameof(CallInfoArgumentIsNotOutOrRef), id: DiagnosticIdentifiers.CallInfoArgumentIsNotOutOrRef, - category: DiagnosticCategories.Usage, + category: DiagnosticCategory.ArgumentSpecification.GetDisplayName(), defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); + public static DiagnosticDescriptor ReEntrantSubstituteCall { get; } = + CreateDiagnosticDescriptor( + name: nameof(ReEntrantSubstituteCall), + id: DiagnosticIdentifiers.ReEntrantSubstituteCall, + category: DiagnosticCategory.CallConfiguration.GetDisplayName(), + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor UnusedReceived { get; } = + CreateDiagnosticDescriptor( + name: nameof(UnusedReceived), + id: DiagnosticIdentifiers.UnusedReceived, + category: DiagnosticCategory.Usage.GetDisplayName(), + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor UnusedReceivedForOrdinaryMethod { get; } = + CreateDiagnosticDescriptor( + name: nameof(UnusedReceivedForOrdinaryMethod), + id: DiagnosticIdentifiers.UnusedReceived, + category: DiagnosticCategory.Usage.GetDisplayName(), + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + private static DiagnosticDescriptor CreateDiagnosticDescriptor( string name, string id, string category, DiagnosticSeverity defaultSeverity, bool isEnabledByDefault) { diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs index 1903d04c..932314e6 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticIdentifiers.cs @@ -2,24 +2,28 @@ { internal class DiagnosticIdentifiers { - public const string NonVirtualSetupSpecification = "NS1001"; - public const string UnusedReceived = "NS1002"; - public const string PartialSubstituteForUnsupportedType = "NS1003"; - public const string SubstituteForWithoutAccessibleConstructor = "NS1004"; - public const string SubstituteForConstructorParametersMismatch = "NS1005"; - public const string SubstituteForInternalMember = "NS1006"; - public const string SubstituteConstructorMismatch = "NS1007"; - public const string SubstituteMultipleClasses = "NS1008"; - public const string SubstituteConstructorArgumentsForInterface = "NS1009"; - public const string SubstituteConstructorArgumentsForDelegate = "NS1010"; - public const string NonVirtualReceivedSetupSpecification = "NS1011"; - public const string NonVirtualWhenSetupSpecification = "NS1012"; - public const string ReEntrantSubstituteCall = "NS1013"; - public const string CallInfoArgumentOutOfRange = "NS1014"; - public const string CallInfoCouldNotConvertParameterAtPosition = "NS1015"; - public const string CallInfoCouldNotFindArgumentToThisCall = "NS1016"; - public const string CallInfoMoreThanOneArgumentOfType = "NS1017"; - public const string CallInfoArgumentSetWithIncompatibleValue = "NS1018"; - public const string CallInfoArgumentIsNotOutOrRef = "NS1019"; + public const string NonVirtualSetupSpecification = "NS1000"; + public const string NonVirtualReceivedSetupSpecification = "NS1001"; + public const string NonVirtualWhenSetupSpecification = "NS1002"; + + public const string PartialSubstituteForUnsupportedType = "NS2000"; + public const string SubstituteForWithoutAccessibleConstructor = "NS2001"; + public const string SubstituteForConstructorParametersMismatch = "NS2002"; + public const string SubstituteForInternalMember = "NS2003"; + public const string SubstituteConstructorMismatch = "NS2004"; + public const string SubstituteMultipleClasses = "NS2005"; + public const string SubstituteConstructorArgumentsForInterface = "NS2006"; + public const string SubstituteConstructorArgumentsForDelegate = "NS2007"; + + public const string CallInfoArgumentOutOfRange = "NS3000"; + public const string CallInfoCouldNotConvertParameterAtPosition = "NS3001"; + public const string CallInfoCouldNotFindArgumentToThisCall = "NS3002"; + public const string CallInfoMoreThanOneArgumentOfType = "NS3003"; + public const string CallInfoArgumentSetWithIncompatibleValue = "NS3004"; + public const string CallInfoArgumentIsNotOutOrRef = "NS3005"; + + public const string ReEntrantSubstituteCall = "NS4000"; + + public const string UnusedReceived = "NS5000"; } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/DisplayNameAttribute.cs b/src/NSubstitute.Analyzers.Shared/DisplayNameAttribute.cs new file mode 100644 index 00000000..d49d9c03 --- /dev/null +++ b/src/NSubstitute.Analyzers.Shared/DisplayNameAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace NSubstitute.Analyzers.Shared +{ + [AttributeUsage(AttributeTargets.Field)] + internal class DisplayNameAttribute : Attribute + { + public string Name { get; } + + public DisplayNameAttribute(string name) + { + Name = name; + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/EnumExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/EnumExtensions.cs new file mode 100644 index 00000000..a3bfbbea --- /dev/null +++ b/src/NSubstitute.Analyzers.Shared/Extensions/EnumExtensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Reflection; + +namespace NSubstitute.Analyzers.Shared.Extensions +{ + internal static class EnumExtensions + { + public static string GetDisplayName(this Enum @enum) + { + var field = @enum.GetType().GetTypeInfo().GetDeclaredField(@enum.ToString()); + return field.GetCustomAttribute().Name; + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs index 78838604..d1b77b4b 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs @@ -33,9 +33,9 @@ public void Test() }"; await VerifyCodeActions(source, new[] { - "Suppress NS001 for indexer this[] in nsubstitute.json", - "Suppress NS001 for class Foo in nsubstitute.json", - "Suppress NS001 for namespace MyNamespace in nsubstitute.json" + "Suppress NS1000 for indexer this[] in nsubstitute.json", + "Suppress NS1000 for class Foo in nsubstitute.json", + "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" }); } @@ -62,9 +62,9 @@ public void Test() }"; await VerifyCodeActions(source, new[] { - "Suppress NS001 for property Bar in nsubstitute.json", - "Suppress NS001 for class Foo in nsubstitute.json", - "Suppress NS001 for namespace MyNamespace in nsubstitute.json" + "Suppress NS1000 for property Bar in nsubstitute.json", + "Suppress NS1000 for class Foo in nsubstitute.json", + "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" }); } @@ -94,9 +94,9 @@ public void Test() }"; await VerifyCodeActions(source, new[] { - "Suppress NS001 for method Bar in nsubstitute.json", - "Suppress NS001 for class Foo in nsubstitute.json", - "Suppress NS001 for namespace MyNamespace in nsubstitute.json" + "Suppress NS1000 for method Bar in nsubstitute.json", + "Suppress NS1000 for class Foo in nsubstitute.json", + "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" }); } diff --git a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs index 8ee125e4..ea802aaa 100644 --- a/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.Shared/DiagnosticIdentifierTests/DiagnosticIdentifierTests.cs @@ -1,7 +1,10 @@ +using System; using System.Linq; using System.Reflection; using FluentAssertions; +using Microsoft.CodeAnalysis; using NSubstitute.Analyzers.Shared; +using NSubstitute.Analyzers.Shared.Extensions; using Xunit; namespace NSubstitute.Analyzers.Tests.Shared.DiagnosticIdentifierTests @@ -10,37 +13,41 @@ public class DiagnosticIdentifierTests { private const string IdentifierPrefix = "NS"; - private readonly FieldInfo[] _diagnosticIdentifiers = typeof(DiagnosticIdentifiers) + private static readonly FieldInfo[] DiagnosticIdentifiers = typeof(DiagnosticIdentifiers) .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) .Where(fieldInfo => fieldInfo.IsLiteral && fieldInfo.IsInitOnly == false) .ToArray(); + private static readonly PropertyInfo[] DiagnosticDescriptors = typeof(DiagnosticDescriptors) + .GetProperties(BindingFlags.Public | BindingFlags.Static) + .ToArray(); + [Fact] - public void DiagnosticIdentifiers_HasConstantValue() + public void DiagnosticIdentifiers_ShouldHaveConstantValue() { - _diagnosticIdentifiers.Should().BeEquivalentTo(typeof(DiagnosticIdentifiers).GetFields()); + DiagnosticIdentifiers.Should().BeEquivalentTo(typeof(DiagnosticIdentifiers).GetFields()); } [Fact] public void DiagnosticIdentifiers_StartsWithProperPrefix() { - var invalidDiagnosticNames = _diagnosticIdentifiers.Where(info => ((string)info.GetRawConstantValue()).StartsWith(IdentifierPrefix) == false); + var invalidDiagnosticNames = DiagnosticIdentifiers.Where(info => ((string)info.GetRawConstantValue()).StartsWith(IdentifierPrefix) == false); invalidDiagnosticNames.Should().BeEmpty($"because all diagnostics should start with {IdentifierPrefix} prefix"); } [Fact] - public void DiagnosticIdentifiers_BelongToSpecificCategory() + public void DiagnosticIdentifiers_ShouldBelongToSpecificCategory() { - var invalidCategories = _diagnosticIdentifiers.Where(info => GetCategoryId(info) == 0); + var invalidCategories = DiagnosticIdentifiers.Where(info => GetCategoryId(info) == 0); invalidCategories.Should().BeEmpty("because all diagnostics should belong to specific category"); } [Fact] - public void DiagnosticIdentifiersCategories_ShouldHaveConsecutiveNumbers() + public void DiagnosticIdentifiers_Categories_ShouldHaveConsecutiveNumbers() { - var groupedCategories = _diagnosticIdentifiers.Select(GetCategoryId) + var groupedCategories = DiagnosticIdentifiers.Select(GetCategoryId) .GroupBy(category => category) .Select(group => group.Key) .OrderBy(category => category) @@ -52,18 +59,29 @@ public void DiagnosticIdentifiersCategories_ShouldHaveConsecutiveNumbers() } [Fact] - public void DiagnosticIdentifiersWithinCategory_ShouldHaveConsecutiveNumbers() + public void DiagnosticIdentifiers_WithinCategory_ShouldHaveConsecutiveNumbers() { - var groupedIdentifiers = _diagnosticIdentifiers + var groupedIdentifiers = DiagnosticIdentifiers .GroupBy(GetCategoryId) .Select(group => group.Select(GetDiagnosticId).OrderBy(diagnostic => diagnostic).ToList()) .ToList(); - var expectedGroupedIdentifiers = groupedIdentifiers.Select(group => Enumerable.Range(1, group.Count)); + var expectedGroupedIdentifiers = groupedIdentifiers.Select(group => Enumerable.Range(0, group.Count)); groupedIdentifiers.Should().BeEquivalentTo(expectedGroupedIdentifiers); } + [Fact] + public void DiagnosticDescriptors_Categories_ShouldMatchDiagnosticIdentifiers() + { + var diagnosticDescriptors = DiagnosticDescriptors.Select(desc => (DiagnosticDescriptor)desc.GetValue(null)).ToList(); + var descriptionEnumMap = ((DiagnosticCategory[])Enum.GetValues(typeof(DiagnosticCategory))).ToDictionary(value => value.GetDisplayName()); + + var invalidCategoriesDescriptor = diagnosticDescriptors.Where(desc => (int)descriptionEnumMap[desc.Category] != GetCategoryId(desc.Id)); + + invalidCategoriesDescriptor.Should().BeEmpty("because descriptor category should match identifier category"); + } + private int GetCategoryId(FieldInfo fieldInfo) { return GetCategoryId((string)fieldInfo.GetRawConstantValue()); diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs index 9d751e93..85c92de4 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs @@ -40,9 +40,9 @@ End Class await VerifyCodeActions(source, new[] { - "Suppress NS001 for indexer Item in nsubstitute.json", - "Suppress NS001 for class Foo in nsubstitute.json", - "Suppress NS001 for namespace MyNamespace in nsubstitute.json" + "Suppress NS1000 for indexer Item in nsubstitute.json", + "Suppress NS1000 for class Foo in nsubstitute.json", + "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" }); } @@ -72,9 +72,9 @@ End Class await VerifyCodeActions(source, new[] { - "Suppress NS001 for property Bar in nsubstitute.json", - "Suppress NS001 for class Foo in nsubstitute.json", - "Suppress NS001 for namespace MyNamespace in nsubstitute.json" + "Suppress NS1000 for property Bar in nsubstitute.json", + "Suppress NS1000 for class Foo in nsubstitute.json", + "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" }); } @@ -103,9 +103,9 @@ End Namespace "; await VerifyCodeActions(source, new[] { - "Suppress NS001 for method Bar in nsubstitute.json", - "Suppress NS001 for class Foo in nsubstitute.json", - "Suppress NS001 for namespace MyNamespace in nsubstitute.json" + "Suppress NS1000 for method Bar in nsubstitute.json", + "Suppress NS1000 for class Foo in nsubstitute.json", + "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" }); } From b2980de64a1c82343eb19f564e20acc5aa515b4f Mon Sep 17 00:00:00 2001 From: tpodolak Date: Sat, 6 Oct 2018 18:35:27 +0200 Subject: [PATCH 3/3] [GH-40] - using string interpolation rather than hardcoded strings in tests --- ...pSuppressDiagnosticsCodeFixActionsTests.cs | 19 ++++++++++--------- ...pSuppressDiagnosticsCodeFixActionsTests.cs | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs index d1b77b4b..2c3781a9 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using NSubstitute.Analyzers.CSharp.CodeFixProviders; using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Shared; using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; using Xunit; @@ -33,9 +34,9 @@ public void Test() }"; await VerifyCodeActions(source, new[] { - "Suppress NS1000 for indexer this[] in nsubstitute.json", - "Suppress NS1000 for class Foo in nsubstitute.json", - "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for indexer this[] in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for class Foo in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for namespace MyNamespace in nsubstitute.json" }); } @@ -62,9 +63,9 @@ public void Test() }"; await VerifyCodeActions(source, new[] { - "Suppress NS1000 for property Bar in nsubstitute.json", - "Suppress NS1000 for class Foo in nsubstitute.json", - "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for property Bar in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for class Foo in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for namespace MyNamespace in nsubstitute.json" }); } @@ -94,9 +95,9 @@ public void Test() }"; await VerifyCodeActions(source, new[] { - "Suppress NS1000 for method Bar in nsubstitute.json", - "Suppress NS1000 for class Foo in nsubstitute.json", - "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for method Bar in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for class Foo in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for namespace MyNamespace in nsubstitute.json" }); } diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs index 85c92de4..819f0d16 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/NonVirtualSetupAnalyzerSuppressDiagnosticsCodeFixProviderTests/NonVirtualSetupSuppressDiagnosticsCodeFixActionsTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.Shared; using NSubstitute.Analyzers.Shared.Settings; using NSubstitute.Analyzers.Shared.TinyJson; using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; @@ -40,9 +41,9 @@ End Class await VerifyCodeActions(source, new[] { - "Suppress NS1000 for indexer Item in nsubstitute.json", - "Suppress NS1000 for class Foo in nsubstitute.json", - "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for indexer Item in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for class Foo in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for namespace MyNamespace in nsubstitute.json" }); } @@ -72,9 +73,9 @@ End Class await VerifyCodeActions(source, new[] { - "Suppress NS1000 for property Bar in nsubstitute.json", - "Suppress NS1000 for class Foo in nsubstitute.json", - "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for property Bar in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for class Foo in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for namespace MyNamespace in nsubstitute.json" }); } @@ -103,9 +104,9 @@ End Namespace "; await VerifyCodeActions(source, new[] { - "Suppress NS1000 for method Bar in nsubstitute.json", - "Suppress NS1000 for class Foo in nsubstitute.json", - "Suppress NS1000 for namespace MyNamespace in nsubstitute.json" + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for method Bar in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for class Foo in nsubstitute.json", + $"Suppress {DiagnosticIdentifiers.NonVirtualSetupSpecification} for namespace MyNamespace in nsubstitute.json" }); }