Decompiler search box and member-type filter chips are inert — getFilteredNamespaces() ignores both #901

Open
opened 2026-07-30 04:24:23 +00:00 by spikerj · 0 comments
Owner

Found while adding render coverage for decompiler.component.html (wave-5 coverage loop, angular branch test/component-coverage-wave5).

Symptom

In the .NET decompiler tool, after decompiling a DLL:

  • Typing in the type/member search box does nothing. The tree keeps showing every namespace and type.
  • Clicking the All / Methods / Properties / Fields / Events filter chips highlights the chip but does not change the member lists. Selecting "Methods" still shows fields, properties and events.

Both controls look functional (the search text persists, the chip gets an active style), which makes this read as "my search found everything" rather than "search is broken".

Root cause

DecompilerComponent.getFilteredNamespaces() (libraries/features/dev-tools-decompiler/src/lib/decompiler.component.ts:234) is the method the template iterates, and it never reads either filter signal:

getFilteredNamespaces(): unknown[] {
  const data = this.decompiledData();
  if (!data?.tree?.namespaces) return [];
  return Object.entries(data.tree.namespaces).map(([name, namespace]) => ({
    name,
    types: Object.entries(namespace.types).map(([typeName, typeInfo]) => ({ /* every field, unconditionally */ })),
  }));
}

searchQuery and selectedFilter are written by onSearchChange() / setFilter(), but across the whole 803-line template they appear in exactly two kinds of place:

  • [value]="searchQuery()" — the input echoing its own text back (line 426)
  • [class.active]="selectedFilter() === '…'" — chip styling (lines 436, 445, 454, 463, 472)

Nothing else consumes them. So the name getFilteredNamespaces is aspirational: it is an unfiltered projection.

Impact

The decompiler's whole purpose is exploring a large assembly's type tree. On a real DLL (dozens of namespaces, hundreds of types) search and filtering are the only practical navigation, and both are no-ops. Users are left scrolling and manually expanding.

Fix sketch

  • Make getFilteredNamespaces() a computed() that reads searchQuery() and selectedFilter():
    • case-insensitive substring match against namespace name, type name/fullName, and member names;
    • drop namespaces/types that end up with no matches (and consider auto-expanding matches, since a match hidden inside a collapsed node is still invisible);
    • selectedFilter should restrict which member collections are projected (methods → only methods, etc., all → all four).
  • It is called from @for in the template, so converting it to a computed() also removes a per-change-detection re-projection of the entire tree.

Current behavior is pinned

decompiler.render.spec.ts has two characterization tests referencing this ticket which assert the broken behavior (search leaves the result count unchanged; "Methods" still renders fields) so the suite stays green. Both are written to fail once filtering works — update them as part of the fix.

Found while adding render coverage for `decompiler.component.html` (wave-5 coverage loop, angular branch `test/component-coverage-wave5`). ## Symptom In the **.NET decompiler** tool, after decompiling a DLL: - Typing in the type/member **search box** does nothing. The tree keeps showing every namespace and type. - Clicking the **All / Methods / Properties / Fields / Events** filter chips highlights the chip but does not change the member lists. Selecting "Methods" still shows fields, properties and events. Both controls look functional (the search text persists, the chip gets an active style), which makes this read as "my search found everything" rather than "search is broken". ## Root cause `DecompilerComponent.getFilteredNamespaces()` (`libraries/features/dev-tools-decompiler/src/lib/decompiler.component.ts:234`) is the method the template iterates, and it never reads either filter signal: ```ts getFilteredNamespaces(): unknown[] { const data = this.decompiledData(); if (!data?.tree?.namespaces) return []; return Object.entries(data.tree.namespaces).map(([name, namespace]) => ({ name, types: Object.entries(namespace.types).map(([typeName, typeInfo]) => ({ /* every field, unconditionally */ })), })); } ``` `searchQuery` and `selectedFilter` are written by `onSearchChange()` / `setFilter()`, but across the whole 803-line template they appear in exactly two kinds of place: - `[value]="searchQuery()"` — the input echoing its own text back (line 426) - `[class.active]="selectedFilter() === '…'"` — chip styling (lines 436, 445, 454, 463, 472) Nothing else consumes them. So the name `getFilteredNamespaces` is aspirational: it is an unfiltered projection. ## Impact The decompiler's whole purpose is exploring a large assembly's type tree. On a real DLL (dozens of namespaces, hundreds of types) search and filtering are the only practical navigation, and both are no-ops. Users are left scrolling and manually expanding. ## Fix sketch - Make `getFilteredNamespaces()` a `computed()` that reads `searchQuery()` and `selectedFilter()`: - case-insensitive substring match against namespace name, type name/fullName, and member names; - drop namespaces/types that end up with no matches (and consider auto-expanding matches, since a match hidden inside a collapsed node is still invisible); - `selectedFilter` should restrict which member collections are projected (`methods` → only `methods`, etc., `all` → all four). - It is called from `@for` in the template, so converting it to a `computed()` also removes a per-change-detection re-projection of the entire tree. ## Current behavior is pinned `decompiler.render.spec.ts` has two characterization tests referencing this ticket which assert the *broken* behavior (search leaves the result count unchanged; "Methods" still renders fields) so the suite stays green. **Both are written to fail once filtering works — update them as part of the fix.**
Sign in to join this conversation.