Skip to content

Commit 5fb0a61

Browse files
authored
Fix camel-case conversions to ignore allcaps (#392)
1 parent df2edf0 commit 5fb0a61

4 files changed

Lines changed: 61 additions & 32 deletions

File tree

src/NodeApi.DotNetHost/JSMarshaller.cs

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,59 @@ public JSMarshaller()
131131
/// </summary>
132132
public bool AutoCamelCase { get; set; }
133133

134-
private string ToCamelCase(string name)
134+
public static string ToCamelCase(string name)
135135
{
136-
if (!AutoCamelCase) return name;
136+
if (name.Length == 0)
137+
{
138+
return name;
139+
}
140+
141+
// Skip leading underscores.
142+
int firstLetterIndex = 0;
143+
while (name[firstLetterIndex] == '_')
144+
{
145+
firstLetterIndex++;
146+
if (firstLetterIndex == name.Length)
147+
{
148+
// Only underscores.
149+
return name;
150+
}
151+
}
152+
153+
// Only convert if it looks like title-case. (Avoid converting ALLCAPS.)
154+
if (char.IsUpper(name[firstLetterIndex]))
155+
{
156+
for (int i = firstLetterIndex + 1; i < name.Length; i++)
157+
{
158+
if (char.IsLower(name[i]))
159+
{
160+
// Found at least one lowercase letter. Convert to camel-case and return.
161+
char[] chars = name.ToCharArray();
162+
chars[firstLetterIndex] = char.ToLower(name[firstLetterIndex]);
163+
return new string(chars);
164+
}
165+
}
166+
}
167+
168+
return name;
169+
}
170+
171+
private UnaryExpression JSMemberNameExpression(MemberInfo member)
172+
{
173+
string name = member.Name;
174+
int lastDotIndex = name.LastIndexOf('.');
175+
if (lastDotIndex > 0)
176+
{
177+
// For explicit interface implementations, use the simple name.
178+
name = name.Substring(lastDotIndex + 1);
179+
}
180+
181+
string jsName = AutoCamelCase ? ToCamelCase(name) : name;
137182

138-
StringBuilder sb = new(name);
139-
sb[0] = char.ToLowerInvariant(sb[0]);
140-
return sb.ToString();
183+
return Expression.Convert(
184+
Expression.Constant(jsName),
185+
typeof(JSValue),
186+
typeof(JSValue).GetImplicitConversion(typeof(string), typeof(JSValue)));
141187
}
142188

143189
/// <summary>
@@ -603,14 +649,7 @@ public LambdaExpression BuildToJSMethodExpression(MethodInfo method)
603649
* }
604650
*/
605651

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

615654
Expression ParameterToJSValue(int index) => InlineOrInvoke(
616655
GetToJSValueExpression(methodParameters[index].ParameterType),
@@ -1114,10 +1153,7 @@ public LambdaExpression BuildToJSPropertyGetExpression(PropertyInfo property)
11141153
* }
11151154
*/
11161155

1117-
Expression propertyName = Expression.Convert(
1118-
Expression.Constant(ToCamelCase(property.Name)),
1119-
typeof(JSValue),
1120-
typeof(JSValue).GetImplicitConversion(typeof(string), typeof(JSValue)));
1156+
Expression propertyName = JSMemberNameExpression(property);
11211157

11221158
Expression getStatement = Expression.Assign(
11231159
resultVariable,
@@ -1189,10 +1225,7 @@ public LambdaExpression BuildToJSPropertySetExpression(PropertyInfo property)
11891225
* }
11901226
*/
11911227

1192-
Expression propertyName = Expression.Convert(
1193-
Expression.Constant(ToCamelCase(property.Name)),
1194-
typeof(JSValue),
1195-
typeof(JSValue).GetImplicitConversion(typeof(string), typeof(JSValue)));
1228+
Expression propertyName = JSMemberNameExpression(property);
11961229

11971230
Expression convertStatement = Expression.Assign(
11981231
jsValueVariable,
@@ -2962,7 +2995,8 @@ private IEnumerable<Expression> BuildFromJSToStructExpressions(
29622995
continue;
29632996
}
29642997

2965-
Expression propertyName = Expression.Constant(ToCamelCase(property.Name));
2998+
Expression propertyName = Expression.Constant(
2999+
AutoCamelCase ? ToCamelCase(property.Name) : property.Name);
29663000
memberBindings.Add(Expression.Bind(property, InlineOrInvoke(
29673001
GetFromJSValueExpression(property.PropertyType),
29683002
Expression.Property(valueVariable, s_valueItem, propertyName),
@@ -3021,7 +3055,8 @@ private IEnumerable<Expression> BuildToJSFromStructExpressions(
30213055
continue;
30223056
}
30233057

3024-
Expression propertyName = Expression.Constant(ToCamelCase(property.Name));
3058+
Expression propertyName = Expression.Constant(
3059+
AutoCamelCase ? ToCamelCase(property.Name) : property.Name);
30253060
yield return Expression.Assign(
30263061
Expression.Property(jsValueVariable, s_valueItem, propertyName),
30273062
InlineOrInvoke(

src/NodeApi.Generator/ModuleGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ public static string GetExportName(ISymbol symbol)
859859
}
860860

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

865865
/// <summary>

src/NodeApi.Generator/SourceGenerator.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,6 @@ public static string GetFullName(ISymbol symbol)
7777
return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}";
7878
}
7979

80-
public static string ToCamelCase(string name)
81-
{
82-
StringBuilder sb = new(name);
83-
sb[0] = char.ToLowerInvariant(sb[0]);
84-
return sb.ToString();
85-
}
86-
8780
public void ReportException(Exception ex)
8881
{
8982
// The compiler diagnostic will only show up to the first \r or \n.

src/NodeApi.Generator/TypeDefinitionsGenerator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Xml.Linq;
1616
using Microsoft.CodeAnalysis;
1717
using Microsoft.CodeAnalysis.Text;
18+
using Microsoft.JavaScript.NodeApi.DotNetHost;
1819
using Microsoft.JavaScript.NodeApi.Interop;
1920
using static Microsoft.JavaScript.NodeApi.DotNetHost.JSMarshaller;
2021
using NullabilityInfo = System.Reflection.NullabilityInfo;
@@ -2010,7 +2011,7 @@ private string GetExportName(MemberInfo member)
20102011
}
20112012
}
20122013

2013-
return _autoCamelCase && member is not Type ? ToCamelCase(name) : name;
2014+
return _autoCamelCase && member is not Type ? JSMarshaller.ToCamelCase(name) : name;
20142015
}
20152016
}
20162017

0 commit comments

Comments
 (0)