102 lines
4.0 KiB
Plaintext
102 lines
4.0 KiB
Plaintext
== Why is this an issue?
|
||
|
||
A https://learn.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting[composite format string] is a string that contains placeholders, represented by indices inside curly braces "{0}", "{1}", etc. These placeholders are replaced by values when the string is printed or logged.
|
||
|
||
Because composite format strings are interpreted at runtime, rather than validated by the compiler, they can contain errors that lead to unexpected behaviors or runtime errors.
|
||
|
||
This rule validates the correspondence between arguments and composite formats when calling the
|
||
following methods:
|
||
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.string.format[`String.Format`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder.appendformat[`StringBuilder.AppendFormat`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.console.write[`Console.Write`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.console.writeline[`Console.WriteLine`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.io.textwriter.write[`TextWriter.Write`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.io.textwriter.writeline[`TextWriter.WriteLine`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.writeline[`Debug.WriteLine(String, Object[\])`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.trace.traceerror[`Trace.TraceError(String, Object[\])`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.trace.traceinformation[`Trace.TraceInformation(String, Object[\])`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.trace.tracewarning[`Trace.TraceWarning(String, Object[\])`]
|
||
* https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracesource.traceinformation[`TraceSource.TraceInformation(String, Object[\])`]
|
||
|
||
=== Exceptions
|
||
|
||
* No issue is raised if the format string is not a string literal, but comes from a variable.
|
||
|
||
[source,csharp]
|
||
----
|
||
var pattern = "{0} {1} {2}";
|
||
var res = string.Format(pattern, 1, 2); // Incorrect, but the analyzer doesn't raise any warnings here
|
||
----
|
||
|
||
* No issue is raised if the argument is not an inline created array.
|
||
|
||
[source,csharp]
|
||
----
|
||
var array = new int[] {};
|
||
var res = string.Format("{0} {1}", array); // Compliant; we don't know the size of the array
|
||
----
|
||
|
||
* This rule doesn't check whether the format specifier (defined after the `:`) is actually valid.
|
||
|
||
== How to fix it
|
||
|
||
A composite format string contains placeholders, replaced by values when the string is printed or logged. Mismatch in the format specifiers and the arguments provided can lead to incorrect strings being created.
|
||
|
||
To avoid issues, a developer should ensure that the provided arguments match format specifiers.
|
||
|
||
Moreover, use https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation[string interpolation] when possible.
|
||
|
||
Instead of
|
||
|
||
[source,csharp]
|
||
----
|
||
string str = string.Format("Hello {0} {1}!", firstName, lastName);
|
||
----
|
||
|
||
use
|
||
|
||
[source,csharp]
|
||
----
|
||
string str = $"Hello {firstName} {lastName}!";
|
||
----
|
||
|
||
With string interpolation:
|
||
|
||
* the arguments are validated at compilation time rather than runtime
|
||
* modern code editors provide auto-completion when typing the interpolation expression
|
||
|
||
=== Code examples
|
||
|
||
==== Noncompliant code example
|
||
|
||
[source,csharp,diff-id=1,diff-type=noncompliant]
|
||
----
|
||
s = string.Format("{0}", arg0, arg1); // Noncompliant, arg1 is declared but not used.
|
||
s = string.Format("{0} {2}", arg0, arg1, arg2); // Noncompliant, the format item with index 1 is missing, so arg1 will not be used.
|
||
s = string.Format("foo"); // Noncompliant; there is no need to use "string.Format" here.
|
||
----
|
||
|
||
==== Compliant solution
|
||
|
||
[source,csharp,diff-id=1,diff-type=compliant]
|
||
----
|
||
s = string.Format("{0}", arg0);
|
||
s = string.Format("{0} {1}", arg0, arg2);
|
||
s = "foo";
|
||
----
|
||
|
||
ifdef::env-github,rspecator-view[]
|
||
|
||
'''
|
||
== Implementation Specification
|
||
(visible only on this page)
|
||
|
||
'''
|
||
== Comments And Links
|
||
(visible only on this page)
|
||
|
||
include::../comments-and-links.adoc[]
|
||
|
||
endif::env-github,rspecator-view[]
|