diff --git a/Philips.CodeAnalysis.Common/ParameterPredicates.cs b/Philips.CodeAnalysis.Common/ParameterPredicates.cs new file mode 100644 index 000000000..f8721e575 --- /dev/null +++ b/Philips.CodeAnalysis.Common/ParameterPredicates.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Philips.CodeAnalysis.Common +{ + public static class ParameterPredicates + { + public static bool IsEnum(this ParameterSyntax p, SyntaxNodeAnalysisContext context) + { + if (context.SemanticModel.GetDeclaredSymbol(p).Type is ITypeSymbol typ) + { + return typ.TypeKind == TypeKind.Enum; + } + return false; + } + } +} diff --git a/Philips.CodeAnalysis.Common/Philips.CodeAnalysis.Common.csproj b/Philips.CodeAnalysis.Common/Philips.CodeAnalysis.Common.csproj index 4b713672d..07c52838b 100644 --- a/Philips.CodeAnalysis.Common/Philips.CodeAnalysis.Common.csproj +++ b/Philips.CodeAnalysis.Common/Philips.CodeAnalysis.Common.csproj @@ -12,6 +12,7 @@ 1.0.3.0 + diff --git a/Philips.CodeAnalysis.Common/SingleDiagnosticAnalyzer{TU}.cs b/Philips.CodeAnalysis.Common/SingleDiagnosticAnalyzer{TU}.cs index 76136620d..0a60b821a 100644 --- a/Philips.CodeAnalysis.Common/SingleDiagnosticAnalyzer{TU}.cs +++ b/Philips.CodeAnalysis.Common/SingleDiagnosticAnalyzer{TU}.cs @@ -1,11 +1,15 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Linq; +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.Common { public abstract class SingleDiagnosticAnalyzer : SingleDiagnosticAnalyzer where T : SyntaxNode where TSyntaxNodeAction : SyntaxNodeAction, new() @@ -39,26 +43,23 @@ public override void Initialize(AnalysisContext context) return; } - startContext.RegisterSyntaxNodeAction(StartAnalysis, syntaxKind); + startContext.RegisterSyntaxNodeAction(RunAnalysis, syntaxKind); }); } - private void StartAnalysis(SyntaxNodeAnalysisContext context) + private void RunAnalysis(SyntaxNodeAnalysisContext context) { - GeneratedCodeDetector generatedCodeDetector = new(); - if (generatedCodeDetector.IsGeneratedCode(context)) - { - return; - } - - TSyntaxNodeAction syntaxNodeAction = new() - { - Context = context, - Node = (T)context.Node, - Rule = Rule, - Analyzer = this, - }; - syntaxNodeAction.Analyze(); + _ = new List { new() } + .Filter((g) => !g.IsGeneratedCode(context)) + .Select((g) => new TSyntaxNodeAction() + { + Context = context, + Node = (T)context.Node, + Rule = Rule, + Analyzer = this, + }) + .SelectMany((s) => s.Analyze()) + .Iter(context.ReportDiagnostic); } protected virtual SyntaxKind GetSyntaxKind() diff --git a/Philips.CodeAnalysis.Common/SyntaxNodeAction.cs b/Philips.CodeAnalysis.Common/SyntaxNodeAction.cs index f1e5296de..2024ca981 100644 --- a/Philips.CodeAnalysis.Common/SyntaxNodeAction.cs +++ b/Philips.CodeAnalysis.Common/SyntaxNodeAction.cs @@ -1,5 +1,6 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -14,12 +15,11 @@ public abstract class SyntaxNodeAction where T : SyntaxNode protected Helper Helper { get; init; } = new Helper(); - public abstract void Analyze(); + public abstract IEnumerable Analyze(); - public void ReportDiagnostic(Location location = null, params object[] messageArgs) + public Diagnostic PrepareDiagnostic(Location location = null, params object[] messageArgs) { - var diagnostic = Diagnostic.Create(Rule, location, messageArgs); - Context.ReportDiagnostic(diagnostic); + return Diagnostic.Create(Rule, location, messageArgs); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidEnumParametersAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidEnumParametersAnalyzer.cs index 334f32981..b6806e0be 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidEnumParametersAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidEnumParametersAnalyzer.cs @@ -11,6 +11,7 @@ using Philips.CodeAnalysis.Common; using static LanguageExt.Prelude; +using LanguageExt; namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Cardinality { @@ -29,25 +30,20 @@ public AvoidEnumParametersAnalyzer() public class AvoidEnumParametersSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { - _ = List(Node) + return List(Node) .Filter((m) => !m.IsOverridden()) - .SelectMany(AnalyzeMethodParameters) - .Iter(Context.ReportDiagnostic); + .SelectMany(AnalyzeMethodParameters); } private IEnumerable AnalyzeMethodParameters(MethodDeclarationSyntax m) { - return m.ParameterList.Parameters.SelectMany((p) => AnalyzeParam(m, p)); + return m.ParameterList.Parameters + .Filter((p) => p.IsEnum(Context)) + .Select((p) => m.CreateDiagnostic(Rule)); } - private Optional AnalyzeParam(MethodDeclarationSyntax m, ParameterSyntax p) - { - return Optional(Context.SemanticModel.GetDeclaredSymbol(p).Type) - .Select((t) => m.CreateDiagnostic(Rule)); - - } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidVoidReturnAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidVoidReturnAnalyzer.cs index b6dfa42c0..da1c72e3d 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidVoidReturnAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/AvoidVoidReturnAnalyzer.cs @@ -1,5 +1,6 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -23,13 +24,12 @@ public AvoidVoidReturnAnalyzer() public class AvoidVoidReturnSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { - _ = Optional(Node) + return Optional(Node) .Filter((m) => m.ReturnsVoid()) .Filter((m) => !m.IsOverridden()) - .Select((m) => m.CreateDiagnostic(Rule)) - .Iter(Context.ReportDiagnostic); + .Select((m) => m.CreateDiagnostic(Rule)); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/MethodPredicates.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/MethodPredicates.cs index c15f911df..ce454afa9 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/MethodPredicates.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Cardinality/MethodPredicates.cs @@ -12,8 +12,8 @@ namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Cardinality { - public static class MethodPredicates - { + public static class MethodPredicates + { public static bool IsNotOverridenMethod(MethodDeclarationSyntax m) { return !m.Modifiers.Any(SyntaxKind.OverrideKeyword); @@ -34,5 +34,5 @@ public static (SyntaxToken MethodId, PredefinedTypeSyntax ReturnType) MethodRetu return m.ParameterList.Parameters.Select((p) => (m.Identifier.Text, p, context.SemanticModel.GetDeclaredSymbol(p))); } - } + } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/CopyrightPresentAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/CopyrightPresentAnalyzer.cs index 3958fe5ba..57418fa1c 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/CopyrightPresentAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/CopyrightPresentAnalyzer.cs @@ -1,9 +1,10 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; - +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -11,6 +12,8 @@ using Microsoft.CodeAnalysis.Text; using Philips.CodeAnalysis.Common; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Documentation { [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -29,16 +32,17 @@ public class CopyrightPresentSyntaxNodeAction : SyntaxNodeAction Analyze() { if (Helper.IsAssemblyInfo(Context) || Helper.HasAutoGeneratedComment(Node)) { - return; + return Option.None; } if (Node.FindToken(0).IsKind(SyntaxKind.EndOfFileToken)) { - return; + return Option.None; + ; } Location location = GetSquiggleLocation(Node.SyntaxTree); @@ -47,21 +51,22 @@ public override void Analyze() if (!leadingTrivia.Any(SyntaxKind.SingleLineCommentTrivia) && !leadingTrivia.Any(SyntaxKind.RegionDirectiveTrivia)) { - ReportDiagnostic(location); - return; + return Optional(PrepareDiagnostic(location)); } // Special case: there's a #region, and the Copyright is in the name of the region if (leadingTrivia[0].IsKind(SyntaxKind.RegionDirectiveTrivia) && CheckCopyrightStatement(leadingTrivia[0])) { - return; + return Option.None; } SyntaxTrivia syntaxTrivia = leadingTrivia.FirstOrDefault(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia)); if (!CheckCopyrightStatement(syntaxTrivia)) { - ReportDiagnostic(location); + return Optional(PrepareDiagnostic(location)); } + + return Option.None; } private Location GetSquiggleLocation(SyntaxTree tree) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/DocumentUnhandledExceptionsAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/DocumentUnhandledExceptionsAnalyzer.cs index 583944d70..7df973bb3 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/DocumentUnhandledExceptionsAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/DocumentUnhandledExceptionsAnalyzer.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Documentation { /// @@ -27,11 +30,11 @@ public DocumentUnhandledExceptionsAnalyzer() public class DocumentUnhandledExceptionsSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Context.Compilation?.SyntaxTrees.FirstOrDefault()?.Options.DocumentationMode == DocumentationMode.None) { - return; + return Option.None; } IReadOnlyDictionary aliases = Helper.GetUsingAliases(Node); @@ -62,9 +65,9 @@ public override void Analyze() var methodName = Node.Identifier.Text; var remainingExceptionsString = string.Join(",", remainingExceptions); ImmutableDictionary properties = ImmutableDictionary.Empty.Add(StringConstants.ThrownExceptionPropertyKey, remainingExceptionsString); - var diagnostic = Diagnostic.Create(Rule, loc, properties, methodName, remainingExceptionsString); - Context.ReportDiagnostic(diagnostic); + return Optional(Diagnostic.Create(Rule, loc, properties, methodName, remainingExceptionsString)); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/EnableDocumentationCreationAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/EnableDocumentationCreationAnalyzer.cs index ff3fc3dcd..6d96d1d19 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/EnableDocumentationCreationAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/EnableDocumentationCreationAnalyzer.cs @@ -1,10 +1,14 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Documentation { [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -21,12 +25,13 @@ public EnableDocumentationCreationAnalyzer() public class EnableDocumentationCreationAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.SyntaxTree.Options.DocumentationMode == DocumentationMode.None) { - ReportDiagnostic(); + return Optional(PrepareDiagnostic()); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/OrderPropertyAccessorsAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/OrderPropertyAccessorsAnalyzer.cs index 3eedc3a86..403f5c91a 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/OrderPropertyAccessorsAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/OrderPropertyAccessorsAnalyzer.cs @@ -1,11 +1,16 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using System.Linq; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Documentation { [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -21,40 +26,31 @@ public OrderPropertyAccessorsAnalyzer() public class OrderPropertyAccessorsSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { - AccessorListSyntax accessors = Node.AccessorList; - - if (accessors is null) - { - return; - } - - var getIndex = -1; - var setIndex = int.MaxValue; + return Optional(Node.AccessorList) + .SelectMany(accsessors => + accsessors.Accessors + .Fold(Option.None, ReduceSetIsBeforeGet) + .Filter(setIsBeforeGet => setIsBeforeGet) + .Select((setIsBeforeGet) => PrepareDiagnostic(accsessors.GetLocation())) + ); + } - for (var i = 0; i < accessors.Accessors.Count; i++) + private static Option ReduceSetIsBeforeGet(Option setIsBeforeGet, AccessorDeclarationSyntax accessor) + { + if (setIsBeforeGet.IsNone) { - AccessorDeclarationSyntax accessor = accessors.Accessors[i]; - if (accessor.Keyword.IsKind(SyntaxKind.GetKeyword)) { - getIndex = i; - continue; + return Optional(false); } - - // SyntaxKind.InitKeyword doesn't exist in the currently used version of Roslyn (it exists in at least 3.9.0) - if (accessor.Keyword.IsKind(SyntaxKind.SetKeyword) || accessor.Keyword.Text == "init") + else if (accessor.Keyword.IsKind(SyntaxKind.SetKeyword) || accessor.Keyword.Text == "init") { - setIndex = i; + return Optional(true); } } - - if (setIndex < getIndex) - { - Location location = accessors.GetLocation(); - ReportDiagnostic(location); - } + return setIsBeforeGet; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/RemoveCommentedCodeAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/RemoveCommentedCodeAnalyzer.cs index ae6fa068a..1d97a9c1e 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/RemoveCommentedCodeAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Documentation/RemoveCommentedCodeAnalyzer.cs @@ -1,5 +1,6 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -26,25 +27,21 @@ public class RemoveCommentedCodeSyntaxNodeAction : SyntaxNodeAction Analyze() { - System.Collections.Generic.IEnumerable comments = Node.DescendantTrivia().Where(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)); - if (!comments.Any()) - { - return; - } - - var previousViolationLine = InitialCodeLine; - foreach (Location location in comments.Where(comment => comment.ToString().EndsWith(";")) - .Select(node => node.GetLocation())) - { - var lineNumber = location.GetLineSpan().StartLinePosition.Line + 1; - if (lineNumber - previousViolationLine > 1) + return Node.DescendantTrivia().Where(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) + .Where(comment => comment.ToString().EndsWith(";")) + .Select(node => node.GetLocation()) + .Fold<(List Diagnostics, int Line), Location>((new List(), InitialCodeLine), + (prevStep, location) => { - ReportDiagnostic(location, lineNumber); - } - previousViolationLine = lineNumber; - } + var lineNumber = location.GetLineSpan().StartLinePosition.Line + 1; + if (lineNumber - prevStep.Line > 1) + { + prevStep.Diagnostics.Add(PrepareDiagnostic(location, lineNumber)); + } + return (prevStep.Diagnostics, lineNumber); + }).Diagnostics; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidArrayListAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidArrayListAnalyzer.cs index 642c68db5..334474e85 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidArrayListAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidArrayListAnalyzer.cs @@ -1,11 +1,15 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using LanguageExt; +using LanguageExt.SomeHelp; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Maintainability { [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -22,7 +26,7 @@ public class AvoidArrayListSyntaxNodeAction : SyntaxNodeAction Analyze() { if (Node.Type is not SimpleNameSyntax typeName) { @@ -34,13 +38,13 @@ public override void Analyze() else { // Some thing else is mentioned here. - return; + return Option.None; } } if (!typeName.Identifier.Text.Contains("ArrayList")) { - return; + return Option.None; } // Sanity check if we got ArrayList from the correct namespace. @@ -49,8 +53,9 @@ public override void Analyze() { var variableName = Node.Variables.FirstOrDefault()?.Identifier.Text ?? string.Empty; Location location = typeName.GetLocation(); - ReportDiagnostic(location, variableName); + return PrepareDiagnostic(location, variableName).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidDuplicateStringsAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidDuplicateStringsAnalyzer.cs index 45e3c61c3..21eacc26f 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidDuplicateStringsAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidDuplicateStringsAnalyzer.cs @@ -1,6 +1,7 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; @@ -9,6 +10,9 @@ using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using LanguageExt; +using LanguageExt.SomeHelp; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Maintainability { [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -32,24 +36,24 @@ protected override SyntaxKind GetSyntaxKind() public class AvoidDuplicateStringsSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Ancestors().OfType().Any()) { - return; + return Option.None; } TestHelper testHelper = new(); if (testHelper.IsInTestClass(Context)) { - return; + return Option.None; } SyntaxToken literal = Node.Token; var literalText = literal.Text.Trim('\\', '\"'); if (string.IsNullOrWhiteSpace(literalText) || literalText.Length <= 2) { - return; + return Option.None; } Location location = literal.GetLocation(); @@ -60,8 +64,9 @@ public override void Analyze() _ = usedLiterals.TryGetValue(literalText, out Location firstLocation); var firstFilename = Path.GetFileName(firstLocation.SourceTree.FilePath); var firstLineNumber = firstLocation.GetLineSpan().StartLinePosition.Line + 1; - ReportDiagnostic(location, firstFilename, firstLineNumber, literalText); + return PrepareDiagnostic(location, firstFilename, firstLineNumber, literalText).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidEmptyTypeInitializerAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidEmptyTypeInitializerAnalyzer.cs index bf90e6db0..58d03a368 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidEmptyTypeInitializerAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidEmptyTypeInitializerAnalyzer.cs @@ -1,5 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,28 +25,28 @@ public AvoidEmptyTypeInitializerAnalyzer() public class AvoidEmptyTypeInitializerSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (!Node.Modifiers.Any(SyntaxKind.StaticKeyword)) { //not a static constructor - return; + return Option.None; } if (Node.Body == null) { //during the intellisense phase the body of a constructor can be non-existent. - return; + return Option.None; } if (Node.Body.Statements.Any()) { //not empty - return; + return Option.None; } Location location = Node.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidInvocationAsArgumentAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidInvocationAsArgumentAnalyzer.cs index 6d31979e9..9091a5d27 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidInvocationAsArgumentAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidInvocationAsArgumentAnalyzer.cs @@ -1,6 +1,9 @@ // © 2022 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,45 +25,45 @@ public AvoidInvocationAsArgumentAnalyzer() public class AvoidInvocationAsArgumentSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { // We are looking for method calls as arguments if (Node.Expression is not InvocationExpressionSyntax invocationExpressionSyntax) { - return; + return Option.None; } // If it's an embedded nameof() operation, let it go. if ((invocationExpressionSyntax.Expression as IdentifierNameSyntax)?.Identifier.Text == "nameof") { - return; + return Option.None; } // If it's calling ToString(), let it go. (ToStrings() cognitive load isn't excessive, and lots of violations) var methodName = (invocationExpressionSyntax.Expression as MemberAccessExpressionSyntax)?.Name.Identifier.Text; if (methodName is StringConstants.ToStringMethodName or StringConstants.ToArrayMethodName or StringConstants.ToListMethodName) { - return; + return Option.None; } // If nested calls (e.g., Foo(Bar(Meow()))), only trigger the outer violation Bar(Meow()) if (Node.Ancestors().OfType().Any(arg => !IsStaticMethod(arg.Expression))) { - return; + return Option.None; } // If we're within a constructor initializer (this(...) or base(...) eg), let it go ConstructorInitializerSyntax constructorInitializerSyntax = Node.Ancestors().OfType().FirstOrDefault(); if (constructorInitializerSyntax != null) { - return; + return Option.None; } // If the caller is Assert, let it go. (This is debatable, and ideally warrants a configuration option.) var caller = (Node.Parent.Parent as InvocationExpressionSyntax)?.Expression as MemberAccessExpressionSyntax; if (caller?.Expression is IdentifierNameSyntax identifier && identifier.Identifier.ValueText.Contains(@"Assert")) { - return; + return Option.None; } // If the called method is static, let it go to reduce annoyances. E.g., "Times.Once", "Mock.Of<>", "Marshal.Sizeof", etc. @@ -70,12 +73,12 @@ public override void Analyze() var isStatic = IsStaticMethod(callee); if (isStatic) { - return; + return Option.None; } } Location location = Node.GetLocation(); - ReportDiagnostic(location, Node.ToString()); + return PrepareDiagnostic(location, Node.ToString()).ToSome(); } private bool IsStaticMethod(SyntaxNode node) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidMagicNumbersAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidMagicNumbersAnalyzer.cs index 69d4d2d05..fa93e77d1 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidMagicNumbersAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidMagicNumbersAnalyzer.cs @@ -1,8 +1,11 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -27,45 +30,45 @@ public class AvoidMagicNumbersSyntaxNodeAction : SyntaxNodeAction Analyze() { TestHelper helper = new(); if (helper.IsInTestClass(Context)) { - return; + return Option.None; } if (!Node.Token.IsKind(SyntaxKind.NumericLiteralToken)) { - return; + return Option.None; } if (IsAllowedNumber(Node.Token.Text)) { - return; + return Option.None; } // Magic number are allowed in enumerations, as they give meaning to the number. if (Node.Ancestors().OfType().Any()) { - return; + return Option.None; } // If in a field, the magic number should be defined in a static field. FieldDeclarationSyntax field = Node.Ancestors().OfType().FirstOrDefault(); if (field != null && IsStaticOrConst(field)) { - return; + return Option.None; } LocalDeclarationStatementSyntax local = Node.Ancestors().OfType().FirstOrDefault(); if (local != null && local.Modifiers.Any(SyntaxKind.ConstKeyword)) { - return; + return Option.None; } Location location = Node.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } private static bool IsAllowedNumber(string text) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPragmaAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPragmaAnalyzer.cs index b5b7fde51..e81acceff 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPragmaAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPragmaAnalyzer.cs @@ -1,6 +1,9 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,18 +26,18 @@ public AvoidPragmaAnalyzer() public class AvoidPragmaSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { var myOwnId = Helper.ToDiagnosticId(DiagnosticId.AvoidPragma); if (Node.ErrorCodes.Where(e => e.IsKind(SyntaxKind.IdentifierName)) .Any(i => i.ToString().Contains(myOwnId))) { - return; + return Option.None; } CSharpSyntaxNode violation = Node; Location location = violation.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPrivateKeyPropertyAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPrivateKeyPropertyAnalyzer.cs index 6936918d5..f8424938d 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPrivateKeyPropertyAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPrivateKeyPropertyAnalyzer.cs @@ -1,5 +1,8 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -27,11 +30,11 @@ public class AvoidPrivateKeyPropertySyntaxNodeAction : SyntaxNodeAction Analyze() { if (!Node.Name.ToString().Equals(PrivateKeyProperty, System.StringComparison.Ordinal)) { - return; + return Option.None; } ITypeSymbol typeSymbol = Context.SemanticModel.GetTypeInfo(Node.Expression).Type; @@ -39,8 +42,10 @@ public override void Analyze() if (typeSymbol != null && typeSymbol.Name.Equals(ObjectType, System.StringComparison.Ordinal)) { Location location = Node.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } + + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPublicMemberVariableAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPublicMemberVariableAnalyzer.cs index a16b00c08..392a77b0c 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPublicMemberVariableAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidPublicMemberVariableAnalyzer.cs @@ -1,6 +1,9 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,28 +25,29 @@ public AvoidPublicMemberVariableAnalyzer() } public class AvoidPublicMemberVariableSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Parent.Kind() == SyntaxKind.StructDeclaration) { - return; + return Option.None; } if (Node.Modifiers.Any(SyntaxKind.PublicKeyword)) { if (Node.Modifiers.Any(SyntaxKind.ConstKeyword)) { - return; + return Option.None; } if (Node.Modifiers.Any(SyntaxKind.StaticKeyword)) { - return; + return Option.None; } Location location = Node.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidStaticMethodAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidStaticMethodAnalyzer.cs index c90ae507b..ab843c95f 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidStaticMethodAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidStaticMethodAnalyzer.cs @@ -1,7 +1,10 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,47 +26,47 @@ public AvoidStaticMethodAnalyzer() } public class AvoidStaticMethodSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { // Only analyzing static method declarations if (!Node.Modifiers.Any(SyntaxKind.StaticKeyword)) { - return; + return Option.None; } // If the method is marked "extern", let it go. if (Node.Modifiers.Any(SyntaxKind.ExternKeyword)) { - return; + return Option.None; } // If the class is static, we need to let it go. ClassDeclarationSyntax classDeclarationSyntax = Context.Node.FirstAncestorOrSelf(); if (classDeclarationSyntax == null) { - return; + return Option.None; } if (classDeclarationSyntax.Modifiers.Any(SyntaxKind.StaticKeyword)) { - return; + return Option.None; } // The Main entrypoint to the program must be static if (Node.Identifier.ValueText == @"Main") { - return; + return Option.None; } // Hunt for static members INamedTypeSymbol us = Context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); if (us == null) { - return; + return Option.None; } if (ReferencesAnotherStatic(us, Context)) { - return; + return Option.None; } // Hunt for evidence that this is a factory method @@ -72,7 +75,7 @@ public override void Analyze() ISymbol objectCreationSymbol = Context.SemanticModel.GetSymbolInfo(objectCreationExpressionSyntax).Symbol; if (SymbolEqualityComparer.Default.Equals(objectCreationSymbol?.ContainingType, us)) { - return; + return Option.None; } } @@ -80,11 +83,11 @@ public override void Analyze() var returnType = Node.ReturnType.ToString(); if (string.Equals(returnType, "IEnumerable", StringComparison.OrdinalIgnoreCase)) { - return; + return Option.None; } Location location = Node.Modifiers.First(t => t.Kind() == SyntaxKind.StaticKeyword).GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } private bool ReferencesAnotherStatic(INamedTypeSymbol us, SyntaxNodeAnalysisContext context) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThreadSleepAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThreadSleepAnalyzer.cs index 8f3ac1899..f990051ba 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThreadSleepAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThreadSleepAnalyzer.cs @@ -1,5 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -32,11 +35,11 @@ public AvoidThreadSleepSyntaxNodeAction(AttributeHelper attributeHelper) _attributeHelper = attributeHelper; } - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Expression is not MemberAccessExpressionSyntax memberAccessExpression) { - return; + return Option.None; } var memberName = memberAccessExpression.Expression.ToString(); @@ -50,9 +53,10 @@ public override void Analyze() (Context.SemanticModel.GetSymbolInfo(memberAccessExpression).Symbol is IMethodSymbol memberSymbol) && memberSymbol.ToString().StartsWith("System.Threading.Thread")) { Location location = Node.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThrowingUnexpectedExceptionsAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThrowingUnexpectedExceptionsAnalyzer.cs index 29c1fbaf0..2100ea878 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThrowingUnexpectedExceptionsAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidThrowingUnexpectedExceptionsAnalyzer.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -33,7 +35,7 @@ public class AvoidThrowingUnexpectedExceptionsSyntaxNodeAction : SyntaxNodeActio { StringConstants.ToStringMethodName, "ToString method" } }; - public override void Analyze() + public override IEnumerable Analyze() { // Determine our parent. SyntaxNode methodDeclaration = Node.Ancestors().OfType().FirstOrDefault(); @@ -42,29 +44,31 @@ public override void Analyze() methodDeclaration = Node.Ancestors().OfType().FirstOrDefault(); if (methodDeclaration == null) { - return; + return Option.None; } } - AnalyzeMethod(methodDeclaration); - AnalyzeEqualityOperator(methodDeclaration); - AnalyzeConversionOperator(methodDeclaration); - AnalyzeConstructor(methodDeclaration); - AnalyzeDestructor(methodDeclaration); + return AnalyzeMethod(methodDeclaration) + .Concat(AnalyzeEqualityOperator(methodDeclaration)) + .Concat(AnalyzeConversionOperator(methodDeclaration)) + .Concat(AnalyzeConstructor(methodDeclaration)) + .Concat(AnalyzeDestructor(methodDeclaration)); } - private void AnalyzeMethod(SyntaxNode node) + private IEnumerable AnalyzeMethod(SyntaxNode node) { // Check overriden methods of Object. if (node is MethodDeclarationSyntax method && SpecialMethods.TryGetValue(method.Identifier.Text, out var specialMethodKind)) { Location loc = Node.ThrowKeyword.GetLocation(); - ReportDiagnostic(loc, specialMethodKind); + return PrepareDiagnostic(loc, specialMethodKind).ToSome(); } + return Option.None; } - private void AnalyzeConstructor(SyntaxNode node) + private IEnumerable AnalyzeConstructor(SyntaxNode node) { + var diags = new List(); if (node is ConstructorDeclarationSyntax constructorDeclaration) { // Check constructors of an Exception. @@ -73,44 +77,48 @@ private void AnalyzeConstructor(SyntaxNode node) if (withinExceptionClass.HasValue && (bool)withinExceptionClass) { Location loc = Node.ThrowKeyword.GetLocation(); - ReportDiagnostic(loc, "constructor of an Exception derived type"); + diags.Add(PrepareDiagnostic(loc, "constructor of an Exception derived type")); } if (constructorDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)) { Location loc = Node.ThrowKeyword.GetLocation(); - ReportDiagnostic(loc, "static constructor"); + diags.Add(PrepareDiagnostic(loc, "static constructor")); } } + return diags; } - private void AnalyzeDestructor(SyntaxNode node) + private IEnumerable AnalyzeDestructor(SyntaxNode node) { if (node is DestructorDeclarationSyntax) { // Check finalizers. Location loc = Node.ThrowKeyword.GetLocation(); - ReportDiagnostic(loc, "finalizer"); + return PrepareDiagnostic(loc, "finalizer").ToSome(); } + return Option.None; } - private void AnalyzeEqualityOperator(SyntaxNode node) + private IEnumerable AnalyzeEqualityOperator(SyntaxNode node) { if (node is OperatorDeclarationSyntax { OperatorToken.Text: "==" or "!=" }) { // Check == and != operators. Location loc = Node.ThrowKeyword.GetLocation(); - ReportDiagnostic(loc, "equality comparison operator"); + return PrepareDiagnostic(loc, "equality comparison operator").ToSome(); } + return Option.None; } - private void AnalyzeConversionOperator(SyntaxNode methodDeclaration) + private IEnumerable AnalyzeConversionOperator(SyntaxNode methodDeclaration) { if (methodDeclaration is ConversionOperatorDeclarationSyntax conversion && conversion.ImplicitOrExplicitKeyword.Text == "implicit") { // Check implicit cast operators. Location loc = Node.ThrowKeyword.GetLocation(); - ReportDiagnostic(loc, "implicit cast operator"); + return PrepareDiagnostic(loc, "implicit cast operator").ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidTryParseWithoutCultureAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidTryParseWithoutCultureAnalyzer.cs index c56c44c44..1eea91342 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidTryParseWithoutCultureAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidTryParseWithoutCultureAnalyzer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -31,24 +32,24 @@ public class AvoidTryParseWithoutCultureSyntaxNodeAction : SyntaxNodeAction Analyze() { // Ignore any methods not named TryParse. if (Node.Expression is not MemberAccessExpressionSyntax memberAccessExpressionSyntax || memberAccessExpressionSyntax.Name.ToString() != TryParseMethodName) { - return; + return LanguageExt.Option.None; } // If the invoked method contains an IFormatProvider parameter, stop analyzing. if (Context.SemanticModel.GetSymbolInfo(memberAccessExpressionSyntax).Symbol is not IMethodSymbol invokedMethod || HasCultureParameter(invokedMethod)) { - return; + return LanguageExt.Option.None; } // Only display an error if the class implements an overload of TryParse that accepts IFormatProvider. if (Context.SemanticModel.GetSymbolInfo(Node).Symbol is not IMethodSymbol methodSymbol) { - return; + return LanguageExt.Option.None; } ImmutableArray members = methodSymbol.ContainingType.GetMembers(); @@ -58,8 +59,10 @@ public override void Analyze() { // There is an overload that can accept culture as a parameter. Display an error. Location location = Node.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } + + return LanguageExt.Option.None; } private static bool HasCultureParameter(IMethodSymbol method) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidUnnecessaryWhereAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidUnnecessaryWhereAnalyzer.cs index 12b09d43f..7035e18bf 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidUnnecessaryWhereAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/AvoidUnnecessaryWhereAnalyzer.cs @@ -1,11 +1,15 @@ // © 2022 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Maintainability { /// @@ -24,52 +28,45 @@ public AvoidUnnecessaryWhereAnalyzer() } public class AvoidUnnecessaryWhereSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + private static readonly System.Collections.Generic.HashSet ExpressionsOfInterest = new() + { + @"Count", + @"Any", + @"Single", + @"SingleOrDefault", + @"Last", + @"LastOrDefault", + @"First", + @"FirstOrDefault", + }; + + private static readonly string IEnumerableSymbol = @"System.Collections.Generic.IEnumerable"; + + public override IEnumerable Analyze() { - // Is this a call to Count(), Any(), etc, w/o a predicate? - if (Node.ArgumentList.Arguments.Count != 0) - { - return; - } - if (Node.Expression is not MemberAccessExpressionSyntax expressionOfInterest) - { - return; - } - if (expressionOfInterest.Name.Identifier.Text is not @"Count" - and not @"Any" - and not @"Single" - and not @"SingleOrDefault" - and not @"Last" - and not @"LastOrDefault" - and not @"First" - and not @"FirstOrDefault" - ) - { - return; - } + return Optional(Node) + .Filter(node => node.ArgumentList.Arguments.Count == 0) + .Bind(node => node.Expression as MemberAccessExpressionSyntax) + .Filter(expression => ExpressionsOfInterest.Contains(expression.Name.Identifier.Text)) + .Bind(AnalyzeExpression); + } - // Is it from a Where clause? - if (expressionOfInterest.Expression is not InvocationExpressionSyntax whereInvocationExpression) - { - return; - } - if (whereInvocationExpression.Expression is not MemberAccessExpressionSyntax whereExpression) - { - return; - } - if (whereExpression.Name.Identifier.Text is not @"Where") - { - return; - } + private Option AnalyzeExpression(MemberAccessExpressionSyntax expression) + { + return Optional(expression.Expression as InvocationExpressionSyntax) + .Bind(invocation => invocation.Expression as MemberAccessExpressionSyntax) + .Bind(whereExpression => AnalyzeWhereExpression(expression, whereExpression)); + } - // It's practicially guaranteed we found something, but let's confirm it's System.Linq.Where - var whereSymbol = Context.SemanticModel.GetSymbolInfo(whereExpression.Name).Symbol as IMethodSymbol; - var strWhereSymbol = whereSymbol?.ToString(); - if (strWhereSymbol != null && strWhereSymbol.StartsWith(@"System.Collections.Generic.IEnumerable")) - { - Location location = whereExpression.Name.Identifier.GetLocation(); - ReportDiagnostic(location, expressionOfInterest.Name.Identifier.Text); - } + private Option AnalyzeWhereExpression(MemberAccessExpressionSyntax expressionOfInterest, MemberAccessExpressionSyntax whereExpression) + { + return Optional(Context.SemanticModel.GetSymbolInfo(whereExpression.Name).Symbol as IMethodSymbol) + .Filter(whereSymbol => whereSymbol.ToString().StartsWith(IEnumerableSymbol)) + .Select(whereSymbol => + { + Location location = whereExpression.Name.Identifier.GetLocation(); + return PrepareDiagnostic(location, expressionOfInterest.Name.Identifier.Text); + }); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/CastCompleteObjectAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/CastCompleteObjectAnalyzer.cs index 201eb2d35..3c2c88906 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/CastCompleteObjectAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/CastCompleteObjectAnalyzer.cs @@ -1,6 +1,9 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,7 +26,7 @@ public CastCompleteObjectAnalyzer() public class CastCompleteObjectSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { TypeSyntax container = Node.ParameterList.Parameters.FirstOrDefault()?.Type; if ( @@ -31,15 +34,16 @@ public override void Analyze() Context.SemanticModel.GetSymbolInfo(Node.Type).Symbol is not INamedTypeSymbol convertTo || Context.SemanticModel.GetSymbolInfo(container).Symbol is not INamedTypeSymbol containingType) { - return; + return Option.None; } - System.Collections.Generic.IEnumerable itsFields = containingType.GetMembers().OfType(); + IEnumerable itsFields = containingType.GetMembers().OfType(); if (itsFields is not null && itsFields.Count() > 1 && itsFields.Any(f => f.Type.Name == convertTo.Name)) { Location loc = Node.Type.GetLocation(); - ReportDiagnostic(loc); + return PrepareDiagnostic(loc).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/LockObjectsMustBeReadonlyAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/LockObjectsMustBeReadonlyAnalyzer.cs index 17b63c891..bbe474b62 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/LockObjectsMustBeReadonlyAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/LockObjectsMustBeReadonlyAnalyzer.cs @@ -1,5 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,7 +25,7 @@ public LockObjectsMustBeReadonlyAnalyzer() public class LockObjectsMustBeReadonlySyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Expression is IdentifierNameSyntax identifier) { @@ -31,9 +34,10 @@ public override void Analyze() if (info.Symbol is IFieldSymbol field && !field.IsReadOnly) { Location location = identifier.GetLocation(); - ReportDiagnostic(location, identifier.ToString()); + return PrepareDiagnostic(location, identifier.ToString()).ToSome(); } } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/MergeIfStatementsAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/MergeIfStatementsAnalyzer.cs index 139025c22..b423432a9 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/MergeIfStatementsAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/MergeIfStatementsAnalyzer.cs @@ -1,6 +1,9 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,12 +26,12 @@ public MergeIfStatementsAnalyzer() public class MergeIfStatementsSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { // Node has an else clause if (Node.Else != null) { - return; + return Option.None; } SyntaxNode parent = Node.Parent; @@ -38,7 +41,7 @@ public override void Analyze() // Has multiple statements in the block if (parentBlockSyntax.Statements.Count > 1) { - return; + return Option.None; } parent = parentBlockSyntax.Parent; @@ -47,29 +50,29 @@ public override void Analyze() // Parent is not an If statement if (parent is not IfStatementSyntax parentIfSyntax) { - return; + return Option.None; } // Parent has an else clause if (parentIfSyntax.Else != null) { - return; + return Option.None; } // Has || if (IfConditionHasLogicalAnd(Node)) { - return; + return Option.None; } // Parent has || if (IfConditionHasLogicalAnd(parentIfSyntax)) { - return; + return Option.None; } Location location = Node.IfKeyword.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } private bool IfConditionHasLogicalAnd(IfStatementSyntax ifStatement) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoProtectedFieldsAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoProtectedFieldsAnalyzer.cs index 755afe0e8..c24f00abc 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoProtectedFieldsAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoProtectedFieldsAnalyzer.cs @@ -1,5 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -24,13 +27,14 @@ public NoProtectedFieldsAnalyzer() public class NoProtectedFieldsSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Modifiers.Any(SyntaxKind.ProtectedKeyword)) { Location location = Node.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoRegionsInMethodAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoRegionsInMethodAnalyzer.cs index b31f6cc1e..d6f9dc51f 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoRegionsInMethodAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/NoRegionsInMethodAnalyzer.cs @@ -1,5 +1,6 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,21 +24,24 @@ public NoRegionsInMethodAnalyzer() public class NoRegionsInMethodSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { + var errors = new List(); // Specifying Span instead of FullSpan correctly excludes trivia before or after the method - System.Collections.Generic.IEnumerable descendants = Node.DescendantNodes(Node.Span, null, descendIntoTrivia: true).OfType(); + IEnumerable descendants = Node.DescendantNodes(Node.Span, null, descendIntoTrivia: true).OfType(); foreach (RegionDirectiveTriviaSyntax regionDirective in descendants.OfType()) { Location location = regionDirective.GetLocation(); - ReportDiagnostic(location); + errors.Add(PrepareDiagnostic(location)); } foreach (EndRegionDirectiveTriviaSyntax endRegionDirective in descendants.OfType()) { Location location = endRegionDirective.GetLocation(); - ReportDiagnostic(location); + errors.Add(PrepareDiagnostic(location)); } + + return errors; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/PassSenderToEventHandlerAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/PassSenderToEventHandlerAnalyzer.cs index b51a9fe95..ac527baa8 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/PassSenderToEventHandlerAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/PassSenderToEventHandlerAnalyzer.cs @@ -1,6 +1,8 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -24,12 +26,12 @@ public class PassSenderToEventHandlerSyntaxNodeAction : SyntaxNodeAction Analyze() { VariableDeclaratorSyntax variable = Node.Declaration.Variables.FirstOrDefault(); if (variable == null) { - return; + return Option.None; } var eventName = variable.Identifier.Text; @@ -38,16 +40,18 @@ public override void Analyze() if (parent == null) { // Should never happen, field must be declared inside a type declaration. - return; + return Option.None; } - AnalyzeArguments(parent, eventName); + return AnalyzeArguments(parent, eventName); } - private void AnalyzeArguments(TypeDeclarationSyntax parent, string eventName) + private IEnumerable AnalyzeArguments(TypeDeclarationSyntax parent, string eventName) { + var errors = new List(); + // EventHandlers must have 2 arguments as checked by CA1003, assume this rule is obeyed here. - System.Collections.Generic.IEnumerable invocations = parent.DescendantNodes() + IEnumerable invocations = parent.DescendantNodes() .OfType() .Where(invocation => IsOurEvent(invocation, eventName)) .Where(i => i.ArgumentList.Arguments.Count == 2); @@ -58,15 +62,17 @@ private void AnalyzeArguments(TypeDeclarationSyntax parent, string eventName) if (_helper.IsLiteralNull(arguments[0].Expression)) { Location loc = arguments[0].GetLocation(); - ReportDiagnostic(loc, eventName); + errors.Add(PrepareDiagnostic(loc, eventName)); } if (_helper.IsLiteralNull(arguments[1].Expression)) { Location loc = arguments[1].GetLocation(); - ReportDiagnostic(loc, eventName); + errors.Add(PrepareDiagnostic(loc, eventName)); } } + + return errors; } private bool IsOurEvent(InvocationExpressionSyntax invocation, string eventName) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ReduceCognitiveLoadAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ReduceCognitiveLoadAnalyzer.cs index 72991510f..31d9a7372 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ReduceCognitiveLoadAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ReduceCognitiveLoadAnalyzer.cs @@ -1,13 +1,19 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Globalization; using System.Linq; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; + using Philips.CodeAnalysis.Common; +using LanguageExt; +using LanguageExt.SomeHelp; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Maintainability { [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -33,9 +39,20 @@ public ReduceCognitiveLoadAnalyzer(AdditionalFilesHelper additionalFilesHelper) public class ReduceCognitiveLoadSyntaxNodeAction : SyntaxNodeAction { + private static readonly System.Collections.Generic.HashSet matchingTokens = new() + { + SyntaxKind.BarBarToken, + SyntaxKind.AmpersandAmpersandToken, + SyntaxKind.ExclamationToken, + SyntaxKind.ExclamationEqualsToken, + SyntaxKind.BreakKeyword, + SyntaxKind.ContinueKeyword, + }; + private int MaxCognitiveLoad { get; set; } private const int DefaultMaxCognitiveLoad = 25; + private int CalcCognitiveLoad(BlockSyntax blockSyntax) { var cognitiveLoad = 1; @@ -46,33 +63,24 @@ private int CalcCognitiveLoad(BlockSyntax blockSyntax) return cognitiveLoad; } - public override void Analyze() + public override IEnumerable Analyze() { BlockSyntax blockSyntax = Node.DescendantNodes().OfType().FirstOrDefault(); if (blockSyntax == null) { - return; + return Option.None; } - var cognitiveLoad = CalcCognitiveLoad(blockSyntax); - cognitiveLoad += blockSyntax.DescendantTokens().Count((token) => - { - return - token.IsKind(SyntaxKind.BarBarToken) || - token.IsKind(SyntaxKind.AmpersandAmpersandToken) || - token.IsKind(SyntaxKind.ExclamationToken) || - token.IsKind(SyntaxKind.ExclamationEqualsToken) || - token.IsKind(SyntaxKind.BreakKeyword) || - token.IsKind(SyntaxKind.ContinueKeyword) - ; - }); + var cognitiveLoad = CalcCognitiveLoad(blockSyntax); + cognitiveLoad += blockSyntax.DescendantTokens().Count(token => matchingTokens.Contains(token.Kind())); InitializeMaxCognitiveLoad(); if (cognitiveLoad > MaxCognitiveLoad) { Location location = Node.Identifier.GetLocation(); - ReportDiagnostic(location, cognitiveLoad, MaxCognitiveLoad); + return PrepareDiagnostic(location, cognitiveLoad, MaxCognitiveLoad).ToSome(); } + return Option.None; } private void InitializeMaxCognitiveLoad() diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ServiceContractHasOperationContractAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ServiceContractHasOperationContractAnalyzer.cs index 2c77caa35..b0c51007b 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ServiceContractHasOperationContractAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ServiceContractHasOperationContractAnalyzer.cs @@ -1,6 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -24,21 +26,25 @@ public class ServiceContractHasOperationContractSyntaxNodeAction : SyntaxNodeAct { private readonly AttributeHelper _attributeHelper = new(); - public override void Analyze() + public override IEnumerable Analyze() { if (!_attributeHelper.HasAttribute(Node.AttributeLists, Context, "ServiceContract", null, out _)) { - return; + return Option.None; } + var errors = new List(); + foreach (MethodDeclarationSyntax method in Node.Members.OfType()) { if (!_attributeHelper.HasAttribute(method.AttributeLists, Context, "OperationContract", null, out _)) { Location location = method.Identifier.GetLocation(); - ReportDiagnostic(location, method.Identifier); + errors.Add(PrepareDiagnostic(location, method.Identifier)); } } + + return errors; } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/SetPropertiesInAnyOrderAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/SetPropertiesInAnyOrderAnalyzer.cs index 5b2fdec68..af91d9e52 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/SetPropertiesInAnyOrderAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/SetPropertiesInAnyOrderAnalyzer.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -26,18 +28,18 @@ public SetPropertiesInAnyOrderAnalyzer() public class SetPropertiesInAnyOrderSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { PropertyDeclarationSyntax prop = Node.Ancestors().OfType().FirstOrDefault(); if (Node.Body == null || prop == null) { - return; + return Option.None; } BaseTypeDeclarationSyntax type = prop.Ancestors().OfType().FirstOrDefault(); if (type == null) { - return; + return Option.None; } IEnumerable propertiesInType = GetProperties(type); @@ -47,8 +49,10 @@ public override void Analyze() { var propertyName = prop.Identifier.Text; Location loc = Node.GetLocation(); - ReportDiagnostic(loc, propertyName); + return PrepareDiagnostic(loc, propertyName).ToSome(); } + + return Option.None; } private static IEnumerable GetProperties(BaseTypeDeclarationSyntax type) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ThrowInnerExceptionAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ThrowInnerExceptionAnalyzer.cs index ba0788df1..d1bba9a52 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ThrowInnerExceptionAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/ThrowInnerExceptionAnalyzer.cs @@ -1,7 +1,8 @@ // © 2020 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; - +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -9,6 +10,8 @@ using Philips.CodeAnalysis.Common; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Maintainability { /// @@ -28,16 +31,17 @@ public ThrowInnerExceptionAnalyzer() public class ThrowInnerExceptionSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { // Look for throw statements and check them. - var hasBadThrowNodes = Node.DescendantNodes() - .OfType().Any(node => !IsCorrectThrow(node)); - if (hasBadThrowNodes) + var hasOnlyCorrectThrow = Node.DescendantNodes() + .OfType().ForAll(IsCorrectThrow); + if (hasOnlyCorrectThrow) { - Location location = Node.CatchKeyword.GetLocation(); - ReportDiagnostic(location); + return Option.None; } + Location location = Node.CatchKeyword.GetLocation(); + return Optional(PrepareDiagnostic(location)); } // Throw should rethrow same exception, or include original exception @@ -46,7 +50,7 @@ public override void Analyze() private bool IsCorrectThrow(ThrowStatementSyntax node) { var isOk = true; - System.Collections.Generic.IEnumerable newNodes = node.ChildNodes().OfType(); + IEnumerable newNodes = node.ChildNodes().OfType(); if (newNodes.Any()) { foreach (ObjectCreationExpressionSyntax creation in newNodes) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/WinFormsInitializeComponentMustBeCalledOnceAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/WinFormsInitializeComponentMustBeCalledOnceAnalyzer.cs index b283f21d4..a8db15868 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/WinFormsInitializeComponentMustBeCalledOnceAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Maintainability/WinFormsInitializeComponentMustBeCalledOnceAnalyzer.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -28,18 +30,19 @@ public class WinFormsInitializeComponentMustBeCalledOnceSyntaxNodeAction : Synta { private readonly TestHelper _testHelper = new(); - private void IsInitializeComponentInConstructors(ConstructorDeclarationSyntax[] constructors) + private IEnumerable IsInitializeComponentInConstructors(ConstructorDeclarationSyntax[] constructors) { if (constructors.Length == 0) { Location identifierLocation = Node.Identifier.GetLocation(); - ReportDiagnostic(identifierLocation, Node.Identifier.ToString(), 0); - return; + return PrepareDiagnostic(identifierLocation, Node.Identifier.ToString(), 0).ToSome(); } ConstructorSyntaxHelper constructorSyntaxHelper = new(); IReadOnlyDictionary mapping = constructorSyntaxHelper.CreateMapping(Context, constructors); + var errors = new List(); + foreach (ConstructorDeclarationSyntax ctor in constructors) { IReadOnlyList chain = constructorSyntaxHelper.GetCtorChain(mapping, ctor); @@ -55,9 +58,10 @@ private void IsInitializeComponentInConstructors(ConstructorDeclarationSyntax[] { location = ctor.Initializer.GetLocation(); } - ReportDiagnostic(location, ctor.Identifier, count); + errors.Add(PrepareDiagnostic(location, ctor.Identifier, count)); } } + return errors; } private bool IsInitializeComponentInConstructorChainOnce(IReadOnlyList chain, out int count) @@ -89,28 +93,28 @@ private int IsInitializeComponentInConstructor(ConstructorDeclarationSyntax cons } - public override void Analyze() + public override IEnumerable Analyze() { if (!Node.Modifiers.Any(SyntaxKind.PartialKeyword) && !Node.Members.OfType().Any(x => x.Identifier.Text == "InitializeComponent")) { - return; + return Option.None; } // If we're in a TestClass, let it go. if (_testHelper.IsInTestClass(Context)) { - return; + return Option.None; } // If we're not within a Control/Form, let it go. INamedTypeSymbol type = Context.SemanticModel.GetDeclaredSymbol(Node); if (!Helper.IsUserControl(type)) { - return; + return Option.None; } ConstructorDeclarationSyntax[] constructors = Node.Members.OfType().Where(x => !x.Modifiers.Any(SyntaxKind.StaticKeyword)).ToArray(); - IsInitializeComponentInConstructors(constructors); + return IsInitializeComponentInConstructors(constructors); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchAssemblyNameAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchAssemblyNameAnalyzer.cs index 62a1e5f40..cf4f11954 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchAssemblyNameAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchAssemblyNameAnalyzer.cs @@ -1,6 +1,9 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -22,26 +25,27 @@ public NamespaceMatchAssemblyNameAnalyzer() public class NamespaceMatchAssemblyNameSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { var myNamespace = Node.Name.ToString(); var myAssemblyName = Context.Compilation?.AssemblyName; if (string.IsNullOrEmpty(myAssemblyName)) { - return; + return Option.None; } if (IsNamespacePartOfAssemblyName(myNamespace, myAssemblyName)) { - return; + return Option.None; } if (Helper.IsNamespaceExempt(myNamespace)) { - return; + return Option.None; } - ReportDiagnostic(); + Location location = Node.Name.GetLocation(); + return PrepareDiagnostic(location).ToSome(); } private bool IsNamespacePartOfAssemblyName(string ns, string assemblyName) @@ -49,10 +53,5 @@ private bool IsNamespacePartOfAssemblyName(string ns, string assemblyName) return ns.StartsWith(assemblyName, StringComparison.OrdinalIgnoreCase); } - private void ReportDiagnostic() - { - Location location = Node.Name.GetLocation(); - ReportDiagnostic(location); - } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchFilePathAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchFilePathAnalyzer.cs index 870a1de90..73ebab533 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchFilePathAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/NamespaceMatchFilePathAnalyzer.cs @@ -1,7 +1,10 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Collections.Generic; using System.IO; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -34,7 +37,7 @@ public class NamespaceMatchFilePathSyntaxNodeAction : SyntaxNodeAction Analyze() { var myNamespace = Node.Name.ToString(); var myFilePath = Context.Node.SyntaxTree.FilePath; @@ -46,7 +49,7 @@ public override void Analyze() // Does the namespace exactly match the trailing folders? if (DoesFilePathEndWithNamespace(myNamespace, myFilePath)) { - return; + return Option.None; } } else @@ -54,17 +57,17 @@ public override void Analyze() // Does the namespace exactly match one of the folders in the path? if (IsNamespacePartOfPath(myNamespace, myFilePath)) { - return; + return Option.None; } } if (Helper.IsNamespaceExempt(myNamespace)) { - return; + return Option.None; } Location location = Node.Name.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } private bool IsNamespacePartOfPath(string ns, string path) { diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/PassByRefAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/PassByRefAnalyzer.cs index 6d23b356b..fe7c71cf0 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/PassByRefAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Naming/PassByRefAnalyzer.cs @@ -1,5 +1,7 @@ // © 2021 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,14 +24,17 @@ public PassByRefAnalyzer() public class PassByRefSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.ParameterList is null || Node.ParameterList.Parameters.Count == 0) { - return; + return Option.None; } bool? isInterfaceMethod = null; + + var errors = new List(); + foreach (ParameterSyntax parameterSyntax in Node.ParameterList.Parameters) { if (!parameterSyntax.Modifiers.Any(SyntaxKind.RefKeyword)) @@ -49,12 +54,13 @@ public override void Analyze() if (isInterfaceMethod.Value) { - return; + return Option.None; } Location location = parameterSyntax.GetLocation(); - ReportDiagnostic(location, parameterSyntax.Identifier); + errors.Add(PrepareDiagnostic(location, parameterSyntax.Identifier)); } + return errors; } private bool IsInterfaceOrBaseClassMethod() diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/AvoidInlineNewAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/AvoidInlineNewAnalyzer.cs index 61ff392a1..6050280a0 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/AvoidInlineNewAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/AvoidInlineNewAnalyzer.cs @@ -1,6 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -22,24 +24,24 @@ public AvoidInlineNewAnalyzer() public class AvoidInlineNewSyntaxNodeAction : SyntaxNodeAction { - private static readonly HashSet AllowedMethods = new() { StringConstants.ToStringMethodName, StringConstants.ToListMethodName, StringConstants.ToArrayMethodName, "AsSpan" }; + private static readonly System.Collections.Generic.HashSet AllowedMethods = new() { StringConstants.ToStringMethodName, StringConstants.ToListMethodName, StringConstants.ToArrayMethodName, "AsSpan" }; - public override void Analyze() + public override IEnumerable Analyze() { SyntaxNode parent = Node.Parent; if (!IsInlineNew(parent)) { - return; + return Option.None; } if (IsCallingAllowedMethod(parent)) { - return; + return Option.None; } Location location = Node.GetLocation(); - ReportDiagnostic(location, Node.Type.ToString()); + return PrepareDiagnostic(location, Node.Type.ToString()).ToSome(); } private static bool IsInlineNew(SyntaxNode node) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/EveryLinqStatementOnSeparateLineAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/EveryLinqStatementOnSeparateLineAnalyzer.cs index 3338845a5..0194ddb84 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/EveryLinqStatementOnSeparateLineAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/EveryLinqStatementOnSeparateLineAnalyzer.cs @@ -1,5 +1,6 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,13 +23,15 @@ public EveryLinqStatementOnSeparateLineAnalyzer() public class EveryLinqStatementOnSeparateLineSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { + var errors = new List(); + FromClauseSyntax from = Node.FromClause; if (!EndsWithNewline(from)) { Location fromLocation = from.GetLocation(); - ReportDiagnostic(fromLocation); + errors.Add(PrepareDiagnostic(fromLocation)); } foreach (QueryClauseSyntax clause in Node.Body.Clauses) @@ -36,9 +39,10 @@ public override void Analyze() if (!EndsWithNewline(clause)) { Location clauseLocation = clause.GetLocation(); - ReportDiagnostic(clauseLocation); + errors.Add(PrepareDiagnostic(clauseLocation)); } } + return errors; } private static bool EndsWithNewline(QueryClauseSyntax clause) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreferNamedTuplesAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreferNamedTuplesAnalyzer.cs index b6d317c6d..59584d4f7 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreferNamedTuplesAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreferNamedTuplesAnalyzer.cs @@ -1,5 +1,7 @@ // © 2021 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -24,16 +26,11 @@ public PreferNamedTuplesAnalyzer() public class PreferNamedTuplesSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { - foreach (TupleElementSyntax element in Node.Elements) - { - if (element.Identifier.Kind() == SyntaxKind.None) - { - Location location = element.GetLocation(); - ReportDiagnostic(location); - } - } + return Node.Elements + .Filter(element => element.Identifier.Kind() == SyntaxKind.None) + .Select(element => PrepareDiagnostic(element.GetLocation())); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreventUnnecessaryRangeChecksAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreventUnnecessaryRangeChecksAnalyzer.cs index b8dad6eff..42080b2f9 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreventUnnecessaryRangeChecksAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/Readability/PreventUnnecessaryRangeChecksAnalyzer.cs @@ -1,6 +1,9 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,30 +26,30 @@ public PreventUnnecessaryRangeChecksAnalyzer() public class PreventUnnecessaryRangeChecksSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Else != null) { - return; + return Option.None; } if (Node.Condition == null) { - return; + return Option.None; } if (!TryFindForeach(out ForEachStatementSyntax forEachStatementSyntax)) { - return; + return Option.None; } if (!IsCountGreaterThanZero(Node.Condition, forEachStatementSyntax.Expression, Context.SemanticModel)) { - return; + return Option.None; } Location location = Node.IfKeyword.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } private bool TryFindForeach(out ForEachStatementSyntax forEachStatementSyntax) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/AvoidImplementingFinalizersAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/AvoidImplementingFinalizersAnalyzer.cs index 46bdf6402..3902b76fa 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/AvoidImplementingFinalizersAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/AvoidImplementingFinalizersAnalyzer.cs @@ -1,7 +1,10 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -23,16 +26,16 @@ public AvoidImplementingFinalizersAnalyzer() public class AvoidImplementingFinalizersSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { BlockSyntax body = Node.Body; - System.Collections.Generic.IEnumerable children = body != null ? body.ChildNodes() : Array.Empty(); + IEnumerable children = body != null ? body.ChildNodes() : Array.Empty(); if (children.Any() && children.All(IsDisposeCall)) { - return; + return Option.None; } Location loc = Node.GetLocation(); - ReportDiagnostic(loc); + return PrepareDiagnostic(loc).ToSome(); } private static bool IsDisposeCall(SyntaxNode node) diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/DereferenceNullAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/DereferenceNullAnalyzer.cs index 489d2a341..807c71d13 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/DereferenceNullAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/DereferenceNullAnalyzer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -28,10 +29,10 @@ public DereferenceNullAnalyzer() public class DereferenceNullSyntaxNodeAction : SyntaxNodeAction { - private void Report(IdentifierNameSyntax identifier) + private Diagnostic Report(IdentifierNameSyntax identifier) { Location location = identifier.GetLocation(); - ReportDiagnostic(location, identifier.Identifier.ValueText); + return PrepareDiagnostic(location, identifier.Identifier.ValueText); } /// @@ -136,17 +137,17 @@ private bool HasNullCheck(ExpressionSyntax condition) return false; } - public override void Analyze() + public override IEnumerable Analyze() { if (!IsCaseWeUnderstand(Node)) { - return; + return Option.None; } // Collect some items we'll use repeatedly if (Node.Parent?.Parent is not VariableDeclaratorSyntax variableDeclarationSyntax) { - return; + return Option.None; } BlockSyntax blockOfInterest = variableDeclarationSyntax.Ancestors().OfType().First(); @@ -157,27 +158,37 @@ public override void Analyze() IdentifierNameSyntax identifierNameSyntax = GetFirstMemberAccess(ourSymbol, model, blockOfInterest); if (identifierNameSyntax == null) { - return; + return Option.None; } if (!SameBlock(blockOfInterest, identifierNameSyntax)) { - return; + return Option.None; } // Evaluate the code between (ie after) "y = x as yType" and "y.Foo" (ie before) to see if y is read (ie checked for null) or written to (ie rendering our check moot) (StatementSyntax firstStatementOfAnalysis, var firstStatementOfAnalysisIndex) = GetStatement(blockOfInterest, variableDeclarationSyntax, offset: 1); (StatementSyntax lastStatementOfAnalysis, var lastStatementOfAnalysisIndex) = GetStatement(blockOfInterest, identifierNameSyntax, offset: -1); - if (CheckStatements(lastStatementOfAnalysisIndex, firstStatementOfAnalysisIndex, firstStatementOfAnalysis, identifierNameSyntax)) + var errors = new List(); + + (var cont, var report) = CheckStatements(lastStatementOfAnalysisIndex, firstStatementOfAnalysisIndex, firstStatementOfAnalysis); + if (report) + { + errors.Add(Report(identifierNameSyntax)); + } + + if (!cont) { - return; + return errors; } var isOurSymbolReadOrWritten = OurSymbolIsReadOrWritten(model, firstStatementOfAnalysis, lastStatementOfAnalysis, ourSymbol); if (!isOurSymbolReadOrWritten) { - Report(identifierNameSyntax); + errors.Add(Report(identifierNameSyntax)); } + + return errors; } private static bool OurSymbolIsReadOrWritten(SemanticModel model, StatementSyntax firstStatementOfAnalysis, @@ -194,9 +205,8 @@ private static bool OurSymbolIsReadOrWritten(SemanticModel model, StatementSynta return isOurSymbolReadOrWritten; } - private bool CheckStatements(int lastStatementOfAnalysisIndex, - int firstStatementOfAnalysisIndex, StatementSyntax firstStatementOfAnalysis, - IdentifierNameSyntax identifierNameSyntax) + private (bool, bool) CheckStatements(int lastStatementOfAnalysisIndex, + int firstStatementOfAnalysisIndex, StatementSyntax firstStatementOfAnalysis) { if (lastStatementOfAnalysisIndex < firstStatementOfAnalysisIndex) { @@ -206,32 +216,30 @@ private bool CheckStatements(int lastStatementOfAnalysisIndex, if (firstStatementOfAnalysis is IfStatementSyntax ifStatementSyntax && HasNullCheck(ifStatementSyntax.Condition)) { - return true; + return (true, false); } if (firstStatementOfAnalysis is WhileStatementSyntax whileStatementSyntax && HasNullCheck(whileStatementSyntax.Condition)) { - return true; + return (true, false); } if (firstStatementOfAnalysis is ReturnStatementSyntax returnStatementSyntax && HasNullCheck(returnStatementSyntax.Expression)) { - return true; + return (true, false); } if (firstStatementOfAnalysis.DescendantNodesAndSelf().OfType() .Any(c => HasNullCheck(c.Condition))) { - return true; + return (true, false); } - - Report(identifierNameSyntax); - return true; + return (true, true); } - return false; + return (false, false); } } } diff --git a/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/UnmanagedObjectsNeedDisposingAnalyzer.cs b/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/UnmanagedObjectsNeedDisposingAnalyzer.cs index 08a249f88..156668696 100644 --- a/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/UnmanagedObjectsNeedDisposingAnalyzer.cs +++ b/Philips.CodeAnalysis.MaintainabilityAnalyzers/RuntimeFailure/UnmanagedObjectsNeedDisposingAnalyzer.cs @@ -1,7 +1,10 @@ // © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -27,20 +30,21 @@ public UnmanagedObjectsNeedDisposingAnalyzer() public class UnmanagedObjectsNeedDisposingSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { BaseTypeDeclarationSyntax typeDeclaration = Node.Ancestors().OfType().FirstOrDefault(); if (typeDeclaration is StructDeclarationSyntax) { - return; + return Option.None; } if (IsUnmanaged(Node.Declaration.Type) && !TypeImplementsIDisposable()) { var variableName = Node.Declaration.Variables[0].Identifier.Text; Location loc = Node.Declaration.Variables[0].Identifier.GetLocation(); - ReportDiagnostic(loc, variableName); + return PrepareDiagnostic(loc, variableName).ToSome(); } + return Option.None; } private bool IsUnmanaged(TypeSyntax type) diff --git a/Philips.CodeAnalysis.MsTestAnalyzers/AssertAreEqualTypesMatchAnalyzer.cs b/Philips.CodeAnalysis.MsTestAnalyzers/AssertAreEqualTypesMatchAnalyzer.cs index dcbbd6759..a53b79b82 100644 --- a/Philips.CodeAnalysis.MsTestAnalyzers/AssertAreEqualTypesMatchAnalyzer.cs +++ b/Philips.CodeAnalysis.MsTestAnalyzers/AssertAreEqualTypesMatchAnalyzer.cs @@ -1,5 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,28 +26,28 @@ public AssertAreEqualTypesMatchAnalyzer() public class AssertAreEqualTypesMatchSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Expression is not MemberAccessExpressionSyntax maes) { - return; + return Option.None; } var memberName = maes.Name.ToString(); if (memberName is not StringConstants.AreEqualMethodName and not StringConstants.AreNotEqualMethodName) { - return; + return Option.None; } if (Context.SemanticModel.GetSymbolInfo(maes).Symbol is not IMethodSymbol memberSymbol || !memberSymbol.ToString().StartsWith(StringConstants.AssertFullyQualifiedName)) { - return; + return Option.None; } // If it resolved to Are[Not]Equal, then we know the types are the same. if (memberSymbol.IsGenericMethod) { - return; + return Option.None; } ArgumentListSyntax argumentList = Node.ArgumentList; @@ -58,8 +61,9 @@ public override void Analyze() if (!Context.SemanticModel.Compilation.ClassifyConversion(ti2.Type, ti1.Type).IsImplicit) { Location location = Node.GetLocation(); - ReportDiagnostic(location, ti1.Type.ToString(), ti2.Type.ToString()); + return PrepareDiagnostic(location, ti1.Type.ToString(), ti2.Type.ToString()).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MsTestAnalyzers/AssertIsTrueParenthesisAnalyzer.cs b/Philips.CodeAnalysis.MsTestAnalyzers/AssertIsTrueParenthesisAnalyzer.cs index fc1ee3c14..6c88ecc6a 100644 --- a/Philips.CodeAnalysis.MsTestAnalyzers/AssertIsTrueParenthesisAnalyzer.cs +++ b/Philips.CodeAnalysis.MsTestAnalyzers/AssertIsTrueParenthesisAnalyzer.cs @@ -1,11 +1,15 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using static LanguageExt.Prelude; + namespace Philips.CodeAnalysis.MsTestAnalyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -22,22 +26,22 @@ public AssertIsTrueParenthesisAnalyzer() public class AssertIsTrueParenthesisSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Expression is not MemberAccessExpressionSyntax memberAccessExpression) { - return; + return Option.None; } var memberName = memberAccessExpression.Name.ToString(); if (memberName is not StringConstants.IsTrue and not StringConstants.IsFalse) { - return; + return Option.None; } if (Node.ArgumentList.Arguments.Count == 0) { - return; + return Option.None; } ArgumentSyntax arg0 = Node.ArgumentList.Arguments[0]; @@ -45,8 +49,10 @@ public override void Analyze() if (arg0.Expression.Kind() == SyntaxKind.ParenthesizedExpression) { Location location = arg0.GetLocation(); - ReportDiagnostic(location); + return Optional(PrepareDiagnostic(location)); } + + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MsTestAnalyzers/AvoidMsFakesAnalyzer.cs b/Philips.CodeAnalysis.MsTestAnalyzers/AvoidMsFakesAnalyzer.cs index 69e94ae21..517f6ae28 100644 --- a/Philips.CodeAnalysis.MsTestAnalyzers/AvoidMsFakesAnalyzer.cs +++ b/Philips.CodeAnalysis.MsTestAnalyzers/AvoidMsFakesAnalyzer.cs @@ -1,5 +1,8 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -21,20 +24,21 @@ public AvoidMsFakesAnalyzer() } public class AvoidMsFakesSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { ExpressionSyntax expression = Node.Expression; if (expression == null) { - return; + return Option.None; } if (expression.ToString().Contains(@"ShimsContext.Create")) { CSharpSyntaxNode violation = expression; Location location = violation.GetLocation(); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MsTestAnalyzers/ExpectedExceptionAttributeAnalyzer.cs b/Philips.CodeAnalysis.MsTestAnalyzers/ExpectedExceptionAttributeAnalyzer.cs index 37fe06516..7b405cd62 100644 --- a/Philips.CodeAnalysis.MsTestAnalyzers/ExpectedExceptionAttributeAnalyzer.cs +++ b/Philips.CodeAnalysis.MsTestAnalyzers/ExpectedExceptionAttributeAnalyzer.cs @@ -1,6 +1,9 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; +using LanguageExt; +using LanguageExt.SomeHelp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -23,13 +26,14 @@ public ExpectedExceptionAttributeAnalyzer() } public class ExpectedExceptionAttributeSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + public override IEnumerable Analyze() { if (Node.Attributes.Any(attr => attr.Name.ToString().Contains(@"ExpectedException"))) { var location = Location.Create(Node.SyntaxTree, Node.Attributes.FullSpan); - ReportDiagnostic(location); + return PrepareDiagnostic(location).ToSome(); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.MsTestAnalyzers/TestMethodNameAnalyzer.cs b/Philips.CodeAnalysis.MsTestAnalyzers/TestMethodNameAnalyzer.cs index 60acaa230..b097fa665 100644 --- a/Philips.CodeAnalysis.MsTestAnalyzers/TestMethodNameAnalyzer.cs +++ b/Philips.CodeAnalysis.MsTestAnalyzers/TestMethodNameAnalyzer.cs @@ -1,11 +1,15 @@ // © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Philips.CodeAnalysis.Common; +using LanguageExt; + +using static LanguageExt.Prelude; namespace Philips.CodeAnalysis.MsTestAnalyzers { @@ -23,48 +27,26 @@ public TestMethodNameAnalyzer() } public class TestMethodNameSyntaxNodeAction : SyntaxNodeAction { - public override void Analyze() + private readonly List PrefixChecks = new() { - // Only interested in TestMethod attributes - if (Node.Attributes.All(attr => attr.Name.ToString() != @"TestMethod")) - { - return; - } + StringConstants.TestAttributeName, + StringConstants.EnsureAttributeName, + StringConstants.VerifyAttributeName, + }; - SyntaxNode methodNode = Node.Parent; - - // Confirm this is actually a method... - if (methodNode.Kind() != SyntaxKind.MethodDeclaration) - { - return; - } - - var invalidPrefix = string.Empty; - foreach (SyntaxToken token in methodNode.ChildTokens()) + public override IEnumerable Analyze() + { + if (Node.Attributes.Any(attr => attr.Name.ToString() == @"TestMethod")) { - if (token.Kind() == SyntaxKind.IdentifierToken) - { - if (token.ValueText.StartsWith(StringConstants.TestAttributeName)) - { - invalidPrefix = StringConstants.TestAttributeName; - } - else if (token.ValueText.StartsWith(StringConstants.EnsureAttributeName)) - { - invalidPrefix = StringConstants.EnsureAttributeName; - } - else if (token.ValueText.StartsWith(StringConstants.VerifyAttributeName)) - { - invalidPrefix = StringConstants.VerifyAttributeName; - } - - if (!string.IsNullOrEmpty(invalidPrefix)) - { - Location location = token.GetLocation(); - ReportDiagnostic(location, invalidPrefix); - return; - } - } + return Optional(Node.Parent) + .Filter(methodName => methodName.Kind() == SyntaxKind.MethodDeclaration) + .SelectMany(methodName => methodName.ChildTokens()) + .SelectMany(token => + PrefixChecks.FindAll(token.ValueText.StartsWith) + .Select((invalidPrefix) => PrepareDiagnostic(token.GetLocation(), invalidPrefix)) + ); } + return Option.None; } } } diff --git a/Philips.CodeAnalysis.Test/Cardinality/AvoidEnumParametersTest.cs b/Philips.CodeAnalysis.Test/Cardinality/AvoidEnumParametersTest.cs index a12fa76d8..515b72a88 100644 --- a/Philips.CodeAnalysis.Test/Cardinality/AvoidEnumParametersTest.cs +++ b/Philips.CodeAnalysis.Test/Cardinality/AvoidEnumParametersTest.cs @@ -14,16 +14,16 @@ public class AvoidEnumParametersAnalyzerTest : DiagnosticVerifier { //No diagnostics expected to show up [TestMethod] - public void NotFireForEmptyFiles() + public async Task NotFireForEmptyFiles() { var test = ""; - VerifySuccessfulCompilation(test); + await VerifySuccessfulCompilation(test); } //No diagnostics expected to show up [TestMethod] - public void NotFireForNonVoidMethods() + public async Task NotFireForNonVoidMethods() { var test = @" using System; @@ -41,11 +41,11 @@ class MyClass } }"; - VerifySuccessfulCompilation(test); + await VerifySuccessfulCompilation(test); } [TestMethod] - public void FireForVoidReturn() + public async Task FireForVoidReturn() { var test = @" using System; @@ -63,7 +63,7 @@ public void Foo() {} } }"; - VerifyDiagnostic(test, DiagnosticId.AvoidVoidReturn, regex: "Foo"); + await VerifyDiagnostic(test, DiagnosticId.AvoidVoidReturn, regex: "Foo"); } /** @@ -71,7 +71,7 @@ public void Foo() {} * when they override those of base class */ [TestMethod] - public void NotFireForOverridenVoidReturn() + public async Task NotFireForOverridenVoidReturn() { var test = @" using System; @@ -96,7 +96,7 @@ public override void Foo(int param) } } }"; - VerifyDiagnostic(test, DiagnosticId.AvoidVoidReturn, regex: "Foo"); + await VerifyDiagnostic(test, DiagnosticId.AvoidVoidReturn, regex: "Foo"); } protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() diff --git a/Philips.CodeAnalysis.Test/Helpers/DiagnosticResultLocation.cs b/Philips.CodeAnalysis.Test/Helpers/DiagnosticResultLocation.cs index 32347eea9..6866ebcc3 100644 --- a/Philips.CodeAnalysis.Test/Helpers/DiagnosticResultLocation.cs +++ b/Philips.CodeAnalysis.Test/Helpers/DiagnosticResultLocation.cs @@ -1,4 +1,4 @@ -// © 2019 Koninklijke Philips N.V. See License.md in the project root for license information. +// © 2023 Koninklijke Philips N.V. See License.md in the project root for license information. using System;