Avoid defining multiple types in a source file

Rule ND2102 Source Files Organization

Multiple types in one source file decrease readability (developers expect one type per file in the Solution Explorer) and lead to files with too many lines. Each match is such a source file, indexed by one of its types (preferably the one named like the file).

Nested types, enumerations and file-visibility types are exempt: it is acceptable to declare them alongside their principal user type.

How to fix

Create a source file per type. Severity is Low: more an advice than a problem.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Avoid defining multiple types in a source file</Name>
// <Id>ND2102:AvoidDefiningMultipleTypesInASourceFile</Id>

warnif count > 0 

// Build a lookup indexed by source files, values being a sequence of types defined in the source file.
let lookup = JustMyCode.Types
              // When a source file is referenced by several assemblies, 
              // type(s) contained in the source file are seen as distinct types, both by the CLR and by NDepend.
              // This Distinct clause based on type full-name (type name prefixed with namespace) 
              // avoids matching the multiple versions of such type.
             .Distinct(t => t.FullName)
             .Where(t => t.SourceFileDeclAvailable && 
                         // except enumerations, nested types and types generated by compilers!
                        !t.IsEnumeration &&          
                        !t.IsNested &&                   
                        !t.IsGeneratedByCompiler &&
                        // Allow to have multiple 'file' types in the same source file
                        !t.IsVisibleOnlyInFile)
             // We use multi-key, since a type can be declared in multiple source files.
             .ToMultiKeyLookup(t => t.SourceDecls.Select(d => d.SourceFile))
 
from @group in lookup where @group.Count() > 1
   let sourceFile = @group.Key

   // CQLinq doesn't let indexing result with sourceFile 
   // so we choose a typeIndex in types, 
   // preferably the type that has the file name.
   let typeWithSourceFileName = @group.FirstOrDefault(t => t.SimpleNameEquals(sourceFile.FileNameWithoutExtension))
   let typeIndex = typeWithSourceFileName ?? @group.First()

// Don't warn for types with at least one declaration in a generated file
// like the UWP generated file 'App.g.i.cs'
where sourceFile.FilePathString.IndexOf(".g.", StringComparison.OrdinalIgnoreCase) == -1

select new { 
   typeIndex, 
   TypesInSourceFile = @group as IEnumerable<IType>, 
   SourceFilePathString = sourceFile.FilePathString,
   SourceFileNameString = sourceFile.FileName,
   Debt = 3.ToMinutes().ToDebt(),
   Severity = Severity.Low
}

//<Expl>The source file *{3}* should contain only one type declaration instead of *{1}*.</Expl>
//<Desc>
// Multiple types in one source file decrease readability (developers expect
// one type per file in the *Solution Explorer*) and lead to files with too
// many lines. Each match is such a source file, indexed by one of its types
// (preferably the one named like the file).
//
// Nested types, enumerations and *file*-visibility types are exempt: it is
// acceptable to declare them alongside their principal user type.
//</Desc>

//<HowToFix>
// Create a source file per type. Severity is **Low**: more an advice than a
// problem.
//</HowToFix>

Source Files Organization: all 6 items