TSpec.Architecture.CSharp 1.1.0

Prefix Reserved
dotnet add package TSpec.Architecture.CSharp --version 1.1.0
                    
NuGet\Install-Package TSpec.Architecture.CSharp -Version 1.1.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="TSpec.Architecture.CSharp" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TSpec.Architecture.CSharp" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="TSpec.Architecture.CSharp" />
                    
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 TSpec.Architecture.CSharp --version 1.1.0
                    
#r "nuget: TSpec.Architecture.CSharp, 1.1.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 TSpec.Architecture.CSharp@1.1.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=TSpec.Architecture.CSharp&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=TSpec.Architecture.CSharp&version=1.1.0
                    
Install as a Cake Tool

TSpec.Architecture.CSharp

Architecture rules on the code inside the projects of a TSpec build: their source files, the types those declare and their Roslyn syntax. The projects and their references are TSpec's own, in its README's Architecture section.

Install the package in your architecture spec project:

dotnet add package TSpec.Architecture.CSharp

With using TSpec.Architecture.CSharp;, a project's Files are those it compiles from its own folder, leaving out what the build generates under obj/ and files linked in from elsewhere. A file has its Path in the project, written with /, its Lines and its Raw text, and a failure names it by project and path: Data.Auth/Stores/UserStore.cs. Its Types are those declared at its top level, each with its Name (Repository), its FullName (Data.Repository<T>), Namespace, Kind, IsPublic, the Files it is declared in, several for a partial type, and its Nested types, declared inside it. A nested type's IsPublic is in effect: false inside a type that is not public. A failure names a type by its full name. A project's Types are those of all its files, a partial type once. A rule of one public type per file, adopted with today's breaches listed in Allowed:

[Fact]
public void Only_the_listed_files_declare_more_than_one_public_type()
    => Source.Projects.SelectMany(project => project.Files)
        .Select(file => (file.ToString(), Public: file.Types.Count(type => type.IsPublic)))
        .Where(file => file.Public > 1)
        .Are(Allowed);

A file's Root is its Roslyn syntax as the build parsed it, so code under an #if the build left out is not in it, and SpanOf(node) is where a node or token starts and ends, its lines and columns counted from 1:

[Fact]
public void No_arrow_starts_a_line()
    => Source.Projects.SelectMany(project => project.Files)
        .SelectMany(file => file.Root.DescendantTokens()
            .Where(token => token.IsKind(SyntaxKind.EqualsGreaterThanToken))
            .Where(arrow => file.SpanOf(arrow).Start.Line > file.SpanOf(arrow.GetPreviousToken()).End.Line)
            .Select(arrow => $"{file}:{file.SpanOf(arrow).Start.Line}"))
        .Is().Empty();

A file's Members are those it declares, a nested type's and local functions too, and a project's are those of all its files. A record's positional parameters are its constructor's and its properties. A member's Accessibility is the one it has in effect, no more than the types around it allow: a public member of an internal class is internal. Its Type and Parameters are syntax, as written, and a failure names it by file, line and name: Data.Auth/Stores/UserStore.cs:12 Find.

[Fact]
public void No_method_name_ends_in_Async()
    => Source.Projects.SelectMany(project => project.Members)
        .Where(member => member.Kind == MemberKind.Method)
        .Has().None(method => method.Name.EndsWith("Async"));

A file's Uses are the build's types its code uses: those it names, and those whose members it names, such as an extension method's class or a constant's type; a nested type is one of its own. A type Exposes the build's types its signature names where other assemblies see it: its base type, interfaces and constraints, and the types of its public and protected members, itself included. Both compile the project with Roslyn, once in a run, and a use by reflection is not one the compiler sees. A project OpensInternalsTo the projects its InternalsVisibleTo names.

[Fact]
public void The_domain_uses_nothing_of_persistence()
    => Source.Projects["Core"].Files.Where(file => file.Path.StartsWith("Domain/"))
        .Has().None(file => file.Uses.Any(type => type.Namespace.EndsWith(".Persistence")));

A file is the same object every time, so an analysis TSpec doesn't have can be added to it as an extension member that keeps what it computes per file in a ConditionalWeakTable<SourceFile, T>.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.1.0 0 10/18/2026
1.0.0 43 10/16/2026

NEW
* A project's Types: the top-level types its files declare, a partial type listed once
* A file's Root: its Roslyn syntax tree, parsed with the build's language version and #if symbols
* A file's SpanOf(node): where a syntax node or token starts and ends
* A file's and a project's Members: each member's name, kind, accessibility, parameters and type
* A file's Uses: the build's types its code uses
* A type's Exposes: the build's types its signature names where other assemblies see it
* A project's OpensInternalsTo: the projects its InternalsVisibleTo names
* A type's Nested: the types declared inside it