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] Add support for [ObsoletedOSPlatform]. #1026

Merged
merged 1 commit into from
Aug 23, 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
13 changes: 13 additions & 0 deletions src/utils/XmlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,18 @@ public static string XGetAttribute (this XPathNavigator nav, string name, string
var attr = nav.GetAttribute (name, ns);
return attr != null ? attr.Trim () : null;
}

public static int? XGetAttributeAsIntOrNull (this XElement element, string name)
{
var attr = element.Attribute (name);

if (attr?.Value is null)
return null;

if (int.TryParse (attr.Value, out var val))
return val;

return null;
}
}
}
80 changes: 80 additions & 0 deletions tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,86 @@ public void ObsoleteBoundMethodAbstractDeclaration ()
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is so old!\")]"), writer.ToString ());
}

[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='com.xamarin.android' jni-name='com/xamarin/android'>
<class abstract='false' deprecated='This is a class deprecated since 25!' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;' deprecated-since='25'>
<field deprecated='This is a field deprecated since 25!' final='true' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false' deprecated-since='25'></field>
<constructor deprecated='This is a constructor deprecated since 25!' final='false' name='MyClass' jni-signature='()V' bridge='false' static='false' type='com.xamarin.android.MyClass' synthetic='false' visibility='public' deprecated-since='25'></constructor>
<method abstract='true' deprecated='This is a method deprecated since 25!' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'></method>
<method abstract='false' deprecated='This is a property getter deprecated since 25!' final='false' name='getCount' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'></method>
<method abstract='false' deprecated='This is a property setter deprecated since 25!' final='false' name='setCount' jni-signature='(I)V' bridge='false' native='false' return='void' jni-return='V' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'>
<parameter name='count' type='int' jni-type='I'></parameter>
</method>
</class>
</package>
</api>";

options.UseObsoletedOSPlatformAttributes = true;

var gens = ParseApiDefinition (xml);
var iface = gens.Single (g => g.Name == "MyClass");

generator.Context.ContextTypes.Push (iface);
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

// Ensure [ObsoletedOSPlatform] was written
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a class deprecated since 25!\")]"), writer.ToString ());
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a field deprecated since 25!\")]"), writer.ToString ());
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a constructor deprecated since 25!\")]"), writer.ToString ());
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a method deprecated since 25!\")]"), writer.ToString ());
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a property getter deprecated since 25! This is a property setter deprecated since 25!\")]"), writer.ToString ());
}

[Test]
public void ObsoletedOSPlatformAttributeUnneededSupport ()
{
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='com.xamarin.android' jni-name='com/xamarin/android'>
<class abstract='false' deprecated='This is a class deprecated since 19!' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;' deprecated-since='19'>
<field deprecated='This is a field deprecated since 0!' final='true' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false' deprecated-since='0'></field>
<constructor deprecated='This is a constructor deprecated since empty string!' final='false' name='MyClass' jni-signature='()V' bridge='false' static='false' type='com.xamarin.android.MyClass' synthetic='false' visibility='public' deprecated-since=''></constructor>
<method abstract='true' deprecated='deprecated' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'></method>
<method abstract='true' deprecated='This method has an invalid deprecated-since!' final='false' name='countAffectedRows2' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='foo'></method>
<method abstract='false' deprecated='deprecated' final='false' name='getCount' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='22'></method>
<method abstract='false' deprecated='deprecated' final='false' name='setCount' jni-signature='(I)V' bridge='false' native='false' return='void' jni-return='V' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='22'>
<parameter name='count' type='int' jni-type='I'></parameter>
</method>
</class>
</package>
</api>";

options.UseObsoletedOSPlatformAttributes = true;

var gens = ParseApiDefinition (xml);
var iface = gens.Single (g => g.Name == "MyClass");

generator.Context.ContextTypes.Push (iface);
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

// These should use [Obsolete] because they have always been obsolete in all currently supported versions (21+)
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a class deprecated since 19!\")]"), writer.ToString ());
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a field deprecated since 0!\")]"), writer.ToString ());
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a constructor deprecated since empty string!\")]"), writer.ToString ());

// This should not have a message because the default "deprecated" message isn't useful
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\")]"), writer.ToString ());
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android22.0\")]"), writer.ToString ());

// This should use [Obsolete] because the 'deprecated-since' attribute could not be parsed
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This method has an invalid deprecated-since!\")]"), writer.ToString ());
}

[Test]
[NonParallelizable] // We are setting a static property on Report
public void WarnIfTypeNameMatchesNamespace ()
Expand Down
1 change: 1 addition & 0 deletions tools/generator/CodeGenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public SymbolTable SymbolTable {
public bool SupportNestedInterfaceTypes { get; set; }
public bool SupportNullableReferenceTypes { get; set; }
public bool UseShallowReferencedTypes { get; set; }
public bool UseObsoletedOSPlatformAttributes { get; set; }
public bool RemoveConstSugar => BuildingCoreAssembly;

bool? buildingCoreAssembly;
Expand Down
1 change: 1 addition & 0 deletions tools/generator/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve
SupportDefaultInterfaceMethods = options.SupportDefaultInterfaceMethods,
SupportNestedInterfaceTypes = options.SupportNestedInterfaceTypes,
SupportNullableReferenceTypes = options.SupportNullableReferenceTypes,
UseObsoletedOSPlatformAttributes = options.UseObsoletedOSPlatformAttributes,
};
var resolverCache = new TypeDefinitionCache ();

Expand Down
4 changes: 3 additions & 1 deletion tools/generator/CodeGeneratorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public CodeGeneratorOptions ()
public bool SupportNestedInterfaceTypes { get; set; }
public bool SupportNullableReferenceTypes { get; set; }
public bool UseLegacyJavaResolver { get; set; }
public bool UseObsoletedOSPlatformAttributes { get; set; }

public XmldocStyle XmldocStyle { get; set; } = XmldocStyle.IntelliSense;

Expand Down Expand Up @@ -102,12 +103,13 @@ public static CodeGeneratorOptions Parse (string[] args)
"SDK Platform {VERSION}/API level.",
v => opts.ApiLevel = v },
{ "lang-features=",
"For internal use. (Flags: interface-constants,default-interface-methods,nullable-reference-types)",
"For internal use. (Flags: interface-constants,default-interface-methods,nested-interface-types,nullable-reference-types,obsoleted-platform-attributes)",
v => {
opts.SupportInterfaceConstants = v?.Contains ("interface-constants") == true;
opts.SupportDefaultInterfaceMethods = v?.Contains ("default-interface-methods") == true;
opts.SupportNestedInterfaceTypes = v?.Contains ("nested-interface-types") == true;
opts.SupportNullableReferenceTypes = v?.Contains ("nullable-reference-types") == true;
opts.UseObsoletedOSPlatformAttributes = v?.Contains ("obsoleted-platform-attributes") == true;
}},
{ "preserve-enums",
"For internal use.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public static Ctor CreateCtor (GenBase declaringType, XElement elem, CodeGenerat
ApiAvailableSince = declaringType.ApiAvailableSince,
CustomAttributes = elem.XGetAttribute ("customAttributes"),
Deprecated = elem.Deprecated (),
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
GenericArguments = elem.GenericArguments (),
Name = elem.XGetAttribute ("name"),
Visibility = elem.Visibility ()
Expand Down Expand Up @@ -200,6 +201,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener
var field = new Field {
ApiAvailableSince = declaringType.ApiAvailableSince,
DeprecatedComment = elem.XGetAttribute ("deprecated"),
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
IsAcw = true,
IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated",
IsDeprecatedError = elem.XGetAttribute ("deprecated-error") == "true",
Expand Down Expand Up @@ -237,6 +239,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener
public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, CodeGenerationOptions opt, bool isInterface)
{
var support = new GenBaseSupport {
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
IsAcw = true,
IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated",
IsGeneratable = true,
Expand Down Expand Up @@ -348,6 +351,7 @@ public static Method CreateMethod (GenBase declaringType, XElement elem, CodeGen
ArgsType = elem.Attribute ("argsType")?.Value,
CustomAttributes = elem.XGetAttribute ("customAttributes"),
Deprecated = elem.Deprecated (),
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
ExplicitInterface = elem.XGetAttribute ("explicitInterface"),
EventName = elem.Attribute ("eventName")?.Value,
GenerateAsyncWrapper = elem.Attribute ("generateAsyncWrapper") != null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class Field : ApiVersionsSupport.IApiAvailability, ISourceLineInfo
public string Annotation { get; set; }
public int ApiAvailableSince { get; set; }
public string DeprecatedComment { get; set; }
public int? DeprecatedSince { get; set; }
public bool IsAcw { get; set; }
public bool IsDeprecated { get; set; }
public bool IsDeprecatedError { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ public bool ContainsProperty (string name, bool check_ifaces, bool check_base_if

public string DeprecatedComment => support.DeprecatedComment;

public int? DeprecatedSince => support.DeprecatedSince;

IEnumerable<GenBase> Descendants (IList<GenBase> gens)
{
foreach (var directDescendants in gens.Where (x => x.BaseGen == this)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class GenBaseSupport
public bool IsAcw { get; set; }
public bool IsDeprecated { get; set; }
public string DeprecatedComment { get; set; }
public int? DeprecatedSince { get; set; }
public bool IsGeneratable { get; set; }
public bool IsGeneric { get; set; }
public bool IsObfuscated { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protected MethodBase (GenBase declaringType)
public string AssemblyName { get; set; }
public GenBase DeclaringType { get; }
public string Deprecated { get; set; }
public int? DeprecatedSince { get; set; }
public GenericParameterDefinitionList GenericArguments { get; set; }
public bool IsAcw { get; set; }
public bool IsValid { get; private set; }
Expand Down
12 changes: 5 additions & 7 deletions tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,19 @@ public class ObsoleteAttr : AttributeWriter

public ObsoleteAttr (string message = null, bool isError = false)
{
Message = message;
Message = message?.Replace ("\"", "\"\"").Trim ();
IsError = isError;
}

public override void WriteAttribute (CodeWriter writer)
{
var parts = new List<string> ();
var content = string.Empty;

if (Message != null)
parts.Add ($"@\"{Message}\"");
if (Message != null || IsError)
content += $"@\"{Message}\"";

if (IsError)
parts.Add ("error: true");

var content = string.Join (", ", parts.ToArray ());
content += ", error: true";

if (content.HasValue ())
writer.WriteLine ($"[global::System.Obsolete ({content})]");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using Xamarin.SourceWriter;

namespace generator.SourceWriters
{
public class ObsoletedOSPlatformAttr : AttributeWriter
{
public string Message { get; set; }
public int Version { get; }

public ObsoletedOSPlatformAttr (string message, int version)
{
Message = message;
Version = version;
}

public override void WriteAttribute (CodeWriter writer)
{
if (Message.HasValue ())
writer.WriteLine ($"[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android{Version}.0\", @\"{Message.Replace ("\"", "\"\"")}\")]");
else
writer.WriteLine ($"[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android{Version}.0\")]");
}
}
}
2 changes: 1 addition & 1 deletion tools/generator/SourceWriters/BoundClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public BoundClass (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorConte
klass.JavadocInfo?.AddJavadocs (Comments);
Comments.Add ($"// Metadata.xml XPath class reference: path=\"{klass.MetadataXPathReference}\"");

SourceWriterExtensions.AddObsolete (Attributes, klass.DeprecatedComment, klass.IsDeprecated);
SourceWriterExtensions.AddObsolete (Attributes, klass.DeprecatedComment, opt, forceDeprecate: klass.IsDeprecated, deprecatedSince: klass.DeprecatedSince);

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, klass, opt);

Expand Down
2 changes: 1 addition & 1 deletion tools/generator/SourceWriters/BoundConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public BoundConstructor (ClassGen klass, Ctor constructor, bool useBase, CodeGen
Attributes.Add (new RegisterAttr (".ctor", constructor.JniSignature, string.Empty, additionalProperties: constructor.AdditionalAttributeString ()));
}

SourceWriterExtensions.AddObsolete (Attributes, constructor.Deprecated);
SourceWriterExtensions.AddObsolete (Attributes, constructor.Deprecated, opt, deprecatedSince: constructor.DeprecatedSince);

if (constructor.CustomAttributes != null)
Attributes.Add (new CustomAttr (constructor.CustomAttributes));
Expand Down
2 changes: 1 addition & 1 deletion tools/generator/SourceWriters/BoundField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public BoundField (GenBase type, Field field, CodeGenerationOptions opt)
if (field.IsEnumified)
Attributes.Add (new GeneratedEnumAttr ());

SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, field.IsDeprecated, isError: field.IsDeprecatedError);
SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, opt, field.IsDeprecated, isError: field.IsDeprecatedError, deprecatedSince: field.DeprecatedSince);

if (field.Annotation.HasValue ())
Attributes.Add (new CustomAttr (field.Annotation));
Expand Down
2 changes: 1 addition & 1 deletion tools/generator/SourceWriters/BoundFieldAsProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions op
Attributes.Add (new RegisterAttr (field.JavaName, additionalProperties: field.AdditionalAttributeString ()));
}

SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, field.IsDeprecated, isError: field.IsDeprecatedError);
SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, opt, field.IsDeprecated, isError: field.IsDeprecatedError, deprecatedSince: field.DeprecatedSince);

SetVisibility (field.Visibility);
UseExplicitPrivateKeyword = true;
Expand Down
2 changes: 1 addition & 1 deletion tools/generator/SourceWriters/BoundInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public BoundInterface (InterfaceGen iface, CodeGenerationOptions opt, CodeGenera
iface.JavadocInfo?.AddJavadocs (Comments);
Comments.Add ($"// Metadata.xml XPath interface reference: path=\"{iface.MetadataXPathReference}\"");

SourceWriterExtensions.AddObsolete (Attributes, iface.DeprecatedComment, iface.IsDeprecated);
SourceWriterExtensions.AddObsolete (Attributes, iface.DeprecatedComment, opt, iface.IsDeprecated, deprecatedSince: iface.DeprecatedSince);

if (!iface.IsConstSugar (opt)) {
var signature = string.IsNullOrWhiteSpace (iface.Namespace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGener
if (method.DeclaringType.IsGeneratable)
Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\"");

SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated);
SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated, opt, deprecatedSince: method.DeprecatedSince);

if (method.IsReturnEnumified)
Attributes.Add (new GeneratedEnumAttr (true));
Expand Down
2 changes: 1 addition & 1 deletion tools/generator/SourceWriters/BoundMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool
if (method.DeclaringType.IsGeneratable)
Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\"");

SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated);
SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated, opt, deprecatedSince: method.DeprecatedSince);

if (method.IsReturnEnumified)
Attributes.Add (new GeneratedEnumAttr (true));
Expand Down
Loading