Skip to content

Comments

MA0028: Detect StringBuilder.AppendFormat with no-placeholder format strings#1041

Merged
meziantou merged 2 commits intomainfrom
copilot/fix-ma0183-reporting-errors
Feb 22, 2026
Merged

MA0028: Detect StringBuilder.AppendFormat with no-placeholder format strings#1041
meziantou merged 2 commits intomainfrom
copilot/fix-ma0183-reporting-errors

Conversation

Copy link
Contributor

Copilot AI commented Feb 22, 2026

MA0028 didn't cover StringBuilder.AppendFormat calls where the format string has no {n} placeholders, making any extra arguments dead weight.

Changes

  • Analyzer — Detects AppendFormat(format, args...) and AppendFormat(provider, format, args...) when the format string is a compile-time constant with no placeholders
  • Fixer — Replaces the call with Append(formatString), dropping unused arguments and the optional IFormatProvider
  • Common — Added HasFormatPlaceholders helper (handles escaped braces {{}}, alignment {0,10}, format specifiers {0:D2})

Example

// Before — args are never used
sb.AppendFormat("NO PLACEHOLDERS", arg1, arg2);
sb.AppendFormat(provider, "NO PLACEHOLDERS", arg1);

// After
sb.Append("NO PLACEHOLDERS");
sb.Append("NO PLACEHOLDERS");

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • 1s1vsblobprodcus386.vsblob.vsassets.io
    • Triggering command: /usr/share/dotnet/dotnet dotnet restore --no-dependencies /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/Meziantou.Analyzer.slnx --packages /home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:AllowMissingPrunePackageData=true (dns block)
  • pdfvsblobprodcus380.vsblob.vsassets.io
    • Triggering command: /usr/share/dotnet/dotnet dotnet restore --no-dependencies /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/Meziantou.Analyzer.slnx --packages /home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:AllowMissingPrunePackageData=true (dns block)
  • st8vsblobprodcus339.vsblob.vsassets.io
    • Triggering command: /usr/share/dotnet/dotnet dotnet restore --no-dependencies /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/Meziantou.Analyzer.slnx --packages /home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:AllowMissingPrunePackageData=true (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>MA0183 only reports string.Format invocations</issue_title>
<issue_description>### Version of the Meziantou.Analyzer NuGet package

3.0.10

Rule Identifier

MA0183

Target Framework

.NET 10.0

C# Language version

C# 14

Description

In it's current state, MA0183 is a partial duplicate of S3457 and CA2241.

  • MA00183 and S3457 report diagnostics for use of String.Format(....) without placeholders and no arguments.
  • MA00183, S3457 and CA2241 report diagnostic for use of String.Format(....) without placeholders and one or more arguments.
  • S3457 and CA2241 also report diagnostics for other methods (eg. Console.Write(...), StringBuilder.AppendFormat(...)). CA2241 appears to have a whitelist of classes/methods that it reports diagnostics for and can be configured to scan additional methods or automatically detect formatting methods. Not sure about S3457, and since the documentation is no longer available ...

I propose to either:

  • Explicitly consider the following additional overloads as formatting methods:
    • System.Console.Write(string format, object arg0)
    • System.Console.Write(string format, object arg0, object arg1)
    • System.Console.Write(string format, object arg0, object arg1, object arg2)
    • System.Console.Write(string format, params object[] arg)
    • System.Console.WriteLine(string format, object arg0)
    • System.Console.WriteLine(string format, object arg0, object arg1)
    • System.Console.WriteLine(string format, object arg0, object arg1, object arg2)
    • System.Console.WriteLine(string format, params object[] arg)
    • System.Text.StringBuilder.AppendFormat(string format, object arg0)
    • System.Text.StringBuilder.AppendFormat(string format, object arg0, object arg1)
    • System.Text.StringBuilder.AppendFormat(string format, object arg0, object arg1, object arg2)
    • System.Text.StringBuilder.AppendFormat(string format, params object[] args)
    • System.Text.StringBuilder.AppendFormat(IFormatProvider provider, string format, object arg0)
    • System.Text.StringBuilder.AppendFormat(IFormatProvider provider, string format, object arg0, object arg1)
    • System.Text.StringBuilder.AppendFormat(IFormatProvider provider, string format, object arg0, object arg1, object arg2)
    • System.Text.StringBuilder.AppendFormat(IFormatProvider provider, string format, params object[] args)
  • Automatically consider methods as formatting methods if they have a string argument named format (preceded by zero of more other arguments) following by either one or more object arguments or one params object[] argument.
  • Combination of both but opt-in to automatic detection of formatting methods.

Reproduction Steps

Minimal code:

_ = string.Format("ABC"); // NOK, reported by S3457 and MA0183
_ = string.Format("ABC", true); // NOK, reported by S3457, CA2241 and MA0183

Console.WriteLine("NO PLACEHOLDERS"); // OK
Console.WriteLine("NO PLACEHOLDERS", true); // NOK, reported by S3457 and CA2241
Console.WriteLine("NO PLACEHOLDERS", true, true); // NOK, reported by S3457 and CA2241
Console.WriteLine("NO PLACEHOLDERS", true, true, true); // NOK, reported by S3457 and CA2241
Console.WriteLine("NO PLACEHOLDERS", true, true, true, true); // NOK, reported by S3457 and CA2241

var sb = new StringBuilder();
sb.AppendFormat("NO PLACEHOLDERS"); // NOK, reported by S3457
sb.AppendFormat("NO PLACEHOLDERS", true); // NOK, reported by S3457 and CA2241
sb.AppendFormat("NO PLACEHOLDERS", true, true); // NOK, reported by S3457 and CA2241
sb.AppendFormat("NO PLACEHOLDERS", true, true, true); // NOK, reported by S3457 and CA2241
sb.AppendFormat("NO PLACEHOLDERS", true, true, true, true); // NOK, reported by S3457 and CA2241

CustomWrite("NO PLACEHOLDER"); // OK ?; not reported by any analyzer
CustomWrite("NO PLACEHOLDER", true); // NOK; not reported by any analyzer
CustomWrite("NO PLACEHOLDER", true, false); // NOK; reported by CA2241 when opting in to automatic detection of formatting methods

CustomWrite2("NO PLACEHOLDER"); // OK
CustomWrite2("NO PLACEHOLDER", true); // NOK; reported by CA2241 when opting in to automatic detection of formatting methods

private static void CustomWrite(string format, object arg0)
{
    Console.WriteLine(format, arg0);
}

private static void CustomWrite(string format, params object?[] args)
{
    Console.WriteLine(format, args);
}

private static void CustomWrite2(string message)
{
    Console.WriteLine(message);
}

private static void CustomWrite2(string format, params object?[] args)
{
    Console.WriteLine(format, args);
}

Other information

No response</issue_description>

<agent_instructions>For

sb.AppendFormat("NO PLACEHOLDERS"); // NOK, reported by S3457
sb.AppendFormat("NO PLACEHOLDERS", true); // NOK, reported by S3457 and CA2241
sb.AppendFormat("NO ...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Contributes to meziantou/Meziantou.Analyzer#1029

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security)

…strings

Co-authored-by: meziantou <509220+meziantou@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix MA0183 to report only string.Format invocations MA0028: Detect StringBuilder.AppendFormat with no-placeholder format strings Feb 22, 2026
Copilot AI requested a review from meziantou February 22, 2026 16:34
@meziantou meziantou marked this pull request as ready for review February 22, 2026 18:20
@meziantou meziantou merged commit 9d2958f into main Feb 22, 2026
12 checks passed
@meziantou meziantou deleted the copilot/fix-ma0183-reporting-errors branch February 22, 2026 22:26
This was referenced Feb 23, 2026
IhateTrains pushed a commit to ParadoxGameConverters/ImperatorToCK3 that referenced this pull request Feb 23, 2026
Updated
[Meziantou.Analyzer](https://github.com/meziantou/Meziantou.Analyzer)
from 2.0.302 to 3.0.15.

<details>
<summary>Release notes</summary>

_Sourced from [Meziantou.Analyzer's
releases](https://github.com/meziantou/Meziantou.Analyzer/releases)._

## 3.0.15

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.15>

## What's Changed
* MA0183: Extend detection to Console.Write, Console.WriteLine, and
StringBuilder.AppendFormat by @​Copilot in
meziantou/Meziantou.Analyzer#1042


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.14...3.0.15

## 3.0.14

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.14>

## What's Changed
* docs(MA0028): Remove incorrect AppendFormat optimization example by
@​Copilot in meziantou/Meziantou.Analyzer#1039
* docs: Document MA0158 as similar to IDE0330 by @​Copilot in
meziantou/Meziantou.Analyzer#1040
* MA0028: Detect StringBuilder.AppendFormat with no-placeholder format
strings by @​Copilot in
meziantou/Meziantou.Analyzer#1041


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.13...3.0.14

## 3.0.13

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.13>

## What's Changed
* Fix incorrect CA1065 → MA0072 equivalence mapping by @​Copilot in
meziantou/Meziantou.Analyzer#1028
* Fix IDE0330 comparison incorrectly referencing MA0153 instead of
MA0158 by @​Copilot in
meziantou/Meziantou.Analyzer#1036
* docs(MA0042): Document false positive when calling sync method from
its own async counterpart by @​Copilot in
meziantou/Meziantou.Analyzer#1038
* Fix MA0158 not reported when lock field is initialized in constructor
by @​Copilot in
meziantou/Meziantou.Analyzer#1037


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.12...3.0.13

## 3.0.12

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.12>

## What's Changed
* MA0056: Reclassify as "similar to" CA2214 and document differences by
@​Copilot in meziantou/Meziantou.Analyzer#1024
* Fix MA0027 not firing for derived exception types by @​Copilot in
meziantou/Meziantou.Analyzer#1026


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.11...3.0.12

## 3.0.11

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.11>

## What's Changed
* Reduce test fakiness with source generators by @​meziantou in
meziantou/Meziantou.Analyzer#1022


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.10...3.0.11

## 3.0.10

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.10>

## What's Changed
* Add test for implicit params array in AppendLine with AppendFormat by
@​meziantou in meziantou/Meziantou.Analyzer#1021


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.9...3.0.10

## 3.0.9

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.9>

## What's Changed
* MA0029: Suppress all Where().X() diagnostics on IQueryable by
@​Copilot in meziantou/Meziantou.Analyzer#1017


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.8...3.0.9

## 3.0.8

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.8>

## What's Changed
* Remove exception handling from WithSourceGeneratorsFromNuGet and
document Roslyn version testing by @​Copilot in
meziantou/Meziantou.Analyzer#1015


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.7...3.0.8

## 3.0.7

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.7>

## What's Changed
* Fix MA0110 to handle property initializers by @​Copilot in
meziantou/Meziantou.Analyzer#1014


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.6...3.0.7

## 3.0.6

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.6>

## What's Changed
* MA0110: Suggest partial property before partial method by @​Copilot in
meziantou/Meziantou.Analyzer#1013


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.5...3.0.6

## 3.0.5

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.5>

## What's Changed
* MA0110: Support top-level statements by @​Copilot in
meziantou/Meziantou.Analyzer#1012


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.4...3.0.5

## 3.0.4

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.4>

## What's Changed
* MA0110: Remove field/variable when generating partial property code
fix by @​Copilot in
meziantou/Meziantou.Analyzer#1011


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.3...3.0.4

## 3.0.3

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.3>

## What's Changed
* MA0110: Derive generated regex names from field/variable context by
@​Copilot in meziantou/Meziantou.Analyzer#1010


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.2...3.0.3

## 3.0.2

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.2>

## What's Changed
* Add partial property support for MA0110 Regex Source Generator by
@​Copilot in meziantou/Meziantou.Analyzer#1009


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.1...3.0.2

## 3.0.1

NuGet package: <https://www.nuget.org/packages/Meziantou.Analyzer/3.0.1>

## What's Changed
* Remove Roslyn 3.x support by @​Copilot in
meziantou/Meziantou.Analyzer#1007


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.302...3.0.1

Commits viewable in [compare
view](meziantou/Meziantou.Analyzer@2.0.302...3.0.15).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Meziantou.Analyzer&package-manager=nuget&previous-version=2.0.302&new-version=3.0.15)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This was referenced Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants