Skip to content

Commit

Permalink
Add USP0019 Don't flag private methods decorated with `PreserveAttrib…
Browse files Browse the repository at this point in the history
…ute` or `UsedImplicitlyAttribute` as unused (#214)

* Add USP0019 Don't flag private methods decorated with PreserveAttribute or UsedImplicitlyAttribute as unused

* Not needed yet

* indent

* Refactor
  • Loading branch information
sailro authored Mar 22, 2022
1 parent 05a545a commit cfbc837
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 0 deletions.
34 changes: 34 additions & 0 deletions doc/USP0019.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# USP0019 Don't flag private methods decorated with PreserveAttribute or UsedImplicitlyAttribute as unused

Methods decorated with `PreserveAttribute` or `UsedImplicitlyAttribute` attributes are not unused.

## Suppressed Diagnostic ID

IDE0051 - Remove unused private members

## Examples of code that produces a suppressed diagnostic
```csharp
using UnityEngine;
using UnityEgine.Scripting;

class Loader
{
[PreserveAttribute]
private void InvokeMe()
{
}

public string Name; // "InvokeMe" serialized
private void Update() {
Invoke(Name, 0);
}
}
```

## Why is the diagnostic reported?

The IDE cannot find any references to the method `InvokeMe` and believes it to be unused.

## Why do we suppress this diagnostic?

Even though the IDE cannot find any references to `InvokeMe` , it will be called by Unity, and should not be removed.
1 change: 1 addition & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ ID | Suppressed ID | Justification
[USP0016](USP0016.md) | CS8618 | Initialization detection with nullable reference types
[USP0017](USP0017.md) | IDE0074 | Unity objects should not use coalescing assignment
[USP0018](USP0018.md) | IDE0016 | Unity objects should not be used with throw expressions
[USP0019](USP0012.md) | IDE0051 | Don't flag private methods with PreserveAttribute or UsedImplicitlyAttribute as unused
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*-------------------------------------------------------------------------------------------*/

using System.Threading.Tasks;
using Xunit;

namespace Microsoft.Unity.Analyzers.Tests;

public class ImplicitUsageAttributeSuppressorTests : BaseSuppressorVerifierTest<ImplicitUsageAttributeSuppressor>
{
[Fact]
public async Task UnityPreserveTest()
{
const string test = @"
using UnityEngine;
using UnityEngine.Scripting;
class Camera : MonoBehaviour
{
[Preserve]
private void Foo() {
}
}
";

var suppressor = ExpectSuppressor(ImplicitUsageAttributeSuppressor.Rule)
.WithLocation(8, 18);

await VerifyCSharpDiagnosticAsync(test, suppressor);
}

[Fact]
public async Task OwnPreserveTest()
{
const string test = @"
using UnityEngine;
class Camera : MonoBehaviour
{
[My.Own.Stuff.Preserve]
private void Foo() {
}
}
namespace My.Own.Stuff {
public class PreserveAttribute : System.Attribute { }
}
";

var suppressor = ExpectSuppressor(ImplicitUsageAttributeSuppressor.Rule)
.WithLocation(7, 18);

await VerifyCSharpDiagnosticAsync(test, suppressor);
}

[Fact]
public async Task UsedImplicitlyTest()
{
const string test = @"
using UnityEngine;
using JetBrains.Annotations;
class Camera : MonoBehaviour
{
[UsedImplicitly]
private void Foo() {
}
}
";

var suppressor = ExpectSuppressor(ImplicitUsageAttributeSuppressor.Rule)
.WithLocation(8, 18);

await VerifyCSharpDiagnosticAsync(test, suppressor);
}
}
54 changes: 54 additions & 0 deletions src/Microsoft.Unity.Analyzers/ImplicitUsageAttributeSuppressor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*-------------------------------------------------------------------------------------------*/

using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Unity.Analyzers.Resources;

namespace Microsoft.Unity.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ImplicitUsageAttributeSuppressor : DiagnosticSuppressor
{
internal static readonly SuppressionDescriptor Rule = new(
id: "USP0019",
suppressedDiagnosticId: "IDE0051",
justification: Strings.ImplicitUsageAttributeSuppressorJustification);

public override void ReportSuppressions(SuppressionAnalysisContext context)
{
foreach (var diagnostic in context.ReportedDiagnostics)
{
AnalyzeDiagnostic(diagnostic, context);
}
}

public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Rule);

private void AnalyzeDiagnostic(Diagnostic diagnostic, SuppressionAnalysisContext context)
{
var methodDeclarationSyntax = context.GetSuppressibleNode<MethodDeclarationSyntax>(diagnostic);
if (methodDeclarationSyntax == null)
return;

var model = context.GetSemanticModel(diagnostic.Location.SourceTree);
if (model.GetDeclaredSymbol(methodDeclarationSyntax) is not IMethodSymbol methodSymbol)
return;

if (!IsSuppressable(methodSymbol))
return;

context.ReportSuppression(Suppression.Create(Rule, diagnostic));
}

private bool IsSuppressable(IMethodSymbol methodSymbol)
{
// The Unity code stripper will consider any attribute with the exact name "PreserveAttribute", regardless of the namespace or assembly
return methodSymbol.GetAttributes().Any(a => a.AttributeClass.Matches(typeof(JetBrains.Annotations.UsedImplicitlyAttribute)) || a.AttributeClass.Name == nameof(UnityEngine.Scripting.PreserveAttribute));
}
}
9 changes: 9 additions & 0 deletions src/Microsoft.Unity.Analyzers/Resources/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.Unity.Analyzers/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -486,4 +486,7 @@
<data name="ThrowExpressionSuppressorJustification" xml:space="preserve">
<value>Do not use Throw expressions with Unity objects.</value>
</data>
<data name="ImplicitUsageAttributeSuppressorJustification" xml:space="preserve">
<value>Don't flag private methods decorated with PreserveAttribute or UsedImplicitlyAttribute as unused.</value>
</data>
</root>
10 changes: 10 additions & 0 deletions src/Microsoft.Unity.Analyzers/UnityStubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,11 @@ namespace UnityEngine.UIElements
class VisualElement { }
}

namespace UnityEngine.Scripting
{
class PreserveAttribute : Attribute { }
}

namespace UnityEditor.AssetImporters
{
class MaterialDescription { }
Expand Down Expand Up @@ -728,4 +733,9 @@ static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAsse

}

namespace JetBrains.Annotations
{
class UsedImplicitlyAttribute : Attribute { }
}

#pragma warning enable

0 comments on commit cfbc837

Please sign in to comment.