From eefa1150c8b5028b82cdc9f102f2b8035e516736 Mon Sep 17 00:00:00 2001 From: Kasperki Date: Thu, 6 Apr 2017 23:24:34 +0300 Subject: [PATCH] Animator Analyzer Checks that project is using hash and not strings. --- .../DoNotUseStateNameAnalyzerTests.cs | 86 ++++++++++++ .../UnityEngineAnalyzer.Test.csproj | 1 + .../Animator/DoNotUseStateNameAnalyzer.cs | 66 +++++++++ .../DoNotUseStateNameResource.Designer.cs | 91 ++++++++++++ .../Animator/DoNotUseStateNameResource.resx | 132 ++++++++++++++++++ .../DiagnosticDescriptors.cs | 13 +- .../UnityEngineAnalyzer/DiagnosticIDs.cs | 1 + .../UnityEngineAnalyzer.csproj | 10 ++ 8 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Animator/DoNotUseStateNameAnalyzerTests.cs create mode 100644 UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameAnalyzer.cs create mode 100644 UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.Designer.cs create mode 100644 UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.resx diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Animator/DoNotUseStateNameAnalyzerTests.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Animator/DoNotUseStateNameAnalyzerTests.cs new file mode 100644 index 0000000..b245e8f --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/Animator/DoNotUseStateNameAnalyzerTests.cs @@ -0,0 +1,86 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using RoslynNUnitLight; +using UnityEngineAnalyzer.Animator; + +namespace UnityEngineAnalyzer.Test.Animator +{ + + [TestFixture] + sealed class DoNotSetAnimatorParameterWithNameAnalyzerTests : AnalyzerTestFixture + { + protected override string LanguageName => LanguageNames.CSharp; + protected override DiagnosticAnalyzer CreateAnalyzer() => new DoNotUseStateNameAnalyzer(); + + [Test] + public void AnimatorSetFloatStringName() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Animator animator; + + void Start() + { + [|animator.SetFloat(""Run"", 1.2f)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStateNameInAnimator); + } + + [Test] + public void AnimatorSetIntStringName() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Animator animator; + + void Start() + { + [|animator.SetInteger(""Walk"", 1)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStateNameInAnimator); + } + + [Test] + public void AnimatorSetBoolStringName() + { + const string code = @" +using UnityEngine; + +class C : MonoBehaviour +{ + Animator animator; + + void Start() + { + [|animator.SetBool(""Fly"", true)|]; + } +}"; + + Document document; + TextSpan span; + TestHelpers.TryGetDocumentAndSpanFromMarkup(code, LanguageName, MetadataReferenceHelper.UsingUnityEngine, out document, out span); + + HasDiagnostic(document, span, DiagnosticIDs.DoNotUseStateNameInAnimator); + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj index d5501b0..cc86884 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer.Test/UnityEngineAnalyzer.Test.csproj @@ -126,6 +126,7 @@ + diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameAnalyzer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameAnalyzer.cs new file mode 100644 index 0000000..7165657 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameAnalyzer.cs @@ -0,0 +1,66 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; + +namespace UnityEngineAnalyzer.Animator +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DoNotUseStateNameAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.DoNotUseStateName); + + private static readonly ImmutableHashSet animatorStateNameMethods = ImmutableHashSet.Create( + "GetBool", + "GetFloat", + "GetInteger", + "GetVector", + "GetQuaternion", + "SetBool", + "SetFloat", + "SetInteger", + "SetVector", + "SetQuaternion", + "SetTrigger", + "PlayInFixedTime", + "Play", + "IsParameterControlledByCurve", + "CrossFade", + "CrossFadeInFixedTime"); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); + } + + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var invocation = context.Node as InvocationExpressionSyntax; + if (invocation == null) + { + return; + } + + var name = invocation.MethodName(); + + // check if any of the methods are used + if (!animatorStateNameMethods.Contains(name)) { return; } + + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation); + var methodSymbol = symbolInfo.Symbol as IMethodSymbol; + + var containingClass = methodSymbol.ContainingType; + + // check if the method is the one from UnityEngine.Animator + if (containingClass.ContainingNamespace.Name.Equals("UnityEngine") && containingClass.Name.Equals("Animator")) + { + if (methodSymbol.Parameters[0].Type.MetadataName == "String") + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.DoNotUseStateName, invocation.GetLocation(), containingClass.Name, methodSymbol.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.Designer.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.Designer.cs new file mode 100644 index 0000000..3de929d --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UnityEngineAnalyzer.Animator { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DoNotUseStateNameResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DoNotUseStateNameResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityEngineAnalyzer.Animator.DoNotUseStateNameResource", typeof(DoNotUseStateNameResource).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Animator.StringToHash can convert your state name to hash, it's faster than string comparison. + /// + internal static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use stateNameHash instead of stateName. + /// + internal static string MessageFormat { + get { + return ResourceManager.GetString("MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use stateNameHash instead of stateName. + /// + internal static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.resx b/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.resx new file mode 100644 index 0000000..db5b682 --- /dev/null +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/Animator/DoNotUseStateNameResource.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Animator.StringToHash can convert your state name to hash, it's faster than string comparison + An optional longer localizable description of the diagnostic. + + + Use stateNameHash instead of stateName + The format-able message the diagnostic displays. + + + Use stateNameHash instead of stateName + The title of the diagnostic. + + \ No newline at end of file diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs index 6d21546..0ac196e 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticDescriptors.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using UnityEngineAnalyzer.Animator; using UnityEngineAnalyzer.AOT; using UnityEngineAnalyzer.CompareTag; using UnityEngineAnalyzer.Coroutines; @@ -135,6 +136,16 @@ static class DiagnosticDescriptors defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, description: new LocalizableResourceString(nameof(InvokeFunctionMissingResources.Description), InvokeFunctionMissingResources.ResourceManager, typeof(InvokeFunctionMissingResources)) - ); + ); + + public static readonly DiagnosticDescriptor DoNotUseStateName = new DiagnosticDescriptor( + id: DiagnosticIDs.DoNotUseStateNameInAnimator, + title: new LocalizableResourceString(nameof(DoNotUseStateNameResource.Title), DoNotUseStateNameResource.ResourceManager, typeof(DoNotUseStateNameResource)), + messageFormat: new LocalizableResourceString(nameof(DoNotUseStateNameResource.MessageFormat), DoNotUseStateNameResource.ResourceManager, typeof(DoNotUseStateNameResource)), + category: DiagnosticCategories.Performance, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(DoNotUseStateNameResource.Description), DoNotUseStateNameResource.ResourceManager, typeof(DoNotUseStateNameResource)) + ); } } diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs index 7c154b4..5d99f94 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/DiagnosticIDs.cs @@ -12,6 +12,7 @@ public static class DiagnosticIDs public const string DoNotUseForEachInUpdate = "UEA0007"; public const string UnsealedDerivedClass = "UEA0008"; public const string InvokeFunctionMissing = "UEA0009"; + public const string DoNotUseStateNameInAnimator = "UEA0010"; //NOTES: These should probably be on their own analyzer - as they are not specific to Unity public const string DoNotUseRemoting = "AOT0001"; diff --git a/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj b/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj index 5ee6832..bd0fcda 100644 --- a/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj +++ b/UnityEngineAnalyzer/UnityEngineAnalyzer/UnityEngineAnalyzer.csproj @@ -32,6 +32,12 @@ 4 + + True + True + DoNotUseStateNameResource.resx + + True @@ -112,6 +118,10 @@ + + ResXFileCodeGenerator + DoNotUseStateNameResource.Designer.cs + ResXFileCodeGenerator DoNotUseReflectionEmitResources.Designer.cs