diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/Analyzers/Logging/ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzer.cs b/src/Dhgms.GripeWithRoslyn.Analyzer/Analyzers/Logging/ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzer.cs new file mode 100644 index 0000000..f5b565f --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/Analyzers/Logging/ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzer.cs @@ -0,0 +1,215 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Language; +using Dhgms.GripeWithRoslyn.Analyzer.CodeCracker.Extensions; +using Dhgms.GripeWithRoslyn.Analyzer.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Logging +{ + /// + /// Analyzer for checking a constructor has a logging framework instance passed into it. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzer : DiagnosticAnalyzer + { + internal const string Title = "Constructor should have a logging framework instance as the final parameter."; + + private const string MessageFormat = Title; + + private const string Category = SupportedCategories.Design; + + private readonly DiagnosticDescriptor _rule; + + /// + /// Initializes a new instance of the class. + /// + public ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzer() + { + _rule = new DiagnosticDescriptor( + DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Title, + MessageFormat, + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: DiagnosticResultDescriptionFactory.ConstructorShouldAcceptLoggingFrameworkArgument()); + } + + /// + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(_rule); + + /// + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.RegisterSyntaxNodeAction(AnalyzeInvocationExpression, SyntaxKind.ConstructorDeclaration); + } + + private static string GetFullName( + ConstructorDeclarationSyntax constructorDeclarationSyntax, + ClassDeclarationSyntax classDeclarationSyntax) + { + var namespaceDeclarationSyntax = constructorDeclarationSyntax.GetAncestor(); + + var namespaceName = namespaceDeclarationSyntax.Name.ToString(); + return $"global::{namespaceName}.{classDeclarationSyntax.Identifier}"; + } + + private void AnalyzeInvocationExpression(SyntaxNodeAnalysisContext context) + { + var node = context.Node; + + var constructorDeclarationSyntax = (ConstructorDeclarationSyntax)context.Node; + var classDeclarationSyntax = constructorDeclarationSyntax.GetAncestor(); + + // skip if the class implements whipstaff ILogMessageActions or ILogMessageActionsWrapper + // or Splat IEnableLogger + var baseClasses = Array.Empty(); + + var interfaces = new[] + { + "global::Whipstaff.Core.Logging.ILogMessageActions", + "global::Whipstaff.Core.Logging.ILogMessageActionsWrapper", + "global::Splat.IEnableLogger" + }; + + if (classDeclarationSyntax.HasImplementedAnyOfType(baseClasses, interfaces, context.SemanticModel)) + { + return; + } + + // we can also skip out if the class has no methods + // as there won't be any logging going on. + if (!classDeclarationSyntax.ChildNodes().OfType().Any()) + { + return; + } + + // check the parameters + var parametersList = constructorDeclarationSyntax.ParameterList.Parameters; + + if (parametersList.Count == 0) + { + // no parameters, so no logging framework + LogWarning(context, node); + return; + } + + var lastParameter = parametersList.Last(); + var lastParameterType = lastParameter.Type; + if (lastParameterType == null) + { + // this is a problem, as we can't determine the type + LogWarning(context, node); + return; + } + + var typeInfo = ModelExtensions.GetTypeInfo(context.SemanticModel, lastParameterType); + var argType = typeInfo.Type; + + if (argType == null) + { + // this is a problem, as we can't determine the type + LogWarning(context, node); + return; + } + + // TODO: refactor this to take an array of types to check for, with an optional generic types array + var myType = GetFullName(constructorDeclarationSyntax, classDeclarationSyntax); + var typeFullName = argType.GetFullName(); + if (typeFullName.Equals( + $"global::XUnit.Abstractions.ITestOutputHelper", + StringComparison.Ordinal)) + { + return; + } + + if (typeFullName.Equals( + $"global::Microsoft.Extensions.Logging.ILogger", + StringComparison.Ordinal)) + { + CheckGenericArgument(context, lastParameter, node, myType); + + return; + } + + // check if implementing Whipstaff.Core.Logging.ILogMessageActionsWrapper + var lastParameterAllInterfaces = argType.AllInterfaces; + foreach (var namedTypeSymbol in lastParameterAllInterfaces) + { + var interfaceName = namedTypeSymbol.GetFullName(); + if (interfaceName.Equals( + $"global::Whipstaff.Core.Logging.ILogMessageActionsWrapper", + StringComparison.Ordinal) + && namedTypeSymbol.TypeArguments.Any(x => x.GetFullName().Equals(myType, StringComparison.Ordinal))) + { + return; + } + } + + LogWarning(context, node); + } + + private void CheckGenericArgument( + SyntaxNodeAnalysisContext context, + ParameterSyntax lastParameter, + SyntaxNode node, + string myType) + { + // var genericArgs = argType.GetGenericArguments(); + var childNodes = lastParameter.ChildNodes(); + + // QualifiedNameSyntax + var qualifiedNameSyntax = childNodes.OfType().FirstOrDefault(); + + // GenericNameSyntax + var genericNameSyntax = qualifiedNameSyntax.ChildNodes().OfType().ToArray(); + + // GenericTokenSyntax + var genericTokenSyntax = genericNameSyntax[0]; + + // type arg list + var typeArgumentList = genericTokenSyntax.TypeArgumentList; + var typeArgumentListArgs = typeArgumentList.Arguments; + + // we should only have 1 arg for ILogger. + if (typeArgumentListArgs.Count != 1) + { + LogWarning(context, node); + return; + } + + var genericArgType = ModelExtensions.GetTypeInfo(context.SemanticModel, typeArgumentListArgs[0]); + var genericArgTypeType = genericArgType.Type; + if (genericArgTypeType == null) + { + // this is a problem, as we can't determine the type + LogWarning(context, node); + return; + } + + var genericArgTypeTypeFullName = genericArgTypeType.GetFullName(); + if (!genericArgTypeTypeFullName.Equals( + myType, + StringComparison.Ordinal)) + { + LogWarning(context, node); + } + } + + private void LogWarning(SyntaxNodeAnalysisContext context, SyntaxNode node) + { + context.ReportDiagnostic(Diagnostic.Create(_rule, node.GetLocation())); + } + } +} diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/Analyzers/Logging/MethodShouldInvokeLoggingActionAnalyzer.cs b/src/Dhgms.GripeWithRoslyn.Analyzer/Analyzers/Logging/MethodShouldInvokeLoggingActionAnalyzer.cs new file mode 100644 index 0000000..cf71c69 --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/Analyzers/Logging/MethodShouldInvokeLoggingActionAnalyzer.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Logging +{ + internal class MethodShouldInvokeLoggingActionAnalyzer + { + } +} diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/Dhgms.GripeWithRoslyn.Analyzer.csproj b/src/Dhgms.GripeWithRoslyn.Analyzer/Dhgms.GripeWithRoslyn.Analyzer.csproj index 8f01b05..aaa7338 100644 --- a/src/Dhgms.GripeWithRoslyn.Analyzer/Dhgms.GripeWithRoslyn.Analyzer.csproj +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/Dhgms.GripeWithRoslyn.Analyzer.csproj @@ -8,6 +8,7 @@ true $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput false + latest diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticIdsHelper.cs b/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticIdsHelper.cs index c46f8e5..1823404 100644 --- a/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticIdsHelper.cs +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticIdsHelper.cs @@ -57,5 +57,7 @@ internal static class DiagnosticIdsHelper internal static string DoNotUseEnumToString => "GR0025"; internal static string DoNotUseXUnitInlineDataAttribute => "GR0026"; + + internal static string ConstructorShouldAcceptLoggingFrameworkArgument => "GR0027"; } } diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticResultDescriptionFactory.cs b/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticResultDescriptionFactory.cs new file mode 100644 index 0000000..3930343 --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticResultDescriptionFactory.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; + +namespace Dhgms.GripeWithRoslyn.Analyzer +{ + internal static class DiagnosticResultDescriptionFactory + { + internal static string ConstructorShouldAcceptLoggingFrameworkArgument() => $"Constructors should have a final parameter of \nMicrosoft.Extensions.Logging.ILogging or, \na sublass of Whipstaff.Core.ILogMessageActionsWrapper or,\nXUnit.Abstractions.ITestOutputHelper.\n\nThis is to encourage a design that contains sufficient logging."; + } +} diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticResultTitleFactory.cs b/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticResultTitleFactory.cs new file mode 100644 index 0000000..f0779f7 --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/DiagnosticResultTitleFactory.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Dhgms.GripeWithRoslyn.Analyzer +{ + internal static class DiagnosticResultTitleFactory + { + internal static string ConstructorShouldAcceptLoggingFrameworkArgument() => "Constructor should have a logging framework instance as the final parameter."; + } +} diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/Extensions/BaseTypeSyntaxExtensions.cs b/src/Dhgms.GripeWithRoslyn.Analyzer/Extensions/BaseTypeSyntaxExtensions.cs new file mode 100644 index 0000000..9648f66 --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/Extensions/BaseTypeSyntaxExtensions.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Dhgms.GripeWithRoslyn.Analyzer.Extensions +{ + /// + /// Extensions for . + /// + public static class BaseTypeSyntaxExtensions + { + /// + /// Checks if the has implemented any of the specified types. + /// + /// The base type syntax node to check. + /// The base classes to check for. + /// The interfaces to check for. + /// The semantic model for the code being checked. + /// Whether the has implemented any of the specified types. + public static bool HasImplementedAnyOfType( + this BaseTypeSyntax baseTypeSyntax, + string[] baseClasses, + string[] interfaces, + SemanticModel semanticModel) + { + var typeSyntax = baseTypeSyntax.Type; + var baseTypeInfo = semanticModel.GetTypeInfo(typeSyntax); + var baseTypeSymbol = baseTypeInfo.Type; + + if (baseTypeSymbol == null) + { + return false; + } + + var baseTypeFullName = baseTypeSymbol.GetFullName(); + + if (baseClasses.Any(bc => bc.Equals(baseTypeFullName, StringComparison.Ordinal))) + { + return true; + } + + if (interfaces.Any(i => i.Equals(baseTypeFullName, StringComparison.Ordinal))) + { + return true; + } + + if (baseTypeSymbol.AllInterfaces.Any(symbol => + { + var fn = symbol.GetFullName(); + return interfaces.Any(i => i.Equals(fn, StringComparison.Ordinal)); + })) + { + return true; + } + + return false; + } + } +} diff --git a/src/Dhgms.GripeWithRoslyn.Analyzer/Extensions/ClassDeclarationSyntaxExtensions.cs b/src/Dhgms.GripeWithRoslyn.Analyzer/Extensions/ClassDeclarationSyntaxExtensions.cs new file mode 100644 index 0000000..6fc9bb3 --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.Analyzer/Extensions/ClassDeclarationSyntaxExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Dhgms.GripeWithRoslyn.Analyzer.Extensions +{ + /// + /// Extensions for . + /// + public static class ClassDeclarationSyntaxExtensions + { + /// + /// Checks if the has implemented any of the specified types. + /// + /// The class declaration node to check. + /// The base classes to check for. + /// The interfaces to check for. + /// The semantic model for the code being checked. + /// Whether the has implemented any of the specified types. + public static bool HasImplementedAnyOfType( + this ClassDeclarationSyntax classDeclarationSyntax, + string[] baseClasses, + string[] interfaces, + SemanticModel semanticModel) + { + var baseList = classDeclarationSyntax.BaseList; + if (baseList == null) + { + return false; + } + + var baseTypes = baseList.Types; + if (baseTypes.Count < 1) + { + return false; + } + + foreach (var baseTypeSyntax in baseTypes) + { + if (baseTypeSyntax.HasImplementedAnyOfType( + baseClasses, + interfaces, + semanticModel)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Dhgms.GripeWithRoslyn.UnitTests/Analyzers/Logging/ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzerTest.cs b/src/Dhgms.GripeWithRoslyn.UnitTests/Analyzers/Logging/ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzerTest.cs new file mode 100644 index 0000000..5826012 --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.UnitTests/Analyzers/Logging/ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzerTest.cs @@ -0,0 +1,327 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Dhgms.GripeWithRoslyn.Analyzer; +using Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Logging; +using Dhgms.GripeWithRoslyn.UnitTests.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Xunit; + +namespace Dhgms.GripeWithRoslyn.UnitTests.Analyzers.Logging +{ + /// + /// Unit tests for the class. + /// + public sealed class ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzerTest : CodeFixVerifier + { + /// + /// Test to ensure good code doesn't return a warning. + /// + [Fact] + public void ReturnsNoWarnings() + { + var test = @" + namespace Microsoft.Extensions.Logging + { + public interface ILogger + { + } + } + + namespace Whipstaff.Core.Logging + { + public abstract class AbstractLogMessageActionsWrapper : ILogMessageActionsWrapper + { + } + + public interface ILogMessageActionsWrapper + { + } + } + + namespace ConsoleApplication1 + { + using XUnit; + + public class TypeWithCorrectLoggerType + { + public TypeWithWrongLoggerType(Microsoft.Extensions.Logging.ILogger logger) + { + } + + public void SomeMethod() + { + } + } + + public class TypeWithLoggerTypeInCorrectPosition + { + public TypeWithLoggerTypeInCorrectPosition(string someArg, Microsoft.Extensions.Logging.ILogger logger) + { + } + + public void SomeMethod() + { + } + } + + /// + /// This should not be flagged as it is a message action helper. + /// + public sealed class TypeWithCorrectLoggerTypeAndLogMessageActionsInCorrectOrderMessageActions : Whipstaff.Core.Logging.ILogMessageActions + { + public TypeWithCorrectLoggerTypeAndLogMessageActionsInCorrectOrderMessageActions() + { + } + + public void SomeLoggingMethod(int someId) + { + } + } + + public class TypeWithCorrectLoggerTypeAndLogMessageActionsInCorrectOrder + { + public TypeWithCorrectLoggerTypeAndLogMessageActionsInCorrectOrder(TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrderMessageActions someArg, Microsoft.Extensions.Logging.ILogger logger) + { + } + + public void SomeMethod() + { + } + } + + public sealed class TypeWithLogMessageActionWrapperPassedMessageActions : Whipstaff.Core.Logging.ILogMessageActions + { + public void SomeLoggingMethod(int someId) + { + } + } + + public sealed class LogMessageActionWrapper : Whipstaff.Core.Logging.AbstractLogMessageActionsWrapper + { + public void SomeLoggingMethod(int someId) + { + } + } + + public class TypeWithLogMessageActionWrapperPassed + { + public TypeWithLogMessageActionWrapperPassed(LogMessageActionWrapper logMessageActionsWrapper) + { + } + + public void SomeMethod() + { + } + } + + public class TypeWithNoLoggerTypeButNoMethods + { + public TypeWithNoLoggerTypeButNoMethods() + { + } + } + }"; + + VerifyCSharpDiagnostic(test); + } + + /// + /// Test to ensure bad code returns a warning. + /// + [Fact] + public void ReturnsWarning() + { + var test = @" + namespace Microsoft.Extensions.Logging + { + public interface ILogger + { + } + } + + namespace ConsoleApplication1 + { + using XUnit; + + public class TypeWithEmptyCtor + { + public TypeWithEmptyCtor() + { + } + + public void SomeMethod() + { + } + } + + public class TypeWithSingleArgument + { + public TypeWithSingleArgument(string someArg) + { + } + + public void SomeMethod() + { + } + } + + public class TypeWithWrongLoggerType + { + public TypeWithWrongLoggerType(Microsoft.Extensions.Logging.ILogger logger) + { + } + + public void SomeMethod() + { + } + } + + public class TypeWithWrongLoggerTypeInWrongPosition + { + public TypeWithWrongLoggerTypeInWrongPosition(Microsoft.Extensions.Logging.ILogger logger, string someArg) + { + } + + public void SomeMethod() + { + } + } + + /// + /// This should not be flagged as it is a message action helper. + /// + public sealed class TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrderMessageActions : ILogMessageActions + { + public TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrder() + { + } + + public void SomeMethod() + { + } + } + + public class TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrder + { + public TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrder(Microsoft.Extensions.Logging.ILogger logger, TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrderMessageActions someArg) + { + } + + public void SomeMethod() + { + } + } + + public sealed class LogMessageActionWrapper : Whipstaff.Core.Logging.AbstractLogMessageActionsWrapper + { + public void SomeMethod() + { + } + } + + public class TypeWithWrongLogMessageType + { + public TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrder(Microsoft.Extensions.Logging.ILogger logger, TypeWithWrongLoggerTypeAndLogMessageActionsInWrongOrderMessageActions someArg) + { + } + + public void SomeMethod() + { + } + } + }"; + + var expected = new[] + { + new DiagnosticResult + { + Id = DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Message = DiagnosticResultTitleFactory.ConstructorShouldAcceptLoggingFrameworkArgument(), + Severity = DiagnosticSeverity.Warning, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 15, 13), + } + }, + new DiagnosticResult + { + Id = DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Message = DiagnosticResultTitleFactory.ConstructorShouldAcceptLoggingFrameworkArgument(), + Severity = DiagnosticSeverity.Warning, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 26, 13), + } + }, + new DiagnosticResult + { + Id = DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Message = DiagnosticResultTitleFactory.ConstructorShouldAcceptLoggingFrameworkArgument(), + Severity = DiagnosticSeverity.Warning, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 37, 13), + } + }, + new DiagnosticResult + { + Id = DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Message = DiagnosticResultTitleFactory.ConstructorShouldAcceptLoggingFrameworkArgument(), + Severity = DiagnosticSeverity.Warning, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 48, 13), + } + }, + new DiagnosticResult + { + Id = DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Message = DiagnosticResultTitleFactory.ConstructorShouldAcceptLoggingFrameworkArgument(), + Severity = DiagnosticSeverity.Warning, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 62, 13), + } + }, + new DiagnosticResult + { + Id = DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Message = DiagnosticResultTitleFactory.ConstructorShouldAcceptLoggingFrameworkArgument(), + Severity = DiagnosticSeverity.Warning, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 73, 13) + } + }, + new DiagnosticResult + { + Id = DiagnosticIdsHelper.ConstructorShouldAcceptLoggingFrameworkArgument, + Message = DiagnosticResultTitleFactory.ConstructorShouldAcceptLoggingFrameworkArgument(), + Severity = DiagnosticSeverity.Warning, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 91, 13) + } + }, + }; + + VerifyCSharpDiagnostic(test, expected); + } + + /// + protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() + { + return new ConstructorShouldAcceptLoggingFrameworkArgumentAnalyzer(); + } + } +} diff --git a/src/Dhgms.GripeWithRoslyn.UnitTests/Analyzers/Logging/MethodShouldInvokeLoggingActionAnalyzerTest.cs b/src/Dhgms.GripeWithRoslyn.UnitTests/Analyzers/Logging/MethodShouldInvokeLoggingActionAnalyzerTest.cs new file mode 100644 index 0000000..0db0df1 --- /dev/null +++ b/src/Dhgms.GripeWithRoslyn.UnitTests/Analyzers/Logging/MethodShouldInvokeLoggingActionAnalyzerTest.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Dhgms.GripeWithRoslyn.Analyzer.Analyzers.Logging; + +namespace Dhgms.GripeWithRoslyn.UnitTests.Analyzers.Logging +{ + /// + /// Unit tests for the class. + /// + public sealed class MethodShouldInvokeLoggingActionAnalyzerTest + { + } +} diff --git a/version.json b/version.json index cdc9437..6bc3574 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "1.12", + "version": "1.13", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/preview/.*",