Skip to content

Commit a675045

Browse files
[build] enable CA1305 for most projects
Context: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305 Context: https://learn.microsoft.com/dotnet/csharp/tutorials/string-interpolation#how-to-create-a-culture-specific-result-string-with-string-interpolation In b1079a0, we found a real bug where omitting `CultureInfo.InvariantCulture` caused a problem under certain locales. Let's take this a step further by making `CA1305` a build error. In every case I found, it seemed that invariant culture was fine -- and some might be bugs? This also catches cases like this that use the current culture: $"My integer: {x}" Where we should do this instead: FormattableString.Invariant($"My integer: {x}") Only a single instance for displaying a localized string/error message from MSBuild seemed like it needed the current culture: string.Format (CultureInfo.CurrentCulture, Resources.XA2000, assembly)); And this one might not even matter, really? Some projects that are not shipped I didn't enable yet, to keep this change smaller: * Projects in `build-tools` * MSBuild test projects
1 parent c523686 commit a675045

File tree

36 files changed

+128
-119
lines changed

36 files changed

+128
-119
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public
215215
# Code files
216216
[*.{cs,vb}]
217217

218+
dotnet_diagnostic.CA1305.severity = error # Specify IFormatProvider
218219
dotnet_diagnostic.CA2153.severity = error # Do Not Catch Corrupted State Exceptions
219220
dotnet_diagnostic.CA2301.severity = error # Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder
220221
dotnet_diagnostic.CA2302.severity = error # Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize
@@ -337,7 +338,6 @@ dotnet_diagnostic.CA1070.severity = none # Do not declare event fields as virtu
337338
dotnet_diagnostic.CA1200.severity = none # Avoid using cref tags with a prefix
338339
dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters
339340
dotnet_diagnostic.CA1304.severity = none # Specify CultureInfo
340-
dotnet_diagnostic.CA1305.severity = none # Specify IFormatProvider
341341
dotnet_diagnostic.CA1307.severity = none # Specify StringComparison
342342
dotnet_diagnostic.CA1308.severity = none # Normalize strings to uppercase
343343
dotnet_diagnostic.CA1309.severity = none # Use ordinal stringcomparison

build-tools/api-merge/api-merge.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<TargetFramework>$(DotNetStableTargetFramework)</TargetFramework>
77
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
88
<OutputPath>..\..\bin\Build$(Configuration)</OutputPath>
9+
<NoWarn>$(NoWarn);CA1305</NoWarn>
910
</PropertyGroup>
1011

1112
<Import Project="..\..\Configuration.props" />

build-tools/jnienv-gen/jnienv-gen.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<TargetFramework>$(DotNetStableTargetFramework)</TargetFramework>
77
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
88
<OutputPath>..\..\bin\Build$(Configuration)</OutputPath>
9+
<NoWarn>$(NoWarn);CA1305</NoWarn>
910
</PropertyGroup>
1011

1112
<Import Project="..\..\Configuration.props" />

build-tools/remap-assembly-ref/remap-assembly-ref.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public static int Main (String[] args) {
3232
}
3333
}
3434
if (!found) {
35-
Console.Error.WriteLine (string.Format ("remap-assembly-ref.exe: warning: Assembly reference '{0}' not found in file '{1}'.", ref1, in_aname));
35+
Console.Error.WriteLine (FormattableString.Invariant ($"remap-assembly-ref.exe: warning: Assembly reference '{ref1}' not found in file '{in_aname}'."));
3636
}
3737

3838
ad.Write (out_aname);

build-tools/xaprepare/xaprepare/xaprepare.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<AssemblyName>xaprepare</AssemblyName>
99
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1010
<Nullable>enable</Nullable>
11+
<NoWarn>$(NoWarn);CA1305</NoWarn>
1112
</PropertyGroup>
1213

1314
<Import Project="../../../Configuration.props" />

src/Mono.Android.Export/Mono.CodeGeneration/CodeCustomAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public static CodeCustomAttribute Create (Type attributeType, Type [] ctorArgTyp
4242
if (members [i] == null)
4343
members [i] = attributeType.GetProperty (namedArgNames [i]);
4444
if (members [i] == null)
45-
throw new ArgumentException (String.Format ("Named argument {0} was not found in attribute type {1}", namedArgNames [i], attributeType));
45+
throw new ArgumentException (FormattableString.Invariant ($"Named argument {namedArgNames [i]} was not found in attribute type {attributeType}"));
4646
}
4747

4848
CodeLiteral [] args = new CodeLiteral [ctorArgs.Length];
@@ -64,7 +64,7 @@ public static CodeCustomAttribute Create (Type attributeType, Type [] ctorArgTyp
6464
ArrayList fvalues = new ArrayList ();
6565
for (int i = 0; i < members.Length; i++) {
6666
if (members [i] == null)
67-
throw new ArgumentException (String.Format ("MemberInfo at {0} was null for type {1}.", i, attributeType));
67+
throw new ArgumentException (FormattableString.Invariant ($"MemberInfo at {i} was null for type {attributeType}."));
6868
if (members [i] is PropertyInfo) {
6969
props.Add ((PropertyInfo) members [i]);
7070
pvalues.Add (values [i].Value);

src/Mono.Android/Android.Graphics/Color.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Globalization;
45
using System.Linq.Expressions;
56
using System.Reflection;
67
using System.Text;
@@ -64,7 +65,7 @@ public int ToArgb ()
6465

6566
public override string ToString ()
6667
{
67-
return String.Format ("Color [A={0}, R={1}, G={2}, B={3}]", A, R, G, B);
68+
return FormattableString.Invariant ($"Color [A={A}, R={R}, G={G}, B={B}]");
6869
}
6970

7071
public override bool Equals (object obj)
@@ -217,9 +218,8 @@ private static void CheckRGBValues (int red, int green, int blue)
217218

218219
private static ArgumentException CreateColorArgumentException (int value, string color)
219220
{
220-
return new ArgumentException (string.Format ("'{0}' is not a valid"
221-
+ " value for '{1}'. '{1}' should be greater or equal to 0 and"
222-
+ " less than or equal to 255.", value, color));
221+
return new ArgumentException (FormattableString.Invariant (
222+
$"'{value}' is not a valid value for '{color}'. '{color}' should be greater or equal to 0 and less than or equal to 255."));
223223
}
224224

225225
public static Color ParseColor (string colorString)

src/Mono.Android/Android.Runtime/AndroidEnvironment.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ static bool TrustEvaluateSsl (List <byte[]> certsRawData)
188188
if (certStore == null)
189189
return null;
190190

191-
var alias = string.Format ("{0}:{1:x8}.0", userStore ? "user" : "system", hash);
191+
var store = userStore ? "user" : "system";
192+
var alias = FormattableString.Invariant ($"{store}:{hash:x8}.0");
192193
var certificate = certStore.GetCertificate (alias);
193194
if (certificate == null)
194195
return null;
@@ -220,7 +221,7 @@ static void NotifyTimeZoneChanged ()
220221
try {
221222
clearInfo.Method ();
222223
} catch (Exception e) {
223-
Logger.Log (LogLevel.Warn, "MonoAndroid", string.Format ("Ignoring exception from {0}: {1}", clearInfo.Description, e));
224+
Logger.Log (LogLevel.Warn, "MonoAndroid", FormattableString.Invariant ($"Ignoring exception from {clearInfo.Description}: {e}"));
224225
}
225226
}
226227
}
@@ -426,11 +427,7 @@ public Uri GetProxy (Uri destination)
426427
if (address == null) // FIXME
427428
return destination;
428429

429-
#if ANDROID_19
430-
return new Uri (string.Format ("http://{0}:{1}/", address.HostString, address.Port));
431-
#else
432-
return new Uri (string.Format ("http://{0}:{1}/", address.HostName, address.Port));
433-
#endif
430+
return new Uri (FormattableString.Invariant ($"http://{address.HostName}:{address.Port}/"));
434431
}
435432

436433
public bool IsBypassed (Uri host)

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.Globalization;
45
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67
using System.Runtime.Versioning;
@@ -165,7 +166,7 @@ public override IntPtr ReleaseLocalReference (ref JniObjectReference value, ref
165166

166167
public override void WriteGlobalReferenceLine (string format, params object?[] args)
167168
{
168-
RuntimeNativeMethods._monodroid_gref_log (string.Format (format, args));
169+
RuntimeNativeMethods._monodroid_gref_log (string.Format (CultureInfo.InvariantCulture, format, args));
169170
}
170171

171172
public override JniObjectReference CreateGlobalReference (JniObjectReference value)
@@ -530,7 +531,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
530531
}
531532

532533
if (minfo == null)
533-
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname.ToString (), signature.ToString ()));
534+
throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}"));
534535
callback = CreateDynamicCallback (minfo);
535536
needToRegisterNatives = true;
536537
} else {
@@ -659,9 +660,8 @@ internal void AddPeer (IJavaPeerable value, JniObjectReference reference, IntPtr
659660
if (JniEnvironment.Types.IsSameObject (value.PeerReference, target!.PeerReference)) {
660661
found = true;
661662
if (Logger.LogGlobalRef) {
662-
Logger.Log (LogLevel.Info, "monodroid-gref",
663-
string.Format ("warning: not replacing previous registered handle {0} with handle {1} for key_handle 0x{2}",
664-
target.PeerReference.ToString (), reference.ToString (), hash.ToString ("x")));
663+
Logger.Log (LogLevel.Info, "monodroid-gref", FormattableString.Invariant (
664+
$"warning: not replacing previous registered handle {target.PeerReference} with handle {reference} for key_handle 0x{hash:x}"));
665665
}
666666
}
667667
}
@@ -704,10 +704,11 @@ internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership tr
704704
}
705705

706706
if (Logger.LogGlobalRef) {
707-
RuntimeNativeMethods._monodroid_gref_log ("handle 0x" + handleField.ToString ("x") +
708-
"; key_handle 0x" + hash.ToString ("x") +
709-
": Java Type: `" + JNIEnv.GetClassNameFromInstance (handleField) + "`; " +
710-
"MCW type: `" + value.GetType ().FullName + "`\n");
707+
RuntimeNativeMethods._monodroid_gref_log (
708+
FormattableString.Invariant ($"handle 0x{handleField:x}") +
709+
FormattableString.Invariant ($"; key_handle 0x{hash:x}") +
710+
FormattableString.Invariant ($": Java Type: `{JNIEnv.GetClassNameFromInstance (handleField)}`; ") +
711+
FormattableString.Invariant ($"MCW type: `{value.GetType ().FullName}`\n"));
711712
}
712713
}
713714

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.Globalization;
45
using System.IO;
56
using System.Reflection;
67
using System.Runtime.CompilerServices;
@@ -262,8 +263,8 @@ public static unsafe void InvokeConstructor (IntPtr instance, string jniCtorSign
262263
try {
263264
IntPtr ctor = JNIEnv.GetMethodID (lrefClass, "<init>", jniCtorSignature);
264265
if (ctor == IntPtr.Zero)
265-
throw new ArgumentException (string.Format ("Could not find constructor JNI signature '{0}' on type '{1}'.",
266-
jniCtorSignature, Java.Interop.TypeManager.GetClassName (lrefClass)));
266+
throw new ArgumentException (FormattableString.Invariant (
267+
$"Could not find constructor JNI signature '{jniCtorSignature}' on type '{Java.Interop.TypeManager.GetClassName (lrefClass)}'."));
267268
CallNonvirtualVoidMethod (instance, lrefClass, ctor, constructorParameters);
268269
} finally {
269270
DeleteLocalRef (lrefClass);
@@ -280,8 +281,8 @@ public static unsafe IntPtr CreateInstance (IntPtr jniClass, string signature, J
280281
{
281282
IntPtr ctor = JNIEnv.GetMethodID (jniClass, "<init>", signature);
282283
if (ctor == IntPtr.Zero)
283-
throw new ArgumentException (string.Format ("Could not find constructor JNI signature '{0}' on type '{1}'.",
284-
signature, Java.Interop.TypeManager.GetClassName (jniClass)));
284+
throw new ArgumentException (FormattableString.Invariant (
285+
$"Could not find constructor JNI signature '{signature}' on type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."));
285286
return JNIEnv.NewObject (jniClass, ctor, constructorParameters);
286287
}
287288

@@ -627,9 +628,8 @@ static void AssertCompatibleArrayTypes (Type sourceType, IntPtr destArray)
627628
IntPtr lrefDest = GetObjectClass (destArray);
628629
try {
629630
if (!IsAssignableFrom (grefSource, lrefDest)) {
630-
throw new InvalidCastException (string.Format ("Unable to cast from '{0}' to '{1}'.",
631-
Java.Interop.TypeManager.GetClassName (grefSource),
632-
Java.Interop.TypeManager.GetClassName (lrefDest)));
631+
throw new InvalidCastException (FormattableString.Invariant (
632+
$"Unable to cast from '{Java.Interop.TypeManager.GetClassName (grefSource)}' to '{Java.Interop.TypeManager.GetClassName (lrefDest)}'."));
633633
}
634634
} finally {
635635
DeleteGlobalRef (grefSource);
@@ -643,9 +643,8 @@ static void AssertCompatibleArrayTypes (IntPtr sourceArray, Type destType)
643643
IntPtr lrefSource = GetObjectClass (sourceArray);
644644
try {
645645
if (!IsAssignableFrom (lrefSource, grefDest)) {
646-
throw new InvalidCastException (string.Format ("Unable to cast from '{0}' to '{1}'.",
647-
Java.Interop.TypeManager.GetClassName (lrefSource),
648-
Java.Interop.TypeManager.GetClassName (grefDest)));
646+
throw new InvalidCastException (FormattableString.Invariant (
647+
$"Unable to cast from '{Java.Interop.TypeManager.GetClassName (lrefSource)}' to '{Java.Interop.TypeManager.GetClassName (grefDest)}'."));
649648
}
650649
} finally {
651650
DeleteGlobalRef (grefDest);
@@ -1145,7 +1144,7 @@ static int _GetArrayLength (IntPtr array_ptr)
11451144
ret [i] = value;
11461145
ret [i] = targetType == null || targetType.IsInstanceOfType (value)
11471146
? value
1148-
: Convert.ChangeType (value, targetType);
1147+
: Convert.ChangeType (value, targetType, CultureInfo.InvariantCulture);
11491148
}
11501149

11511150
return ret;

0 commit comments

Comments
 (0)