Meziantou.Analyzer Documentation
repository·main·Indexed 22 days ago
https://github.com/meziantou/meziantou.analyzerA Roslyn-based static analysis tool for C# designed to identify bugs, security risks, and best practice violations. It provides a wide range of diagnostic rules categorized by Usage, Style, Performance, Design, and Security (e.g., MA0001-MA0055), along with automated code fixes and refactorings like ConvertToStringFormat and MakeInterpolatedString. The analyzer also includes Meziantou.Analyzer.Annotations for rule configuration and specific MAS rules to suppress standard .NET analyzer warnings in certain contexts.
What's inside Meziantou.Analyzer
- Meziantou.Analyzer is a Roslyn-based static analysis tool for C#. It is designed to detect bugs, surface security vulnerabilities, and enforce coding best practices during development. It provides real-time feedback as you write code and offers automated fixes (code actions) for many detected issues, making it suitable for both manual development and AI-assisted coding workflows.
Overview of Meziantou.Analyzer rules
mainMeziantou.Analyzer provides a collection of code analysis rules for C# to improve code quality, performance, security, and design. The rules are categorized into several types:
- Usage: Focuses on correct usage of APIs and language features (e.g., missing
StringComparison,IFormatProvider, orCancellationToken). - Style: Focuses on code readability and formatting (e.g., adding parameter names or trailing commas).
- Performance: Focuses on optimizing code execution (e.g., using
Array.Empty<T>(), optimizingStringBuilder, or avoiding unnecessary LINQ calls). - Design: Focuses on architectural best practices (e.g., making classes
sealed, using collection abstractions, or avoiding blocking calls in async methods). - Security: Focuses on preventing security vulnerabilities (e.g., requiring regex evaluation timeouts or preventing custom certificate validation).
Rules vary in Severity (Info ℹ️, Warning ⚠️, Error ❌) and support for Code Fixes and Configuration.
- Usage: Focuses on correct usage of APIs and language features (e.g., missing
MA0047 - Declare types in namespaces
mainTheMA0047rule flags types that are declared outside of any named namespace. Types declared without a namespace reside in the global namespace, which cannot be explicitly referenced in code and increases the risk of name collisions. To resolve this, wrap your types in a named namespace to improve organization and prevent collisions.MA0062 - Non-flags enums should not be marked with "FlagsAttribute"
mainThis rule flags enumeration types that are decorated with
[Flags]but contain members that are not powers of two or bitwise combinations of other power-of-two members. Using[Flags]on an enum that doesn't follow bitwise logic can lead to unexpected behavior during bitwise operations.[Flags] // Non-compliant, as 'Orange' is neither a power of two, nor a bitwise combination of existing "power of two" members public enum Color { None = 0, Red = 1, Orange = 3, Yellow = 4, }MA0100 - Await task before disposing of resources
mainThe MA0100 rule detects instances where a
Task,Task<T>,ValueTask,ValueTask<T>, or any type following the awaitable pattern is returned from a method while a disposable resource (like ausingblock) is still in scope. If the task is not awaited within the method, the resource may be disposed of before the task actually completes, leading to unpredictable behavior or errors.To fix this, ensure the task is awaited before the scope of the disposable resource ends.
using System; using System.Threading.Tasks; class TestClass { // Violates MA0100: scope is disposed before the returned task completes Task Demo1() { using var scope = new Disposable(); return Task.Delay(1); } // Correct: task is awaited before scope ends async Task Demo2() { using var scope = new Disposable(); return await Task.Delay(1); } }MA0076 - Do not use implicit culture-sensitive ToString in interpolated strings
mainThis rule flags interpolated strings that implicitly call
ToString()on culture-sensitive types. Because the output of these calls depends on the current thread's culture, the resulting string can vary across different environments, leading to unexpected behavior in data processing or UI rendering.To comply with this rule, you should use methods that allow specifying an explicit culture, such as
FormattableString.Invariantorstring.CreatewithCultureInfo.InvariantCulture.// Non-compliant: result depends on current culture _ = $"abc{-1}"; // Compliant: uses Invariant culture _ = FormattableString.Invariant($"abc{-1}"); // Compliant: uses string.Create with explicit culture _ = string.Create(CultureInfo.InvariantCulture, $"abc{-1}"); // Compliant: implicit conversion to FormattableString FormattableString str = $"abc{-1}";Understand rule MA0135: The log parameter has no configured type
mainRule
MA0135checks that every placeholder used in aMicrosoft.Extensions.Loggingcall is registered in yourLoggerParameterTypes.txtconfiguration file.- Non-compliant: Using a placeholder in a log message (e.g.,
{Prop}) that is not defined in the configuration file. - Compliant: Using a placeholder (e.g.,
{Name}) that has a corresponding entry in the configuration file.
using Microsoft.Extensions.Logging; ILogger logger = null; // Non-compliant: "Prop" is not defined in LoggerParameterTypes.txt logger.LogInformation("{Prop}", 2); // Compliant: "Name" is defined in LoggerParameterTypes.txt logger.LogInformation("{Name}", "");- Non-compliant: Using a placeholder in a log message (e.g.,
Use CultureInsensitiveTypeAttribute to suppress culture-related analyzer rules
mainThe
CultureInsensitiveTypeAttributeis used to mark types whoseToString()methods and format strings are culture-insensitive. Applying this attribute suppresses culture-related analyzer rules:- MA0011: IFormatProvider is missing
- MA0075: Do not use implicit culture-sensitive ToString
- MA0076: Do not use implicit culture-sensitive ToString in interpolated strings
You can obtain the attribute via the
Meziantou.Analyzer.AnnotationsNuGet package, or by copying the attribute definition directly into your project. The analyzer identifies it by name and namespace.using Meziantou.Analyzer.Annotations; [CultureInsensitiveType] public struct Ulid { public override string ToString() => "..."; // Culture-insensitive implementation } // Usage - no warning var id = new Ulid(); id.ToString(); // OK - Type is marked as culture-insensitiveException for ExecutionContext.SuppressFlow() in MA0100
mainThe MA0100 rule explicitly ignores cases where the disposable resource is
System.Threading.AsyncFlowControl(the type returned byExecutionContext.SuppressFlow()).This is considered safe because the execution context is captured at the moment the task is created, meaning the task does not need to be awaited before the
usingblock ends to ensure the context is correctly applied.using System; using System.Threading; using System.Threading.Tasks; class TestClass { Task Demo() { // OK: execution context is captured at task creation using (ExecutionContext.SuppressFlow()) { return Task.Run(() => DoWork()); } } }MA0184 - Do not use interpolated string without parameters
mainThe MA0184 rule identifies unnecessary interpolated strings that do not contain any parameters. Using an interpolated string (e.g.,
$"text") without any holes (e.g.,{variable}) is considered redundant. For better clarity and consistency, these should be converted to regular string literals ("text").// ❌ Bad: Using interpolated string without parameters var message = $"Required attribute 'output' not found."; // ✅ Good: Use a regular string literal var message = "Required attribute 'output' not found.";MA0053 - Make class or record sealed
mainThe MA0053 rule identifies classes and records that are not marked as
sealedbut have no inheritors within the current project context. Marking classes assealedcan provide performance benefits in .NET and clearly communicates intent.By default, the analyzer does not report
publictypes (because it cannot know if they are inherited in other projects), types withvirtualmembers, or types that inherit fromException.public class Foo // compliant { } public class Bar : Foo // Non compliant { } // Should be public sealed class Bar : Foo { }MA0050 - Validate arguments correctly in iterator methods
mainIn C#, methods containing a
yieldstatement use deferred execution. This means the method body is not executed until the returnedIEnumerableis actually enumerated. If you perform argument validation (like checking fornull) inside an iterator method, the validation will not occur when the method is called, but rather when the caller starts iterating over the results. This can lead to bugs where invalid arguments are not caught until much later in the program execution.To fix this, you should validate arguments in a non-iterator wrapper method and then call a private local function or nested method that contains the actual
yieldlogic. This ensures thatArgumentException-derived exceptions (including those thrown viaArgumentNullException.ThrowIfNullor similar static helpers) are thrown immediately upon method invocation.// INCORRECT: Validation is deferred until enumeration IEnumerable<int> Sample(string a) { if (a == null) throw new System.ArgumentNullException(nameof(a)); yield return 0; } // CORRECT: Validation happens immediately IEnumerable<int> Sample(string a) { if (a == null) throw new System.ArgumentNullException(nameof(a)); return SampleInternal(); IEnumerable<int> SampleInternal() { yield return 0; } }