Skip to content

Commit

Permalink
[UnconditionalSuppressMessage] Add missing using if needed
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanpeppers committed Jul 24, 2023
1 parent e81eeeb commit 051f34d
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 7 deletions.
39 changes: 39 additions & 0 deletions MemoryAnalyzers/MemoryAnalyzers.CodeFixes/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace MemoryAnalyzers;

static class Extensions
{
// https://github.com/protobuf-net/protobuf-net/blob/ae84aba11942eb45d261316a966c811bac8565b3/src/protobuf-net.BuildTools/Internal/Roslyn/Extensions/CompilationUnitSyntaxExtensions.cs
public static CompilationUnitSyntax AddUsingsIfNotExist(
this CompilationUnitSyntax compilationUnitSyntax,
params string[]? usingDirectiveNames)
{
if (usingDirectiveNames is null || usingDirectiveNames.Length == 0)
return compilationUnitSyntax;

// build a hashset for efficient lookup
// comparison is done based on string value, because different usings can have different types of identifiers:
// - IdentifierName
// - QualifiedNameSyntax
var existingUsingDirectiveNames = compilationUnitSyntax.Usings
.Select(x => x.Name?.ToString().Trim())
.ToImmutableHashSet();

foreach (var directive in usingDirectiveNames)
{
var directiveTrimmed = directive.Trim();
if (!existingUsingDirectiveNames.Contains(directiveTrimmed))
{
compilationUnitSyntax = compilationUnitSyntax.AddUsings(
SyntaxFactory.UsingDirective(
SyntaxFactory.ParseName(" " + directiveTrimmed)));
}
}

return compilationUnitSyntax;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,14 @@ async Task<Solution> RemoveMember(Document document, SyntaxNode node, Cancellati
async Task<Solution> AddUnconditionalSuppressMessage(Diagnostic diagnostic, Document document, MemberDeclarationSyntax member, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken);
if (root is null || member.Parent is null)
if (root is null)
return document.Project.Solution;

if (root is CompilationUnitSyntax unit)
{
root = unit.AddUsingsIfNotExist("System.Diagnostics.CodeAnalysis");
}

// Used: http://roslynquoter.azurewebsites.net/
var attributes = member.AttributeLists.Add(
AttributeList(SingletonSeparatedList(
Expand Down
16 changes: 14 additions & 2 deletions MemoryAnalyzers/MemoryAnalyzers.Test/CodeFixUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class Foo : NSObject
""";

var codefix = """
using System.Diagnostics.CodeAnalysis;

class Foo : NSObject
{
[UnconditionalSuppressMessage("Memory", "MA0001", Justification = "Proven safe in test: XYZ")]
Expand All @@ -49,7 +51,7 @@ class Foo : NSObject
""";

var expected = VerifyCS.Diagnostic("MA0001").WithLocation(0).WithArguments("EventName");
await VerifyCS.VerifyCodeFixAsync(test, expected, codefix, index: 1);
await VerifyCS.VerifyCodeFixAsync(test, expected, codefix, index: 1, iterations: 2);
}

[TestMethod]
Expand Down Expand Up @@ -84,6 +86,8 @@ class Foo : NSObject
""";

var codefix = """
using System.Diagnostics.CodeAnalysis;

class Foo : NSObject
{
[UnconditionalSuppressMessage("Memory", "MA0002", Justification = "Proven safe in test: XYZ")]
Expand All @@ -92,7 +96,7 @@ class Foo : NSObject
""";

var expected = VerifyCS.Diagnostic("MA0002").WithLocation(0).WithArguments("FieldName");
await VerifyCS.VerifyCodeFixAsync(test, expected, codefix, index: 1);
await VerifyCS.VerifyCodeFixAsync(test, expected, codefix, index: 1, iterations: 2);
}

[TestMethod]
Expand Down Expand Up @@ -141,6 +145,8 @@ class Foo : NSObject
public async Task MA0003_Remove()
{
var test = """
using System.Diagnostics.CodeAnalysis;

[Register("UITextField", true)]
class UITextField
{
Expand All @@ -162,6 +168,8 @@ void OnEditingDidBegin(object sender, EventArgs e)
""";

var codefix = """
using System.Diagnostics.CodeAnalysis;

[Register("UITextField", true)]
class UITextField
{
Expand Down Expand Up @@ -190,6 +198,8 @@ void OnEditingDidBegin(object sender, EventArgs e)
public async Task MA0003_MakeStatic()
{
var test = """
using System.Diagnostics.CodeAnalysis;

[Register("UITextField", true)]
class UITextField
{
Expand All @@ -211,6 +221,8 @@ void OnEditingDidBegin(object sender, EventArgs e)
""";

var codefix = """
using System.Diagnostics.CodeAnalysis;

[Register("UITextField", true)]
class UITextField
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticRes

AddTestCode(test.TestState);

// Intentionally left out of codefix tests
test.TestState.Sources.Add("global using System.Diagnostics.CodeAnalysis;");

test.ExpectedDiagnostics.AddRange(expected);
await test.RunAsync(CancellationToken.None);
}
Expand All @@ -44,17 +47,21 @@ public static async Task VerifyCodeFixAsync(string source, string fixedSource, i
=> await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource, index);

/// <inheritdoc cref="CodeFixVerifier{TAnalyzer, TCodeFix, TTest, TVerifier}.VerifyCodeFixAsync(string, DiagnosticResult, string)"/>
public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource, int index)
=> await VerifyCodeFixAsync(source, new[] { expected }, fixedSource, index);
public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource, int index, int iterations = 1)
=> await VerifyCodeFixAsync(source, new[] { expected }, fixedSource, index, iterations);

/// <inheritdoc cref="CodeFixVerifier{TAnalyzer, TCodeFix, TTest, TVerifier}.VerifyCodeFixAsync(string, DiagnosticResult[], string)"/>
public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource, int index)
public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource, int index, int iterations = 1)
{
var test = new Test
{
TestCode = source,
FixedCode = fixedSource,
CodeActionIndex = index,
NumberOfIncrementalIterations = iterations,
NumberOfFixAllInDocumentIterations = iterations,
NumberOfFixAllInProjectIterations = iterations,
NumberOfFixAllIterations = iterations,
};

AddTestCode(test.TestState);
Expand All @@ -68,7 +75,6 @@ static void AddTestCode(SolutionState testState)
{
// Global usings
testState.Sources.Add("global using System;");
testState.Sources.Add("global using System.Diagnostics.CodeAnalysis;");
testState.Sources.Add("global using Foundation;");
testState.Sources.Add("global using UIKit;");

Expand Down
1 change: 1 addition & 0 deletions MemoryAnalyzers/MemoryAnalyzers/MemoryAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ static bool IsDelegateType(INamedTypeSymbol type)

return IsDelegateType(baseType);
}

static bool IsObject(INamedTypeSymbol type) =>
type.ContainingNamespace.Name == "System" && type.Name == "Object";

Expand Down

0 comments on commit 051f34d

Please sign in to comment.