Meziantou.Analyzer Documentation

repository·main·Indexed 22 days ago

https://github.com/meziantou/meziantou.analyzer

A 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.

Tokens
73.1K
Snippets
266
Records
290
Agent score
77%

What's inside Meziantou.Analyzer

  1. Overview of Meziantou.Analyzer

    main
    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.
  2. Overview of Meziantou.Analyzer rules

    main

    Meziantou.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, or CancellationToken).
    • 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>(), optimizing StringBuilder, 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.

  3. MA0047 - Declare types in namespaces

    main
    The MA0047 rule 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.
  4. MA0062 - Non-flags enums should not be marked with "FlagsAttribute"

    main

    This 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,
    }
  5. MA0100 - Await task before disposing of resources

    main

    The 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 a using block) 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);
        }
    }
  6. MA0076 - Do not use implicit culture-sensitive ToString in interpolated strings

    main

    This 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.Invariant or string.Create with CultureInfo.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}";
  7. Understand rule MA0135: The log parameter has no configured type

    main

    Rule MA0135 checks that every placeholder used in a Microsoft.Extensions.Logging call is registered in your LoggerParameterTypes.txt configuration 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}", "");
  8. Use CultureInsensitiveTypeAttribute to suppress culture-related analyzer rules

    main

    The CultureInsensitiveTypeAttribute is used to mark types whose ToString() 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.Annotations NuGet 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-insensitive
  9. Exception for ExecutionContext.SuppressFlow() in MA0100

    main

    The MA0100 rule explicitly ignores cases where the disposable resource is System.Threading.AsyncFlowControl (the type returned by ExecutionContext.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 using block 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());
            }
        }
    }
  10. MA0184 - Do not use interpolated string without parameters

    main

    The 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.";
  11. MA0053 - Make class or record sealed

    main

    The MA0053 rule identifies classes and records that are not marked as sealed but have no inheritors within the current project context. Marking classes as sealed can provide performance benefits in .NET and clearly communicates intent.

    By default, the analyzer does not report public types (because it cannot know if they are inherited in other projects), types with virtual members, or types that inherit from Exception.

    public class Foo // compliant
    {
    }
    
    public class Bar : Foo // Non compliant
    {
    }
    
    // Should be
    public sealed class Bar : Foo
    {
    }
  12. MA0050 - Validate arguments correctly in iterator methods

    main

    In C#, methods containing a yield statement use deferred execution. This means the method body is not executed until the returned IEnumerable is actually enumerated. If you perform argument validation (like checking for null) 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 yield logic. This ensures that ArgumentException-derived exceptions (including those thrown via ArgumentNullException.ThrowIfNull or 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;
        }
    }