Skip to content

About

Roslyn-based CLI that reports which projects, entry points and impact areas a .NET change affects — transitive caller analysis through interface and base-class dispatch.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ImpactReport

ImpactReport is a Roslyn-based CLI tool that analyses a .NET solution and tells you which projects, namespaces and logical impact areas are affected by a change.

Point it at a branch and it answers the question a reviewer actually asks:

Impact Areas: Invoice Rebuild, Order Listing

Affected Projects:
  MyApp.Web                        2 member(s)     2 call site(s)  nearest hop 1
  MyApp.Services                   1 member(s)     1 call site(s)  nearest hop 1

Affected entry points (nothing else calls these):
  OrderListController.List                 hop 1  MyApp.Web  [Order Listing]
  BillingRebuildController.Rebuild         hop 2  MyApp.Web  [Invoice Rebuild]

Methods analysed: 1 of 1 changed  |  Affected members: 3  |  Call sites: 3
Highest fan-out: MyApp.Services.Orders.OrderService.List(DateTime) (3 call sites across 2 project(s))

Full report: impact-report.md

What it does

  • Finds every call site of one or more methods
  • Follows callers transitively, so a repository change still surfaces the controllers and handlers above it
  • Resolves calls made through interfaces and base classes, which is how most DI-based code actually calls things
  • Names the members that depend on the change, and flags which of them are entry points that nothing else calls
  • Rolls those up into affected projects and your own impact areas
  • Ranks methods by risk (fan-out across projects)
  • Writes a Markdown report for the PR, and a summary to the terminal

Two modes:

  • Changed mode - analyse everything that changed against a git base ref
  • Single-method mode - analyse one specific method

Requirements

  • .NET 8.0 SDK or newer
  • The solution's required SDKs and targeting packs installed
  • git on PATH (required for changed mode; used opportunistically otherwise)
  • Access to the solution source. No build is required, but packages must be restored — a project whose restore failed will not load, and its call sites are silently absent from the report. Run dotnet restore first.

Installation

dotnet pack -c Release -o ./nupkg
dotnet tool install -g ImpactReport --add-source ./nupkg
impactreport --help

To update:

dotnet tool update -g ImpactReport --add-source ./nupkg

Usage

Changed-methods (PR-style) analysis:

impactreport --sln MySolution.sln --changed --areas areas.json

Single method analysis:

impactreport --sln MySolution.sln --type MyApp.Services.OrderService --method GetById

Committed vs uncommitted changes

By default changed mode diffs <base>...HEAD, so it sees committed work only. An edit you have saved but not committed produces an empty report, which looks like "no impact" rather than "nothing to compare".

--uncommitted diffs the merge base against your working tree instead, so you can edit, save and re-run without committing:

impactreport --sln MySolution.sln --changed --base origin/main --uncommitted

This is the flag you want when iterating on a change, or demonstrating the tool.


Impact areas

Without --areas, the report gives you projects and namespaces. An areas file turns those into the names your team uses.

Create areas.json:

{
  "areas": [
    {
      "name": "Customer Dashboard",
      "namespaces": ["MyApp.Web.Features.Dashboard"],
      "paths": ["**/Features/Dashboard/**"]
    },
    {
      "name": "Invoicing",
      "namespaces": ["MyApp.Billing.Invoices", "MyApp.Web.Features.Billing"],
      "types": ["InvoiceController"]
    },
    {
      "name": "Order Listing",
      "projects": ["MyApp.Orders"],
      "paths": ["**/Orders/Listing/**"]
    }
  ]
}

A call site belongs to an area if it matches any of that area's patterns:

Key Matches
namespaces Namespace prefix, on a dot boundary (MyApp.Ops ≠ MyApp.OpsAdmin)
paths Glob against the file path; ** spans directories
projects Exact project name
types Substring of the fully qualified type name

A call site can belong to more than one area. The older flat format { "Namespace.Prefix": "Area Name" } is still accepted.

See areas.sample.json for a working example.


"Will it tell me that feature X is affected?"

The usual question. Say someone changes OrderService.List, and somewhere downstream a nightly invoice rebuild depends on it. There are four levels of answer, and it is worth knowing which one you are relying on.

1. Affected Members — always available, no configuration needed. Every method that transitively depends on the change, by name:

Member Project Hop
OrderListController.List MyApp.Web 1
InvoiceRebuildService.Rebuild MyApp.Services 1
BillingRebuildController.Rebuild MyApp.Web 2

2. Affected Entry Points — the subset that nothing else in the solution calls. These are the features, as opposed to the plumbing between them. If BillingRebuildController.Rebuild appears here, the invoice rebuild is reachable from your change.

3. Affected Projects — the same data rolled up per project, with the nearest hop and the areas inside it.

4. Impact Areas — only if you write an areas.json rule naming "Invoice Rebuild". This is the one that needs setting up; the three above work out of the box.

A member is only marked an entry point if the tool actually searched for its callers. One sitting at the --depth limit is marked * in the report instead, because "we did not look" is not the same as "nothing calls it".

What it cannot see

Roslyn finds C# call sites. If a feature is triggered by a message on a queue, a Hangfire or Quartz job resolved by name, a Razor view, reflection, or convention-based DI registration, it will not appear at any depth. An empty result means "no compile-time caller found", never "safe to change".


Frontend

--frontend adds two things the C# analysis cannot reach on its own.

Changed frontend files are mapped to areas by path, so a branch that only touches Angular still reports impact areas:

File Feature Areas
src/app/features/billing/…/settings-page.component.ts billing Invoicing

Screens that call an affected endpoint are named. When the backend walk reaches a controller, the tool reports which frontend features call it:

Feature Files Via endpoint Areas
orders 1 OrderLine Order Listing

The join is convention-based: it extracts api/<Name> from the frontend sources and matches it to <Name>Controller. It also tries the member name, which catches HTTP-triggered functions the frontend calls directly. Endpoints with no frontend caller are listed at the end of the section rather than hidden, so you can see what the convention missed.

The two halves have different requirements. Naming the changed frontend files needs a diff, so it only applies to --changed. Naming the screens needs no git at all, so it works in single-method mode too:

impactreport --sln MySolution.sln --type MyApp.Services.OrderService --method List --frontend --frontend-root src/WebClient

That answers "which screens would this method's change reach?" without editing anything.

This is deliberately not a TypeScript call graph. There is no analysis within the frontend: a changed component that another component imports will not pull that second component into the report.

How depth works

Impact areas depend on how far the tool walks outwards from the change:

OrderRepository.GetForRange   <- you changed this
  hop 1  OrderScheduleService.Schedule
  hop 2    DashboardController.Index               <- "Customer Dashboard"

At --depth 1 you only see the service layer. The default of 3 clears the usual repository → service → controller stack, which is what makes UI-level area names appear. Raise it for deeper architectures; --max-nodes caps the walk so a hub method cannot blow up into the whole solution.

Reference counts are transitive, so a method reached at hop 3 still counts. The Hop column in the Markdown report tells you how far away each call site is.


Options

Option Description
--sln <path> Path to the .sln/.slnx file (required)
--changed Analyse methods changed compared to a git base ref
--base <ref> Git base ref to diff against (default: origin/main)
--uncommitted Include working-tree edits, not just committed ones (changed mode only)
--type <type> Fully qualified type name (single-method mode)
--method <name> Method name to analyse (single-method mode)
--areas <file> JSON file naming the impact areas of your codebase — see Impact areas
--depth <n> Caller hops to follow (default: 3)
--max-nodes <n> Cap on methods visited while walking (default: 2000)
--top <n> Show only the top N methods by risk (default: 25)
--all Show all methods (disables --top)
--min-refs <n> Only include methods with at least N references (default: 1, or 0 with --include-zero)
--min-projects <n> Only include methods impacting at least N projects (default: 1)
--include-zero Include methods with 0 references (noisy; for debugging)
--include-tests Include test projects in the analysis
--out <file> Output Markdown file (default: impact-report.md)
--max <n> Max sample call sites per project (default: 10)
--quiet Suppress progress output; print the summary only
--frontend Also analyse the frontend — see Frontend
--frontend-root <dir> Where to scan for frontend sources, relative to the repo root; implies --frontend
--frontend-ext <list> Extensions counted as frontend changes (default: .ts,.html,.scss,.css)
-h, --help Show help and exit

Exit codes: 0 success, 1 unexpected error, 2 bad command line, 3 git problem, 130 cancelled.


Examples

# What does this branch touch?
impactreport --sln MySolution.sln --changed --areas areas.json

# Against a different base, looking further out
impactreport --sln MySolution.sln --changed --base origin/develop --depth 4

# One method, including test call sites
impactreport --sln MySolution.sln --type MyApp.Services.OrderService --method GetById --include-tests

Limitations

  • Only C# call sites are found. Razor views, DI registration by convention, reflection and serialisation boundaries are invisible to Roslyn's reference search, so treat the report as a strong hint rather than proof.
  • Changed mode detects changed methods, constructors and accessors. A changed field initialiser or type declaration is not itself a seed.
  • A call through a non-generic interface counts against every implementation of it, because any of them could be the one registered at runtime. In a codebase with many implementations of one interface this overstates impact. Calls through a generic interface are narrowed by the type argument, so ISettings<UserSettings> does not pull in ISettings<BillingSettings>.
  • Accuracy depends on the solution loading cleanly. Projects that fail to load are reported on stderr with a WARNING, and their call sites are missing from the report — restore packages before trusting a result.
  • Analysis runs against the working tree, not the base commit, so a method that was deleted on your branch has nothing left to analyse.
  • "Entry point" means nothing in this solution calls it. A public API consumed by another repository will look like an entry point because, locally, it is one.
  • Call-site counts are transitive: a member reached at hop 3 still counts, so the totals answer "how much code is downstream" rather than "how many lines call it".

Development

dotnet build
dotnet test

Licence

MIT - see LICENSE.txt.

About

Roslyn-based CLI that reports which projects, entry points and impact areas a .NET change affects — transitive caller analysis through interface and base-class dispatch.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages