Skip to content

Commit

Permalink
fixes Remora#6
Browse files Browse the repository at this point in the history
  • Loading branch information
pobiega committed May 6, 2023
1 parent 806de47 commit 1df4a25
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 1 deletion.
89 changes: 89 additions & 0 deletions Remora.Results.Analyzers/Analyzers/REM0003UnusedResultAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// REM0003UnusedResultAnalyzer.cs
//
// Author:
// Jarl Gullberg <[email protected]>
//
// Copyright (c) Jarl Gullberg
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Remora.Results.Analyzers;

/// <summary>
/// Detects and flags unused result returning method calls.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class REM0003UnusedResultAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(Descriptors.REM0003UnusedResult);

/// <inheritdoc />
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis
(
GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics
);
context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
}

private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
{
if (context.Node is not InvocationExpressionSyntax invocation ||
!context.Node.IsKind(SyntaxKind.InvocationExpression))
{
return;
}

if (context.SemanticModel.GetSymbolInfo(invocation).Symbol is not IMethodSymbol methodSymbol)
{
return;
}

// Filter out everything except Result and Result<T> return types
if (methodSymbol?.ReturnType.Name != "Result")
{
return;
}

var parentSyntax = invocation.Parent;

if (parentSyntax
is AssignmentExpressionSyntax
or EqualsValueClauseSyntax
or ReturnStatementSyntax
or ArrowExpressionClauseSyntax)
{
return;
}

// Bad !
var diagnostic = Diagnostic.Create(
descriptor: Descriptors.REM0003UnusedResult,
location: invocation.GetLocation(),
messageArgs: methodSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
13 changes: 13 additions & 0 deletions Remora.Results.Analyzers/Descriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,17 @@ internal static class Descriptors
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

/// <summary>
/// Holds the descriptor for unused results.
/// </summary>
internal static readonly DiagnosticDescriptor REM0003UnusedResult = new
(
id: "REM0003",
title: "Unused result",
messageFormat: "Result from \"{0}\" is never used.",
category: DiagnosticCategories.Usage,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);
}
7 changes: 6 additions & 1 deletion Remora.Results.Analyzers/DiagnosticCategories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ internal static class DiagnosticCategories
/// <summary>
/// Gets the category string for analyzers that flag redundant code.
/// </summary>
internal static string Redundancies => "Redundancies in Code";
internal const string Redundancies = "Redundancies in Code";

/// <summary>
/// Gets the category string for analyzers that flag improper usage.
/// </summary>
internal const string Usage = "Usage";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
//
// UnusedResultAnalyzerTests.cs
//
// Author:
// Jarl Gullberg <[email protected]>
//
// Copyright (c) Jarl Gullberg
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using Remora.Results.Analyzers.Tests.TestBases;
using Xunit;

namespace Remora.Results.Analyzers.Tests;

/// <summary>
/// Tests the <see cref="REM0003UnusedResultAnalyzer"/> analyzer.
/// </summary>
public class UnusedResultAnalyzerTests : ResultAnalyzerTests<REM0003UnusedResultAnalyzer>
{
/// <summary>
/// Tests that the analyzer raises a warning when a Result of T is unused.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task RaisesWarningForUnusedResultOfT()
{
this.TestCode =
@"
using System;
using Remora.Results;
public class Program
{
public Result<int> MyMethod() => 1;
public void Main()
{
MyMethod();
}
}
";

this.ExpectedDiagnostics.Clear();
this.ExpectedDiagnostics.Add(DiagnosticResult.CompilerWarning("REM0003")
.WithSpan(11, 21, 11, 31)
.WithArguments("MyMethod"));

await RunAsync();
}

/// <summary>
/// Tests that the analyzer raises a warning when a Result of T is unused.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task RaisesWarningForUnusedResult()
{
this.TestCode =
@"
using System;
using Remora.Results;
public class Program
{
public Result MyMethod() => Result.FromSuccess();
public void Main()
{
MyMethod();
}
}
";

this.ExpectedDiagnostics.Clear();
this.ExpectedDiagnostics.Add(DiagnosticResult.CompilerWarning("REM0003")
.WithSpan(11, 21, 11, 31)
.WithArguments("MyMethod"));

await RunAsync();
}

/// <summary>
/// Tests that the analyzer doesn't raise a warning when Result of T is used.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task IgnoresUsedResultOfTWithArrowSyntax()
{
this.TestCode =
@"
using System;
using Remora.Results;
public class Program
{
public Result<int> MyMethod() => 1;
public void Main()
{
var result = MyMethod();
}
}
";

this.ExpectedDiagnostics.Clear();

await RunAsync();
}

/// <summary>
/// Tests that the analyzer doesn't raise a warning when Result of T is used.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task IgnoresUsedResultOfT()
{
this.TestCode =
@"
using System;
using Remora.Results;
public class Program
{
public Result<int> MyMethod()
{
return 1;
}
public void Main()
{
var result = MyMethod();
}
}
";

this.ExpectedDiagnostics.Clear();

await RunAsync();
}

/// <summary>
/// Tests that the analyzer doesn't raise a warning when Result is used.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task IgnoresUsedResultWithArrowSyntax()
{
this.TestCode =
@"
using System;
using Remora.Results;
public class Program
{
public Result MyMethod() => Result.FromSuccess();
public void Main()
{
var result = MyMethod();
}
}
";

this.ExpectedDiagnostics.Clear();

await RunAsync();
}

/// <summary>
/// Tests that the analyzer doesn't raise a warning when Result is used.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task IgnoresUsedResult()
{
this.TestCode =
@"
using System;
using Remora.Results;
public class Program
{
public Result MyMethod()
{
return Result.FromSuccess();
}
public void Main()
{
var result = MyMethod();
}
}
";

this.ExpectedDiagnostics.Clear();

await RunAsync();
}
}

0 comments on commit 1df4a25

Please sign in to comment.