Skip to content

Deepstaging.Roslyn

Roslyn's missing standard library. Fluent queries, safe projections, code generation, analyzer base classes, and snapshot testing — everything you need to build production source generators without fighting the compiler.

Why Deepstaging.Roslyn? Get Started


  • Find Symbols, Not Syntax Trees


    Fluent queries replace raw GetMembers().OfType<>() chains. Filter by visibility, modifiers, attributes, names, return types — and get back actual ISymbol objects you can use with any Roslyn API.

  • No More Null Checks


    OptionalSymbol<T> and ValidAttribute wrap Roslyn's nullable minefield in a safe-access pattern. Extract attribute arguments, named args, and type info without ever writing ?. chains or null guards.

  • Generate Code That Reads Like Code


    TypeBuilder, MethodBuilder, and PropertyBuilder compose entire classes with a fluent API. Parse complex signatures from strings or build them piece by piece — either way you get valid CompilationUnitSyntax.

  • One-Method Analyzers


    TypeAnalyzer<T>, MethodAnalyzer<T>, FieldAnalyzer<T> — override a single boolean method. The base class handles registration, symbol matching, diagnostic creation, and concurrent execution.

  • Code Fixes in 5 Lines


    AddPartialModifierAction, AddModifierAction, ModifyPropsFileAction — pre-built actions for common fixes. Pair with analyzers using [CodeFix("DIAG001")] and ship one-click repairs.

  • Test Everything, Mock Nothing


    RoslynTestBase gives you generator snapshot tests, analyzer diagnostic assertions, code fix verification, and template rendering — all with consistent, readable APIs. No manual compilation setup.


What It Looks Like

Find → Project → Emit

// Query: find types with your attribute
var models = context.ForAttribute<AutoNotifyAttribute>()
    .Map(static (ctx, _) => ctx.TargetSymbol
        .AsValidNamedType()
        .QueryAutoNotify());

// Emit: generate a class from the model
context.RegisterSourceOutput(models, static (ctx, model) => TypeBuilder
    .Class(model.TypeName)
    .AsPartial()
    .InNamespace(model.Namespace)
    .ImplementsINotifyPropertyChanged()
    .WithEach(model.Fields, (type, field) => type
        .AddProperty(field.PropertyName, field.TypeName, p => p
            .WithGetter(b => b.AddStatement($"return {field.FieldName}"))
            .WithSetter(b => b
                .AddStatement($"{field.FieldName} = value")
                .AddStatement($"OnPropertyChanged()"))))
    .Emit()
    .AddSourceTo(ctx, HintName.From(model.Namespace, model.TypeName)));

One-Method Analyzers

[Reports("RK001", "Type with [AutoNotify] must be partial",
    Category = "Usage", Severity = DiagnosticSeverity.Error)]
public sealed class MustBePartialAnalyzer : TypeAnalyzer<AutoNotifyAttribute>
{
    protected override bool ShouldReport(ValidSymbol<INamedTypeSymbol> type)
        => !type.IsPartial;
}

Pipeline Models That Just Work

// [PipelineModel] generates Equals/GetHashCode for incremental caching
[PipelineModel]
internal sealed record AutoNotifyModel(
    string Namespace,
    string TypeName,
    EquatableArray<FieldModel> Fields);

Install

dotnet add package Deepstaging.Roslyn --prerelease

Packages

Package Purpose
Deepstaging.Roslyn Core toolkit — Queries, Projections, Emit, Analyzers, Generators, Code Fixes, Scriban
Deepstaging.Roslyn.Testing Test utilities for generators, analyzers, and code fixes
Deepstaging.Roslyn.LanguageExt LanguageExt integration — Eff lifting, expressions, types

Learn

  • Getting Started

    Build a complete [AutoNotify] generator — attribute, query, emit, analyzer, code fix, and tests — in one sitting.

  • End-to-End Walkthrough

    Trace a real feature ([StrongId]) across all five layers from the Deepstaging source.

  • Templates

    dotnet new roslynkit scaffolds a full five-layer solution with CI, docs, and packaging.

  • Guides

    Project organization, projection patterns, generator architecture, analyzer base classes, code fix helpers, and testing strategies.

Philosophy

This is utility code, not a framework. It should feel like the standard library Roslyn forgot to ship.

  • Roslyn in, Roslyn out. .GetAll() returns actual ISymbol objects. .Emit() returns CompilationUnitSyntax. No wrappers that hide the platform.
  • Reading and writing are symmetric. TypeQuery finds types, TypeBuilder creates them. Same mental model both ways.
  • Composition over inheritance. Fluent APIs compose — chain what you need, skip what you don't. No deep class hierarchies to learn.

Built With

Deepstaging — a declarative application framework — is built entirely on this toolkit. 6 generators, 50+ analyzers, 20+ code fixes, all using the patterns described in these docs.

License

RPL-1.5 (Reciprocal Public License) — Real reciprocity, no loopholes.

You can use this code, modify it, and share it freely. But when you deploy it — internally or externally, as a service or within your company — you share your improvements back under the same license.

Why? We believe if you benefit from this code, the community should benefit from your improvements. That's the deal we think is fair.

Personal research and experimentation? No obligations. Go learn, explore, and build.

See LICENSE for the full legal text.