27 Commits
Author SHA1 Message Date
Christoph Wille 519ed6e98c Skip the StringBuilder in EscapeIdentifier when nothing needs escaping
EscapeIdentifier runs for every identifier token emitted, yet it built
a StringBuilder plus a fresh result string even for the overwhelmingly
common case of an identifier with no escapable characters. A pre-scan
now returns the original instance untouched in that case, and the
surrogate-pair copy appends the two chars directly instead of cutting
a two-char substring. New unit tests pin the escaping behavior and the
identity fast path.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-10-10 18:30:53 +02:00
Siegfried Pammer 8f66608d82 Improve dynamic reference tooltips
Dynamic member tooltips should describe the visible static target, while punctuation should stay reserved for useful call targets such as constructors. This keeps dynamic object creation tooltips on the constructor parentheses but avoids noisy synthetic member references on dynamic invocation parentheses.

Assisted-by: OpenAI:openai/gpt-5.5:OpenCode
2026-10-06 07:35:36 +02:00
Siegfried Pammer 8faa1848d5 Close the test fixtures that hold an assembly open
A PEFile keeps its assembly mapped until it is disposed, and three fixtures
opened one and never did: DuplicateAssemblyReferenceTests both directly and
for every reference its resolver hands back, TypeForwarderResolutionTests for
everything the resolver probes, and LocationsInAstTests for the module behind
each decompiler. Their teardowns then deleted the directory those files live
in, which cannot work while the mapping is open, so a Windows run reported a
failed teardown for a run that otherwise passed. They now dispose what they
opened, the way WpfProjectExportTests already did.

Deleting is cleanup either way, so it goes through RepeatOnIOError rather than
straight at the file system, and that helper no longer rethrows: its last of
five attempts was deliberately unguarded, and four retries ten milliseconds
apart is a short window to wait for a handle that a virus scanner or the
indexer still holds. It now backs off to about a second and a half and, failing
that, reports and leaves the file behind, which costs nothing.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-09-19 08:43:07 +02:00
Siegfried Pammer 800efc6e10 Fix #749: toggle a member's fold together with its documentation
Toggle folding picked the innermost fold containing the offset, but a
member's logical region is fragmented: the body fold starts at the
opening brace, the XML documentation has an independent fold, and the
header line belongs to neither, so toggling there collapsed the whole
enclosing type. Visual Studio's source editor keeps documentation
regions independent, but its metadata-as-source view treats the
member's leading trivia as one hideable unit; for a read-only
decompiler view the grouped behavior is the intuitive one.

The writer now records where an entity declaration begins, and the
definition's fold carries that logical start. Toggling targets the fold
whose logical region innermost-contains the offset, so the header line
targets the member rather than the type, and leading documentation
folds follow the member fold's new state. Inside the documentation the
doc fold itself is the innermost region and still toggles alone.

Toggle all folding now follows Visual Studio's Toggle All Outlining
parity: a mixed state expands everything, a uniform state flips.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-07-28 16:15:42 +02:00
Siegfried Pammer 7865f9b23e Restore parameter lists of parameterized properties in signatures
The main tree, tooltips, and search results once showed the parameter
list of a parameterized property; the ambience lost that when property
rendering went through the converted AST node, whose C# property syntax
cannot carry parameters. Take the parameter list from the symbol
instead and render it in parentheses (matching VB.NET usage syntax and
distinguishing these properties from indexers).

Assisted-by: Claude:claude-fable-5:Claude Code
2026-07-27 16:28:34 +02:00
Siegfried Pammer a926ae7e8b Show a hover for the indexer of a dynamic index access
A dynamic index access (a[b]) gave its IndexerExpression a
DynamicInvocationResolveResult with no symbol, so the brackets carried no
tooltip. Synthesize an indexer (FakeProperty, IsIndexer) on the target
type with the index parameters typed from the callsite delegate, and
attach it. Route it hover-only by detecting a DynamicInvocationResolveResult
directly on the node - which also covers an invoke-member's own
parentheses, so those stop producing a dead navigation link too.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-07-23 06:32:02 +02:00
Siegfried Pammer cba8b0d7a5 Test dynamic member tooltip rendering
Lock the hover content the dynamic-tooltip work produces. Ambience-level
cases (CSharpAmbienceTests) pin that SpecialType.Dynamic renders as
"dynamic" and that a synthetic dynamic method renders its return and
per-argument types - including the full hover form, confirming the
unnamed synthetic parameters collapse to their types with no dangling
name. An end-to-end case (HoverOnlyReferenceTests) decompiles a dynamic
call and renders the symbol GetSymbol hands back, exercising the actual
synthesis (argument typing from the callsite delegate), not a hand-built
stand-in.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-07-23 06:32:02 +02:00
Siegfried Pammer 4ff11dd796 Add a hover-only reference mode, distinct from local highlighting
References had two behaviors encoded in one bool: navigable links, and
"local" references that are non-navigable but highlight all occurrences
on click. Synthesized dynamic members were routed through the latter, so
they picked up the occurrence highlight even though each use is a
distinct synthetic member with nothing to group.

Model the three modes explicitly with a ReferenceMode enum on
ReferenceSegment (Link / LocalHighlight / HoverOnly). Dynamic members,
the dynamic constructor and the dynamic keyword now use HoverOnly: they
still show a hover tooltip (BuildHoverContent resolves any IEntity
reference regardless of mode) but get the arrow cursor, do not navigate,
and do not highlight. Local variables keep LocalHighlight. Plumbed via a
new isHoverOnly flag on ITextOutput.WriteLocalReference.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-07-23 06:32:02 +02:00
Siegfried Pammer f2b51d2c34 Fix #2078: Generic local functions not highlighted properly
LocalFunctionMethod.MemberDefinition returned the instance itself, so the
declaration of a generic local function (whose base method is an identity
specialization) and its use sites (whose base methods carry the use-site
substitutions) never compared equal, and click-highlighting could not group
them. Returning a wrapper around the unspecialized base method lets the
token writer record the same definition object for the declaration and all
use sites. Method-group references to local functions additionally need
their own lookup, because GetSymbol() does not surface a
MethodGroupResolveResult.

The token-writer tests now keep the PEFile alive for the duration of each
test: recorded references are type-system entities that lazily read from
the PE image, and formatting one in an assertion message after disposal
crashed with an AccessViolationException.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-07-05 08:30:40 +02:00
Siegfried Pammer 847324e836 Fix #3845: Add option to expand XML documentation comments
Doc-comment folds were created with defaultCollapsed hardcoded to true,
so /// blocks always started collapsed. The new Display option is
bridged into DecompilerSettings like the other expand flags and drives
the fold's default state.

Detecting the last line of a doc-comment block requires sibling
navigation between trivia nodes, so trivia now carry parent, sibling
list, and index state. That state lives on the Trivia subclass to keep
AstNode's size unchanged, and the tree API treats trivia as a separate
navigation space: Next/PrevSibling and Remove work within the owning
list, Slot is null, ReplaceWith throws instead of aliasing the trivia
index into the parent's child slots, GetNextNode/GetPrevNode stop at
the list boundary, Clone and CopyAnnotationsFrom deep-copy and reparent
trivia, and CheckInvariant validates the trivia lists.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-07-04 10:23:56 +02:00
Siegfried Pammer 04505bbdf2 Highlight and scroll to the changed instruction in the ILAst view
The C# debug-steps view highlights and centers the exact AST node a
transform changed; the ILAst view already had the step tree and
replay-at-step but produced no highlight. Bring it to parity.

IL rendering has no token-writer seam like the C# output visitor, so
per-instruction text spans are recorded by bracketing
ILInstruction.WriteTo via a new INodeTrackingOutput. The dominant
inst.ReplaceWith(newInst) transform pattern detaches the instruction
passed to Step, so ILTransformContext gains EndStep to record the
produced instruction; Stepper additionally records the position's
ancestor chain as fallback candidates before the step-limit throw, so
the "show state before" view -- which halts at the selected step --
still resolves to a surviving ancestor (ultimately the ILFunction).
The highlight-range resolver is shared with the C# language.

Assisted-by: Claude:claude-opus-4-8:Claude Code
2026-07-02 01:08:51 +02:00
Siegfried Pammer e461940d40 Move the LocationsInAst sample beside its test, add a license header
Assisted-by: Claude:claude-opus-4-8:Claude Code
2026-06-23 07:43:42 +02:00
Siegfried Pammer ed7bfbf85a Guard null-object regressions in lambda body and token decorator
Two crash paths surfaced by the null-object -> nullable-reference migration of the
C# AST. A void 'return;' forced into lambda syntax NRE'd on
ReturnStatement.Expression.Detach() now that Expression is nullable; the body now
falls through to the block form, matching the existing guards in CallBuilder and
InferReturnType. Separately, the location-setting token decorator routed a parentless
print-only node (e.g. the detached name-token clone of a renamed constructor) into the
child setter as SlotKind.None, which throws since the Role hierarchy was removed; such
nodes have no slot and are now skipped. A regression test covers the decorator path.

Assisted-by: Claude:claude-opus-4-8:Claude Code
2026-06-23 07:43:42 +02:00
Siegfried Pammer e007387144 Reword a SequencePointBuilder comment in the locations test 2026-06-23 07:43:42 +02:00
Siegfried Pammer be565b5b4c Store locations at print time; derive sequence points from nodes
Source locations were virtual, computed by recursing to the first and last
child, whose leftmost and rightmost leaves are token nodes; sequence-point
coordinates likewise came from reconstructed token nodes. Store locations as
fields assigned while printing, and derive sequence-point coordinates from the
surrounding real nodes plus the decompiler's fixed formatting, so neither
depends on token children. The using/foreach await modifier becomes a plain
bool field. Characterization gates lock the emitted locations and PDB
coordinates, which are unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
2026-06-23 07:43:42 +02:00
Siegfried Pammer ac65f0d27a Add regression coverage for the AST-with-locations output path
The InsertMissingTokensDecorator path (TokenWriter.CreateWriterThatSetsLocationsInAST)
reconstructs token nodes and assigns source locations onto the AST, feeding PDB
sequence points and GUI navigation. The Pretty suite never drives it, so it had no
coverage at all. Before reworking the token model, lock its observable consequences:
the located path emits the same text as the plain path, real nodes receive ordered
locations, location-based navigation resolves into the method body, and sequence
points are produced for a method body.

Assisted-by: Claude:claude-opus-4-8:Claude Code
2026-06-23 07:43:42 +02:00
Siegfried Pammer 34c50c19b7 Fix #2035: Make the override modifier a link to the overridden member
The decompiled view only offered navigation on member names; finding
what an override actually overrides required opening the analyzer.
Attaching a reference to the modifier token gives the same
go-to-definition affordance Visual Studio has on 'override'. The
reference resolves via InheritanceHelper.GetBaseMember, so it targets
the nearest overridden member and degrades to plain text when the base
member cannot be resolved.

Assisted-by: Claude:claude-fable-5:Claude Code
2026-06-11 13:02:09 +02:00
Siegfried Pammer 0cd53418ad Add ILAmbience 2025-12-21 13:47:18 +01:00
Siegfried Pammer e34db06a67 Fix #3543: Missing parentheses around field assignment expression in list initializers 2025-09-05 10:42:17 +02:00
Peter Crabtree e1e16b64f5 dev: Strip BOM mark from text files 2025-08-28 20:56:19 -04:00
Siegfried Pammer 3c70224441 Fix detection and display of explicitly implemented operators. 2025-02-21 11:59:21 +01:00
Christoph Wille fba0dbae8d Migrate to NUnit 4 (#3132)
* Migrate to NUnit 4 based on https://docs.nunit.org/articles/nunit/release-notes/Nunit4.0-MigrationGuide.html. Needed fix for https://github.com/nunit/nunit/issues/4564
* Test 4.1.0-adev.1 from NUnit MyGet feed (works locally in VS)
* Update to bugfix version adev.2 (works on GH)
* NUnit 4.0.1 final
2023-12-02 13:38:55 +01:00
Daniel Grunwald 03787bfc70 Avoid parentheses around lambdas where possible. 2020-09-23 18:44:31 +02:00
dotnet format 0d9f871a4f #2128: Reformat the whole code base. 2020-08-28 01:05:38 +02:00
Siegfried Pammer f33f26fddd Extend TypeSystem/TypeSystemLoaderTests and CSharpAmbienceTests. 2018-08-07 16:31:16 +02:00
Siegfried Pammer a7ad5990ac CSharpAmbience: Add ConversionFlags.ShowParameterModifiers and ShowTypeParameterVarianceModifier to make labels in ILSpy main tree view more concise. + Added Tests. 2018-08-07 14:10:23 +02:00
Siegfried Pammer 23907c8d7d Use IAmbience API in CSharpLanguage. 2018-08-06 23:23:01 +02:00