Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Use `docs/test-doc-maintenance-plan.md` before moving oversized suites or adding
End-to-end upgrade path: seeds a pre-column legacy DB, opens it through `TryMigrateForRead`, and exercises the read paths that touch nullable symbol ordinals (outline, symbol search, nearby, unused, analyze bundle) to lock in the real-world failure mode behind #58 / #49. Migration ownership coverage must also distinguish caller-owned transactions from owned transactions, propagate unrelated `BEGIN` errors, preserve populated foreign-key rows across rebuilds, and verify rollback, foreign-key restoration, and successful retry after injected failures.
- `IndexCommandRunner*Tests.cs`, `QueryCommandRunner*Tests.cs`, `ProgramCliTests.cs`, `InstallScriptTests.cs`
CLI parsing, command execution, and installer behavior. Index command coverage is split by run mode or feature area, and query command coverage is split by command family with partial test classes so shared console and fixture helpers stay centralized. Keep repeated query-result fixtures, such as overlapping chunk content used by multiple search deduplication tests, in narrow class-level helpers instead of duplicating local builders. `ProgramCliTests.cs` covers top-level entrypoint behavior that must be exercised through a subprocess, while `InstallScriptTests.cs` runs focused bash snippets against `install.sh` in library mode to lock in release-installer regressions without performing real network installs. Installer bundle-generation tests must also verify that `install.sh` is marked generated while every canonical `install_modules/` source remains unmarked.
C# `unused` partial-family coverage must reuse one multi-file fixture across regular JSON, compact, `--by-bucket`, and `--actionable`; include top-level and nested partial types, genuinely unused private members, an unrelated same-named family, a containing-type generic-arity collision, and a family-external occurrence in a matched peer file so semantic membership cannot regress into name-only, flattened-qualified-name, or whole-file evidence sharing.
`ConsoleUiTests.cs` keeps each generated shell's complete long-flag catalog aligned with `CliFlagSchema` and checks every Fish command scope against the shared per-command completion projection. Add flags to the schema rather than weakening this Bash / Zsh / Fish / PowerShell parity contract.
`ProgramRunnerTests.cs` enumerates `CliCommandCatalog.CommandSubcommands` and requires every valid nested verb to resolve to verb-specific usage with an example. It also pins destructive index confirmation and aggregate dependency-filter help, plus the read-only GitHub duplicate-preflight boundary for suggestion exports. Installer cancellation coverage must wait until the PID file contains a complete positive integer before cancelling, using a bounded dedicated waiter instead of a fixed-delay timer so parallel load cannot expose a redirection-created empty file.
Global-tool-log home-shorthand coverage resolves the normalized first candidate through the test-only no-write-probe seam, so sandbox permissions cannot turn an expansion assertion into a writability-fallback assertion.
Expand Down Expand Up @@ -1070,6 +1071,7 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests"
エンドツーエンドのアップグレード経路: カラム追加前のレガシー DB を用意し、`TryMigrateForRead` 経由で開いてから NULL になりうるシンボル列を触る read path(outline、シンボル検索、近傍、unused、analyze バンドル)を一通り叩き、#58 / #49 の実機失敗モードを固定する。migration ownership の coverage では、caller-owned transaction と cdidx-owned transaction を区別し、無関係な `BEGIN` error を伝播させ、foreign key が設定された既存 row を rebuild 後も保持し、注入 failure 後の rollback、foreign key 復元、retry 成功まで検証する。
- `IndexCommandRunner*Tests.cs`、`QueryCommandRunner*Tests.cs`、`ProgramCliTests.cs`、`InstallScriptTests.cs`
CLI の引数解析、コマンド実行、installer 挙動のテスト。Index command coverage は run mode または機能領域ごとの partial suite に分割し、Query command coverage は command family ごとの partial test class に分割して、共有 console / fixture helper は一箇所に保ちます。`ProgramCliTests.cs` はグローバル引数の解釈や完全な CLI 起動フローのように subprocess 経由で確認すべき Program エントリポイント挙動を扱い、`InstallScriptTests.cs` は `install.sh` を library mode で source した bash snippet を実行して、実ネットワーク install を行わずに release installer の回帰を固定する。installer bundle 生成テストでは、`install.sh` が generated と判定される一方、canonical な `install_modules/` source はすべて unmarked のままであることも検証してください。
C# `unused` の partial-family coverage では、通常の JSON、compact、`--by-bucket`、`--actionable` で1つの multi-file fixture を共有してください。top-level / nested partial type、本当に未使用の private member、無関係な同名 family、containing type の generic-arity collision、matched peer file 内の family 外 occurrence を含め、semantic membership が name-only、平坦化された qualified name、または file 全体の evidence 共有へ戻らないことを固定します。
`ConsoleUiTests.cs` は各生成シェルの long flag 全カタログを `CliFlagSchema` と同期させ、Fish の全 command scope を共有の command 別 completion 射影と照合します。この Bash / Zsh / Fish / PowerShell parity 契約を弱めず、新しい flag は schema に追加してください。
`ProgramRunnerTests.cs` は `CliCommandCatalog.CommandSubcommands` を列挙し、すべての有効な nested verb が例を含む verb 固有 usage に解決されることを必須とします。さらに destructive な index confirmation、aggregate dependency filter help、suggestion export の read-only GitHub duplicate-preflight 境界を固定します。installer cancellation coverage は固定時間タイマーではなく上限付きの専用 waiter を使い、PID ファイルが完全な正の整数を含むまで待ってから cancel してください。これにより並列負荷下でも redirection によって作成された空ファイルを読みません。
global tool log の home shorthand coverage は、test 専用の write probe なし seam から正規化済みの先頭 candidate を解決します。これにより sandbox permission の影響で、展開の assertion が writability fallback の assertion に変わることを防ぎます。
Expand Down
13 changes: 13 additions & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,13 @@ When `unused` returns `next_cursor`, pass that opaque value back unchanged.
The cursor is bound to the effective audit scope, filters, ordering, and index
generation. Changing those inputs or refreshing the index requires restarting
without `--cursor`; JSON pages also expose `result_stable_at`.
For C# private members declared in partial types, `unused` aggregates use
evidence across sibling files by fully qualified logical type name, including
nested partial types. Same-named types in other namespaces or containing types
do not share evidence; containing-type kind and generic arity also remain part
of the logical identity. Genuinely unreferenced members in the family remain
actionable candidates. Regular JSON, compact, `--by-bucket`, and
`--actionable` all use the same family-aware classification.
Public APIs, framework entrypoints, DTOs, serialization contracts, generated
hooks, test-only hooks, Markdown headings and fenced-code language markers,
reflection, and configuration-based usage can be false positives and are
Expand Down Expand Up @@ -3745,6 +3752,12 @@ filter context だけが必要な場合は `--compact` を使ってください
`unused` が `next_cursor` を返した場合は、その opaque 値を変更せず次の呼び出しへ渡してください。
cursor は有効な audit scope、filter、ordering、index generation に束縛されます。条件を変更した場合や
index を更新した場合は `--cursor` なしで再開する必要があり、JSON page は `result_stable_at` も返します。
C# の partial type で宣言された private member について、`unused` は nested partial type を含む
完全修飾された logical type 名を使い、sibling file 全体の使用 evidence を集約します。
別 namespace または別 containing type にある同名 type とは evidence を共有せず、
containing type の kind と generic arity も logical identity の一部として維持します。family 内で
本当に参照されていない member は actionable candidate のまま残ります。通常の JSON、compact、
`--by-bucket`、`--actionable` はすべて同じ family-aware classification を使います。
Public API、framework entrypoint、DTO、serialization contract、generated hook、test-only hook、Markdown heading と fenced-code の
language marker、reflection、config 経由の使用は false positive になりうるため、
低 confidence bucket に寄せられます。
Expand Down
19 changes: 19 additions & 0 deletions changelog.d/unreleased/4834.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
category: fixed
issues:
- 4834
affected:
- src/CodeIndex/Database/DbSymbolReader.UnusedSymbols.cs
- src/CodeIndex/Database/DbContext.ConnectionFunctions.cs
- tests/CodeIndex.Tests/QueryCommandRunnerSymbolTests.cs
- USER_GUIDE.md
- TESTING_GUIDE.md
---

## English

- **Restored cross-file C# partial-family usage detection in `unused` (#4834)** — the #3673 fallback now matches sibling declarations by their fully qualified logical type name and semantic containing-type shape, including nested partial types and generic arity, and limits use evidence to the matched peer declaration's source range. Private fields used from another partial file are no longer reported as actionable, while unrelated same-named types, family-external occurrences, and genuinely unused fields remain separate.

## 日本語

- **`unused` の C# partial family に対する cross-file 使用検出を復旧しました (#4834)** — #3673 で追加した fallback が nested partial type と generic arity を含む sibling declaration を完全修飾 logical type 名および semantic な containing-type shape で照合し、使用 evidence を matched peer declaration の source range 内に限定するようになりました。別 partial file から使われる private field を actionable として誤報せず、無関係な同名 type、family 外 occurrence、本当に未使用の field は分離したまま扱います。
67 changes: 67 additions & 0 deletions src/CodeIndex/Database/DbContext.ConnectionFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ internal static void RegisterConnectionFunctions(SqliteConnection connection)
connection.CreateFunction(
"csharp_identifier_occurrence_count",
(string? text, string? identifier) => CountCSharpIdentifierOccurrences(text, identifier));
connection.CreateFunction(
"csharp_identifier_occurrence_count_in_line_range",
(string? text, long? chunkStartLine, long? rangeStartLine, long? rangeEndLine, string? identifier) =>
CountCSharpIdentifierOccurrencesInLineRange(
text,
chunkStartLine,
rangeStartLine,
rangeEndLine,
identifier));
connection.CreateFunction(
"csharp_reference_type_arity",
(string? context, string? identifier, long? columnNumber) =>
Expand Down Expand Up @@ -207,6 +216,64 @@ internal static int CountCSharpIdentifierOccurrences(string? text, string? ident
return count;
}

internal static int CountCSharpIdentifierOccurrencesInLineRange(
string? text,
long? chunkStartLine,
long? rangeStartLine,
long? rangeEndLine,
string? identifier)
{
if (string.IsNullOrEmpty(text)
|| string.IsNullOrEmpty(identifier)
|| chunkStartLine is null
|| rangeStartLine is null
|| rangeEndLine is null
|| chunkStartLine <= 0
|| rangeStartLine <= 0
|| rangeEndLine < rangeStartLine)
{
return 0;
}

var relativeStartLine = Math.Max(0, rangeStartLine.Value - chunkStartLine.Value);
var relativeEndLineExclusive = rangeEndLine.Value - chunkStartLine.Value + 1;
if (relativeEndLineExclusive <= 0
|| relativeStartLine > int.MaxValue
|| relativeEndLineExclusive > int.MaxValue)
{
return 0;
}

var startOffset = FindTextLineStartOffset(text, (int)relativeStartLine);
var endOffset = FindTextLineStartOffset(text, (int)relativeEndLineExclusive);
if (startOffset >= endOffset)
return 0;

var scopedText = startOffset == 0 && endOffset == text.Length
? text
: text.Substring(startOffset, endOffset - startOffset);
return CountCSharpIdentifierOccurrences(scopedText, identifier);
}

private static int FindTextLineStartOffset(string text, int zeroBasedLine)
{
if (zeroBasedLine <= 0)
return 0;

var line = 0;
for (var i = 0; i < text.Length; i++)
{
if (text[i] != '\n')
continue;

line++;
if (line == zeroBasedLine)
return i + 1;
}

return text.Length;
}

private static bool CSharpBaseReferenceMatches(
string? baseReference,
string? candidateName,
Expand Down
Loading
Loading