|
| 1 | +# Utf8StringInterpolation.Analyzers |
| 2 | + |
| 3 | +[](https://www.nuget.org/packages/Utf8StringInterpolation.Analyzers) |
| 4 | +[](LICENSE) |
| 5 | + |
| 6 | +Roslyn analyzers for [Utf8StringInterpolation](https://github.com/Cysharp/Utf8StringInterpolation) — the zero-allocation UTF-8 string interpolation library by [Cysharp](https://github.com/Cysharp). These analyzers catch usage patterns that silently defeat the library's performance guarantees at compile time, turning invisible runtime regressions into build errors. |
| 7 | + |
| 8 | +## The problem |
| 9 | + |
| 10 | +`Utf8StringInterpolation` achieves zero allocation by making `Utf8StringWriter<T>` an [interpolated string handler](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/interpolated-string-handler). When you call `AppendFormat($"...")`, the C# compiler routes the interpolation directly into the handler — no intermediate `string` is ever created. |
| 11 | + |
| 12 | +However, `Utf8StringWriter<T>` also exposes `Append(string?)`. When you write `Append($"...")`, the compiler sees a plain `string` parameter with no handler attribute, so it **materializes the entire interpolated string as a heap-allocated `string` first**, then passes it in. The code compiles cleanly, produces correct output, and silently allocates — the exact opposite of the library's purpose. |
| 13 | + |
| 14 | +```csharp |
| 15 | +// Looks identical. Behaves very differently. |
| 16 | +zsb.Append($"Hello, {name}!"); // allocates a string — WRONG |
| 17 | +zsb.AppendFormat($"Hello, {name}!"); // zero allocation — correct |
| 18 | +``` |
| 19 | + |
| 20 | +This package makes the wrong form a **build error**. |
| 21 | + |
| 22 | +## Diagnostics |
| 23 | + |
| 24 | +### U8SI001 — Use AppendFormat instead of Append with interpolated strings |
| 25 | + |
| 26 | +**Severity:** Error |
| 27 | +**Category:** Performance |
| 28 | + |
| 29 | +**Before (build error):** |
| 30 | +```csharp |
| 31 | +using Utf8StringInterpolation; |
| 32 | + |
| 33 | +var zsb = Utf8String.CreateWriter(stream); |
| 34 | +zsb.Append($"Hello, {name}!"); // error U8SI001 |
| 35 | +zsb.Append($"You have {count} items."); // error U8SI001 |
| 36 | +``` |
| 37 | + |
| 38 | +**After (zero allocation):** |
| 39 | +```csharp |
| 40 | +zsb.AppendFormat($"Hello, {name}!"); |
| 41 | +zsb.AppendFormat($"You have {count} items."); |
| 42 | +``` |
| 43 | + |
| 44 | +A **code fix** is provided. In Visual Studio, click the lightbulb on any U8SI001 error to replace `Append` with `AppendFormat` automatically. On the command line: |
| 45 | + |
| 46 | +```bash |
| 47 | +dotnet format analyzers <project> --diagnostics U8SI001 --severity error |
| 48 | +``` |
| 49 | + |
| 50 | +## Installation |
| 51 | + |
| 52 | +```xml |
| 53 | +<PackageReference Include="Utf8StringInterpolation.Analyzers" Version="x.y.z"> |
| 54 | + <PrivateAssets>all</PrivateAssets> |
| 55 | + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
| 56 | +</PackageReference> |
| 57 | +``` |
| 58 | + |
| 59 | +The package contains both the analyzer and the code fix provider. It has no runtime impact — `PrivateAssets="all"` ensures it is not listed as a dependency of your package. |
| 60 | + |
| 61 | +## Compatibility |
| 62 | + |
| 63 | +- C# projects targeting any .NET version (.NET Framework, .NET Core, .NET 5+) |
| 64 | +- Requires the [Utf8StringInterpolation](https://www.nuget.org/packages/Utf8StringInterpolation) NuGet package |
| 65 | +- Tested with Visual Studio, VS Code, and `dotnet build` |
| 66 | + |
| 67 | +## How it works |
| 68 | + |
| 69 | +The analyzer registers on every `InvocationExpression` and checks: |
| 70 | + |
| 71 | +1. The method name is `Append` |
| 72 | +2. At least one argument is an `InterpolatedStringExpressionSyntax` — meaning the compiler resolved it to `Append(string?)`, not to an interpolated string handler |
| 73 | +3. The receiver type is `Utf8StringInterpolation.Utf8StringWriter<T>` (verified via semantic model, not string matching) |
| 74 | + |
| 75 | +If all three conditions hold, U8SI001 is reported on the method name. The code fix rewrites the method name to `AppendFormat`, leaving the argument unchanged. |
| 76 | + |
| 77 | +## Related |
| 78 | + |
| 79 | +- [Utf8StringInterpolation](https://github.com/Cysharp/Utf8StringInterpolation) — the library this package guards |
| 80 | +- [ZString](https://github.com/Cysharp/ZString) — the predecessor library (`Utf8StringInterpolation` is its successor) |
| 81 | +- [InterpolatedStringHandler](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/interpolated-string-handler) — the C# feature that makes zero-allocation interpolation possible |
| 82 | + |
| 83 | +## Contributing |
| 84 | + |
| 85 | +Bug reports and pull requests welcome. The repository includes unit tests (Roslyn testing framework) and an integration test that builds a real .NET Framework project, applies the code fix via `dotnet format`, and verifies the result. |
| 86 | + |
| 87 | +## Versioning |
| 88 | + |
| 89 | +Every push to `main` publishes a new patch version automatically via [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning). The version is `major.minor.<commit-height>`. To bump major or minor, edit `version.json`. |
| 90 | + |
| 91 | +## License |
| 92 | + |
| 93 | +[MIT](LICENSE) |
0 commit comments