StrictAnalyzers 1.3.0

dotnet add package StrictAnalyzers --version 1.3.0
                    
NuGet\Install-Package StrictAnalyzers -Version 1.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="StrictAnalyzers" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StrictAnalyzers" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="StrictAnalyzers" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add StrictAnalyzers --version 1.3.0
                    
#r "nuget: StrictAnalyzers, 1.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package StrictAnalyzers@1.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=StrictAnalyzers&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=StrictAnalyzers&version=1.3.0
                    
Install as a Cake Tool

StrictAnalyzers Documentation

Please put a star for this project if you find out it is useful! Thank you!

Get Started

All settings are required except commented as optional

<PropertyGroup>
    <NoWarn>
        $(NoWarn);
    
    </NoWarn>

    <WarningsAsErrors>
        $(WarningsAsErrors);
        
        CS8019;
        
        IDE0005;
        
        IDE0130;

        
        RS1032;

        
        
        CA1508;
        STAR_NO_ASSIGN;
        STAR_NO_NULLABLE_CHECKS_FOR_NON_NULL_TYPES;
        STAR_NO_POST_DECREMENT;
        STAR_NO_POST_INCREMENT;
        STAR_NO_PRE_DECREMENT;
        STAR_NO_PRE_INCREMENT;
        STAR_REQUIRED_PROPERTIES;
        STAR_REQUIRED_FIELDS;
        STAR_MAX_LINE_LENGTH;
        STAR_IMMUTABLE_CLASSES;
        STAR_NO_MUTABLE_COLLECTIONS;
        STAR_NO_MUTABLE_PARAMETERS;
    </WarningsAsErrors>

    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <AnalysisLevel>latest</AnalysisLevel>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
    <RunCodeAnalysis>true</RunCodeAnalysis>
    <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>

    
    <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>

Rules

  1. STAR_IMMUTABLE_CLASSES - Class Immutability Analyzer
  2. STAR_NO_ASSIGN - No Assignment Analyzer
  3. STAR_REQUIRED_FIELDS - Required Fields Analyzer
  4. STAR_NO_MUTABLE_PARAMETERS - Method Parameter Immutability Analyzer
  5. STAR_NO_MUTABLE_COLLECTIONS - No Mutable Collections Analyzer
  6. STAR_REQUIRED_PROPERTIES - Required Properties Analyzer
  7. STAR_IMMUTABLE_RECORDS - Record Immutability Analyzer
  8. STAR_MAX_LINE_LENGTH - Max Line Length Analyzer
  9. STAR_NO_PRE_INCREMENT - Pre-Increment Analyzer
  10. STAR_NO_POST_INCREMENT - Post-Increment Analyzer
  11. STAR_NO_PRE_DECREMENT - Pre-Decrement Analyzer
  12. STAR_NO_POST_DECREMENT - Post-Decrement Analyzer
  13. STAR_NO_NULLABLE_CHECKS_FOR_NON_NULL_TYPES - No Nullable Null Check Analyzer
  14. STAR_NO_THROW - No Throw Analyzer

Class Immutability Analyzer

Diagnostic ID: STAR_IMMUTABLE_CLASSES

Category: Design

Severity: Warning

Description: Classes should be immutable to ensure thread safety and prevent unexpected state changes. This analyzer detects classes with mutable properties or non-readonly fields.

Incorrect Code Examples

// ❌ Class with mutable properties
public class Person
{
    public string Name { get; set; }  // Mutable setter
    public int Age { get; set; }      // Mutable setter
    private string _email;             // Non-readonly field
    
    public string Email
    {
        get => _email;
        set => _email = value;        // Mutable setter
    }
}

// ❌ Class with non-readonly fields
public class Configuration
{
    private string _connectionString;  // Should be readonly
    public string ConnectionString
    {
        get => _connectionString;
        set => _connectionString = value;
    }
}

Correct Code Examples

// ✅ Immutable class with init-only properties
public class Person
{
    public string Name { get; init; }  // Init-only setter
    public int Age { get; init; }      // Init-only setter
    
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

// ✅ Immutable class with readonly fields
public class Configuration
{
    private readonly string _connectionString;
    
    public string ConnectionString => _connectionString;
    
    public Configuration(string connectionString)
    {
        _connectionString = connectionString;
    }
}

// ✅ Class with expression-bodied properties (readonly)
public class Point
{
    public int X { get; }
    public int Y { get; }
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

No Assignment Analyzer

Diagnostic ID: STAR_NO_ASSIGN

Category: Syntax

Severity: Warning

Description: Assignment operations are not allowed outside of variable declaration, object initialization, or constructor contexts. This analyzer enforces strict control over when assignments can occur.

Incorrect Code Examples

// ❌ Assignment outside of declaration
public class Example
{
    private string _name;
    
    public void UpdateName(string newName)
    {
        _name = newName;  // Assignment not allowed
    }
    
    public void ProcessData()
    {
        int value = 10;
        value = 20;        // Assignment not allowed
    }
}

// ❌ Assignment in method body
public void DoSomething()
{
    var config = new Configuration();
    config.IsEnabled = true;  // Assignment not allowed
}

Correct Code Examples

// ✅ Assignment in variable declaration
public class Example
{
    private readonly string _name;
    
    public Example(string name)
    {
        _name = name;  // Allowed in constructor
    }
    
    public void ProcessData()
    {
        int value = 20;  // Declaration with assignment
        var result = ProcessValue(value);
    }
}

// ✅ Assignment in object initialization
public void CreateConfiguration()
{
    var config = new Configuration
    {
        IsEnabled = true,  // Allowed in object initializer
        Name = "Default"
    };
}

// ✅ Assignment in constructor
public class Person
{
    private readonly string _name;
    private readonly int _age;
    
    public Person(string name, int age)
    {
        _name = name;  // Allowed in constructor
        _age = age;    // Allowed in constructor
    }
}

Required Fields Analyzer

Diagnostic ID: STAR_REQUIRED_FIELDS

Category: Syntax

Severity: Warning

Description: Public fields must be declared as required to ensure they are properly initialized. This analyzer enforces the use of the required modifier for public fields.

Incorrect Code Examples

// ❌ Public field without required modifier
public class Person
{
    public string Name;  // Should be required
    public int Age;      // Should be required
}

// ❌ Public field without required modifier
public class Configuration
{
    public string ConnectionString;  // Should be required
    public bool IsEnabled;           // Should be required
}

Correct Code Examples

// ✅ Public fields with required modifier
public class Person
{
    public required string Name;
    public required int Age;
}

// ✅ Public fields with required modifier
public class Configuration
{
    public required string ConnectionString;
    public required bool IsEnabled;
}

// ✅ Non-public fields (no required needed)
public class Example
{
    private string _name;           // Private - no required needed
    internal int _age;              // Internal - no required needed
    protected string _description;  // Protected - no required needed
}

// ✅ Static or const fields (no required needed)
public class Constants
{
    public static string DefaultName = "Unknown";
    public const int MaxAge = 150;
}

Method Parameter Immutability Analyzer

Diagnostic ID: STAR_NO_MUTABLE_PARAMETERS

Category: Immutability

Severity: Warning

Description: Method parameters should be immutable to prevent unexpected side effects. This analyzer detects when mutable types are used as method parameters.

Configuration

The analyzer can be configured using an .editorconfig file or analyzer configuration options:

[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_types = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces_start_with = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces_start_with = System.Collections.Immutable,System.Threading
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_types = CustomType,System.Collections.Generic.IReadOnlyCollection`1,System.Collections.Generic.IReadOnlyDictionary`2,Task,CustomType
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces = MyNamespace,MyNamespace2

... where `1 and `2 mean number of type parameters for generic types. FROM VERSION 1.1.0 COMMA , USED AS DELIMITER FOR ARRAY PARAMETERS, because ; mean "comment line" in .editorconfig according to format documentation.

Configuration Parameters:

  • use_predefined_immutable_types: Boolean flag to enable/disable predefined immutable types (e.g., string, int, DateTime). Default: true
  • use_predefined_immutable_namespaces: Boolean flag to enable/disable predefined immutable namespaces (e.g., System.Threading.Tasks, System.Collections.Immutable). Default: true
  • immutable_types: Comma-separated list of custom types to treat as immutable
  • immutable_namespaces: Comma-separated list of custom namespaces to treat as immutable
  • use_predefined_immutable_namespaces_start_with: Boolean flag to enable/disable predefined immutable namespaces parts
  • immutable_namespaces_start_with: Comma-separated list of custom namespaces first part to treat as immutable

Incorrect Code Examples

// ❌ Mutable types as parameters
public class Example
{
    public void ProcessPerson(Person person)  // Person is mutable
    {
        person.Name = "Updated";  // Modifies the parameter
    }
    
    public void UpdateList(List<string> items)  // List is mutable
    {
        items.Add("New Item");  // Modifies the parameter
    }
    
    public void ModifyArray(int[] numbers)  // Array is mutable
    {
        numbers[0] = 999;  // Modifies the parameter
    }
}

// ❌ Mutable reference types
public void ProcessObject(object obj)  // Object is mutable
{
    // obj can be modified
}

Correct Code Examples

// ✅ Immutable types as parameters
public class Example
{
    public void ProcessPerson(Person person)  // Person is immutable
    {
        var updatedPerson = person with { Name = "Updated" };  // Create new instance
    }
    
    public void ProcessList(IReadOnlyList<string> items)  // Read-only interface
    {
        foreach (var item in items)
        {
            // Process without modification
        }
    }
    
    public void ProcessArray(ReadOnlySpan<int> numbers)  // Read-only span
    {
        foreach (var number in numbers)
        {
            // Process without modification
        }
    }
}

// ✅ Value types (immutable by default)
public void ProcessValue(int number, string text, DateTime date)
{
    // These parameters cannot be modified
}

// ✅ Immutable reference types
public void ProcessString(string text)  // String is immutable
{
    var upperText = text.ToUpper();  // Creates new string
}

// ✅ Using readonly parameters
public void ProcessData(in Person person)  // in parameter (readonly)
{
    // person cannot be modified
}

Configuration Examples

Example 1: Disabling Predefined Immutable Types

[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_types = false

With this configuration, even built-in types like string, int, and DateTime will be flagged as mutable:

// ❌ Will now produce diagnostics with predefined_immutable_types = false
public void ProcessData(string text, int number, DateTime date)
{
    // These parameters will now be flagged as mutable
}

Example 2: Adding Custom Immutable Types

[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_types = CustomImmutableType,MyNamespace.AnotherType

With this configuration, custom types are treated as immutable even if they have mutable properties:

// ✅ No diagnostic - CustomImmutableType is configured as immutable
public class CustomImmutableType
{
    public int Value { get; set; }  // Mutable property but type is configured as immutable
}

public void ProcessData(CustomImmutableType custom)
{
    // No diagnostic - type is configured as immutable
}

Example 3: Adding Custom Immutable Namespaces

[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces = MyNamespace,ThirdParty.Immutable

With this configuration, all types in specified namespaces are treated as immutable:

namespace MyNamespace
{
    public class CustomType
    {
        public int Value { get; set; }  // Mutable property but in immutable namespace
    }
}

// ✅ No diagnostic - MyNamespace is configured as immutable
public void ProcessData(MyNamespace.CustomType custom)
{
    // No diagnostic - namespace is configured as immutable
}

Example 4: Combined Configuration

[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_types = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces_start_with = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces_start_with = System.Collections.Immutable,System.Threading
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_types = CustomType,System.Collections.Generic.IReadOnlyCollection`1,System.Collections.Generic.IReadOnlyDictionary`2",Task,CustomType
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces = MyNamespace,MyNamespace2

This configuration disables all predefined immutable types and namespaces, but adds custom ones:

using System.Threading.Tasks;

namespace MyNamespace
{
    public class CustomType
    {
        public int Value { get; set; }
    }
}

public class MyClass
{
    public void MyMethod(
        string text,              // ❌ Diagnostic - predefined types disabled
        Task task,                // ❌ Diagnostic - predefined namespaces disabled
        MyNamespace.CustomType c, // ✅ No diagnostic - custom namespace
        CustomType type)          // ✅ No diagnostic - custom type
    {
    }
}

No Mutable Collections Analyzer

Diagnostic ID: STAR_NO_MUTABLE_COLLECTIONS

Category: Immutability

Severity: Warning

Description: Collections should be immutable or readonly to ensure thread safety and prevent unexpected state changes. This analyzer detects when mutable collection types are used in variable declarations, method parameters, return types, and properties.

What the Analyzer Detects

The analyzer checks for mutable collection usage in:

  • Variable declarations (e.g., var list = new List<int>())
  • Method parameters (e.g., public void Process(List<int> items))
  • Method return types (e.g., public List<int> GetItems())
  • Properties (e.g., public List<int> Items { get; set; })
  • Local functions and arrow functions (e.g., () => new List<string>())
  • Conditional operators (e.g., var result = condition ? new List<int>() : new List<int>())

What the Analyzer Allows

The analyzer allows:

  • Immutable collections from System.Collections.Immutable namespace
  • Readonly interfaces like IReadOnlyList<T>, IReadOnlyCollection<T>, IReadOnlyDictionary<TKey, TValue>
  • Non-collection types like string, int, DateTime, etc.

Configuration

You can extend the set of types treated as immutable for this analyzer via .editorconfig. Use the following option to whitelist fully qualified type names (including arity suffix like \1, `2` for generics):

dotnet_diagnostic.STAR_NO_MUTABLE_COLLECTIONS.consider_as_immutable = System.Collections.Generic.List`1
  • Place this under the desired scope in your .editorconfig (for example, under [*.cs]).
  • Multiple values can be provided as a comma-separated list.
  • Generic types must include the arity (number of generic parameters) as shown above.

Incorrect Code Examples

// ❌ Variable declarations with mutable collections
public class Example
{
    public void ProcessData()
    {
        List<int> numbers = new List<int>();           // Diagnostic
        Dictionary<string, int> scores = new Dictionary<string, int>();  // Diagnostic
        HashSet<string> uniqueItems = new HashSet<string>();            // Diagnostic
        Queue<double> values = new Queue<double>();                     // Diagnostic
        Stack<bool> flags = new Stack<bool>();                          // Diagnostic
        LinkedList<string> items = new LinkedList<string>();            // Diagnostic
        
        // LINQ operations that return mutable collections
        var list = Enumerable.Range(1, 10).ToList();                   // Diagnostic
        var dict = items.ToDictionary(x => x, x => x.Length);          // Diagnostic
    }
}

// ❌ Method parameters with mutable collections
public class DataProcessor
{
    public void ProcessItems(List<int> items)           // Diagnostic
    {
        // Process items
    }
    
    public void ProcessScores(Dictionary<string, int> scores)  // Diagnostic
    {
        // Process scores
    }
}

// ❌ Method return types with mutable collections
public class DataProvider
{
    public List<string> GetNames()                      // Diagnostic
    {
        return new List<string> { "John", "Jane" };
    }
    
    public Dictionary<int, string> GetLookup()          // Diagnostic
    {
        return new Dictionary<int, string>();
    }
}

// ❌ Properties with mutable collections
public class Configuration
{
    public List<string> AllowedUsers { get; set; }     // Diagnostic
    public Dictionary<string, object> Settings { get; } // Diagnostic
}

// ❌ Constructor parameters with mutable collections
public class Person
{
    public Person(List<string> names, Dictionary<string, int> scores)  // Diagnostic
    {
        // Initialize
    }
}

// ❌ Local functions with mutable collections
public class Example
{
    public void ProcessData()
    {
        List<int> GetNumbers()                          // Diagnostic
        {
            return new List<int>();
        }
        
        var numbers = GetNumbers();                     // Diagnostic
    }
}

// ❌ Arrow functions with mutable collections
public class Example
{
    public void ProcessData()
    {
        var getList = () => new List<int>();            // Diagnostic
        var getDict = () => new Dictionary<string, int>();  // Diagnostic
        
        var list = getList();                           // Diagnostic
        var dict = getDict();                           // Diagnostic
    }
}

// ❌ Conditional operators with mutable collections
public class Example
{
    public void ProcessData(bool useFirst)
    {
        var collection = useFirst ? new List<int>() : new List<int>();  // Diagnostic
        var dict = useFirst ? new Dictionary<string, int>() : new Dictionary<string, int>();  // Diagnostic
    }
}

Correct Code Examples

// ✅ Using readonly interfaces
public class Example
{
    public void ProcessData()
    {
        IReadOnlyList<int> numbers = ImmutableList<int>.Empty;
        IReadOnlyDictionary<string, int> scores = ImmutableDictionary<string, int>.Empty;
        IReadOnlyCollection<string> uniqueItems = ImmutableHashSet<string>.Empty;
    }
}

// ✅ Method parameters with readonly interfaces
public class DataProcessor
{
    public void ProcessItems(IReadOnlyList<int> items)           // No diagnostic
    {
        // Process items without modification
        foreach (var item in items)
        {
            // Process item
        }
    }
    
    public void ProcessScores(IReadOnlyDictionary<string, int> scores)  // No diagnostic
    {
        // Process scores without modification
        foreach (var kvp in scores)
        {
            // Process key-value pair
        }
    }
}

// ✅ Method return types with readonly interfaces
public class DataProvider
{
    public IReadOnlyList<string> GetNames()                      // No diagnostic
    {
        return ImmutableList<string>.Empty.Add("John").Add("Jane");
    }
    
    public IReadOnlyDictionary<int, string> GetLookup()          // No diagnostic
    {
        return ImmutableDictionary<int, string>.Empty;
    }
}

// ✅ Properties with readonly interfaces
public class Configuration
{
    public IReadOnlyList<string> AllowedUsers { get; }           // No diagnostic
    public IReadOnlyDictionary<string, object> Settings { get; } // No diagnostic
}

// ✅ Constructor parameters with readonly interfaces
public class Person
{
    public Person(IReadOnlyList<string> names, IReadOnlyDictionary<string, int> scores)  // No diagnostic
    {
        // Initialize
    }
}

// ✅ Using immutable collections from System.Collections.Immutable
public class Example
{
    public void ProcessData()
    {
        var numbers = ImmutableList<int>.Empty.Add(1).Add(2).Add(3);           // No diagnostic
        var scores = ImmutableDictionary<string, int>.Empty.Add("John", 95);    // No diagnostic
        var uniqueItems = ImmutableHashSet<string>.Empty.Add("A").Add("B");     // No diagnostic
        var queue = ImmutableQueue<double>.Empty.Enqueue(1.5).Enqueue(2.7);    // No diagnostic
        var stack = ImmutableStack<bool>.Empty.Push(true).Push(false);          // No diagnostic
    }
}

// ✅ Local functions with readonly interfaces
public class Example
{
    public void ProcessData()
    {
        IReadOnlyList<int> GetNumbers()                          // No diagnostic
        {
            return ImmutableList<int>.Empty.Add(1).Add(2).Add(3);
        }
        
        var numbers = GetNumbers();                              // No diagnostic
    }
}

// ✅ Arrow functions with readonly interfaces
public class Example
{
    public void ProcessData()
    {
        var getList = () => ImmutableList<int>.Empty;            // No diagnostic
        var getDict = () => ImmutableDictionary<string, int>.Empty;  // No diagnostic
        
        var list = getList();                                    // No diagnostic
        var dict = getDict();                                    // No diagnostic
    }
}

// ✅ Conditional operators with compatible types
public class Example
{
    public void ProcessData(bool useFirst)
    {
        var collection = useFirst ? ImmutableList<int>.Empty : ImmutableList<int>.Empty;  // No diagnostic
        var dict = useFirst ? ImmutableDictionary<string, int>.Empty : ImmutableDictionary<string, int>.Empty;  // No diagnostic
    }
}

// ✅ Converting mutable collections to readonly
public class Example
{
    public void ProcessData()
    {
        var mutableList = new List<int> { 1, 2, 3 };
        var mutableDict = new Dictionary<string, int> { ["A"] = 1, ["B"] = 2 };
        
        // Convert to readonly for public API
        IReadOnlyList<int> readonlyList = mutableList.AsReadOnly();           // No diagnostic
        IReadOnlyDictionary<string, int> readonlyDict = mutableDict.AsReadOnly();  // No diagnostic
    }
}

// ✅ Non-collection types (no diagnostics)
public class Example
{
    public void ProcessData()
    {
        string text = "Hello World";                             // No diagnostic
        int number = 42;                                         // No diagnostic
        DateTime date = DateTime.Now;                            // No diagnostic
        var point = new { X = 10, Y = 20 };                     // No diagnostic
    }
}

Best Practices

  1. Use readonly interfaces (IReadOnlyList<T>, IReadOnlyCollection<T>, IReadOnlyDictionary<TKey, TValue>) for public APIs
  2. Use immutable collections from System.Collections.Immutable namespace when possible
  3. Convert mutable collections to readonly using .AsReadOnly() method before exposing them
  4. Prefer immutable collections for thread-safe scenarios
  5. Use readonly properties instead of setters for collection properties

Migration Guide

To migrate from mutable collections to immutable ones:

// Before (mutable)
public class OldExample
{
    public List<string> Items { get; set; }
    
    public void AddItem(string item)
    {
        Items.Add(item);  // Modifies the collection
    }
}

// After (immutable)
public class NewExample
{
    public IReadOnlyList<string> Items { get; private set; }
    
    public void AddItem(string item)
    {
        var newItems = Items.ToList();  // Create new list
        newItems.Add(item);             // Modify the new list
        Items = newItems.AsReadOnly();  // Assign readonly version
    }
}

// Alternative using ImmutableList
public class BetterExample
{
    public IReadOnlyList<string> Items { get; private set; } = ImmutableList<string>.Empty;
    
    public void AddItem(string item)
    {
        Items = Items.Add(item);  // ImmutableList.Add returns new instance
    }
}

Required Properties Analyzer

Diagnostic ID: STAR_REQUIRED_PROPERTIES

Category: Syntax

Severity: Warning

Description: Public properties must be declared as required to ensure they are properly initialized. This analyzer enforces the use of the required modifier for public properties.

Incorrect Code Examples

// ❌ Public properties without required modifier
public class Person
{
    public string Name { get; set; }  // Should be required
    public int Age { get; set; }      // Should be required
    public string Email { get; set; }  // Should be required
}

// ❌ Public properties without required modifier
public class Configuration
{
    public string ConnectionString { get; init; }  // Should be required
    public bool IsEnabled { get; init; }           // Should be required
}

Correct Code Examples

// ✅ Public properties with required modifier
public class Person
{
    public required string Name { get; set; }
    public required int Age { get; set; }
    public required string Email { get; set; }
}

// ✅ Public properties with required modifier
public class Configuration
{
    public required string ConnectionString { get; init; }
    public required bool IsEnabled { get; init; }
}

// ✅ Non-public properties (no required needed)
public class Example
{
    private string Name { get; set; }           // Private - no required needed
    internal int Age { get; set; }              // Internal - no required needed
    protected string Email { get; set; }        // Protected - no required needed
}

// ✅ Static properties (no required needed)
public class Constants
{
    public static string DefaultName { get; set; } = "Unknown";
    public static int MaxAge { get; } = 150;
}

Record Immutability Analyzer

Diagnostic ID: STAR_IMMUTABLE_RECORDS

Category: Design

Severity: Warning

Description: Records should be immutable to ensure thread safety and prevent unexpected state changes. This analyzer detects records with mutable properties or non-readonly fields.

Incorrect Code Examples

// ❌ Record with mutable properties
public record Person
{
    public string Name { get; set; }  // Mutable setter
    public int Age { get; set; }      // Mutable setter
}

// ❌ Record with mutable fields
public record Configuration
{
    private string _connectionString;  // Non-readonly field
    
    public string ConnectionString
    {
        get => _connectionString;
        set => _connectionString = value;  // Mutable setter
    }
}

Correct Code Examples

// ✅ Immutable record with init-only properties
public record Person
{
    public string Name { get; init; }  // Init-only setter
    public int Age { get; init; }      // Init-only setter
}

// ✅ Immutable record with readonly fields
public record Configuration
{
    private readonly string _connectionString;
    
    public string ConnectionString => _connectionString;
    
    public Configuration(string connectionString)
    {
        _connectionString = connectionString;
    }
}

// ✅ Positional record (immutable by default)
public record Point(int X, int Y);

// ✅ Record with expression-bodied properties (readonly)
public record Circle
{
    public double Radius { get; }
    public double Area => Math.PI * Radius * Radius;
    
    public Circle(double radius)
    {
        Radius = radius;
    }
}

Max Line Length Analyzer

Diagnostic ID: STAR_MAX_LINE_LENGTH

Category: Formatting

Severity: Warning

Description: Lines should not exceed a maximum allowed length to maintain code readability. The default maximum is 80 characters, but this can be configured.

Configuration

The analyzer can be configured using an .editorconfig file:

[*.cs]
dotnet_diagnostic.STAR_MAX_LINE_LENGTH.max_line_length = 100

Incorrect Code Examples

// ❌ Line exceeds maximum length (assuming 80 character limit)
public class VeryLongClassNameThatExceedsTheMaximumAllowedLineLengthAndShouldBeRefactored
{
    public void VeryLongMethodNameThatAlsoExceedsTheMaximumAllowedLineLengthAndShouldBeRefactored(string veryLongParameterNameThatExceedsTheMaximumAllowedLineLength)
    {
        var veryLongVariableNameThatExceedsTheMaximumAllowedLineLength = "This is a very long string literal that exceeds the maximum allowed line length";
    }
}

Correct Code Examples

// ✅ Lines within maximum length
public class Person
{
    public void ProcessPerson(
        string name,
        int age,
        string email)
    {
        var person = new Person
        {
            Name = name,
            Age = age,
            Email = email
        };
        
        // Process the person
        ProcessPersonData(person);
    }
    
    private void ProcessPersonData(Person person)
    {
        // Implementation here
    }
}

// ✅ Using line breaks for long lines
public class Configuration
{
    public void UpdateConfiguration(
        string connectionString,
        bool isEnabled,
        int timeout,
        string databaseName)
    {
        var config = new Config
        {
            ConnectionString = connectionString,
            IsEnabled = isEnabled,
            Timeout = timeout,
            DatabaseName = databaseName
        };
    }
}

Pre-Increment Analyzer

Diagnostic ID: STAR_NO_PRE_INCREMENT

Category: Syntax

Severity: Warning

Description: Pre-increment operations (++variable) are not allowed outside of for loop headers. This analyzer enforces restricted usage of pre-increment operators.

Incorrect Code Examples

// ❌ Pre-increment outside for loop
public class Example
{
    public void ProcessData()
    {
        int counter = 0;
        ++counter;  // Not allowed
        
        var result = ProcessValue(++counter);  // Not allowed
    }
    
    public int GetNextValue(int current)
    {
        return ++current;  // Not allowed
    }
}

Correct Code Examples

// ✅ Pre-increment in for loop header
public class Example
{
    public void ProcessData()
    {
        for (int i = 0; i < 10; ++i)  // Allowed in for loop
        {
            ProcessItem(i);
        }
    }
    
    public void ProcessArray(int[] items)
    {
        for (int i = 0; i < items.Length; ++i)  // Allowed in for loop
        {
            ProcessItem(items[i]);
        }
    }
    
    // ✅ Alternative: use post-increment or simple addition
    public void AlternativeApproach()
    {
        int counter = 0;
        counter++;  // Post-increment is allowed
        counter += 1;  // Simple addition is allowed
        
        var result = ProcessValue(counter + 1);  // Expression is allowed
    }
}

Post-Increment Analyzer

Diagnostic ID: STAR_NO_POST_INCREMENT

Category: Syntax

Severity: Warning

Description: Post-increment operations (variable++) are not allowed outside of for loop headers. This analyzer enforces restricted usage of post-increment operators.

Incorrect Code Examples

// ❌ Post-increment outside for loop
public class Example
{
    public void ProcessData()
    {
        int counter = 0;
        counter++;  // Not allowed
        
        var result = ProcessValue(counter++);  // Not allowed
    }
    
    public int GetNextValue(int current)
    {
        return current++;  // Not allowed
    }
}

Correct Code Examples

// ✅ Post-increment in for loop header
public class Example
{
    public void ProcessData()
    {
        for (int i = 0; i < 10; i++)  // Allowed in for loop
        {
            ProcessItem(i);
        }
    }
    
    public void ProcessArray(int[] items)
    {
        for (int i = 0; i < items.Length; i++)  // Allowed in for loop
        {
            ProcessItem(items[i]);
        }
    }
    
    // ✅ Alternative: use simple addition
    public void AlternativeApproach()
    {
        int counter = 0;
        counter += 1;  // Simple addition is allowed
        
        var result = ProcessValue(counter + 1);  // Expression is allowed
    }
}

Pre-Decrement Analyzer

Diagnostic ID: STAR_NO_PRE_DECREMENT

Category: Syntax

Severity: Warning

Description: Pre-decrement operations (--variable) are not allowed outside of for loop headers. This analyzer enforces restricted usage of pre-decrement operators.

Incorrect Code Examples

// ❌ Pre-decrement outside for loop
public class Example
{
    public void ProcessData()
    {
        int counter = 10;
        --counter;  // Not allowed
        
        var result = ProcessValue(--counter);  // Not allowed
    }
    
    public int GetPreviousValue(int current)
    {
        return --current;  // Not allowed
    }
}

Correct Code Examples

// ✅ Pre-decrement in for loop header
public class Example
{
    public void ProcessData()
    {
        for (int i = 10; i > 0; --i)  // Allowed in for loop
        {
            ProcessItem(i);
        }
    }
    
    public void ProcessArrayReverse(int[] items)
    {
        for (int i = items.Length - 1; i >= 0; --i)  // Allowed in for loop
        {
            ProcessItem(items[i]);
        }
    }
    
    // ✅ Alternative: use simple subtraction
    public void AlternativeApproach()
    {
        int counter = 10;
        counter -= 1;  // Simple subtraction is allowed
        
        var result = ProcessValue(counter - 1);  // Expression is allowed
    }
}

Post-Decrement Analyzer

Diagnostic ID: STAR_NO_POST_DECREMENT

Category: Syntax

Severity: Warning

Description: Post-decrement operations (variable--) are not allowed outside of for loop headers. This analyzer enforces restricted usage of post-decrement operators.

Incorrect Code Examples

// ❌ Post-decrement outside for loop
public class Example
{
    public void ProcessData()
    {
        int counter = 10;
        counter--;  // Not allowed
        
        var result = ProcessValue(counter--);  // Not allowed
    }
    
    public int GetPreviousValue(int current)
    {
        return current--;  // Not allowed
    }
}

Correct Code Examples

// ✅ Post-decrement in for loop header
public class Example
{
    public void ProcessData()
    {
        for (int i = 10; i > 0; i--)  // Allowed in for loop
        {
            ProcessItem(i);
        }
    }
    
    public void ProcessArrayReverse(int[] items)
    {
        for (int i = items.Length - 1; i >= 0; i--)  // Allowed in for loop
        {
            ProcessItem(items[i]);
        }
    }
    
    // ✅ Alternative: use simple subtraction
    public void AlternativeApproach()
    {
        int counter = 10;
        counter -= 1;  // Simple subtraction is allowed
        
        var result = ProcessValue(counter - 1);  // Expression is allowed
    }
}

No Nullable Null Check Analyzer

Diagnostic ID: STAR_NO_NULLABLE_CHECKS_FOR_NON_NULL_TYPES

Category: Usage

Severity: Warning

Description: Types marked as non-nullable cannot be null, so checking them for null is unnecessary. This analyzer detects redundant null checks on non-nullable types.

Incorrect Code Examples

// ❌ Unnecessary null checks on non-nullable types
public class Example
{
    public void ProcessPerson(Person person)  // Person is non-nullable
    {
        if (person == null)  // Unnecessary check
        {
            return;
        }
        
        // Process person
    }
    
    public void ProcessString(string text)  // String is non-nullable
    {
        if (text != null)  // Unnecessary check
        {
            ProcessText(text);
        }
    }
    
    public void ProcessInt(int number)  // int is non-nullable
    {
        if (number == null)  // Unnecessary check
        {
            return;
        }
    }
}

Correct Code Examples

// ✅ No null checks on non-nullable types
public class Example
{
    public void ProcessPerson(Person person)  // Person is non-nullable
    {
        // No null check needed - person cannot be null
        ProcessPersonData(person);
    }
    
    public void ProcessString(string text)  // String is non-nullable
    {
        // No null check needed - text cannot be null
        ProcessText(text);
    }
    
    public void ProcessInt(int number)  // int is non-nullable
    {
        // No null check needed - number cannot be null
        ProcessNumber(number);
    }
}

// ✅ Null checks on nullable types
public class ExampleWithNullable
{
    public void ProcessPerson(Person? person)  // Person? is nullable
    {
        if (person == null)  // Necessary check
        {
            return;
        }
        
        ProcessPersonData(person);
    }
    
    public void ProcessString(string? text)  // string? is nullable
    {
        if (text != null)  // Necessary check
        {
            ProcessText(text);
        }
    }
    
    public void ProcessInt(int? number)  // int? is nullable
    {
        if (number.HasValue)  // Necessary check
        {
            ProcessNumber(number.Value);
        }
    }
}

No Throw Analyzer

Diagnostic ID: STAR_NO_THROW

Category: Syntax

Severity: Warning

Description: Throw statements and expressions are not allowed anywhere in the code. This analyzer enforces a strict no-exception policy, requiring all error handling to be done through return values, error objects, or other non-exception mechanisms.

Incorrect Code Examples

// ❌ Throw statement in method
public class Example
{
    public void ProcessData(string input)
    {
        if (string.IsNullOrEmpty(input))
            throw new ArgumentException("Input cannot be null or empty");  // Not allowed
        
        // Process input
    }
    
    public string GetValue(int index)
    {
        if (index < 0)
            throw new ArgumentOutOfRangeException(nameof(index));  // Not allowed
        
        return "value";
    }
}

// ❌ Throw expression in property initializer
public class Configuration
{
    public string ConnectionString { get; } = 
        throw new InvalidOperationException("Connection string not configured");  // Not allowed
}

// ❌ Throw statement in constructor
public class Person
{
    public Person(string name)
    {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be null or empty", nameof(name));  // Not allowed
        
        Name = name;
    }
    
    public string Name { get; }
}

// ❌ Throw statement in catch block
public void HandleException()
{
    try
    {
        // Some operation
    }
    catch (Exception ex)
    {
        throw new CustomException("Custom error", ex);  // Not allowed
    }
}

// ❌ Throw expression in conditional operator
public string GetResult(bool condition)
{
    return condition ? "success" : throw new Exception("Condition failed");  // Not allowed
}

Correct Code Examples

// ✅ Return error result instead of throwing
public class Example
{
    public Result<string> ProcessData(string input)
    {
        if (string.IsNullOrEmpty(input))
            return Result<string>.Failure("Input cannot be null or empty");
        
        // Process input
        return Result<string>.Success("Processed successfully");
    }
    
    public Result<string> GetValue(int index)
    {
        if (index < 0)
            return Result<string>.Failure($"Index {index} is out of range");
        
        return Result<string>.Success("value");
    }
}

// ✅ Use default values instead of throw expressions
public class Configuration
{
    public string ConnectionString { get; } = "DefaultConnectionString";
}

// ✅ Validate in constructor without throwing
public class Person
{
    public Person(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            Name = "DefaultName";  // Use default value
            return;
        }
        
        Name = name;
    }
    
    public string Name { get; }
}

// ✅ Handle exceptions without re-throwing
public void HandleException()
{
    try
    {
        // Some operation
    }
    catch (Exception ex)
    {
        LogError(ex);  // Log the error
        // Handle gracefully without throwing
    }
}

// ✅ Use conditional logic instead of throw expressions
public string GetResult(bool condition)
{
    return condition ? "success" : "failure";  // Return value instead of throwing
}

// ✅ Use Result pattern for error handling
public class Result<T>
{
    public bool IsSuccess { get; }
    public T Value { get; }
    public string Error { get; }
    
    private Result(bool isSuccess, T value, string error)
    {
        IsSuccess = isSuccess;
        Value = value;
        Error = error;
    }
    
    public static Result<T> Success(T value) => new Result<T>(true, value, null);
    public static Result<T> Failure(string error) => new Result<T>(false, default, error);
}

Summary

The StrictAnalyzers project provides a comprehensive set of analyzers that enforce strict coding standards and best practices. These analyzers help maintain:

  • Immutability: Ensuring classes and records are immutable for thread safety
  • Code Quality: Enforcing consistent formatting and syntax rules
  • Best Practices: Promoting the use of modern C# features like records and required members
  • Performance: Restricting potentially problematic operations like assignments and increment/decrement outside of loops

Each analyzer can be configured and customized to fit your project's specific requirements. The analyzers integrate seamlessly with Visual Studio and other IDEs that support Roslyn analyzers.

For more information about configuring these analyzers or contributing to the project, please refer to the project documentation and source code.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.0 105 1/28/2026
1.2.11 149 1/1/2026
1.2.10 92 12/31/2025
1.2.9 98 12/31/2025
1.2.8 96 12/31/2025
1.2.7 96 12/31/2025
1.2.6 99 12/31/2025
1.2.5 99 12/30/2025
1.2.4 99 12/30/2025
1.1.4 155 12/26/2025
1.1.3 554 12/9/2025
1.1.2 220 11/9/2025
1.1.1 220 11/9/2025
1.1.0 147 11/9/2025
1.0.7 157 11/9/2025
1.0.6 155 11/8/2025
1.0.5 191 10/6/2025
1.0.4 208 10/6/2025
1.0.3 190 10/6/2025
1.0.2 180 10/5/2025
Loading failed