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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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