[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:
Родитель
ec67c91627
Коммит
be1bee04b4
|
@ -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 ());
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче