Skip to content

Commit

Permalink
Recognise and handle custom serialized classes
Browse files Browse the repository at this point in the history
Fixes #419, fixes RIDER-9341, fixes RIDER-12239
  • Loading branch information
citizenmatt committed Jul 9, 2018
1 parent 39429c8 commit ee06fb5
Show file tree
Hide file tree
Showing 16 changed files with 423 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected override void Analyze(IAttribute attribute, ElementProblemAnalyzerData
if (!(fieldDeclaration.DeclaredElement is IField field))
return;

if (!Api.IsUnityField(field))
if (!Api.IsSerialisedField(field))
{
consumer.AddHighlighting(new RedundantFormerlySerializedAsAttributeWarning(attribute));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protected override void Analyze(IAttribute attribute, ElementProblemAnalyzerData
var fieldDeclarations = FieldDeclarationNavigator.GetByAttribute(attribute);
foreach (var fieldDeclaration in fieldDeclarations)
{
if (!Api.IsUnityField(fieldDeclaration.DeclaredElement))
if (!Api.IsSerialisedField(fieldDeclaration.DeclaredElement))
{
consumer.AddHighlighting(new RedundantHideInInspectorAttributeWarning(attribute));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected override void Analyze(IAttribute attribute, ElementProblemAnalyzerData
if (!(fieldDeclaration.DeclaredElement is IField field))
continue;

if (!Api.IsUnityField(field))
if (!Api.IsSerialisedField(field))
{
consumer.AddHighlighting(new RedundantSerializeFieldAttributeWarning(attribute));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public UnityFieldDetector(UnityApi unityApi)
protected override void Analyze(IFieldDeclaration element, ElementProblemAnalyzerData data, IHighlightingConsumer consumer)
{
var field = element.DeclaredElement;
if (field != null && Api.IsUnityField(field))
if (field != null && Api.IsSerialisedField(field))
{
var highlighting = new UnityGutterMarkInfo(element, "This field is initialised by Unity");
consumer.AddHighlighting(highlighting);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@ public UnityTypeDetector(UnityApi unityApi)
protected override void Analyze(IClassLikeDeclaration element, ElementProblemAnalyzerData data,
IHighlightingConsumer consumer)
{
var @class = element.DeclaredElement;
if (@class != null && Api.IsUnityType(@class))
var typeElement = element.DeclaredElement;
if (typeElement != null)
{
var highlighting = new UnityGutterMarkInfo(element, "Unity scripting component");
consumer.AddHighlighting(highlighting);
if (Api.IsUnityType(typeElement))
{
var highlighting = new UnityGutterMarkInfo(element, "Unity scripting component");
consumer.AddHighlighting(highlighting);
}
else if (Api.IsSerializableType(typeElement))
{
var highlighting = new UnityGutterMarkInfo(element, "Unity custom serializable type");
consumer.AddHighlighting(highlighting);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public bool SuppressUsageInspectionsOnElement(IDeclaredElement element, out Impl
flags = ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature;
return true;

case ITypeElement typeElement when unityApi.IsSerializableType(typeElement):
// TODO: We should only really mark it as in use if it's actually used somewhere
// That is, it should be used as a field in a Unity type, or another serializable type
flags = ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature;
return true;

case IMethod method:
var function = unityApi.GetUnityEventFunction(method, out var match);
if (function != null)
Expand Down Expand Up @@ -79,7 +85,7 @@ public bool SuppressUsageInspectionsOnElement(IDeclaredElement element, out Impl

break;

case IField field when unityApi.IsUnityField(field):
case IField field when unityApi.IsSerialisedField(field):
// Public fields gets exposed to the Unity Editor and assigned from the UI.
// But it still should be checked if the field is ever accessed from the code.
flags = ImplicitUseKindFlags.Assign;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public IEnumerable<IntentionAction> CreateBulbItems()
var multipleFieldDeclaration = MultipleFieldDeclarationNavigator.GetByDeclarator(fieldDeclaration);
var unityApi = myDataProvider.Solution.GetComponent<UnityApi>();

if (!unityApi.IsUnityField(fieldDeclaration?.DeclaredElement) || multipleFieldDeclaration == null)
if (!unityApi.IsSerialisedField(fieldDeclaration?.DeclaredElement) || multipleFieldDeclaration == null)
return EmptyList<IntentionAction>.Enumerable;

var existingAttribute = AttributeUtil.GetAttribute(fieldDeclaration, KnownTypes.HideInInspector);
Expand All @@ -66,7 +66,7 @@ public bool IsAvailable(IUserDataHolder cache)

var unityApi = myDataProvider.Solution.GetComponent<UnityApi>();
var fieldDeclaration = myDataProvider.GetSelectedElement<IFieldDeclaration>();
return unityApi.IsUnityField(fieldDeclaration?.DeclaredElement);
return unityApi.IsSerialisedField(fieldDeclaration?.DeclaredElement);
}

private class ToggleHideInInspectorAll : BulbActionBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public IEnumerable<IntentionAction> CreateBulbItems()
return EmptyList<IntentionAction>.Enumerable;

var unityApi = myDataProvider.Solution.GetComponent<UnityApi>();
var isSerialized = unityApi.IsUnityField(fieldDeclaration.DeclaredElement);
var isSerialized = unityApi.IsSerialisedField(fieldDeclaration.DeclaredElement);

if (multipleFieldDeclaration.Declarators.Count == 1)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ protected override void Process(CSharpGeneratorContext context, IProgressIndicat

private bool HasUnityBaseType(CSharpGeneratorContext context)
{
var typeElement = context.ClassDeclaration.DeclaredElement as IClass;
return typeElement != null && myUnityApi.GetBaseUnityTypes(typeElement).Any();
return context.ClassDeclaration.DeclaredElement is IClass typeElement && myUnityApi.IsUnityType(typeElement);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public bool IsApplicable(IDeclaredElement declaredElement)
return false;

var unityApi = declaredElement.GetSolution().GetComponent<UnityApi>();
return unityApi.IsUnityField(declaredElement as IField);
return unityApi.IsSerialisedField(declaredElement as IField);
}

public RenameAvailabilityCheckResult CheckRenameAvailability(IDeclaredElement element)
Expand Down
27 changes: 22 additions & 5 deletions resharper/src/resharper-unity/UnityApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using JetBrains.Annotations;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp.Util;
using JetBrains.ReSharper.Psi.Modules;
using JetBrains.Util;

Expand All @@ -26,7 +27,7 @@ public UnityApi(UnityVersion unityVersion)
}

[NotNull]
public IEnumerable<UnityType> GetBaseUnityTypes([CanBeNull] ITypeElement type)
private IEnumerable<UnityType> GetBaseUnityTypes([CanBeNull] ITypeElement type)
{
if (type?.Module is IProjectPsiModule projectPsiModule)
{
Expand All @@ -37,7 +38,7 @@ public IEnumerable<UnityType> GetBaseUnityTypes([CanBeNull] ITypeElement type)
}

[NotNull]
public IEnumerable<UnityType> GetBaseUnityTypes([NotNull] ITypeElement type, Version unityVersion)
private IEnumerable<UnityType> GetBaseUnityTypes([NotNull] ITypeElement type, Version unityVersion)
{
var types = myTypes.Value;
unityVersion = types.NormaliseSupportedVersion(unityVersion);
Expand All @@ -49,18 +50,34 @@ public bool IsUnityType([CanBeNull] ITypeElement type)
return GetBaseUnityTypes(type).Any();
}

public bool IsSerializableType([CanBeNull] ITypeElement type)
{
// A class or struct with the `[System.Serializable]` attribute
// Should not be abstract, static or generic
// We'll ignore abstract or generic because it might be being used as a base class
// TODO: Add a warning if the serializable class isn't inherited
var clazz = type as IClass;
if (clazz?.IsStaticClass() == true)
return false;

if (type?.IsClassLike() == true)
return type.HasAttributeInstance(PredefinedType.SERIALIZABLE_ATTRIBUTE_CLASS, true);

return false;
}

public bool IsEventFunction([NotNull] IMethod method)
{
return GetUnityEventFunction(method) != null;
}

public bool IsUnityField([CanBeNull] IField field)
public bool IsSerialisedField([CanBeNull] IField field)
{
if (field == null || field.IsStatic || field.IsConstant || field.IsReadonly)
return false;

var containingType = field.GetContainingType();
if (containingType == null || !IsUnityType(containingType))
if (!IsUnityType(containingType) && !IsSerializableType(containingType))
return false;

// [NonSerialized] trumps everything, even if there's a [SerializeField] as well
Expand Down Expand Up @@ -91,7 +108,7 @@ public IEnumerable<UnityEventFunction> GetEventFunctions(ITypeElement type, Vers

public UnityEventFunction GetUnityEventFunction([NotNull] IMethod method)
{
return GetUnityEventFunction(method, out var _);
return GetUnityEventFunction(method, out _);
}

public UnityEventFunction GetUnityEventFunction([NotNull] IMethod method, out MethodSignatureMatch match)
Expand Down
61 changes: 61 additions & 0 deletions resharper/test/data/CSharp/Daemon/Stages/GutterMark/Test01.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,64 @@ public void NotAppliedToInstanceMethod()
Debug.Log("Project loaded in Unity Editor");
}
}

[Serializable]
class SerialisableClass
{
// All serialised by Unity - gutter icons
public string ImplicitlyAssignedField;
public string ImplicitlyAssignedMultiField1, ImplicitlyAssignedMultiField2;
[SerializeField] private int myImplicitlyAssignedPrivateField;

// Not serialized by Unity
public const string UnusedConst = "hello";
private const string UnusedPrivateConst = "hello";
[SerializeField] private const string UnusedPrivateConst2 = "hello";
private string myUnusedField;
public readonly string UnusedReadonlyField;
[NonSerialized] public string ExplicitlyUnusedField;
[NonSerialized, SerializeField] public string ExplicitlyUnusedField2;
[NonSerialized, SerializeField] private string myExplicitlyUnusedField3;
public static string UnusedStaticField;
[SerializeField] private static string ourUnusedPrivateStaticField;
}

[Serializable]
struct SerialisableStruct
{
// All serialised by Unity - gutter icons
public string ImplicitlyAssignedField;
public string ImplicitlyAssignedMultiField1, ImplicitlyAssignedMultiField2;
[SerializeField] private int myImplicitlyAssignedPrivateField;

// Not serialized by Unity
public const string UnusedConst = "hello";
private const string UnusedPrivateConst = "hello";
[SerializeField] private const string UnusedPrivateConst2 = "hello";
private string myUnusedField;
public readonly string UnusedReadonlyField;
[NonSerialized] public string ExplicitlyUnusedField;
[NonSerialized, SerializeField] public string ExplicitlyUnusedField2;
[NonSerialized, SerializeField] private string myExplicitlyUnusedField3;
public static string UnusedStaticField;
[SerializeField] private static string ourUnusedPrivateStaticField;
}

class NotSerialisableClass
{
public string NotSerialised1;
[SerializeField] public string NotSerialised2;
}

struct NotSerialisableStruct
{
public string NotSerialised1;
[SerializeField] public string NotSerialised2;
}

[Serializable]
static class NotSerialisableClass
{
public string NotSerialised1;
[SerializeField] public string NotSerialised2;
}
71 changes: 71 additions & 0 deletions resharper/test/data/CSharp/Daemon/Stages/GutterMark/Test01.cs.gold
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,67 @@ class MyClass
}
}

[Serializable]
class |SerialisableClass|(9)
{
// All serialised by Unity - gutter icons
public string |ImplicitlyAssignedField|(10);
public string |ImplicitlyAssignedMultiField1|(11), |ImplicitlyAssignedMultiField2|(12);
[SerializeField] private int |myImplicitlyAssignedPrivateField|(13);

// Not serialized by Unity
public const string UnusedConst = "hello";
private const string UnusedPrivateConst = "hello";
[SerializeField] private const string UnusedPrivateConst2 = "hello";
private string myUnusedField;
public readonly string UnusedReadonlyField;
[NonSerialized] public string ExplicitlyUnusedField;
[NonSerialized, SerializeField] public string ExplicitlyUnusedField2;
[NonSerialized, SerializeField] private string myExplicitlyUnusedField3;
public static string UnusedStaticField;
[SerializeField] private static string ourUnusedPrivateStaticField;
}

[Serializable]
struct |SerialisableStruct|(14)
{
// All serialised by Unity - gutter icons
public string |ImplicitlyAssignedField|(15);
public string |ImplicitlyAssignedMultiField1|(16), |ImplicitlyAssignedMultiField2|(17);
[SerializeField] private int |myImplicitlyAssignedPrivateField|(18);

// Not serialized by Unity
public const string UnusedConst = "hello";
private const string UnusedPrivateConst = "hello";
[SerializeField] private const string UnusedPrivateConst2 = "hello";
private string myUnusedField;
public readonly string UnusedReadonlyField;
[NonSerialized] public string ExplicitlyUnusedField;
[NonSerialized, SerializeField] public string ExplicitlyUnusedField2;
[NonSerialized, SerializeField] private string myExplicitlyUnusedField3;
public static string UnusedStaticField;
[SerializeField] private static string ourUnusedPrivateStaticField;
}

class NotSerialisableClass
{
public string NotSerialised1;
[SerializeField] public string NotSerialised2;
}

struct NotSerialisableStruct
{
public string NotSerialised1;
[SerializeField] public string NotSerialised2;
}

[Serializable]
static class NotSerialisableClass
{
public string NotSerialised1;
[SerializeField] public string NotSerialised2;
}

---------------------------------------------------------
(0): Unity Gutter Icon: Unity scripting component
(1): Unity Gutter Icon: This field is initialised by Unity
Expand All @@ -114,3 +175,13 @@ This function can be a coroutine.
OnCollisionStay is called once per frame for every collider/rigidbody that is touching rigidbody/collider.
This function can be a coroutine.
(8): Unity Gutter Icon: Called when Unity first launches the editor, the player, or recompiles scripts
(9): Unity Gutter Icon: Unity custom serializable type
(10): Unity Gutter Icon: This field is initialised by Unity
(11): Unity Gutter Icon: This field is initialised by Unity
(12): Unity Gutter Icon: This field is initialised by Unity
(13): Unity Gutter Icon: This field is initialised by Unity
(14): Unity Gutter Icon: Unity custom serializable type
(15): Unity Gutter Icon: This field is initialised by Unity
(16): Unity Gutter Icon: This field is initialised by Unity
(17): Unity Gutter Icon: This field is initialised by Unity
(18): Unity Gutter Icon: This field is initialised by Unity
Loading

0 comments on commit ee06fb5

Please sign in to comment.