Skip to content

Epic: codeanalyzer-dotnet (cansharp) — a C# analyzer for CLDK #34

Description

@rahlk

Spec

docs/design/specs/codeanalyzer-dotnet.md (this repo)

Summary

Adds C# to CLDK as a new language pack: codeanalyzer-dotnet, a Roslyn-based analyzer
shipped as a self-contained binary on PyPI under the package name cansharp. It emits
canonical schema v2 (analysis.json plus a Neo4j projection) and is consumed by a new
csharp model package and facade in python-sdk.

Schema impact: additive only, per the parity clause. New kind values (record,
record_struct, delegate, property, event) and new typed fields (parts,
declared_in, ref_kind, positional_parameters, invoke_signature, is_auto,
project). No shared field renamed, no shared kind repurposed, no new edge family.

The analyzer is greenfield — there is no v1 output to migrate from, so it targets v2
directly.

Affected repos

  • codeanalyzer-dotnet — the analyzer itself; new repo, currently empty
  • python-sdk — new cldk/models/csharp/ and cldk/analysis/csharp/; cansharp pinned in
    dependencies and [tool.backend-versions]
  • docs — language-support matrix and C# quickstart

Design decisions

Sixteen decisions are recorded in the spec. The load-bearing ones:

  • Scope is C# only; the language token is csharp. VB.NET and F# can be added later as
    additive tokens in the same repo. A dotnet token would lie about scope and force an id
    migration when VB arrives.
  • No MSBuild. The analyzer reads obj/project.assets.json and the .csproj directly and
    calls CSharpCompilation.Create. This keeps the shipped binary genuinely self-contained and
    avoids MSBuildLocator version coupling, which is the most common failure mode for
    Roslyn-based tools. Cost: source generators do not run, and only the first target framework
    of a multi-targeted project is analyzed.
  • Roslyn's own ControlFlowGraph, flattened to statement nodes, rather than a
    hand-built CFG — async state machines, iterator lowering, using, foreach, and pattern
    switches arrive already lowered.
  • Partial types merge into one type node homed at the primary declaring file, with
    parts[] and declared_in on out-of-file members so span.bytes slices the right
    module.source. Roslyn already merges them semantically, so the merged view is the natural
    output.
  • Properties and events get both views — a field node and, for non-auto accessors,
    #get/#set callables — because idiomatic C# exposes state as properties but accessors
    hold real logic that L3 must see.
  • By-ref parameters carry ref_kind at L1 and get their own formal_out at L4, so
    TryParse-style output is not a dataflow dead end. C# is the first language to exercise the
    canonical schema's by-ref formal_out slot.
  • Distribution: self-contained single-file ReadyToRun binaries, one per-platform wheel on
    PyPI. NativeAOT was rejected — Roslyn's reflection paths break under trimming in ways that
    surface at runtime on user code rather than in our tests.
  • Sibling divergences followed to codeanalyzer-python: span offsets are UTF-8 bytes
    (D15) and Neo4j labels are per-language prefixed, :CsNode / CS_* (D16). In both cases
    codeanalyzer-typescript currently does the opposite; reconciling it is out of scope here
    and recorded in the spec's § 8.

The initial instruction was to emit schema v1 for SDK compatibility. On inspection
python-sdk 1.5.0 already consumes v2 from both its pinned analyzers
(codeanalyzer-python==0.3.1, codeanalyzer-typescript==0.4.3), and no dotnet consumer
exists yet, so v2 was confirmed instead.

Scope guard — explicitly out:

  • VB.NET and F#
  • Source-generator execution
  • A .NET points-to oracle. L4 ships with a flow-insensitive alias stub; no DDG edge may claim
    prov:["points-to"] until a real oracle exists, and choosing one is its own design session.
  • Reconciling codeanalyzer-typescript to D15 or D16
  • Entrypoint and CRUD detection (the framework/domain axis, not the structural schema)

Release plan

Release Contents Gated on
cansharp 0.1.0 L1 + L2, JSON + Neo4j, five platform wheels L1/L2 schema gates, Neo4j conformance
python-sdk 1.6.0 cldk/models/csharp/ + facade, pins cansharp==0.1.0 0.1.0 on PyPI
cansharp 0.2.0 L3 (CFG / CDG / DDG) L3 gates, monotonicity
python-sdk 1.7.0 L3 surface; prov literal accepts reaching-defs 0.2.0 on PyPI
cansharp 0.3.0 L4 its own points-to design session first
docs language matrix, C# quickstart tracks 0.1.0

Version lockstep: each python-sdk release pins an exact cansharp version in both
dependencies and [tool.backend-versions], as it already does for the Python, Java, and
TypeScript analyzers. The analyzer always ships first.

Release-plan risk: five wheels at roughly 40–60MB each. PyPI's default 100MB per-file limit
accommodates this, but if compressed size creeps past it a limit increase must be requested
before the first release, not discovered on release day.

Definition of done (epic-level)

  • Every sub-issue closed with its gate green.
  • cansharp on PyPI with working wheels for osx-arm64, osx-x64, linux-x64, linux-arm64, and
    win-x64; pip install cansharp && cansharp -i <repo> produces valid analysis.json on each.
  • All six fixtures analyze clean: Console.Basic, Api.Di, Forms.Partial,
    Modern.Records, Async.Iterators, Solution.MultiProject.
  • L1 ⊆ L2 ⊆ L3 ⊆ L4 monotonicity gate green at every shipped level, modulo the sanctioned
    callee: null → id refinement.
  • No dangling endpoints in any edge list at any level.
  • Byte-slice gate green including the non-ASCII fixture — source[span.bytes] equals the
    declaration text under UTF-8 offsets (D15).
  • Neo4j conformance green against generated schema.neo4j.json, with the _k discriminant
    on CS_DDG and CS_CFG_NEXT and keyed MERGE materializing exactly row-count
    relationships.
  • python-sdk round-trips every fixture through the csharp facade; prov literal accepts
    reaching-defs; the body accessor honours declared_in for partial-type members.
  • .claude/SCHEMA_DECISIONS.md in codeanalyzer-dotnet carries all sixteen decisions.
  • Docs language matrix lists C#; CHANGELOGs updated; versions pinned in lockstep.

Metadata

Metadata

Assignees

No one assigned

    Labels

    EpicCross-repo coordination epic

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions