Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[generator] Support [Obsolete]/[SupportedOSPlatform] attributes for enum members. #1066

Merged
merged 1 commit into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 80 additions & 7 deletions tests/generator-Tests/Unit-Tests/EnumGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Java.Interop.Tools.Generator.Enumification;
using MonoDroid.Generation;
using NUnit.Framework;
using NUnit.Framework.Internal;
using Xamarin.Android.Binder;

namespace generatortests
{
[TestFixture]
class EnumGeneratorTests
class EnumGeneratorTests : CodeGeneratorTestBase
{
protected EnumGenerator generator;
protected StringBuilder builder;
protected StringWriter writer;
protected new EnumGenerator generator;

protected override CodeGenerationTarget Target => CodeGenerationTarget.XAJavaInterop1;

[SetUp]
public void SetUp ()
public new void SetUp ()
{
builder = new StringBuilder ();
writer = new StringWriter (builder);
Expand Down Expand Up @@ -60,7 +62,78 @@ public void WriteEnumWithGens ()
Assert.AreEqual (GetExpected (nameof (WriteEnumWithGens)), writer.ToString ().NormalizeLineEndings ());
}

protected string GetExpected (string testName)
[Test]
public void ObsoletedOSPlatformAttributeSupport ()
{
var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='android.app' jni-name='android/app'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='ActivityManager' static='false' visibility='public' jni-signature='Landroid/app/ActivityManager;'>
<field deprecated='deprecated' final='true' name='RECENT_IGNORE_UNAVAILABLE' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.BIND_CHOOSER_TARGET_SERVICE&quot;' visibility='public' volatile='false' deprecated-since='31' api-since='30' />
</class>
</package>
</api>";

options.UseObsoletedOSPlatformAttributes = true;

var enu = CreateEnum ();
var gens = ParseApiDefinition (xml);

generator.WriteEnumeration (options, enu, gens.ToArray ());

// Ensure [ObsoletedOSPlatform] and [SupportedOSPlatform] are written
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Runtime.Versioning.SupportedOSPlatformAttribute(\"android30.0\")][global::System.Runtime.Versioning.ObsoletedOSPlatform(\"android31.0\")]WithExcluded=1"), writer.ToString ());
}

[Test]
public void ObsoleteAttributeSupport ()
{
var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='android.app' jni-name='android/app'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='ActivityManager' static='false' visibility='public' jni-signature='Landroid/app/ActivityManager;'>
<field deprecated='deprecated' final='true' name='RECENT_IGNORE_UNAVAILABLE' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.BIND_CHOOSER_TARGET_SERVICE&quot;' visibility='public' volatile='false' deprecated-since='31' api-since='30' />
</class>
</package>
</api>";

var enu = CreateEnum ();
var gens = ParseApiDefinition (xml);

generator.WriteEnumeration (options, enu, gens.ToArray ());

// Ensure [Obsolete] is written
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete(@\"deprecated\")]WithExcluded=1"), writer.ToString ());
}

[Test]
public void ObsoleteFieldButNotEnumAttributeSupport ()
{
var xml = @"<api>
<package name='java.lang' jni-name='java/lang'>
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
</package>
<package name='android.app' jni-name='android/app'>
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='ActivityManager' static='false' visibility='public' jni-signature='Landroid/app/ActivityManager;'>
<field deprecated='This constant will be removed in the future version. Use Android.App.RecentTaskFlags enum directly instead of this field.' final='true' name='RECENT_IGNORE_UNAVAILABLE' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.BIND_CHOOSER_TARGET_SERVICE&quot;' visibility='public' volatile='false' deprecated-since='31' api-since='30' />
</class>
</package>
</api>";

var enu = CreateEnum ();
var gens = ParseApiDefinition (xml);

generator.WriteEnumeration (options, enu, gens.ToArray ());

// [Obsolete] should not be written because the value isn't deprecated, just the _field_ is deprecated because we want people to use the enum instead
Assert.False (writer.ToString ().NormalizeLineEndings ().Contains ("[global::System.Obsolete(@\"deprecated\")]WithExcluded=1"), writer.ToString ());
}

protected new string GetExpected (string testName)
{
var root = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location);

Expand All @@ -71,7 +144,7 @@ protected string GetExpected (string testName)
{
var enu = new EnumMappings.EnumDescription {
Members = new List<ConstantEntry> {
new ConstantEntry { EnumMember = "WithExcluded", Value = "1", JavaSignature = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE" },
new ConstantEntry { EnumMember = "WithExcluded", Value = "1", JavaSignature = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE", ApiLevel = 30 },
new ConstantEntry { EnumMember = "IgnoreUnavailable", Value = "2", JavaSignature = "android/app/ActivityManager.RECENT_WITH_EXCLUDED" }
},
BitField = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,36 +50,43 @@ EnumWriter CreateWriter (CodeGenerationOptions opt, KeyValuePair<string, EnumDes
Value = member.Value.Trim (),
};

// Try to find the original field in our model
var managedMember = FindManagedMember (enu.Value, member, gens);
var managedMemberName = managedMember != null ? $"{managedMember.Value.Cls.FullName}.{managedMember.Value.Field.Name}" : null;

if (opt.CodeGenerationTarget != CodeGenerationTarget.JavaInterop1)
m.Attributes.Add (new IntDefinitionAttr (managedMember, StripExtraInterfaceSpec (member.JavaSignature)));
m.Attributes.Add (new IntDefinitionAttr (managedMemberName, StripExtraInterfaceSpec (member.JavaSignature)));

SourceWriterExtensions.AddSupportedOSPlatform (m.Attributes, member.ApiLevel, opt);

// Some of our source fields may have been marked with:
// "This constant will be removed in the future version. Use XXX enum directly instead of this field."
// We don't want this message to propogate to the enum.
if (managedMember != null && managedMember.Value.Field?.DeprecatedComment?.Contains ("enum directly instead of this field") == false)
SourceWriterExtensions.AddObsolete (m.Attributes, managedMember.Value.Field.DeprecatedComment, opt, deprecatedSince: managedMember.Value.Field.DeprecatedSince);

enoom.Members.Add (m);
}

return enoom;
}

string FindManagedMember (EnumDescription desc, ConstantEntry member, IEnumerable<GenBase> gens)
WeakReference cache_found_class;

(GenBase Cls, Field Field)? FindManagedMember (EnumDescription desc, ConstantEntry constant, IEnumerable<GenBase> gens)
{
if (desc.FieldsRemoved)
return null;

var jniMember = member.JavaSignature;
var jniMember = constant.JavaSignature;

if (string.IsNullOrWhiteSpace (jniMember)) {
// enum values like "None" falls here.
return null;
}
return FindManagedMember (jniMember, gens);
}

WeakReference cache_found_class;
ParseJniMember (jniMember, out var package, out var type, out var member);

string FindManagedMember (string jniMember, IEnumerable<GenBase> gens)
{
string package, type, member;
ParseJniMember (jniMember, out package, out type, out member);
var fullJavaType = (string.IsNullOrEmpty (package) ? "" : package + ".") + type;

var cls = cache_found_class != null ? cache_found_class.Target as GenBase : null;
Expand All @@ -96,7 +103,7 @@ string FindManagedMember (string jniMember, IEnumerable<GenBase> gens)
// The field was not found e.g. removed by metadata fixup.
return null;
}
return cls.FullName + "." + fld.Name;
return (cls, fld);
}

internal void ParseJniMember (string jniMember, out string package, out string type, out string member)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,12 +293,14 @@ public static void AddParameterListCallArgs (List<string> body, ParameterList pa
}

public static void AddSupportedOSPlatform (List<AttributeWriter> attributes, ApiVersionsSupport.IApiAvailability member, CodeGenerationOptions opt)
=> AddSupportedOSPlatform (attributes, member.ApiAvailableSince, opt);

public static void AddSupportedOSPlatform (List<AttributeWriter> attributes, int since, CodeGenerationOptions opt)
{
// There's no sense in writing say 'android15' because we do not support older APIs,
// so those APIs will be available in all of our versions.
if (member.ApiAvailableSince > 21 && opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1)
attributes.Add (new SupportedOSPlatformAttr (member.ApiAvailableSince));

if (since > 21 && opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1)
attributes.Add (new SupportedOSPlatformAttr (since));
}

public static void AddObsolete (List<AttributeWriter> attributes, string message, CodeGenerationOptions opt, bool forceDeprecate = false, bool isError = false, int? deprecatedSince = null)
Expand Down