Skip to content

Commit

Permalink
Add option to suppress diagnostic from Unity script methods (RCS1213) (
Browse files Browse the repository at this point in the history
…fix #585).
  • Loading branch information
josefpihrt committed Dec 28, 2020
1 parent 62786df commit 16d2766
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/Analyzers/Analyzers.Template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<After><![CDATA[]]></After>
</Sample>
</Samples>
<Configuration></Configuration>
<Options>
<Option Identifier="">
<Title></Title>
Expand Down
4 changes: 4 additions & 0 deletions src/Analyzers/Analyzers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4428,6 +4428,10 @@ x = "";]]></Before>
<DefaultSeverity>Info</DefaultSeverity>
<IsEnabledByDefault>true</IsEnabledByDefault>
<SupportsFadeOut>true</SupportsFadeOut>
<Configuration>Use following EditorConfig option to suppress diagnostic from [Unity script methods](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html):
```editorconfig
roslynator.RCS1213.suppress_unity_script_methods = true
```</Configuration>
</Analyzer>
<Analyzer Identifier="UnnecessaryInterpolatedString">
<Id>RCS1214</Id>
Expand Down
96 changes: 96 additions & 0 deletions src/Analyzers/CSharp/Analysis/UnusedMember/UnityScriptMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading;

namespace Roslynator.CSharp.Analysis.UnusedMember
{
internal static class UnityScriptMethods
{
private static ImmutableHashSet<string> _methodNames;

public static MetadataName MonoBehaviourClassName { get; } = MetadataName.Parse("UnityEngine.MonoBehaviour");

public static ImmutableHashSet<string> MethodNames
{
get
{
if (_methodNames == null)
Interlocked.CompareExchange(ref _methodNames, LoadMethodNames(), null);

return _methodNames;
}
}

private static ImmutableHashSet<string> LoadMethodNames()
{
return ImmutableHashSet.CreateRange(new[] {
"Awake",
"FixedUpdate",
"LateUpdate",
"OnAnimatorIK",
"OnAnimatorMove",
"OnApplicationFocus",
"OnApplicationPause",
"OnApplicationQuit",
"OnAudioFilterRead",
"OnBecameInvisible",
"OnBecameVisible",
"OnCollisionEnter",
"OnCollisionEnter2D",
"OnCollisionExit",
"OnCollisionExit2D",
"OnCollisionStay",
"OnCollisionStay2D",
"OnConnectedToServer",
"OnControllerColliderHit",
"OnDestroy",
"OnDisable",
"OnDisconnectedFromServer",
"OnDrawGizmos",
"OnDrawGizmosSelected",
"OnEnable",
"OnFailedToConnect",
"OnFailedToConnectToMasterServer",
"OnGUI",
"OnJointBreak",
"OnJointBreak2D",
"OnMasterServerEvent",
"OnMouseDown",
"OnMouseDrag",
"OnMouseEnter",
"OnMouseExit",
"OnMouseOver",
"OnMouseUp",
"OnMouseUpAsButton",
"OnNetworkInstantiate",
"OnParticleCollision",
"OnParticleSystemStopped",
"OnParticleTrigger",
"OnParticleUpdateJobScheduled",
"OnPlayerConnected",
"OnPlayerDisconnected",
"OnPostRender",
"OnPreCull",
"OnPreRender",
"OnRenderImage",
"OnRenderObject",
"OnSerializeNetworkView",
"OnServerInitialized",
"OnTransformChildrenChanged",
"OnTransformParentChanged",
"OnTriggerEnter",
"OnTriggerEnter2D",
"OnTriggerExit",
"OnTriggerExit2D",
"OnTriggerStay",
"OnTriggerStay2D",
"OnValidate",
"OnWillRenderObject",
"Reset",
"Start",
"Update",
});
}
}
}
48 changes: 37 additions & 11 deletions src/Analyzers/CSharp/Analysis/UnusedMember/UnusedMemberAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,21 @@ private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context)
SemanticModel semanticModel = context.SemanticModel;
CancellationToken cancellationToken = context.CancellationToken;

INamedTypeSymbol declarationSymbol = null;
ImmutableArray<AttributeData> attributes = default;

if (typeDeclaration.IsKind(SyntaxKind.StructDeclaration))
{
INamedTypeSymbol declarationSymbol = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);
declarationSymbol = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);

attributes = declarationSymbol.GetAttributes();

if (attributes.Any(f => f.AttributeClass.HasMetadataName(MetadataNames.System_Runtime_InteropServices_StructLayoutAttribute)))
return;
}

bool? canContainUnityScriptMethods = null;

SyntaxList<MemberDeclarationSyntax> members = typeDeclaration.Members;

UnusedMemberWalker walker = null;
Expand Down Expand Up @@ -125,28 +128,51 @@ private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context)
break;
}
case SyntaxKind.MethodDeclaration:
{
{
var declaration = (MethodDeclarationSyntax)member;

SyntaxTokenList modifiers = declaration.Modifiers;

if (declaration.ExplicitInterfaceSpecifier == null
&& !declaration.AttributeLists.Any()
&& SyntaxAccessibility<MethodDeclarationSyntax>.Instance.GetAccessibility(declaration) == Accessibility.Private)
if (declaration.ExplicitInterfaceSpecifier != null
|| declaration.AttributeLists.Any()
|| SyntaxAccessibility<MethodDeclarationSyntax>.Instance.GetAccessibility(declaration) != Accessibility.Private)
{
string methodName = declaration.Identifier.ValueText;
break;
}

string methodName = declaration.Identifier.ValueText;

if (!IsMainMethod(declaration, modifiers, methodName))
if (IsMainMethod(declaration, modifiers, methodName))
break;

if (declaration.ReturnsVoid()
&& EditorConfigOptions.IsTrue(
context,
declaration.SyntaxTree,
EditorConfigIdentifiers.SuppressUnityScriptMethods))
{
if (canContainUnityScriptMethods == null)
{
if (walker == null)
walker = UnusedMemberWalker.GetInstance();
if (declarationSymbol == null)
declarationSymbol = semanticModel.GetDeclaredSymbol(typeDeclaration, context.CancellationToken);

walker.AddNode(methodName, declaration);
canContainUnityScriptMethods = declarationSymbol.InheritsFrom(UnityScriptMethods.MonoBehaviourClassName);
}

if (canContainUnityScriptMethods == true
&& UnityScriptMethods.MethodNames.Contains(methodName))
{
break;
}
}

if (walker == null)
walker = UnusedMemberWalker.GetInstance();

walker.AddNode(methodName, declaration);

break;
}
}
case SyntaxKind.PropertyDeclaration:
{
var declaration = (PropertyDeclarationSyntax)member;
Expand Down
10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/EditorConfigIdentifiers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Roslynator.CSharp
{
internal static class EditorConfigIdentifiers
{
public static readonly string SuppressUnityScriptMethods
= $"roslynator.{DiagnosticIdentifiers.RemoveUnusedMemberDeclaration}.suppress_unity_script_methods";
}
}
39 changes: 39 additions & 0 deletions src/Common/EditorConfigOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Roslynator
{
internal static class EditorConfigOptions
{
public static bool IsTrue(
SyntaxNodeAnalysisContext context,
SyntaxTree syntaxTree,
string key)
{
return TryGetValue(context, syntaxTree, key, out bool value)
&& value;
}

public static bool TryGetValue(
SyntaxNodeAnalysisContext context,
SyntaxTree syntaxTree,
string key,
out bool value)
{
if (context
.Options
.AnalyzerConfigOptionsProvider
.GetOptions(syntaxTree)
.TryGetValue(key, out string valueText)
&& bool.TryParse(valueText, out value))
{
return true;
}

value = false;
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,29 @@ public void M()
Span<char> buffer = stackalloc char[K];
}
}
");
}

//TODO: how to enable EditorConfig options in tests
//[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnusedMemberDeclaration)]
public async Task TestNoDiagnostic_UnityScriptMethods()
{
await VerifyNoDiagnosticAsync(@"
using UnityEngine;
class C : MonoBehaviour
{
private void Awake()
{
}
}
namespace UnityEngine
{
class MonoBehaviour
{
}
}
");
}
}
Expand Down

0 comments on commit 16d2766

Please sign in to comment.