Namespace name should correspond to file location

Rule ND2103 Source Files Organization

The namespaces hierarchy should be synchronized with the source files directory hierarchy — a widely accepted convention; misalignment makes code less maintainable and harder to navigate. Matches namespaces whose source file locations don't align with the namespace name.

Comparisons are case-sensitive; the justACaseSensitiveIssue flag indicates a pure casing mismatch (slightly lower debt).

Source files out of the project directory are ignored: such linked files are shared across projects and already matched by the rule Avoid referencing source file out of the project directory.

How to fix

Align the namespace with the directory sub-paths containing the source files.

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>Namespace name should correspond to file location</Name>
// <Id>ND2103:NamespaceNameShouldCorrespondToFileLocation</Id>

warnif count > 0

from a in Application.Assemblies 
let assemblyName = a.Name

// Source files out of the project directory are linked files shared across
// projects: they follow no single project's folder layout (ND2100 matches them)
let projectDir = a.VisualStudioProjectFilePath != null ? a.VisualStudioProjectFilePath.ParentDirectoryPath : null

from n in a.ChildNamespaces 
where n.SourceFileDeclAvailable
let namespaceName = n.Name

// Build the   dirShouldContain   string
// and then we check which source file path contains dirShouldContain or not
let dirShouldContain = (
    // namespaceName starts with assembly name => gets only components after the assembly name
    namespaceName.StartsWith(assemblyName) 
    ? namespaceName.Substring(assemblyName.Length)
          
    // namespaceName contains assembly name => gets only components after the assembly name
    : namespaceName.Contains("." + assemblyName)  
      ? namespaceName.Substring(n.Name.IndexOf("." + assemblyName))

      // namespaceName has more than one part => gets only components after the first part
      : namespaceName.Contains(".")
          ? namespaceName.Substring(n.Name.IndexOf("."))
          : namespaceName)

        // Replace dots and underscore by spaces in namespace name
      .Replace('.', ' ')
      .Replace('_', ' ')
         
where dirShouldContain.Length > 0

// Obtain a sourceDecl which path doesn't contains dirShouldContain
let sourceDecl = n.SourceDecls
  .OrderBy(d => d.SourceFile.FilePathString)
  .Where(d => d.SourceFile.CodeElements.All(e => JustMyCode.Contains(e)) &&
              (projectDir == null || d.SourceFile.FilePath.IsChildOf(projectDir)))
  .FirstOrDefault(decl=>
          // Replace dots, path separators, minus and underscores 
          // by spaces in source files names
          !decl.SourceFile.FilePathString
                      .Replace('.',' ').Replace('\\',' ')
                      .Replace('-',' ').Replace('_',' ')
                      .Contains(dirShouldContain)
)
where sourceDecl != null

let dir = sourceDecl.SourceFile.FilePath.ParentDirectoryPath

let justACaseSensitiveIssue = dir.ToString()
                      .Replace('.',' ').Replace('\\',' ')
                      .Replace('-',' ').Replace('_',' ')
                      .IndexOf(dirShouldContain, StringComparison.OrdinalIgnoreCase) >= 0

// Get distinct source files contained in dir through the types thay contain
let typesConcerned = n.ChildTypes
      .Where(t => t.SourceFileDeclAvailable)
      .Distinct(t => t.SourceDecls.First().SourceFile.FilePathString)
      .Where(t => t.SourceDecls.First().SourceFile.FilePath.ParentDirectoryPath.Equals(dir))
      .ToArray()

select new { 
   n, 
   dirShouldContain, 
   dir = dir.ToString(),
   justACaseSensitiveIssueStr = justACaseSensitiveIssue ? " Please note that this is solely a matter of casing." : "",
   typesConcerned,
   Debt = (justACaseSensitiveIssue ? 2 : 3).ToMinutes().ToDebt(),
   Severity = Severity.Medium
} 

//<Expl>
// The source file{4?:s} containing the declaration{4?:s} 
// of the *{0}* {4?is:are} located in the directory
// *{2}* that does not align with the namespace name.{3}
//</Expl>

//<Desc>
// The namespaces hierarchy should be synchronized with the source files
// directory hierarchy — a widely accepted convention; misalignment makes
// code less maintainable and harder to navigate. Matches namespaces whose
// source file locations don't align with the namespace name.
//
// Comparisons are *case-sensitive*; the *justACaseSensitiveIssue* flag
// indicates a pure casing mismatch (slightly lower debt).
//
// Source files out of the project directory are ignored: such linked files
// are shared across projects and already matched by the rule *Avoid
// referencing source file out of the project directory*.
//</Desc>

//<HowToFix>
// Align the namespace with the directory sub-paths containing the source
// files.
//</HowToFix>

Source Files Organization: all 6 items