9.0 KiB
CLAUDE.md
Guidance for Claude Code (and future Claude sessions) when working on ILSpy.
What this codebase is
ILSpy is a cross-platform .NET assembly browser / decompiler built on Avalonia 12, on top of the cross-platform ICSharpCode.ILSpyX and ICSharpCode.Decompiler core libraries. The ILSpy/ project IS the Avalonia UI — there is no separate "WPF version" in this tree.
Tech stack
- Avalonia 12 (not 11.x).
- AvaloniaEdit for the decompiled-code text view.
- Dock (wieslawsoltes/Dock) for the panel layout. NuGet id ≠ CLR namespace —
Dock.Controls.Recyclinglives inAvalonia.Controls.Recycling; decompile before guessing xmlns. - Avalonia.Xaml.Behaviors for attached-behaviour glue.
- Avalonia.ExtendedToolkit (mameolan) for controls not in Avalonia core.
- Simple theme (not Fluent). Check
App.axaml/ csproj before assuming Fluent. Note that Simple's ComboBox chevron isToggleButton#toggle, notBorder#HighlightBackground. - Microsoft.Extensions.DependencyInjection + System.Composition MEF directly, with a small bridge. Do not use TomsToolbox.
- AvaloniaEdit theme must be registered in
Application.Stylesvia the StyleInclude orTextEditorrenders 0×0. - Central package management is enabled — every
PackageReferenceneeds a matchingPackageVersioninDirectory.Packages.props. - Target framework:
net10.0(cross-platform) for the main app and tests.net10.0-windowsonly for tests that intentionally exercise Windows-only behaviour.
Project structure
Avalonia UI:
ILSpy/— the Avalonia UI appILSpy.Tests/— headless Avalonia UI tests (Avalonia.Headless.NUnit)ILSpy.Tests.Windows/— Windows-only UI tests (OS-gated;net10.0-windows)ILSpy.ReadyToRun/— ReadyToRun-viewer plugin, ported to Avalonia (net10.0; part ofILSpy.Desktop.slnf). Not legacy.
Cross-platform core (decompiler engine + shared support):
ICSharpCode.Decompiler/— core decompiler library (multi-targeted, cross-platform)ICSharpCode.ILSpyX/— shared UI-host-agnostic support libraryICSharpCode.BamlDecompiler/— BAML parsing libraryICSharpCode.ILSpyCmd/— CLI front-end (ilspycmd)ICSharpCode.Decompiler.Tests/+ICSharpCode.Decompiler.TestRunner/— decompiler test suite and its out-of-process runnerICSharpCode.Decompiler.PowerShell/— PowerShell cmdlets (netstandard2.0)
Test support:
TestPlugin/— sample plugin exercising the plugin-loading system (net10.0-windows)TestFixtures.Resources/— generates resource fixtures consumed by the decompiler tests
Legacy / Windows-tied (porting backlog; do not modify unless explicitly asked to port them):
ILSpy.AddIn/,ILSpy.AddIn.VS2022/— Visual Studio add-ins (net472)ILSpy.Installer/— WiX installer (net472)ILSpy.BamlDecompiler.Tests/— BAML-decompiler tests, still WPF/Windows-bound (net11.0-windows); there is noILSpy.BamlDecompiler/project, only these tests
Solutions & filters: ILSpy.sln builds everything; ILSpy.XPlat.slnf is the decompiler libs + ilspycmd + their tests (no UI — the Linux CI target); ILSpy.Desktop.slnf is the UI plus its dependencies and tests; ILSpy.Installer.sln / ILSpy.VSExtensions.sln cover the legacy packaging.
Code conventions
- Don't reference "WPF" in source or comments. Describe what code does, not where it came from. "Mirrors WPF" comments rot once the legacy WPF tree is gone.
- Don't invent. Port what the previous app actually shipped, not every conceivable feature. Don't add Options-page settings that weren't there.
- TDD for new features. Write the failing test, show it red, implement, show it green. Never skip the red step.
- Strict pattern matchers. Decompilation pattern matchers should default to
∀over the structurally guaranteed shape — loosen only when a legitimate fixture is rejected (capture it as a regression test first). - No silent returns in tests. When an expected component is missing, assert and fail. Never
returnearly to bypass the assertion. - ASCII-only in code and comments. Do not use non-ASCII characters (em-dash, smart quotes, arrows, math symbols, etc.) unless genuinely necessary for what the code expresses.
- en-US English in all source code, comments, identifiers, and log/error strings.
Build / restore
- After adding or bumping a
PackageReference, regenerate lock files withdotnet restore /p:RestoreEnablePackagePruning=falseso the lock files stay complete. - The pre-commit hook runs
dotnet formaton the whole solution. New files must be CRLF (unix2dosafterWrite) or the hook rewrites them and breaks staging. - Partial commits need stash-and-pop. The format hook only auto-formats when the staged set equals the working tree. Stash unstaged remainder before committing a subset.
Commit workflow
- Subject:
Avalonia: <lowercase phrase>for UI-side changes. Keep it succinct (target <= 72 chars). Subject says what area; the body carries the substance. - Body explains the why and the non-diff context: the constraint, the prior incident, the decision, what was tried and rejected, the invariant that motivated the change. The diff already shows the what — don't restate it.
Fix #NNNN: ...closes an issue.#NNNNreferences one without closing.- Small follow-up fixes against a commit still on the branch get squashed back via
git commit --fixup+git rebase --autosquash, not appended as "fix X" commits. - en-US English in subject and body. ASCII-only unless a non-ASCII character is genuinely required for what the message describes.
- AI attribution: use
Assisted-by:, notCo-Authored-By:. Following the Linux kernel's coding-assistants guidance (https://docs.kernel.org/process/coding-assistants.html#attribution), an AI-assisted commit ends with a trailer of the formAssisted-by: AGENT_NAME:MODEL_VERSION:HARNESS— agent, model id, and the harness that ran it, colon-separated, e.g.Assisted-by: Claude:claude-opus-4-8:Claude Code. Use the session's actual model id and harness. Don't list analysis/build tools. Do not add aCo-Authored-By:line for the AI, and an AI agent must not add aSigned-off-by:(only a human can certify the DCO). - The parity trackers (
AVALONIA_PARITY_TODO.html,AVALONIA_PORT_TASKS.md,AVALONIA_PORT_TASKS.html) are intentionally untracked -- nevergit addthem.
Test discipline
- Always run the test suite with
--report-trxso failures survive:dotnet test ILSpy.sln --report-trx. Don't dismiss failures as flaky without first reproducing in isolation, then running repeatedly. - After matcher / rewriter edits, run the relevant tests, not just the build.
dotnet buildgreen ≠ behaviour correct.
Framework-specific gotchas
- AvaloniaEdit hyperlinks. Flip
Editor.Options.RequireControlModifierForHyperlinkClick=falseonce on the editor; don't overrideConstructElementFromMatchper generator. - AvaloniaEdit hover.
OnQueryCursormust not sete.Handled=true.PointerHoverargs are captured at hover-start, not at fire-time. - AvaloniaEdit TextView coords.
GetPositionwants document-visual coords (addScrollOffset). RedrawIBackgroundRenderers viaTextView.InvalidateLayer. - Avalonia 12 clipboard.
IClipboard.SetTextAsyncis an extension method — needsusing Avalonia.Input.Platform;even when the namespace is already imported (ClipboardExtensionsis a separate static class). - ProDataGrid hierarchical cell layout. Use
Grid Auto/*inDataGridHierarchicalColumncell templates, notStackPanel. The first child gets clipped otherwise. - ProDataGrid two-way expanded sync. Bind
HierarchicalOptions.IsExpandedPropertyPathfor model↔grid sync; don't hand-rollNodeExpanded/PropertyChangedplumbing. - ProDataGrid row drag-drop. DataGrid-level
IsReadOnlyshort-circuits the drag controller. PutIsReadOnlyon the column instead. - Dock setup follows
samples/DockMvvmSample.App.axamlwiring, factory shape,DockControlmarkup,ContextLocator/DockableLocatorshould match the sample. Deviate only with a documented reason. - DockWorkspace tab routing. Every navigation reuses the active tab (replacing in place if the type changes). Only "Open in new tab" and "Freeze tab" carve out new tabs.
- Decompile-progress UI is an overlay. Never wipe
Text/Foldings/ etc. to show a spinner; keep editor state intact so cancel falls back to it. ILSpyTraceListenersurfacesDebug.Assertthrough the global dialog — keep it registered at startup.
Investigating dependencies
- Decompile NuGet packages with this repo's
ilspycmdto inspect dependency internals — don't grep binaries.
Onboarding new contributors
If you're picking up this codebase fresh, read Program.cs → App.axaml.cs → Views/MainWindow.axaml.cs to trace startup, then AssemblyTree/AssemblyListPane.axaml.cs and TextView/DecompilerTextView.axaml.cs for the two main panes. The MEF composition graph in AppEnv/AppComposition.cs wires the rest.