diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f5e42ef4a..42f433b3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - **Developer Guide now distinguishes SQLite's role from "zero dependencies" and labels release numbers as examples** — Clarified that cdidx remains a zero-configuration, single-file CLI with exactly one production dependency (`Microsoft.Data.Sqlite`), and made the release-workflow checklist explicitly state that `1.9.0` is only an example version to substitute during an actual release. Affected: `DEVELOPER_GUIDE.md`. #### Fixed +- **C# wrapped constructor initializer `: base(...)` / `: this(...)` no longer leaks phantom `function base` / `function this` symbols (#331)** — The shared C# `CSharpTypePattern` character class intentionally includes `:` so that alias-qualified return types like `Alias::Type IFoo.Create()` keep matching the explicit-interface implementation path. That makes the method regex vulnerable to wrapped Allman-style constructor initializers such as ` : base(s, 0)` or ` : this(a)` — a future tweak to the existing first-char `(?![?:])` guard could let `returnType=":" + name="base"` slip back in as a phantom `function base` / `function this` symbol. The method regex now carries an additional `(?!(?:base|this)\b)` negative lookahead right before the `name` capture so the phantom cannot surface even if the first-char guard weakens later, while the dedicated indexer pattern continues to match `this[...]` by name. Added a `SymbolExtractor` regression test that pins both the wrapped `: base(...)` / `: this(...)` forms and the same-line plus expression-bodied variants, asserting all five constructors in the fixture still index and neither `function base` nor `function this` is emitted. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`. Closes #331. - **Regression coverage locks in C# `readonly` property extraction and `readonly get =>` phantom suppression (#327)** — The C# property regexes in `SymbolExtractor` already consume `readonly` (alongside `partial` and `ref readonly`) as a modifier, so expression-bodied (`public readonly int A => _v;`), auto-property (`public readonly int B { get; }`), and accessor-body (`public readonly int C { get => _v; }`) declarations all surface as `property` rows, and a standalone `readonly get => _v;` accessor line inside a block-bodied property no longer slips through the expression-bodied regex to create a phantom `property get` / `property set` / `property init` row. There was no dedicated regression test pinning those shapes, so the bug report could not be closed cleanly. Added a `SymbolExtractor` regression test that locks the three `readonly` property shapes (capturing name, return type, and visibility), the Allman-style mixed-accessor property whose body includes a `readonly get => _v;` accessor, and the phantom-suppression contract for `get` / `set` / `init`, while keeping the existing `readonly` method baseline. Affected: `tests/CodeIndex.Tests/SymbolExtractorTests.cs`. Closes #327. - **C# `extern alias` file-prelude declarations now surface as `import` symbols (#326)** — `SymbolExtractor` now matches `extern alias Foo;` before the existing `using` / `using static` / `using X = Y;` rows for C#, so assembly-alias reconciliation directives no longer fall through every row and silently drop from `symbols`, `definition`, and `outline`. Leading-whitespace variants (` extern alias Foo;`) are also captured, and the new row is placed before the two `using` rows so pattern ordering stays deterministic and the existing `using` / `global using` / `using static` / `using X = Y;` captures are unchanged. `references` / `callers` / `callees` behavior is not changed — `Alias::Type` call-site syntax is still not tracked by the reference extractor. Added regression coverage that pins both the single-alias fixture and the combined fixture (multiple `extern alias` lines plus `global using`, `using static`, namespace, class, and method) so cross-family extraction stays correct. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `DEVELOPER_GUIDE.md`. Closes #326. - **Regression coverage for C# multi-section attributes `[A, B(args)]` (#330, #368)** — Earlier attribute-stripper rewrites already stopped the `StripLeadingCSharpAttributeLists` fall-through from leaking comma-separated attribute names like `Conditional`, `Description`, and `Trait` as phantom `function` symbols, and from producing bogus body ranges that straddled adjacent attribute lines. However, there was no dedicated regression test pinning the `[Obsolete, Conditional("DEBUG")]`, `[Fact, Trait("cat", "io")]`, `[Required, StringLength(50), Column("name")]`, and multi-line stacked `[A, B], [C]` shapes that originally motivated the fix, so the issue could not be closed cleanly and the bug could silently re-emerge. Added two `SymbolExtractor` regression tests that lock the method-level and class/property-level multi-section attribute fixtures end to end, covering the stacked `[A][B]` / `[A] [B]` control shapes and the xUnit / ASP.NET / EF DataAnnotations conventions that pile multiple attribute sections on a single line. Affected: `tests/CodeIndex.Tests/SymbolExtractorTests.cs`. Closes #330, closes #368. @@ -683,6 +684,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - **DEVELOPER_GUIDE が SQLite の位置付けとリリース番号の例示を明確化** — cdidx が「設定ゼロ・単一ファイル・本番依存1個」の CLI であり、その唯一の本番依存が `Microsoft.Data.Sqlite` であることを明記した。あわせて、リリース手順のチェックリスト中に出てくる `1.9.0` は固定値ではなく例示であり、実際のリリース番号へ読み替える前提だと明示した。対象: `DEVELOPER_GUIDE.md`。 #### 修正 +- **ラップされた C# コンストラクタ初期化子 `: base(...)` / `: this(...)` が phantom `function base` / `function this` を生まないよう修正 (#331)** — 共有の C# `CSharpTypePattern` の文字クラスには意図的に `:` を含めており、`Alias::Type IFoo.Create()` のような alias-qualified な戻り値型を明示的インターフェース実装の経路で取り続けるために必要な設計になっている。その結果、Allman スタイルで ` : base(s, 0)` や ` : this(a)` のようにラップされたコンストラクタ初期化子が、`returnType=":" + name="base"` として method regex に引っかかる危険が残り、既存の先頭文字ガード `(?![?:])` を将来調整した際に phantom `function base` / `function this` として再発する余地があった。method regex の `name` キャプチャ直前に `(?!(?:base|this)\b)` の negative lookahead を追加し、先頭文字ガードが緩んだ場合でも phantom が漏れないよう二重化した。インデクサ専用パターンは引き続き `this[...]` を name として拾う。ラップされた `: base(...)` / `: this(...)` の両形と、同一行形・式本体形を押さえ、fixture の 5 本のコンストラクタが全て索引され、`function base` / `function this` が出ないことを固定する `SymbolExtractor` 回帰テストも追加した。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`、`tests/CodeIndex.Tests/SymbolExtractorTests.cs`。Closes #331。 - **C# の `readonly` プロパティ抽出と `readonly get =>` 幻影抑制の回帰カバレッジを追加 (#327)** — `SymbolExtractor` の C# プロパティ正規表現はすでに `readonly` を `partial` / `ref readonly` と並ぶ修飾子として受け付けており、式本体 (`public readonly int A => _v;`)、自動プロパティ (`public readonly int B { get; }`)、accessor 本体 (`public readonly int C { get => _v; }`) のいずれも `property` として抽出される。また block-bodied プロパティ内の単独行 `readonly get => _v;` accessor が式本体プロパティ正規表現に吸われて phantom な `property get` / `property set` / `property init` 行を生むこともない。ただしこれらを固定する専用の回帰テストが無かったため、issue を clean に close できない状態だった。`SymbolExtractor` に、3 つの `readonly` プロパティ形(name / return type / visibility まで検証)、`readonly get => _v;` accessor を含む Allman 形 mixed-accessor プロパティ、`get` / `set` / `init` を出さないという phantom 抑制契約を固定する回帰テストを追加し、既存の `readonly` メソッドのベースラインもそのまま保った。対象: `tests/CodeIndex.Tests/SymbolExtractorTests.cs`。Closes #327。 - **C# の `extern alias` 先頭宣言が `import` シンボルとして抽出されるよう修正 (#326)** — `SymbolExtractor` の C# 行セットで `using` / `using static` / `using X = Y;` より前に `extern alias Foo;` を拾う行を追加し、アセンブリエイリアス解決ディレクティブが全ての既存行をすり抜けて `symbols` / `definition` / `outline` から黙って消えないようにした。先頭空白付き (` extern alias Foo;`) も対象。新しい行は既存 2 本の `using` 行の前に置き、パターン順序を決定的に保ちつつ `using` / `global using` / `using static` / `using X = Y;` の既存抽出が変わらないことを担保した。`references` / `callers` / `callees` の挙動は変えていない — `Alias::Type` 形式の呼び出し箇所は依然として reference extractor で追跡していない。単一 `extern alias` の回帰と、複数 `extern alias` に `global using` / `using static` / namespace / class / method を組み合わせた統合回帰を押さえるテストを追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`、`tests/CodeIndex.Tests/SymbolExtractorTests.cs`、`DEVELOPER_GUIDE.md`。Closes #326。 - **C# の複数セクション属性 `[A, B(args)]` のリグレッションテストを追加 (#330, #368)** — 既に入っている属性ストリッパの書き直しで、`StripLeadingCSharpAttributeLists` の fall-through が `Conditional`、`Description`、`Trait` といったカンマ区切り属性名を phantom `function` として漏らし、次行の属性行まで跨ぐ不正な body range を生成する挙動は解消されていた。ただし、元々の報告を支えていた `[Obsolete, Conditional("DEBUG")]`、`[Fact, Trait("cat", "io")]`、`[Required, StringLength(50), Column("name")]`、そして複数行に渡って `[A, B]` と `[C]` を重ねる形を固定する専用の回帰テストが無かったため、issue を clean に閉じられず、リグレッションの再発を静かに許す状態だった。メソッド単位とクラス/プロパティ単位の multi-section attribute fixture、`[A][B]` / `[A] [B]` のコントロール形、xUnit / ASP.NET / EF DataAnnotations 規約で複数セクションを 1 行に積む形を end-to-end で押さえる 2 本の `SymbolExtractor` 回帰テストを追加した。対象: `tests/CodeIndex.Tests/SymbolExtractorTests.cs`。Closes #330、Closes #368。 diff --git a/src/CodeIndex/Indexer/SymbolExtractor.cs b/src/CodeIndex/Indexer/SymbolExtractor.cs index b805848d07..cf9a667c75 100644 --- a/src/CodeIndex/Indexer/SymbolExtractor.cs +++ b/src/CodeIndex/Indexer/SymbolExtractor.cs @@ -299,11 +299,17 @@ private enum JavaScriptTypeScriptFunctionHeaderConsumeResult // Method with return type — visibility optional for explicit interface impl and nested members. // Negative lookahead excludes call-site lines (await/return/throw/yield/var/typeof/sizeof/nameof/default/if/for/while/switch/catch/lock/using) // and ternary continuation branches (`? Foo(...)` / `: Foo(...)`) that would otherwise resemble returnType + name. + // The `(?!(?:base|this)\b)` guard on the name capture belt-and-suspenders against constructor-chain + // initializers (`: base(...)` / `: this(...)`) leaking phantom `function base` / `function this` + // symbols if any upstream guard becomes permissive. Closes #331. // Note: `new` is NOT excluded because `new void Hidden()` is a valid C# member-hiding declaration. // 戻り値型付きメソッド — 明示的インターフェース実装やネストメンバー向けに visibility 省略可。 // negative lookahead で呼び出し行(await/return/throw/yield/var/typeof 等)と ternary continuation を除外する。 + // `(?!(?:base|this)\b)` を name キャプチャに付け、上流ガードが緩んだ場合でも + // コンストラクタ初期化子 (`: base(...)` / `: this(...)`) が phantom `function base` / `function this` + // として漏れないよう二重化する。Closes #331. // 注意: `new` は除外しない。`new void Hidden()` は C# のメンバー隠蔽宣言として有効。 - new("function", new Regex($@"^\s*(?!\[\s*(?:assembly|module|type|return|param|field|property|event|method)\s*:)(?![?:])(?!(?:await|return|throw|yield|var|typeof|sizeof|nameof|default|if|for|foreach|while|switch|catch|lock|using|case|else|when|break|continue|goto)\b)(?!\s*(?:(?:{CSharpVisibilityPattern})\s+)?delegate\b(?!\s*\*))(?:(?{CSharpVisibilityPattern})\s+)?(?:(?:static|sealed|partial|readonly|unsafe|extern|virtual|override|abstract|async|new|file|ref(?:\s+readonly)?)\s+)*(?!{CSharpNonTypeKeywordPattern})(?{CSharpTypePattern})\s+(?\w+)\s*(?:<[^>]+>\s*)?\(", RegexOptions.Compiled), BodyStyle.Brace, "visibility", "returnType"), + new("function", new Regex($@"^\s*(?!\[\s*(?:assembly|module|type|return|param|field|property|event|method)\s*:)(?![?:])(?!(?:await|return|throw|yield|var|typeof|sizeof|nameof|default|if|for|foreach|while|switch|catch|lock|using|case|else|when|break|continue|goto)\b)(?!\s*(?:(?:{CSharpVisibilityPattern})\s+)?delegate\b(?!\s*\*))(?:(?{CSharpVisibilityPattern})\s+)?(?:(?:static|sealed|partial|readonly|unsafe|extern|virtual|override|abstract|async|new|file|ref(?:\s+readonly)?)\s+)*(?!{CSharpNonTypeKeywordPattern})(?{CSharpTypePattern})\s+(?!(?:base|this)\b)(?\w+)\s*(?:<[^>]+>\s*)?\(", RegexOptions.Compiled), BodyStyle.Brace, "visibility", "returnType"), // Constructor (no return type, name followed by parenthesis) — needs visibility // コンストラクタ(戻り値なし、名前の後に括弧)— visibility 必須 new("function", new Regex($@"^\s*(?{CSharpVisibilityPattern})\s+(?\w+)\s*\(", RegexOptions.Compiled), BodyStyle.Brace, "visibility"), diff --git a/tests/CodeIndex.Tests/SymbolExtractorTests.cs b/tests/CodeIndex.Tests/SymbolExtractorTests.cs index 57ccd8f8cf..5ea8d960ba 100644 --- a/tests/CodeIndex.Tests/SymbolExtractorTests.cs +++ b/tests/CodeIndex.Tests/SymbolExtractorTests.cs @@ -3618,6 +3618,56 @@ public class User Assert.DoesNotContain(symbols, s => s.Kind == "function" && s.Name == "ApiController"); } + [Fact] + public void Extract_CSharp_WrappedConstructorInitializer_DoesNotLeakBaseOrThisAsPhantoms() + { + // Wrapped `: base(...)` / `: this(...)` initializers must not surface as phantom + // `function base` / `function this` symbols. The C# returnType char class includes `:` + // to support alias-qualified type names like `Alias::Type`, so a wrapped initializer line + // like ` : base(s, 0)` could otherwise tokenize as returnType=`:` + name=`base` + paren. + // Both the first-char `(?![?:])` guard and the name-level `(?!(?:base|this)\b)` guard + // must cooperate to block it. Closes #331. + // ラップされた `: base(...)` / `: this(...)` 初期化子行が `function base` / `function this` + // の phantom として漏れないことを担保する。Closes #331. + var content = """ + namespace CtorChain; + + public class Base + { + public Base() { } + public Base(int x) { } + public Base(string s, int n) { } + } + + public class Derived : Base + { + public Derived(int x) : base(x) { } + + public Derived(string s) + : base(s, 0) + { + } + + public Derived() : this(0) { } + + public Derived(int a, int b) + : this(a) + { + } + + public Derived(double d) : base((int)d, "d") => System.Console.WriteLine(d); + } + """; + var symbols = SymbolExtractor.Extract(1, "csharp", content); + + Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "Base"); + Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "Derived"); + Assert.DoesNotContain(symbols, s => s.Kind == "function" && s.Name == "base"); + Assert.DoesNotContain(symbols, s => s.Kind == "function" && s.Name == "this"); + // All five Derived constructors should still be captured / 5 つのコンストラクタは正しく取得できること + Assert.Equal(5, symbols.Count(s => s.Kind == "function" && s.Name == "Derived")); + } + [Fact] public void Extract_CSharp_DetectsRecordVariants() {