[Generator] Refactor more string extensions. (#17532)

Move all the string methods that can be an extension to a static class
(re-use the present one) and add tests.

---------

Co-authored-by: GitHub Actions Autoformatter <github-actions-autoformatter@xamarin.com>
Co-authored-by: TJ Lambert <50846373+tj-devel709@users.noreply.github.com>
This commit is contained in:
Manuel de la Pena 2023-02-16 08:45:43 -05:00 коммит произвёл GitHub
Родитель ec67c91627
Коммит be1bee04b4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 176 добавлений и 127 удалений

Просмотреть файл

@ -160,86 +160,3 @@ public static class ReflectionExtensions {
return methods;
}
}
// Fixes bug 27430 - btouch doesn't escape identifiers with the same name as C# keywords
public static class StringExtensions {
public static string? GetSafeParamName (this string? paramName)
{
if (paramName is null)
return paramName;
if (!IsValidIdentifier (paramName, out var hasIllegalChars)) {
return hasIllegalChars ? null : "@" + paramName;
}
return paramName;
}
// Since we're building against the iOS assemblies and there's no code generation there,
// I'm bringing the implementation from:
// mono/mcs/class//System/Microsoft.CSharp/CSharpCodeGenerator.cs
static bool IsValidIdentifier (string? identifier, out bool hasIllegalChars)
{
hasIllegalChars = false;
if (identifier is null || identifier.Length == 0)
return false;
if (keywordsTable is null)
FillKeywordTable ();
if (keywordsTable!.Contains (identifier))
return false;
if (!is_identifier_start_character (identifier [0])) {
// if we are dealing with a number, we are ok, we can prepend @, else we have a problem
hasIllegalChars = !Char.IsNumber (identifier [0]);
return false;
}
for (int i = 1; i < identifier.Length; i++)
if (!is_identifier_part_character (identifier [i])) {
hasIllegalChars = true;
return false;
}
return true;
}
static bool is_identifier_start_character (char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '@' || Char.IsLetter (c);
}
static bool is_identifier_part_character (char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') || Char.IsLetter (c);
}
static void FillKeywordTable ()
{
lock (keywords) {
if (keywordsTable is null) {
keywordsTable = new Hashtable ();
foreach (string keyword in keywords) {
keywordsTable.Add (keyword, keyword);
}
}
}
}
static Hashtable? keywordsTable;
static string [] keywords = new string [] {
"abstract","event","new","struct","as","explicit","null","switch","base","extern",
"this","false","operator","throw","break","finally","out","true",
"fixed","override","try","case","params","typeof","catch","for",
"private","foreach","protected","checked","goto","public",
"unchecked","class","if","readonly","unsafe","const","implicit","ref",
"continue","in","return","using","virtual","default",
"interface","sealed","volatile","delegate","internal","do","is",
"sizeof","while","lock","stackalloc","else","static","enum",
"namespace",
"object","bool","byte","float","uint","char","ulong","ushort",
"decimal","int","sbyte","short","double","long","string","void",
"partial", "yield", "where"
};
}

Просмотреть файл

@ -215,7 +215,7 @@ public partial class Generator {
if (sel.StartsWith ("input", StringComparison.Ordinal))
name = sel;
else
name = "input" + Capitalize (sel);
name = "input" + sel.Capitalize ();
}
if (p.GetGetMethod () is not null) {
@ -239,7 +239,7 @@ public partial class Generator {
var selector = export.Selector;
if (setter)
selector = "set" + Capitalize (selector) + ":";
selector = "set" + selector.Capitalize () + ":";
if (export.ArgumentSemantic != ArgumentSemantic.None && !p.PropertyType.IsPrimitive)
print ($"[Export (\"{selector}\", ArgumentSemantic.{export.ArgumentSemantic})]");

Просмотреть файл

@ -5815,7 +5815,7 @@ public partial class Generator : IMemberGatherer {
if (p == null)
return;
print ($"[Advice ({Quote (p.Message)})]");
print ($"[Advice ({p.Message.Quote ()})]");
}
public void PrintRequiresSuperAttribute (ICustomAttributeProvider mi)
@ -5833,7 +5833,7 @@ public partial class Generator : IMemberGatherer {
if (p == null)
return;
print ($"[NotImplemented ({Quote (p.Message)})]");
print ($"[NotImplemented ({p.Message.Quote ()})]");
}
public void PrintBindAsAttribute (ICustomAttributeProvider mi, StringBuilder sb = null)
@ -6849,7 +6849,7 @@ public partial class Generator : IMemberGatherer {
var sender = pars.Length == 0 ? "this" : pars [0].Name;
var miname = PascalCase (mi.Name);
var miname = mi.Name.PascalCase ();
if (miname == previous_miname) {
// overloads, add a numbered suffix (it's internal)
previous_miname = miname;
@ -6889,7 +6889,7 @@ public partial class Generator : IMemberGatherer {
} else
eaname = "<NOTREACHED>";
print ("var handler = {0};", PascalCase (miname));
print ("var handler = {0};", miname.PascalCase ());
print ("if (handler != null){");
indent++;
string eventArgs;
@ -6926,7 +6926,7 @@ public partial class Generator : IMemberGatherer {
if (debug)
print ("Console.WriteLine (\"Method {0}.{1} invoked\");", dtype.Name, mi.Name);
print ("var handler = {0};", PascalCase (miname));
print ("var handler = {0};", miname.PascalCase ());
print ("if (handler != null)");
print (" return handler ({0}{1});",
sender,
@ -6989,7 +6989,7 @@ public partial class Generator : IMemberGatherer {
print ("if (selHandle.Equals (sel{0}Handle))", mi.Name);
}
++indent;
print ("return {0} != null;", PascalCase (mi.Name));
print ("return {0} != null;", mi.Name.PascalCase ());
--indent;
}
print ("return global::" + ns.Messaging + ".bool_objc_msgSendSuper_IntPtr (SuperHandle, " + selRespondsToSelector + ", selHandle);");
@ -7023,7 +7023,7 @@ public partial class Generator : IMemberGatherer {
string ensureArg = bta.KeepRefUntil == null ? "" : "this";
var miname = PascalCase (mi.Name);
var miname = mi.Name.PascalCase ();
if (miname == prev_miname) {
// overloads, add a numbered suffix (it's internal)
prev_miname = miname;
@ -7035,14 +7035,14 @@ public partial class Generator : IMemberGatherer {
PrintObsoleteAttributes (mi);
if (bta.Singleton && mi.GetParameters ().Length == 0 || mi.GetParameters ().Length == 1)
print ("public event EventHandler {0} {{", CamelCase (GetEventName (mi)));
print ("public event EventHandler {0} {{", GetEventName (mi).CamelCase ());
else
print ("public event EventHandler<{0}> {1} {{", GetEventArgName (mi), CamelCase (GetEventName (mi)));
print ("public event EventHandler<{0}> {1} {{", GetEventArgName (mi), GetEventName (mi).CamelCase ());
print ("\tadd {{ Ensure{0} ({1})!.{2} += value; }}", dtype.Name, ensureArg, miname);
print ("\tremove {{ Ensure{0} ({1})!.{2} -= value; }}", dtype.Name, ensureArg, miname);
print ("}\n");
} else {
print ("public {0}? {1} {{", GetDelegateName (mi), CamelCase (GetDelegateApiName (mi)));
print ("public {0}? {1} {{", GetDelegateName (mi), GetDelegateApiName (mi).CamelCase ());
print ("\tget {{ return Ensure{0} ({1})!.{2}; }}", dtype.Name, ensureArg, miname);
print ("\tset {{ Ensure{0} ({1})!.{2} = value; }}", dtype.Name, ensureArg, miname);
print ("}\n");
@ -7269,7 +7269,7 @@ public partial class Generator : IMemberGatherer {
var safe_name = pi.Name.GetSafeParamName ();
print ("public {0} {1} {{ get; set; }}",
FormatType (type, pi.ParameterType),
Capitalize (safe_name));
safe_name.Capitalize ());
if (comma)
ctor.Append (", ");
@ -7282,7 +7282,7 @@ public partial class Generator : IMemberGatherer {
print ("\npublic {0} ({1}) {{", async_type.Item1, ctor); indent++;
foreach (var pi in async_type.Item2) {
var safe_name = pi.Name.GetSafeParamName ();
print ("this.{0} = {1};", Capitalize (safe_name), safe_name);
print ("this.{0} = {1};", safe_name.Capitalize (), safe_name);
}
print ("Initialize ();");
indent--; print ("}");
@ -7450,14 +7450,6 @@ public partial class Generator : IMemberGatherer {
return null;
}
static string Capitalize (string str)
{
if (str.StartsWith ("@", StringComparison.Ordinal))
return char.ToUpper (str [1]) + str.Substring (2);
return char.ToUpper (str [0]) + str.Substring (1);
}
string GetNotificationCenter (PropertyInfo pi)
{
var a = AttributeManager.GetCustomAttributes<NotificationAttribute> (pi);
@ -7512,10 +7504,10 @@ public partial class Generator : IMemberGatherer {
{
var attrs = AttributeManager.GetCustomAttributes<EventNameAttribute> (pi);
if (attrs.Length == 0)
return CamelCase (pi.Name).GetSafeParamName ();
return pi.Name.CamelCase ().GetSafeParamName ();
var a = attrs [0];
return CamelCase (a.EvtName).GetSafeParamName ();
return a.EvtName.CamelCase ().GetSafeParamName ();
}
string RenderArgs (IEnumerable<ParameterInfo> pi)
@ -7533,16 +7525,6 @@ public partial class Generator : IMemberGatherer {
return parameters.Any (pi => pi.ParameterType.IsByRef);
}
string CamelCase (string ins)
{
return Char.ToUpper (ins [0]) + ins.Substring (1);
}
string PascalCase (string ins)
{
return Char.ToLower (ins [0]) + ins.Substring (1);
}
Dictionary<string, bool> skipGeneration = new Dictionary<string, bool> ();
string GetEventName (MethodInfo mi)
{
@ -7720,16 +7702,6 @@ public partial class Generator : IMemberGatherer {
}
public static string Quote (string s)
{
if (s == null)
return String.Empty;
if (s == string.Empty)
return @"""""";
return $"@\"{s.Replace ("\"", "\"\"")}\"";
}
private static string FormatPropertyInfo (PropertyInfo pi)
{
return pi.DeclaringType.FullName + " " + pi.Name;

Просмотреть файл

@ -0,0 +1,115 @@
using System;
using System.Collections;
#nullable enable
// Fixes bug 27430 - btouch doesn't escape identifiers with the same name as C# keywords
public static class StringExtensions {
public static string Quote (this string? self)
{
return self switch {
null => String.Empty,
"" => @"""""",
_ => $"@\"{self.Replace ("\"", "\"\"")}\""
};
}
public static string CamelCase (this string ins)
{
return Char.ToUpper (ins [0]) + ins.Substring (1);
}
public static string PascalCase (this string ins)
{
return Char.ToLower (ins [0]) + ins.Substring (1);
}
public static string Capitalize (this string str)
{
if (str.StartsWith ("@", StringComparison.Ordinal))
return char.ToUpper (str [1]) + str.Substring (2);
return char.ToUpper (str [0]) + str.Substring (1);
}
public static string? GetSafeParamName (this string? paramName)
{
if (paramName is null)
return paramName;
if (!IsValidIdentifier (paramName, out var hasIllegalChars)) {
return hasIllegalChars ? null : "@" + paramName;
}
return paramName;
}
// Since we're building against the iOS assemblies and there's no code generation there,
// I'm bringing the implementation from:
// mono/mcs/class//System/Microsoft.CSharp/CSharpCodeGenerator.cs
static bool IsValidIdentifier (string? identifier, out bool hasIllegalChars)
{
hasIllegalChars = false;
if (String.IsNullOrEmpty (identifier))
return false;
if (keywordsTable is null)
FillKeywordTable ();
if (keywordsTable!.Contains (identifier))
return false;
if (!is_identifier_start_character (identifier [0])) {
// if we are dealing with a number, we are ok, we can prepend @, else we have a problem
hasIllegalChars = !Char.IsNumber (identifier [0]);
return false;
}
for (int i = 1; i < identifier.Length; i++)
if (!is_identifier_part_character (identifier [i])) {
hasIllegalChars = true;
return false;
}
return true;
}
static bool is_identifier_start_character (char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '@' || Char.IsLetter (c);
}
static bool is_identifier_part_character (char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') || Char.IsLetter (c);
}
static void FillKeywordTable ()
{
lock (keywords) {
if (keywordsTable is null) {
keywordsTable = new Hashtable ();
foreach (string keyword in keywords) {
keywordsTable.Add (keyword, keyword);
}
}
}
}
static Hashtable? keywordsTable;
static string [] keywords = new string [] {
"abstract","event","new","struct","as","explicit","null","switch","base","extern",
"this","false","operator","throw","break","finally","out","true",
"fixed","override","try","case","params","typeof","catch","for",
"private","foreach","protected","checked","goto","public",
"unchecked","class","if","readonly","unsafe","const","implicit","ref",
"continue","in","return","using","virtual","default",
"interface","sealed","volatile","delegate","internal","do","is",
"sizeof","while","lock","stackalloc","else","static","enum",
"namespace",
"object","bool","byte","float","uint","char","ulong","ushort",
"decimal","int","sbyte","short","double","long","string","void",
"partial", "yield", "where"
};
}

Просмотреть файл

@ -114,6 +114,7 @@
<Compile Include="..\src\bgen\NamespaceManager.cs" />
<Compile Include="..\src\bgen\NullabilityInfoContext.cs" />
<Compile Include="..\src\bgen\PlatformNameExtensions.cs" />
<Compile Include="..\src\bgen\StringExtensions.cs" />
<Compile Include="..\src\bgen\TrampolineInfo.cs" />
<Compile Include="..\src\bgen\TypeManager.cs" />
<Compile Include="..\src\bgen\WrapPropMemberInformation.cs" />

Просмотреть файл

@ -1,6 +1,9 @@
using System;
using System.Collections;
using NUnit.Framework;
#nullable enable
namespace GeneratorTests {
public class StringExtensionTests {
@ -45,5 +48,46 @@ namespace GeneratorTests {
Assert.IsNotNull (legal, "legal != null");
Assert.AreEqual ("@" + illegal, legal, "legal");
}
[Test]
public void QuoteNullString ()
{
string? str = null;
Assert.AreEqual (string.Empty, str.Quote ());
}
[Test]
public void QuoteEmptyString ()
{
string str = String.Empty;
Assert.AreEqual (@"""""", str.Quote ());
}
[TestCase ("No quotes", "@\"No quotes\"")]
[TestCase ("\"quotes\"", "@\"\"\"quotes\"\"\"")]
public void QuoteString (string input, string output)
{
Assert.AreEqual (output, input.Quote ());
}
[Test]
public void CamelCaseTest ()
{
var str = "pascalCaseExample";
Assert.AreEqual ("PascalCaseExample", str.CamelCase ());
}
[Test]
public void PascalCaseTest ()
{
var str = "CamelCaseExample";
Assert.AreEqual ("camelCaseExample", str.PascalCase ());
}
[TestCase ("@thisIsNotCapitalized", "ThisIsNotCapitalized")]
[TestCase ("thisIsNotCapitalized", "ThisIsNotCapitalized")]
[TestCase ("t", "T")]
public void CapitalizeTest (string input, string output)
=> Assert.AreEqual (output, input.Capitalize ());
}
}