Skip to content

analyzer: NE0001 — one top-level type per file, file name equals type name #3

Description

@samtrion

Part of the file/namespace organization epic #6. Category: Maintainability · ID: NE0001

Need & Action (Bedarf & Handlung)

Need: Multiple top-level types (class, struct, record, record struct, interface, enum,
delegate) declared in one file, or a file whose name does not match the type it contains, make types
hard to find and produce noisy diffs.

Action: Report when a file contains more than one top-level type, or when the file name does not
equal the contained type's name. One type ⇒ one file, TypeNameTypeName.cs.

Generic overloads (default = strict): by default, overloads that share a base name — Result,
Result<T>, Result<T1, T2> — are treated like any other types: each arity must live in its own file,
with the arity encoded in the name (Result.cs, Result{T}.cs, Result{T1,T2}.cs). Grouping them into a
single file named after the base identifier (Result.cs) is opt-in via the build property below.

Non-compliant example

// File: Shapes.cs
namespace Geometry;

public sealed class Circle { }   // NE0001: file name 'Shapes' does not match type 'Circle'
public sealed class Square { }   // NE0001: more than one top-level type in this file

Compliant example

// File: Circle.cs
namespace Geometry;
public sealed class Circle { }
// File: Square.cs
namespace Geometry;
public sealed class Square { }
// Default (strict): one file per arity
// File: Result.cs          -> public readonly struct Result { }
// File: Result{T}.cs       -> public readonly struct Result<T> { }
// File: Result{T1,T2}.cs   -> public readonly struct Result<T1, T2> { }
// With NetEvolveAnalyzerGroupGenericOverloads = true: grouping into one file is allowed
// File: Result.cs
namespace Geometry;
public readonly struct Result { }
public readonly struct Result<T> { }
public readonly struct Result<T1, T2> { }

Category

Maintainability

Default severity

Warning

Code fix

Maybe — feasible but likely a follow-up. Roslyn already ships a "Move type to X.cs" refactoring;
the fix would (a) move each extra type into its own correctly named file and (b) offer a rename of the
file to match a single contained type.

Configuration & build properties

  • NetEvolveAnalyzerGroupGenericOverloads (bool, default false) — when false (default), each generic
    arity must live in its own arity-encoded file. When true, generic overloads that share a base name may
    be grouped into a single file named after the base identifier (Result.cs).
  • Global opt-outs from the epic apply: auto-off on PublishSingleFile=true, plus
    NetEvolveAnalyzerDisableFileOrganizationRules.

Edge cases & exceptions

  • partial types spread across files: the partial's file(s) must still be named after the type; do not
    count partials as "multiple types".
  • Nested types are ignored (only top-level declarations count).
  • Generated code (generated_code = true, *.g.cs, *.designer.cs) is skipped.
  • File-scoped vs. block-scoped namespaces are treated identically.
  • Arity-encoded file names when strict (default): confirm the encoding convention — Result{T}.cs /
    Result{T1,T2}.cs (curly braces, as used by StyleCop SA1649's fileNamingConvention: metadata).

Metadata

Metadata

Assignees

Labels

state:investigateIndicates issues requiring further investigation to understand or gather information.type:featureIndicates a new feature or enhancement to be added.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions