Skip to content

Commit

Permalink
Fix camel-case conversions to ignore allcaps (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongin authored Oct 15, 2024
1 parent df2edf0 commit 5fb0a61
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 32 deletions.
81 changes: 58 additions & 23 deletions src/NodeApi.DotNetHost/JSMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,59 @@ public JSMarshaller()
/// </summary>
public bool AutoCamelCase { get; set; }

private string ToCamelCase(string name)
public static string ToCamelCase(string name)
{
if (!AutoCamelCase) return name;
if (name.Length == 0)
{
return name;
}

// Skip leading underscores.
int firstLetterIndex = 0;
while (name[firstLetterIndex] == '_')
{
firstLetterIndex++;
if (firstLetterIndex == name.Length)
{
// Only underscores.
return name;
}
}

// Only convert if it looks like title-case. (Avoid converting ALLCAPS.)
if (char.IsUpper(name[firstLetterIndex]))
{
for (int i = firstLetterIndex + 1; i < name.Length; i++)
{
if (char.IsLower(name[i]))
{
// Found at least one lowercase letter. Convert to camel-case and return.
char[] chars = name.ToCharArray();
chars[firstLetterIndex] = char.ToLower(name[firstLetterIndex]);
return new string(chars);
}
}
}

return name;
}

private UnaryExpression JSMemberNameExpression(MemberInfo member)
{
string name = member.Name;
int lastDotIndex = name.LastIndexOf('.');
if (lastDotIndex > 0)
{
// For explicit interface implementations, use the simple name.
name = name.Substring(lastDotIndex + 1);
}

string jsName = AutoCamelCase ? ToCamelCase(name) : name;

StringBuilder sb = new(name);
sb[0] = char.ToLowerInvariant(sb[0]);
return sb.ToString();
return Expression.Convert(
Expression.Constant(jsName),
typeof(JSValue),
typeof(JSValue).GetImplicitConversion(typeof(string), typeof(JSValue)));
}

/// <summary>
Expand Down Expand Up @@ -603,14 +649,7 @@ public LambdaExpression BuildToJSMethodExpression(MethodInfo method)
* }
*/

// If the method is an explicit interface implementation, parse off the simple name.
// Then convert to JSValue for use as a JS property name.
int dotIndex = method.Name.LastIndexOf('.');
Expression methodName = Expression.Convert(
Expression.Constant(ToCamelCase(
dotIndex >= 0 ? method.Name.Substring(dotIndex + 1) : method.Name)),
typeof(JSValue),
typeof(JSValue).GetImplicitConversion(typeof(string), typeof(JSValue)));
Expression methodName = JSMemberNameExpression(method);

Expression ParameterToJSValue(int index) => InlineOrInvoke(
GetToJSValueExpression(methodParameters[index].ParameterType),
Expand Down Expand Up @@ -1114,10 +1153,7 @@ public LambdaExpression BuildToJSPropertyGetExpression(PropertyInfo property)
* }
*/

Expression propertyName = Expression.Convert(
Expression.Constant(ToCamelCase(property.Name)),
typeof(JSValue),
typeof(JSValue).GetImplicitConversion(typeof(string), typeof(JSValue)));
Expression propertyName = JSMemberNameExpression(property);

Expression getStatement = Expression.Assign(
resultVariable,
Expand Down Expand Up @@ -1189,10 +1225,7 @@ public LambdaExpression BuildToJSPropertySetExpression(PropertyInfo property)
* }
*/

Expression propertyName = Expression.Convert(
Expression.Constant(ToCamelCase(property.Name)),
typeof(JSValue),
typeof(JSValue).GetImplicitConversion(typeof(string), typeof(JSValue)));
Expression propertyName = JSMemberNameExpression(property);

Expression convertStatement = Expression.Assign(
jsValueVariable,
Expand Down Expand Up @@ -2962,7 +2995,8 @@ private IEnumerable<Expression> BuildFromJSToStructExpressions(
continue;
}

Expression propertyName = Expression.Constant(ToCamelCase(property.Name));
Expression propertyName = Expression.Constant(
AutoCamelCase ? ToCamelCase(property.Name) : property.Name);
memberBindings.Add(Expression.Bind(property, InlineOrInvoke(
GetFromJSValueExpression(property.PropertyType),
Expression.Property(valueVariable, s_valueItem, propertyName),
Expand Down Expand Up @@ -3021,7 +3055,8 @@ private IEnumerable<Expression> BuildToJSFromStructExpressions(
continue;
}

Expression propertyName = Expression.Constant(ToCamelCase(property.Name));
Expression propertyName = Expression.Constant(
AutoCamelCase ? ToCamelCase(property.Name) : property.Name);
yield return Expression.Assign(
Expression.Property(jsValueVariable, s_valueItem, propertyName),
InlineOrInvoke(
Expand Down
2 changes: 1 addition & 1 deletion src/NodeApi.Generator/ModuleGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ public static string GetExportName(ISymbol symbol)
}

// Member names are automatically formatted as camelCase; type names are not.
return symbol is ITypeSymbol ? symbol.Name : ToCamelCase(symbol.Name);
return symbol is ITypeSymbol ? symbol.Name : JSMarshaller.ToCamelCase(symbol.Name);
}

/// <summary>
Expand Down
7 changes: 0 additions & 7 deletions src/NodeApi.Generator/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,6 @@ public static string GetFullName(ISymbol symbol)
return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}";
}

public static string ToCamelCase(string name)
{
StringBuilder sb = new(name);
sb[0] = char.ToLowerInvariant(sb[0]);
return sb.ToString();
}

public void ReportException(Exception ex)
{
// The compiler diagnostic will only show up to the first \r or \n.
Expand Down
3 changes: 2 additions & 1 deletion src/NodeApi.Generator/TypeDefinitionsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.JavaScript.NodeApi.DotNetHost;
using Microsoft.JavaScript.NodeApi.Interop;
using static Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller;
using NullabilityInfo = System.Reflection.NullabilityInfo;
Expand Down Expand Up @@ -2010,7 +2011,7 @@ private string GetExportName(MemberInfo member)
}
}

return _autoCamelCase && member is not Type ? ToCamelCase(name) : name;
return _autoCamelCase && member is not Type ? JSMarshaller.ToCamelCase(name) : name;
}
}

Expand Down

0 comments on commit 5fb0a61

Please sign in to comment.