From 754caed1bb9587126c9206021850d52b0ef377c7 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Thu, 23 Apr 2026 22:44:55 +0900 Subject: [PATCH 1/4] Fix #496: filter phantom C# local const and call-site symbols --- CHANGELOG.md | 1 + DEVELOPER_GUIDE.md | 4 +- src/CodeIndex/Indexer/SymbolExtractor.cs | 41 ++++++++++++++++++- tests/CodeIndex.Tests/SymbolExtractorTests.cs | 36 ++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4700453de..1544019a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### [Unreleased] #### Fixed +- **C# phantom local `const` and qualified call-site function symbols are now filtered from symbol extraction (#496)** — `SymbolExtractor` now applies the existing column-aware C# type-body gate to field-like `function` rows (`const` / `static readonly`) so local declarations such as `const string content = "hello";` no longer leak into `symbols`, `definition`, or `outline`. It also rejects C# declaration candidates whose captured return-type fragment ends in an operator/contextual suffix, preventing qualified call arguments like `elapsed < TimeSpan.FromSeconds(10)` from surfacing phantom `function FromSeconds` rows. Added a focused regression that locks both repros. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #496. - **C# parenthesized LINQ clause guard no longer mistakes local `const` identifiers for cast targets (#624)** — `ReferenceExtractor` now treats typed local `const` declarations as in-scope value names for the cast-close disambiguation path, so parenthesized `orderby` expressions such as `(READY)` and `(Select)` no longer suppress the real trailing `select(...)` clause solely because the identifier casing looks type-like. Real casts like `(CustomType)select(items)` still keep the clause blocked. Added focused extractor and CLI `references --exact-name --json` regressions for the uppercase-constant repro, and the existing keyword-named-constant regression now passes against the built binary again. Affected: `src/CodeIndex/Indexer/ReferenceExtractor.cs`, `tests/CodeIndex.Tests/ReferenceExtractorTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `CHANGELOG.md`. Closes #624. - **Installer jq-version detection test now uses a real PATH stub instead of a bash function shim (#774)** — `InstallScriptTests.ResolveVersion_UsesJqWhenAvailable` now creates an executable `jq` stub under a temporary directory and prepends that directory to `PATH`, so CI reliably exercises the `command -v jq` branch without depending on bash function lookup details or the runner's preinstalled tools. This keeps the installer's jq-preferring path covered while avoiding spurious stderr on GitHub Actions. Affected: `tests/CodeIndex.Tests/InstallScriptTests.cs`, `CHANGELOG.md`. Closes #774. - **Java same-line annotations, enum-constant body overrides, and record compact constructors now extract correctly (#221, #751, #755)** — `SymbolExtractor` now strips leading same-line Java annotations with the existing lexer-aware scanner instead of a flat `[^)]*` regex prefix, so declarations such as `@Label(")") public int broken()`, `@SuppressWarnings({"unchecked"}) public int first() { ... }`, and annotated `record` headers survive string/comment/paren/brace edge cases. The extractor also emits record compact constructors in both same-line (`public Range { ... }`) and Allman-style (`public Range` followed by next-line `{`) layouts, preserves anonymous enum-member body ranges so overrides like `ADD { @Override public int apply(...) { ... } }` attach to the enum constant container, keeps same-line Java brace-bodied siblings separate (`int first() { ... } int second() { ... }`) instead of swallowing later methods into the first signature, and now restarts same-line Java scanning after enum constants so methods inside `A { @Override int f() { ... } int g() { ... } }` also reach the symbol table. Added focused Java regressions for the reported repros plus the enum-member body range contract. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #221. Closes #751. Closes #755. diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 830fc54bcc..72b0f40767 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -378,7 +378,7 @@ Supported symbol kinds by language (33 languages with symbol extraction): | Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes | | JavaScript | function, arrow, methods (including same-line keyword/modifier-named methods, default arguments, computed names, `#private`, generator, `async *`) | class, export default class, same-line sibling/statement-prefixed public classes, class expressions, multiline/parenthesized/CommonJS class exports | -- | -- | -- | -- | -- | import...from | yes | | TypeScript | function, arrow, methods (including generic and same-line object/conditional/function-return methods, default arguments, computed names, `#private`, generator, `async *`) | class, export default class, anonymous default `abstract class`, `export = class`, same-line sibling/statement-prefixed public classes, class expressions, multiline/parenthesized class expressions, type | -- | interface | enum, const enum | -- | -- | import...from | yes | -| C# | methods, ctors, explicit-interface impls (including qualifiers with multi-argument generics like `IMap.GetCount`, nullable type arguments like `IFoo.NullableArg`, array type arguments like `IFoo.ArrayArg`, and generic-over-tuple return types such as `Task<(int, string)>`, `Dictionary`, `IEnumerable<(string Key, int Value)>`, and `List<(int, int)> IFoo.GetList()`), and indexers (including `ref` / `ref readonly` returns, pointer / function-pointer returns such as `int*` / `void**` / `delegate*` / `int*[]`, and tuple returns with trailing `[]` / `?` / `[,]` / `[][]` suffixes such as `(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]`; guards named-argument labels only before qualified call expressions; allows `global::` / alias-qualified return types and spaced generic type tokens; attribute-stripper blanks out multi-section attributes such as `[Obsolete, Conditional("DEBUG")]` and `[Fact, Trait("cat","io")]` so trailing attribute names are not leaked as phantom `function` symbols; LINQ query-expression contextual keywords such as `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` are excluded from the return-type position so continuation lines like `where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` do not emit phantom `function` symbols for the qualified member name; modifier order is free, so visibility may appear at any position in the modifier sequence on every C# row — type declarations (`abstract public class`, `sealed public class`, `readonly public struct`, `ref public struct`, `partial public interface`, `abstract public record class`), `const` fields (`new public const int X = 1;`, `public new const int X = 1;`), `static readonly` fields (`readonly public static int E = 6;`), methods (`static public int F() => 0;`), properties (`static public int P { get; set; }`), indexers (`static public int this[int i] => 0;`), events, delegates, operator / conversion-operator overloads (including C# 11 `static abstract` / `abstract static` interface operator members such as `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);` — both modifier orders are accepted on both binary/unary operator rows and conversion operator rows so generic-math interfaces such as `System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` are captured), constructors (`unsafe public S(int* p) {}`, `extern public S(int x);`, with a positional negative lookahead that rejects lines whose matching `)` is followed by an identifier + `{` / `(` / `=>` (with optional `?` / `[]` / `[,]` / whitespaced tuple suffixes such as `) []` / `) ?` / `) ?` in between, factored into a shared `CSharpTupleSuffixPattern` constant consumed by both `CSharpTypePattern` and the ctor lookahead so the two stay in lock-step) — the exact shape of a property or expression-bodied method with a modifier keyword, so contextual / reserved keywords like `partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` cannot be captured as the constructor name when the method / property regex upstream fails — e.g. `public required (int, int) R1 { get; init; }` and `public required (int, int) [] R4 { get; init; }` previously emitted a phantom `function required` row; multi-line ctor signatures, `extern` ctors ending in `;`, expression-bodied ctors, `: base(...)` / `: this(...)` initializers, and tuple-parameter ctors are all unaffected), and static constructors (`unsafe static S()`) — `unsafe` / `extern` are also accepted as free-order modifiers on the property / indexer / event / constructor / static-constructor rows, `static` / `readonly` may be interleaved with `new` in any order (e.g. `readonly new static`, `new readonly static`), C# inheritance modifiers `virtual` / `override` / `abstract` / `sealed` / `new` are accepted as free-order modifiers on event declarations (`abstract public event E;`, `sealed public override event E;`), and `partial` is accepted as a free-order modifier on event and indexer declarations so C# 13 partial indexer members (`public partial int this[int i] { get; }`, expression-body and block-body declaration / implementation pairs across partial class fragments) and C# 14 field-like / accessor-based partial events (`public partial event System.Action X;`, `public partial event System.Action OnLog { add { } remove { } }`) are captured instead of silently dropped, and the `file` file-scoped type modifier plus nested `new` are accepted on `interface` and `delegate` rows (`file interface I {}`, `file delegate int D(int x);`)), operators stored as `operator +` / `operator checked +` / `operator checked -` (including unary, binary, and other C# 11 user-defined checked operators), conversion operators stored as `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` (including `unsafe` / `extern` forms and function-pointer target types), indexers normalized to `Item`, const, static readonly, enum members, #region, finalizers | class, record (wrapped headers preserve base list and `where` clauses in `symbols.signature`, including C# 12 primary constructor parameter lists) | struct, record struct, ref struct (wrapped headers preserve base list and `where` clauses in signature) | interface (wrapped headers preserve base list and `where` clauses in signature) | enum (wrapped headers preserve `: underlyingType` in signature) | property, partial property, expression-bodied, `ref` / `ref readonly` properties, pointer properties, tuple-array / nullable-tuple properties, explicit-interface property implementations (both brace-body `int IThing.Value { get; set; }` and expression-body `string IThing.Name => "x";`, including generic interface qualifiers like `IBucket.Items` and multi-argument generic qualifiers like `IMap.PairCount`), record primary components, plain fields (instance, readonly, volatile, plain static, with or without initializer, multi-line declarations that wrap before `;` — including parenthesized / constructor-call initializers such as `= new(\n () => 42);` and object / collection initializers such as `= new() { ... };` / `= new Dictionary<...> { ... };` — declarator lists such as `int _x, _y;` — one symbol per declarator — and function-pointer fields such as `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;`) | event, explicit-interface event implementations (`event EventHandler IFoo.Evt { add { } remove { } }`, including same-line / next-line accessor blocks and generic qualifiers such as `IMap.Evt3`), delegate (including spaced generic type tokens and pointer returns) | using, using alias, extern alias | yes | +| C# | methods, ctors, explicit-interface impls (including qualifiers with multi-argument generics like `IMap.GetCount`, nullable type arguments like `IFoo.NullableArg`, array type arguments like `IFoo.ArrayArg`, and generic-over-tuple return types such as `Task<(int, string)>`, `Dictionary`, `IEnumerable<(string Key, int Value)>`, and `List<(int, int)> IFoo.GetList()`), and indexers (including `ref` / `ref readonly` returns, pointer / function-pointer returns such as `int*` / `void**` / `delegate*` / `int*[]`, and tuple returns with trailing `[]` / `?` / `[,]` / `[][]` suffixes such as `(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]`; guards named-argument labels only before qualified call expressions; allows `global::` / alias-qualified return types and spaced generic type tokens; attribute-stripper blanks out multi-section attributes such as `[Obsolete, Conditional("DEBUG")]` and `[Fact, Trait("cat","io")]` so trailing attribute names are not leaked as phantom `function` symbols; LINQ query-expression contextual keywords such as `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` are excluded from the return-type position so continuation lines like `where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` do not emit phantom `function` symbols for the qualified member name; field-like `function` rows for `const` / `static readonly` are column-gated to real type bodies so method-local declarations such as `const string content = "hello";` do not leak as symbols, and qualified-member rows also reject operator/contextual return-type tails such as `elapsed <` before call-site expressions like `TimeSpan.FromSeconds(...)`; modifier order is free, so visibility may appear at any position in the modifier sequence on every C# row — type declarations (`abstract public class`, `sealed public class`, `readonly public struct`, `ref public struct`, `partial public interface`, `abstract public record class`), `const` fields (`new public const int X = 1;`, `public new const int X = 1;`), `static readonly` fields (`readonly public static int E = 6;`), methods (`static public int F() => 0;`), properties (`static public int P { get; set; }`), indexers (`static public int this[int i] => 0;`), events, delegates, operator / conversion-operator overloads (including C# 11 `static abstract` / `abstract static` interface operator members such as `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);` — both modifier orders are accepted on both binary/unary operator rows and conversion operator rows so generic-math interfaces such as `System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` are captured), constructors (`unsafe public S(int* p) {}`, `extern public S(int x);`, with a positional negative lookahead that rejects lines whose matching `)` is followed by an identifier + `{` / `(` / `=>` (with optional `?` / `[]` / `[,]` / whitespaced tuple suffixes such as `) []` / `) ?` / `) ?` in between, factored into a shared `CSharpTupleSuffixPattern` constant consumed by both `CSharpTypePattern` and the ctor lookahead so the two stay in lock-step) — the exact shape of a property or expression-bodied method with a modifier keyword, so contextual / reserved keywords like `partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` cannot be captured as the constructor name when the method / property regex upstream fails — e.g. `public required (int, int) R1 { get; init; }` and `public required (int, int) [] R4 { get; init; }` previously emitted a phantom `function required` row; multi-line ctor signatures, `extern` ctors ending in `;`, expression-bodied ctors, `: base(...)` / `: this(...)` initializers, and tuple-parameter ctors are all unaffected), and static constructors (`unsafe static S()`) — `unsafe` / `extern` are also accepted as free-order modifiers on the property / indexer / event / constructor / static-constructor rows, `static` / `readonly` may be interleaved with `new` in any order (e.g. `readonly new static`, `new readonly static`), C# inheritance modifiers `virtual` / `override` / `abstract` / `sealed` / `new` are accepted as free-order modifiers on event declarations (`abstract public event E;`, `sealed public override event E;`), and `partial` is accepted as a free-order modifier on event and indexer declarations so C# 13 partial indexer members (`public partial int this[int i] { get; }`, expression-body and block-body declaration / implementation pairs across partial class fragments) and C# 14 field-like / accessor-based partial events (`public partial event System.Action X;`, `public partial event System.Action OnLog { add { } remove { } }`) are captured instead of silently dropped, and the `file` file-scoped type modifier plus nested `new` are accepted on `interface` and `delegate` rows (`file interface I {}`, `file delegate int D(int x);`)), operators stored as `operator +` / `operator checked +` / `operator checked -` (including unary, binary, and other C# 11 user-defined checked operators), conversion operators stored as `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` (including `unsafe` / `extern` forms and function-pointer target types), indexers normalized to `Item`, const, static readonly, enum members, #region, finalizers | class, record (wrapped headers preserve base list and `where` clauses in `symbols.signature`, including C# 12 primary constructor parameter lists) | struct, record struct, ref struct (wrapped headers preserve base list and `where` clauses in signature) | interface (wrapped headers preserve base list and `where` clauses in signature) | enum (wrapped headers preserve `: underlyingType` in signature) | property, partial property, expression-bodied, `ref` / `ref readonly` properties, pointer properties, tuple-array / nullable-tuple properties, explicit-interface property implementations (both brace-body `int IThing.Value { get; set; }` and expression-body `string IThing.Name => "x";`, including generic interface qualifiers like `IBucket.Items` and multi-argument generic qualifiers like `IMap.PairCount`), record primary components, plain fields (instance, readonly, volatile, plain static, with or without initializer, multi-line declarations that wrap before `;` — including parenthesized / constructor-call initializers such as `= new(\n () => 42);` and object / collection initializers such as `= new() { ... };` / `= new Dictionary<...> { ... };` — declarator lists such as `int _x, _y;` — one symbol per declarator — and function-pointer fields such as `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;`) | event, explicit-interface event implementations (`event EventHandler IFoo.Evt { add { } remove { } }`, including same-line / next-line accessor blocks and generic qualifiers such as `IMap.Evt3`), delegate (including spaced generic type tokens and pointer returns) | using, using alias, extern alias | yes | | Go | func, methods | type alias | struct | interface | -- | -- | -- | import | yes | | Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes | | Java | methods (including same-line leading annotations, lexer-aware annotation arguments such as `@Label(")")` and `@SuppressWarnings({"unchecked"})`, compact constructors in both same-line and Allman-style brace layouts, same-line brace-bodied siblings, and same-line enum-constant-body methods), static final, enum members (body-scoped scanner that tracks strings/chars/comments/text blocks and stops at the first top-level `;`, so method calls like `\tRED();` outside the enum body are not captured; enum constants with anonymous bodies retain body ranges so nested overrides and same-line body-local methods attach to the enum-member container) | class, record, sealed, @interface | -- | interface | enum | record primary components | -- | import | yes | @@ -1454,7 +1454,7 @@ LIMIT 20; | Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes | | JavaScript | function, アロー, メソッド(同一行の keyword / modifier 名、default 引数、computed、`#private`、generator、`async *` を含む) | class, export default class, 同一行 sibling / statement-prefixed public class, クラス式, 複数行 / parenthesized / CommonJS クラス export | -- | -- | -- | -- | -- | import...from | yes | | TypeScript | function, アロー, メソッド(generic / 同一行 object-return / conditional / function-return、default 引数、computed、`#private`、generator、`async *` を含む) | class, export default class, 匿名 default `abstract class`, `export = class`, 同一行 sibling / statement-prefixed public class, 複数行 / parenthesized クラス式, type | -- | interface | enum, const enum | -- | -- | import...from | yes | -| C# | メソッド, コンストラクタ, explicit-interface 実装(`IMap.GetCount` のような多引数 generic 修飾子、`IFoo.NullableArg` のような nullable 型引数修飾子、`IFoo.ArrayArg` のような配列型引数修飾子、さらに `Task<(int, string)>`、`Dictionary`、`IEnumerable<(string Key, int Value)>`、`List<(int, int)> IFoo.GetList()` のような generic-over-tuple 戻り値型にも対応), インデクサ(`ref` / `ref readonly` 戻り値、`int*` / `void**` / `delegate*` / `int*[]` のようなポインタ / 関数ポインタ戻り値、`(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]` のような末尾サフィックス付き tuple 戻り値にも対応。qualified call expression の直前にある named-argument label だけを除外し、`global::` / alias-qualified な戻り値型と、スペースを含む generic 型トークンを許可。属性ストリッパは `[Obsolete, Conditional("DEBUG")]` や `[Fact, Trait("cat","io")]` のような複数セクション属性もブランク化して、2 つ目以降の属性名が phantom `function` シンボルとして漏れないようにする。さらに `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` のような LINQ 式 contextual keyword は戻り値型位置から除外し、`where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` のような continuation 行が qualified member 名を phantom `function` シンボルとして出さないようにする。加えて修飾子順序は自由で、visibility を修飾子列の任意位置に置いてよい。型宣言(`abstract public class`、`sealed public class`、`readonly public struct`、`ref public struct`、`partial public interface`、`abstract public record class`)、`const` フィールド(`new public const int X = 1;`、`public new const int X = 1;`)、`static readonly` フィールド(`readonly public static int E = 6;`)、メソッド(`static public int F() => 0;`)、プロパティ(`static public int P { get; set; }`)、インデクサ(`static public int this[int i] => 0;`)、イベント、デリゲート、演算子 / 変換演算子オーバーロード(C# 11 の `static abstract` / `abstract static` interface 演算子メンバも含む。例: `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);`。両方の修飾子順を二項 / 単項演算子行と変換演算子行の双方で受けるため、`System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` のような generic-math interface も捕捉される)、コンストラクタ(`unsafe public S(int* p) {}`、`extern public S(int x);`。コンストラクタ regex の開き括弧直後に位置検査の否定先読みを入れ、「対応する `)` のあとに識別子 + `{` / `(` / `=>`(間に `?` / `[]` / `[,]` / 空白混じりの tuple サフィックスを許容。例: `) []` / `) ?` / `) ?`。サフィックスのバリエーションは共有定数 `CSharpTupleSuffixPattern` に切り出し、`CSharpTypePattern` と ctor lookahead の双方から参照することで両者の整合を保つ)が続く行」——modifier 付き property や式本体メソッドそのものの形——を ctor 候補から弾く。これにより、上流の method / property regex が失敗した行——たとえば `public required (int, int) R1 { get; init; }` や `public required (int, int) [] R4 { get; init; }` が以前 phantom `function required` を出していたケース——でも、`partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` のような modifier キーワードが ctor 名として拾われない。複数行 ctor シグネチャ、`;` で終わる `extern` ctor、式本体 ctor、`: base(...)` / `: this(...)` 初期化子、tuple パラメータ ctor はすべて影響を受けず引き続き抽出される)、静的コンストラクタ(`unsafe static S()`)のすべてが対象で、`unsafe` / `extern` もプロパティ / インデクサ / イベント / コンストラクタ / 静的コンストラクタ行の自由順序な修飾子として受け付ける。`static` / `readonly` も `new` と任意順に並べられる(例: `readonly new static`、`new readonly static`)。C# の継承修飾子 `virtual` / `override` / `abstract` / `sealed` / `new` も event 宣言行の自由順序修飾子として受け付け(`abstract public event E;`、`sealed public override event E;`)、`partial` は event / indexer 宣言行の自由順序修飾子として受け付けるので、C# 13 の partial indexer(`public partial int this[int i] { get; }`、partial class 断片をまたぐ宣言側と実装側、式本体 / ブロック本体)と C# 14 の field-like / accessor-based partial event(`public partial event System.Action X;`、`public partial event System.Action OnLog { add { } remove { } }`)が silent drop されず捕捉される。file スコープ型修飾子 `file` とネスト隠蔽の `new` は `interface` / `delegate` 行でも受け付ける(`file interface I {}`、`file delegate int D(int x);`)), `operator +` / `operator checked +` / `operator checked -` 形式で保持する演算子(C# 11 のユーザー定義 `operator checked` を単項・二項とも含む), `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` 形式で保持する変換演算子(`unsafe` / `extern` 付きと function-pointer target type を含む), `Item` に正規化するインデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record(折り返されたヘッダでは base list と `where` 句を `symbols.signature` に保持、C# 12 primary constructor パラメータリストも含む) | struct, record struct, ref struct(折り返されたヘッダでは base list と `where` 句を signature に保持) | interface(折り返されたヘッダでは base list と `where` 句を signature に保持) | enum(折り返されたヘッダでは `: underlyingType` を signature に保持) | property, partial property, 式本体, `ref` / `ref readonly` property, ポインタ property, tuple-array / nullable-tuple property, 明示的インターフェースプロパティ実装(ブレース本体の `int IThing.Value { get; set; }` と式本体の `string IThing.Name => "x";` の両形、`IBucket.Items` のような generic interface 修飾子、`IMap.PairCount` のような多引数 generic 修飾子も含む), record primary component, 通常フィールド(instance / readonly / volatile / 通常 static、初期化子の有無を問わず、`;` 前で折り返す multi-line 宣言 — `= new(\n () => 42);` のような括弧付き / コンストラクタ呼び出し初期化子や `= new() { ... };` / `= new Dictionary<...> { ... };` のようなオブジェクト / コレクション初期化子を含む — `int _x, _y;` のような declarator list — declarator ごとに 1 シンボル — および `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;` のような function-pointer フィールド) | event, 明示的インターフェース event 実装(`event EventHandler IFoo.Evt { add { } remove { } }` 形。同一行 / 次行 accessor block と `IMap.Evt3` のような generic qualifier を含む), delegate(generic 型引数内スペースを含む形と、ポインタ戻り値の delegate 宣言も対応) | using, using alias, extern alias | yes | +| C# | メソッド, コンストラクタ, explicit-interface 実装(`IMap.GetCount` のような多引数 generic 修飾子、`IFoo.NullableArg` のような nullable 型引数修飾子、`IFoo.ArrayArg` のような配列型引数修飾子、さらに `Task<(int, string)>`、`Dictionary`、`IEnumerable<(string Key, int Value)>`、`List<(int, int)> IFoo.GetList()` のような generic-over-tuple 戻り値型にも対応), インデクサ(`ref` / `ref readonly` 戻り値、`int*` / `void**` / `delegate*` / `int*[]` のようなポインタ / 関数ポインタ戻り値、`(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]` のような末尾サフィックス付き tuple 戻り値にも対応。qualified call expression の直前にある named-argument label だけを除外し、`global::` / alias-qualified な戻り値型と、スペースを含む generic 型トークンを許可。属性ストリッパは `[Obsolete, Conditional("DEBUG")]` や `[Fact, Trait("cat","io")]` のような複数セクション属性もブランク化して、2 つ目以降の属性名が phantom `function` シンボルとして漏れないようにする。さらに `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` のような LINQ 式 contextual keyword は戻り値型位置から除外し、`where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` のような continuation 行が qualified member 名を phantom `function` シンボルとして出さないようにする。加えて `const` / `static readonly` の field-like `function` 行は実際の type body にだけ列単位で許可するため、`const string content = "hello";` のようなメソッド内宣言はシンボルへ漏れない。qualified member 行も `elapsed <` のような演算子 / contextual suffix で終わる戻り値断片を拒否し、`TimeSpan.FromSeconds(...)` のような call-site 引数から phantom `function` が出ないようにする。さらに修飾子順序は自由で、visibility を修飾子列の任意位置に置いてよい。型宣言(`abstract public class`、`sealed public class`、`readonly public struct`、`ref public struct`、`partial public interface`、`abstract public record class`)、`const` フィールド(`new public const int X = 1;`、`public new const int X = 1;`)、`static readonly` フィールド(`readonly public static int E = 6;`)、メソッド(`static public int F() => 0;`)、プロパティ(`static public int P { get; set; }`)、インデクサ(`static public int this[int i] => 0;`)、イベント、デリゲート、演算子 / 変換演算子オーバーロード(C# 11 の `static abstract` / `abstract static` interface 演算子メンバも含む。例: `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);`。両方の修飾子順を二項 / 単項演算子行と変換演算子行の双方で受けるため、`System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` のような generic-math interface も捕捉される)、コンストラクタ(`unsafe public S(int* p) {}`、`extern public S(int x);`。コンストラクタ regex の開き括弧直後に位置検査の否定先読みを入れ、「対応する `)` のあとに識別子 + `{` / `(` / `=>`(間に `?` / `[]` / `[,]` / 空白混じりの tuple サフィックスを許容。例: `) []` / `) ?` / `) ?`。サフィックスのバリエーションは共有定数 `CSharpTupleSuffixPattern` に切り出し、`CSharpTypePattern` と ctor lookahead の双方から参照することで両者の整合を保つ)が続く行」——modifier 付き property や式本体メソッドそのものの形——を ctor 候補から弾く。これにより、上流の method / property regex が失敗した行——たとえば `public required (int, int) R1 { get; init; }` や `public required (int, int) [] R4 { get; init; }` が以前 phantom `function required` を出していたケース——でも、`partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` のような modifier キーワードが ctor 名として拾われない。複数行 ctor シグネチャ、`;` で終わる `extern` ctor、式本体 ctor、`: base(...)` / `: this(...)` 初期化子、tuple パラメータ ctor はすべて影響を受けず引き続き抽出される)、静的コンストラクタ(`unsafe static S()`)のすべてが対象で、`unsafe` / `extern` もプロパティ / インデクサ / イベント / コンストラクタ / 静的コンストラクタ行の自由順序な修飾子として受け付ける。`static` / `readonly` も `new` と任意順に並べられる(例: `readonly new static`、`new readonly static`)。C# の継承修飾子 `virtual` / `override` / `abstract` / `sealed` / `new` も event 宣言行の自由順序修飾子として受け付け(`abstract public event E;`、`sealed public override event E;`)、`partial` は event / indexer 宣言行の自由順序修飾子として受け付けるので、C# 13 の partial indexer(`public partial int this[int i] { get; }`、partial class 断片をまたぐ宣言側と実装側、式本体 / ブロック本体)と C# 14 の field-like / accessor-based partial event(`public partial event System.Action X;`、`public partial event System.Action OnLog { add { } remove { } }`)が silent drop されず捕捉される。file スコープ型修飾子 `file` とネスト隠蔽の `new` は `interface` / `delegate` 行でも受け付ける(`file interface I {}`、`file delegate int D(int x);`)), `operator +` / `operator checked +` / `operator checked -` 形式で保持する演算子(C# 11 のユーザー定義 `operator checked` を単項・二項とも含む), `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` 形式で保持する変換演算子(`unsafe` / `extern` 付きと function-pointer target type を含む), `Item` に正規化するインデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record(折り返されたヘッダでは base list と `where` 句を `symbols.signature` に保持、C# 12 primary constructor パラメータリストも含む) | struct, record struct, ref struct(折り返されたヘッダでは base list と `where` 句を signature に保持) | interface(折り返されたヘッダでは base list と `where` 句を signature に保持) | enum(折り返されたヘッダでは `: underlyingType` を signature に保持) | property, partial property, 式本体, `ref` / `ref readonly` property, ポインタ property, tuple-array / nullable-tuple property, 明示的インターフェースプロパティ実装(ブレース本体の `int IThing.Value { get; set; }` と式本体の `string IThing.Name => "x";` の両形、`IBucket.Items` のような generic interface 修飾子、`IMap.PairCount` のような多引数 generic 修飾子も含む), record primary component, 通常フィールド(instance / readonly / volatile / 通常 static、初期化子の有無を問わず、`;` 前で折り返す multi-line 宣言 — `= new(\n () => 42);` のような括弧付き / コンストラクタ呼び出し初期化子や `= new() { ... };` / `= new Dictionary<...> { ... };` のようなオブジェクト / コレクション初期化子を含む — `int _x, _y;` のような declarator list — declarator ごとに 1 シンボル — および `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;` のような function-pointer フィールド) | event, 明示的インターフェース event 実装(`event EventHandler IFoo.Evt { add { } remove { } }` 形。同一行 / 次行 accessor block と `IMap.Evt3` のような generic qualifier を含む), delegate(generic 型引数内スペースを含む形と、ポインタ戻り値の delegate 宣言も対応) | using, using alias, extern alias | yes | | Go | func, メソッド | 型エイリアス | struct | interface | -- | -- | -- | import | yes | | Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes | | Java | メソッド(同一行の先頭アノテーション付き、`@Label(")")` や `@SuppressWarnings({"unchecked"})` のような annotation 引数、同一行と Allman スタイル両方の brace 配置を拾う compact constructor、同一行 brace-body sibling、same-line enum 定数 body 内メソッドを含む), static final, enum メンバー(文字列・char・コメント・text block を追跡する body-scoped scanner で抽出し、最初の top-level `;` で停止するため、enum 本体外の `\tRED();` のようなメソッド呼び出しを誤検出しない。匿名 body を持つ enum 定数は body range も保持し、入れ子の override や same-line body-local method が enum 定数コンテナにぶら下がる) | class, record, sealed, @interface | -- | interface | enum | record primary component | -- | import | yes | diff --git a/src/CodeIndex/Indexer/SymbolExtractor.cs b/src/CodeIndex/Indexer/SymbolExtractor.cs index 396c22f094..dd422de9bd 100644 --- a/src/CodeIndex/Indexer/SymbolExtractor.cs +++ b/src/CodeIndex/Indexer/SymbolExtractor.cs @@ -1485,8 +1485,8 @@ public static List Extract(long fileId, string? lang, string conte } if (lang == "csharp" - && pattern.Kind == "property" && pattern.BodyStyle == BodyStyle.None + && (pattern.Kind == "property" || IsCSharpFieldLikeFunctionPattern(pattern)) && csharpInsideTypeBody != null && !csharpInsideTypeBody.IsInsideTypeBodyAt(i, csharpGateRawStartColumn)) { @@ -1503,6 +1503,14 @@ public static List Extract(long fileId, string? lang, string conte lineOffset = FindNextSameLineBraceStatementStart(matchLine, absoluteStartColumn + Math.Max(1, match.Length), lang); continue; } + var rawReturnType = TryGetGroup(match, pattern.ReturnTypeGroup); + if (lang == "csharp" + && pattern.ReturnTypeGroup != null + && HasInvalidCSharpReturnTypeSuffix(rawReturnType)) + { + lineOffset = FindNextSameLineBraceStatementStart(matchLine, absoluteStartColumn + Math.Max(1, match.Length), lang); + continue; + } if (lang == "csharp" && pattern.Kind == "property" && IsStandaloneCSharpAccessorCandidate(patternMatchLine)) @@ -1924,7 +1932,7 @@ public static List Extract(long fileId, string? lang, string conte BodyEndLine = bodyEndLine, Signature = signature, Visibility = TryGetGroup(match, pattern.VisibilityGroup), - ReturnType = NormalizeMetadata(TryGetGroup(match, pattern.ReturnTypeGroup)), + ReturnType = NormalizeMetadata(rawReturnType), }, line); } @@ -9782,6 +9790,35 @@ private static bool CanContinueScanningSameLineBraceBody( || (lang == "csharp" && CanContinueScanningSameLineCSharpBraceBody(kind)); } + private static bool IsCSharpFieldLikeFunctionPattern(SymbolPattern pattern) + => pattern.Kind == "function" + && pattern.BodyStyle == BodyStyle.None + && pattern.ReturnTypeGroup != null; + + private static bool HasInvalidCSharpReturnTypeSuffix(string? returnType) + { + if (string.IsNullOrWhiteSpace(returnType)) + return true; + + var trimmed = returnType.TrimEnd(); + if (trimmed.Length == 0) + return true; + + var lastChar = trimmed[^1]; + if (lastChar is '<' or '=' or ':' or '+' or '-' or '/' or '%' or '!' or '&' or '|' or '^' or '~' or '.') + return true; + + var tokenStart = trimmed.Length - 1; + while (tokenStart > 0 + && (char.IsLetterOrDigit(trimmed[tokenStart - 1]) || trimmed[tokenStart - 1] == '_')) + { + tokenStart--; + } + + var lastToken = trimmed[tokenStart..]; + return lastToken is "as" or "is" or "await" or "return" or "throw" or "yield" or "new"; + } + private static int FindNextSameLineBraceStatementStart(string matchLine, int startIndex, string? lang) { return lang is "javascript" or "typescript" diff --git a/tests/CodeIndex.Tests/SymbolExtractorTests.cs b/tests/CodeIndex.Tests/SymbolExtractorTests.cs index 4667eb3f74..8fba951d57 100644 --- a/tests/CodeIndex.Tests/SymbolExtractorTests.cs +++ b/tests/CodeIndex.Tests/SymbolExtractorTests.cs @@ -8932,6 +8932,42 @@ public void Extract_CSharp_PlainFieldPatternDoesNotLeakLocalVariables() Assert.DoesNotContain(symbols, s => s.Name == "y"); } + [Fact] + public void Extract_CSharp_ConstLocalsAndQualifiedCallArguments_DoNotLeakPhantomFunctions() + { + var content = """ + using System; + + namespace Demo; + + public class Repro + { + public void M(TimeSpan elapsed) + { + const string content = "hello"; + Assert.True( + elapsed < TimeSpan.FromSeconds(10), + $"x {elapsed.TotalSeconds:F2}"); + } + } + + public static class Assert + { + public static void True(bool condition, string message) { } + } + """; + var symbols = SymbolExtractor.Extract(1, "csharp", content); + + Assert.Contains(symbols, s => s.Kind == "namespace" && s.Name == "Demo"); + Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "Repro"); + Assert.Contains(symbols, s => s.Kind == "function" && s.Name == "M"); + Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "Assert"); + Assert.Contains(symbols, s => s.Kind == "function" && s.Name == "True"); + + Assert.DoesNotContain(symbols, s => s.Kind == "function" && s.Name == "content"); + Assert.DoesNotContain(symbols, s => s.Kind == "function" && s.Name == "FromSeconds"); + } + [Fact] public void Extract_CSharp_DetectsMultiLineFieldDeclaration() { From 3d4d551149db9b325c734bd9d9bc94f46b7ce6f9 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Thu, 23 Apr 2026 22:53:29 +0900 Subject: [PATCH 2/4] Fix #496: sync Japanese changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1544019a05..40487068bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -820,6 +820,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### [Unreleased] #### 修正 +- **C# の phantom なローカル `const` と qualified call-site `function` シンボルが symbol extraction から除外されるよう修正 (#496)** — `SymbolExtractor` は既存の列単位 C# type-body gate を field-like な `function` 行(`const` / `static readonly`)にも適用するようになり、`const string content = "hello";` のようなローカル宣言が `symbols` / `definition` / `outline` へ漏れなくなった。さらに、捕捉した戻り値断片が演算子または contextual suffix で終わる C# 宣言候補も拒否するため、`elapsed < TimeSpan.FromSeconds(10)` のような qualified call 引数から phantom な `function FromSeconds` 行が出なくなる。両 repro を固定する focused regression test も追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #496。 - **C# の括弧付き LINQ clause guard が、ローカル `const` 識別子を cast target と誤認しないよう修正 (#624)** — `ReferenceExtractor` は cast-close の曖昧性解消で、型付きローカル `const` 宣言も in-scope の値名として扱うようになった。これにより `(READY)` や `(Select)` のような括弧付き `orderby` 式が、識別子の見た目だけで型名扱いされて本物の後続 `select(...)` clause を潰すことがなくなる。一方で `(CustomType)select(items)` のような実際の cast は従来どおり clause をブロックする。uppercase constant repro を固定する focused な extractor / CLI `references --exact-name --json` 回帰を追加し、既存の keyword-named constant 回帰も built binary に対して再び通るようになった。対象: `src/CodeIndex/Indexer/ReferenceExtractor.cs`, `tests/CodeIndex.Tests/ReferenceExtractorTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `CHANGELOG.md`。Closes #624。 - **installer の jq 版数取得テストが bash 関数 shim ではなく実 PATH スタブを使うよう修正 (#774)** — `InstallScriptTests.ResolveVersion_UsesJqWhenAvailable` は、一時ディレクトリ配下に実行可能な `jq` スタブを作成し、そのディレクトリを `PATH` の先頭へ差し込むようになった。これにより CI でも `command -v jq` 分岐を bash 関数解決やランナーのプリインストールツールに依存せず確実に通せる。installer 本体の jq 優先経路のカバレッジを維持しつつ、GitHub Actions 上の偽陽性 stderr を防ぐ。対象: `tests/CodeIndex.Tests/InstallScriptTests.cs`, `CHANGELOG.md`。Closes #774。 - **Java の同一行アノテーション、enum 定数 body override、record compact constructor が正しく抽出されるよう修正 (#221, #751, #755)** — `SymbolExtractor` は Java 宣言の先頭アノテーション処理を平坦な `[^)]*` regex prefix ではなく既存の lexer-aware scanner に寄せ、`@Label(")") public int broken()`、`@SuppressWarnings({"unchecked"}) public int first() { ... }`、注釈付き `record` ヘッダのような文字列/コメント/括弧/波括弧入りケースでも宣言を落とさなくなった。`public Range { ... }` のような record compact constructor も `function` として出し、匿名 enum 定数 body の範囲を保持することで `ADD { @Override public int apply(...) { ... } }` のような override が enum 定数コンテナへぶら下がる。さらに `int first() { ... } int second() { ... }` のような同一行 Java brace-body sibling も最初の signature に飲み込まれず個別に抽出され、`A { @Override int f() { ... } int g() { ... } }` のような same-line enum 定数 body 内メソッドも symbol table まで到達するようになった。issue の repro 群と enum 定数 body range 契約を固定する focused Java 回帰テストも追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #221。Closes #751。Closes #755。 From 26c8d263bf032226b37828cdd0d959039856b65b Mon Sep 17 00:00:00 2001 From: Widthdom Date: Thu, 23 Apr 2026 23:08:26 +0900 Subject: [PATCH 3/4] Fix #782: preserve verbatim C# return types after #496 --- CHANGELOG.md | 2 ++ DEVELOPER_GUIDE.md | 4 ++-- src/CodeIndex/Indexer/SymbolExtractor.cs | 7 +++++++ tests/CodeIndex.Tests/SymbolExtractorTests.cs | 21 +++++++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40487068bd..a39443558e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), #### Fixed - **C# phantom local `const` and qualified call-site function symbols are now filtered from symbol extraction (#496)** — `SymbolExtractor` now applies the existing column-aware C# type-body gate to field-like `function` rows (`const` / `static readonly`) so local declarations such as `const string content = "hello";` no longer leak into `symbols`, `definition`, or `outline`. It also rejects C# declaration candidates whose captured return-type fragment ends in an operator/contextual suffix, preventing qualified call arguments like `elapsed < TimeSpan.FromSeconds(10)` from surfacing phantom `function FromSeconds` rows. Added a focused regression that locks both repros. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #496. +- **C# verbatim-identifier return types no longer get dropped by the #496 suffix guard (#782)** — `HasInvalidCSharpReturnTypeSuffix` now recognizes real C# verbatim identifiers before rejecting contextual-keyword tails, so declarations such as `public @new Make() => new @new();` remain visible in `symbols` and `outline` while the `elapsed < TimeSpan.FromSeconds(...)` phantom-call-site filter stays intact. Added a focused regression for the verbatim return-type repro. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #782. - **C# parenthesized LINQ clause guard no longer mistakes local `const` identifiers for cast targets (#624)** — `ReferenceExtractor` now treats typed local `const` declarations as in-scope value names for the cast-close disambiguation path, so parenthesized `orderby` expressions such as `(READY)` and `(Select)` no longer suppress the real trailing `select(...)` clause solely because the identifier casing looks type-like. Real casts like `(CustomType)select(items)` still keep the clause blocked. Added focused extractor and CLI `references --exact-name --json` regressions for the uppercase-constant repro, and the existing keyword-named-constant regression now passes against the built binary again. Affected: `src/CodeIndex/Indexer/ReferenceExtractor.cs`, `tests/CodeIndex.Tests/ReferenceExtractorTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `CHANGELOG.md`. Closes #624. - **Installer jq-version detection test now uses a real PATH stub instead of a bash function shim (#774)** — `InstallScriptTests.ResolveVersion_UsesJqWhenAvailable` now creates an executable `jq` stub under a temporary directory and prepends that directory to `PATH`, so CI reliably exercises the `command -v jq` branch without depending on bash function lookup details or the runner's preinstalled tools. This keeps the installer's jq-preferring path covered while avoiding spurious stderr on GitHub Actions. Affected: `tests/CodeIndex.Tests/InstallScriptTests.cs`, `CHANGELOG.md`. Closes #774. - **Java same-line annotations, enum-constant body overrides, and record compact constructors now extract correctly (#221, #751, #755)** — `SymbolExtractor` now strips leading same-line Java annotations with the existing lexer-aware scanner instead of a flat `[^)]*` regex prefix, so declarations such as `@Label(")") public int broken()`, `@SuppressWarnings({"unchecked"}) public int first() { ... }`, and annotated `record` headers survive string/comment/paren/brace edge cases. The extractor also emits record compact constructors in both same-line (`public Range { ... }`) and Allman-style (`public Range` followed by next-line `{`) layouts, preserves anonymous enum-member body ranges so overrides like `ADD { @Override public int apply(...) { ... } }` attach to the enum constant container, keeps same-line Java brace-bodied siblings separate (`int first() { ... } int second() { ... }`) instead of swallowing later methods into the first signature, and now restarts same-line Java scanning after enum constants so methods inside `A { @Override int f() { ... } int g() { ... } }` also reach the symbol table. Added focused Java regressions for the reported repros plus the enum-member body range contract. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #221. Closes #751. Closes #755. @@ -821,6 +822,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), #### 修正 - **C# の phantom なローカル `const` と qualified call-site `function` シンボルが symbol extraction から除外されるよう修正 (#496)** — `SymbolExtractor` は既存の列単位 C# type-body gate を field-like な `function` 行(`const` / `static readonly`)にも適用するようになり、`const string content = "hello";` のようなローカル宣言が `symbols` / `definition` / `outline` へ漏れなくなった。さらに、捕捉した戻り値断片が演算子または contextual suffix で終わる C# 宣言候補も拒否するため、`elapsed < TimeSpan.FromSeconds(10)` のような qualified call 引数から phantom な `function FromSeconds` 行が出なくなる。両 repro を固定する focused regression test も追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #496。 +- **C# の verbatim identifier 戻り値型が #496 の suffix guard で落ちなくなるよう修正 (#782)** — `HasInvalidCSharpReturnTypeSuffix` は contextual keyword の末尾を拒否する前に本物の C# verbatim identifier を認識するようになり、`public @new Make() => new @new();` のような宣言は `symbols` / `outline` に残りつつ、`elapsed < TimeSpan.FromSeconds(...)` の phantom call-site 抑止は維持される。verbatim 戻り値型 repro を固定する focused regression test も追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #782。 - **C# の括弧付き LINQ clause guard が、ローカル `const` 識別子を cast target と誤認しないよう修正 (#624)** — `ReferenceExtractor` は cast-close の曖昧性解消で、型付きローカル `const` 宣言も in-scope の値名として扱うようになった。これにより `(READY)` や `(Select)` のような括弧付き `orderby` 式が、識別子の見た目だけで型名扱いされて本物の後続 `select(...)` clause を潰すことがなくなる。一方で `(CustomType)select(items)` のような実際の cast は従来どおり clause をブロックする。uppercase constant repro を固定する focused な extractor / CLI `references --exact-name --json` 回帰を追加し、既存の keyword-named constant 回帰も built binary に対して再び通るようになった。対象: `src/CodeIndex/Indexer/ReferenceExtractor.cs`, `tests/CodeIndex.Tests/ReferenceExtractorTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `CHANGELOG.md`。Closes #624。 - **installer の jq 版数取得テストが bash 関数 shim ではなく実 PATH スタブを使うよう修正 (#774)** — `InstallScriptTests.ResolveVersion_UsesJqWhenAvailable` は、一時ディレクトリ配下に実行可能な `jq` スタブを作成し、そのディレクトリを `PATH` の先頭へ差し込むようになった。これにより CI でも `command -v jq` 分岐を bash 関数解決やランナーのプリインストールツールに依存せず確実に通せる。installer 本体の jq 優先経路のカバレッジを維持しつつ、GitHub Actions 上の偽陽性 stderr を防ぐ。対象: `tests/CodeIndex.Tests/InstallScriptTests.cs`, `CHANGELOG.md`。Closes #774。 - **Java の同一行アノテーション、enum 定数 body override、record compact constructor が正しく抽出されるよう修正 (#221, #751, #755)** — `SymbolExtractor` は Java 宣言の先頭アノテーション処理を平坦な `[^)]*` regex prefix ではなく既存の lexer-aware scanner に寄せ、`@Label(")") public int broken()`、`@SuppressWarnings({"unchecked"}) public int first() { ... }`、注釈付き `record` ヘッダのような文字列/コメント/括弧/波括弧入りケースでも宣言を落とさなくなった。`public Range { ... }` のような record compact constructor も `function` として出し、匿名 enum 定数 body の範囲を保持することで `ADD { @Override public int apply(...) { ... } }` のような override が enum 定数コンテナへぶら下がる。さらに `int first() { ... } int second() { ... }` のような同一行 Java brace-body sibling も最初の signature に飲み込まれず個別に抽出され、`A { @Override int f() { ... } int g() { ... } }` のような same-line enum 定数 body 内メソッドも symbol table まで到達するようになった。issue の repro 群と enum 定数 body range 契約を固定する focused Java 回帰テストも追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #221。Closes #751。Closes #755。 diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 72b0f40767..3e27eeef58 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -378,7 +378,7 @@ Supported symbol kinds by language (33 languages with symbol extraction): | Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes | | JavaScript | function, arrow, methods (including same-line keyword/modifier-named methods, default arguments, computed names, `#private`, generator, `async *`) | class, export default class, same-line sibling/statement-prefixed public classes, class expressions, multiline/parenthesized/CommonJS class exports | -- | -- | -- | -- | -- | import...from | yes | | TypeScript | function, arrow, methods (including generic and same-line object/conditional/function-return methods, default arguments, computed names, `#private`, generator, `async *`) | class, export default class, anonymous default `abstract class`, `export = class`, same-line sibling/statement-prefixed public classes, class expressions, multiline/parenthesized class expressions, type | -- | interface | enum, const enum | -- | -- | import...from | yes | -| C# | methods, ctors, explicit-interface impls (including qualifiers with multi-argument generics like `IMap.GetCount`, nullable type arguments like `IFoo.NullableArg`, array type arguments like `IFoo.ArrayArg`, and generic-over-tuple return types such as `Task<(int, string)>`, `Dictionary`, `IEnumerable<(string Key, int Value)>`, and `List<(int, int)> IFoo.GetList()`), and indexers (including `ref` / `ref readonly` returns, pointer / function-pointer returns such as `int*` / `void**` / `delegate*` / `int*[]`, and tuple returns with trailing `[]` / `?` / `[,]` / `[][]` suffixes such as `(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]`; guards named-argument labels only before qualified call expressions; allows `global::` / alias-qualified return types and spaced generic type tokens; attribute-stripper blanks out multi-section attributes such as `[Obsolete, Conditional("DEBUG")]` and `[Fact, Trait("cat","io")]` so trailing attribute names are not leaked as phantom `function` symbols; LINQ query-expression contextual keywords such as `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` are excluded from the return-type position so continuation lines like `where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` do not emit phantom `function` symbols for the qualified member name; field-like `function` rows for `const` / `static readonly` are column-gated to real type bodies so method-local declarations such as `const string content = "hello";` do not leak as symbols, and qualified-member rows also reject operator/contextual return-type tails such as `elapsed <` before call-site expressions like `TimeSpan.FromSeconds(...)`; modifier order is free, so visibility may appear at any position in the modifier sequence on every C# row — type declarations (`abstract public class`, `sealed public class`, `readonly public struct`, `ref public struct`, `partial public interface`, `abstract public record class`), `const` fields (`new public const int X = 1;`, `public new const int X = 1;`), `static readonly` fields (`readonly public static int E = 6;`), methods (`static public int F() => 0;`), properties (`static public int P { get; set; }`), indexers (`static public int this[int i] => 0;`), events, delegates, operator / conversion-operator overloads (including C# 11 `static abstract` / `abstract static` interface operator members such as `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);` — both modifier orders are accepted on both binary/unary operator rows and conversion operator rows so generic-math interfaces such as `System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` are captured), constructors (`unsafe public S(int* p) {}`, `extern public S(int x);`, with a positional negative lookahead that rejects lines whose matching `)` is followed by an identifier + `{` / `(` / `=>` (with optional `?` / `[]` / `[,]` / whitespaced tuple suffixes such as `) []` / `) ?` / `) ?` in between, factored into a shared `CSharpTupleSuffixPattern` constant consumed by both `CSharpTypePattern` and the ctor lookahead so the two stay in lock-step) — the exact shape of a property or expression-bodied method with a modifier keyword, so contextual / reserved keywords like `partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` cannot be captured as the constructor name when the method / property regex upstream fails — e.g. `public required (int, int) R1 { get; init; }` and `public required (int, int) [] R4 { get; init; }` previously emitted a phantom `function required` row; multi-line ctor signatures, `extern` ctors ending in `;`, expression-bodied ctors, `: base(...)` / `: this(...)` initializers, and tuple-parameter ctors are all unaffected), and static constructors (`unsafe static S()`) — `unsafe` / `extern` are also accepted as free-order modifiers on the property / indexer / event / constructor / static-constructor rows, `static` / `readonly` may be interleaved with `new` in any order (e.g. `readonly new static`, `new readonly static`), C# inheritance modifiers `virtual` / `override` / `abstract` / `sealed` / `new` are accepted as free-order modifiers on event declarations (`abstract public event E;`, `sealed public override event E;`), and `partial` is accepted as a free-order modifier on event and indexer declarations so C# 13 partial indexer members (`public partial int this[int i] { get; }`, expression-body and block-body declaration / implementation pairs across partial class fragments) and C# 14 field-like / accessor-based partial events (`public partial event System.Action X;`, `public partial event System.Action OnLog { add { } remove { } }`) are captured instead of silently dropped, and the `file` file-scoped type modifier plus nested `new` are accepted on `interface` and `delegate` rows (`file interface I {}`, `file delegate int D(int x);`)), operators stored as `operator +` / `operator checked +` / `operator checked -` (including unary, binary, and other C# 11 user-defined checked operators), conversion operators stored as `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` (including `unsafe` / `extern` forms and function-pointer target types), indexers normalized to `Item`, const, static readonly, enum members, #region, finalizers | class, record (wrapped headers preserve base list and `where` clauses in `symbols.signature`, including C# 12 primary constructor parameter lists) | struct, record struct, ref struct (wrapped headers preserve base list and `where` clauses in signature) | interface (wrapped headers preserve base list and `where` clauses in signature) | enum (wrapped headers preserve `: underlyingType` in signature) | property, partial property, expression-bodied, `ref` / `ref readonly` properties, pointer properties, tuple-array / nullable-tuple properties, explicit-interface property implementations (both brace-body `int IThing.Value { get; set; }` and expression-body `string IThing.Name => "x";`, including generic interface qualifiers like `IBucket.Items` and multi-argument generic qualifiers like `IMap.PairCount`), record primary components, plain fields (instance, readonly, volatile, plain static, with or without initializer, multi-line declarations that wrap before `;` — including parenthesized / constructor-call initializers such as `= new(\n () => 42);` and object / collection initializers such as `= new() { ... };` / `= new Dictionary<...> { ... };` — declarator lists such as `int _x, _y;` — one symbol per declarator — and function-pointer fields such as `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;`) | event, explicit-interface event implementations (`event EventHandler IFoo.Evt { add { } remove { } }`, including same-line / next-line accessor blocks and generic qualifiers such as `IMap.Evt3`), delegate (including spaced generic type tokens and pointer returns) | using, using alias, extern alias | yes | +| C# | methods, ctors, explicit-interface impls (including qualifiers with multi-argument generics like `IMap.GetCount`, nullable type arguments like `IFoo.NullableArg`, array type arguments like `IFoo.ArrayArg`, and generic-over-tuple return types such as `Task<(int, string)>`, `Dictionary`, `IEnumerable<(string Key, int Value)>`, and `List<(int, int)> IFoo.GetList()`), and indexers (including `ref` / `ref readonly` returns, pointer / function-pointer returns such as `int*` / `void**` / `delegate*` / `int*[]`, and tuple returns with trailing `[]` / `?` / `[,]` / `[][]` suffixes such as `(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]`; guards named-argument labels only before qualified call expressions; allows `global::` / alias-qualified return types and spaced generic type tokens; attribute-stripper blanks out multi-section attributes such as `[Obsolete, Conditional("DEBUG")]` and `[Fact, Trait("cat","io")]` so trailing attribute names are not leaked as phantom `function` symbols; LINQ query-expression contextual keywords such as `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` are excluded from the return-type position so continuation lines like `where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` do not emit phantom `function` symbols for the qualified member name; field-like `function` rows for `const` / `static readonly` are column-gated to real type bodies so method-local declarations such as `const string content = "hello";` do not leak as symbols, and qualified-member rows also reject operator/contextual return-type tails such as `elapsed <` before call-site expressions like `TimeSpan.FromSeconds(...)` while preserving real verbatim-identifier return types such as `public @new Make()`; modifier order is free, so visibility may appear at any position in the modifier sequence on every C# row — type declarations (`abstract public class`, `sealed public class`, `readonly public struct`, `ref public struct`, `partial public interface`, `abstract public record class`), `const` fields (`new public const int X = 1;`, `public new const int X = 1;`), `static readonly` fields (`readonly public static int E = 6;`), methods (`static public int F() => 0;`), properties (`static public int P { get; set; }`), indexers (`static public int this[int i] => 0;`), events, delegates, operator / conversion-operator overloads (including C# 11 `static abstract` / `abstract static` interface operator members such as `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);` — both modifier orders are accepted on both binary/unary operator rows and conversion operator rows so generic-math interfaces such as `System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` are captured), constructors (`unsafe public S(int* p) {}`, `extern public S(int x);`, with a positional negative lookahead that rejects lines whose matching `)` is followed by an identifier + `{` / `(` / `=>` (with optional `?` / `[]` / `[,]` / whitespaced tuple suffixes such as `) []` / `) ?` / `) ?` in between, factored into a shared `CSharpTupleSuffixPattern` constant consumed by both `CSharpTypePattern` and the ctor lookahead so the two stay in lock-step) — the exact shape of a property or expression-bodied method with a modifier keyword, so contextual / reserved keywords like `partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` cannot be captured as the constructor name when the method / property regex upstream fails — e.g. `public required (int, int) R1 { get; init; }` and `public required (int, int) [] R4 { get; init; }` previously emitted a phantom `function required` row; multi-line ctor signatures, `extern` ctors ending in `;`, expression-bodied ctors, `: base(...)` / `: this(...)` initializers, and tuple-parameter ctors are all unaffected), and static constructors (`unsafe static S()`) — `unsafe` / `extern` are also accepted as free-order modifiers on the property / indexer / event / constructor / static-constructor rows, `static` / `readonly` may be interleaved with `new` in any order (e.g. `readonly new static`, `new readonly static`), C# inheritance modifiers `virtual` / `override` / `abstract` / `sealed` / `new` are accepted as free-order modifiers on event declarations (`abstract public event E;`, `sealed public override event E;`), and `partial` is accepted as a free-order modifier on event and indexer declarations so C# 13 partial indexer members (`public partial int this[int i] { get; }`, expression-body and block-body declaration / implementation pairs across partial class fragments) and C# 14 field-like / accessor-based partial events (`public partial event System.Action X;`, `public partial event System.Action OnLog { add { } remove { } }`) are captured instead of silently dropped, and the `file` file-scoped type modifier plus nested `new` are accepted on `interface` and `delegate` rows (`file interface I {}`, `file delegate int D(int x);`)), operators stored as `operator +` / `operator checked +` / `operator checked -` (including unary, binary, and other C# 11 user-defined checked operators), conversion operators stored as `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` (including `unsafe` / `extern` forms and function-pointer target types), indexers normalized to `Item`, const, static readonly, enum members, #region, finalizers | class, record (wrapped headers preserve base list and `where` clauses in `symbols.signature`, including C# 12 primary constructor parameter lists) | struct, record struct, ref struct (wrapped headers preserve base list and `where` clauses in signature) | interface (wrapped headers preserve base list and `where` clauses in signature) | enum (wrapped headers preserve `: underlyingType` in signature) | property, partial property, expression-bodied, `ref` / `ref readonly` properties, pointer properties, tuple-array / nullable-tuple properties, explicit-interface property implementations (both brace-body `int IThing.Value { get; set; }` and expression-body `string IThing.Name => "x";`, including generic interface qualifiers like `IBucket.Items` and multi-argument generic qualifiers like `IMap.PairCount`), record primary components, plain fields (instance, readonly, volatile, plain static, with or without initializer, multi-line declarations that wrap before `;` — including parenthesized / constructor-call initializers such as `= new(\n () => 42);` and object / collection initializers such as `= new() { ... };` / `= new Dictionary<...> { ... };` — declarator lists such as `int _x, _y;` — one symbol per declarator — and function-pointer fields such as `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;`) | event, explicit-interface event implementations (`event EventHandler IFoo.Evt { add { } remove { } }`, including same-line / next-line accessor blocks and generic qualifiers such as `IMap.Evt3`), delegate (including spaced generic type tokens and pointer returns) | using, using alias, extern alias | yes | | Go | func, methods | type alias | struct | interface | -- | -- | -- | import | yes | | Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes | | Java | methods (including same-line leading annotations, lexer-aware annotation arguments such as `@Label(")")` and `@SuppressWarnings({"unchecked"})`, compact constructors in both same-line and Allman-style brace layouts, same-line brace-bodied siblings, and same-line enum-constant-body methods), static final, enum members (body-scoped scanner that tracks strings/chars/comments/text blocks and stops at the first top-level `;`, so method calls like `\tRED();` outside the enum body are not captured; enum constants with anonymous bodies retain body ranges so nested overrides and same-line body-local methods attach to the enum-member container) | class, record, sealed, @interface | -- | interface | enum | record primary components | -- | import | yes | @@ -1454,7 +1454,7 @@ LIMIT 20; | Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes | | JavaScript | function, アロー, メソッド(同一行の keyword / modifier 名、default 引数、computed、`#private`、generator、`async *` を含む) | class, export default class, 同一行 sibling / statement-prefixed public class, クラス式, 複数行 / parenthesized / CommonJS クラス export | -- | -- | -- | -- | -- | import...from | yes | | TypeScript | function, アロー, メソッド(generic / 同一行 object-return / conditional / function-return、default 引数、computed、`#private`、generator、`async *` を含む) | class, export default class, 匿名 default `abstract class`, `export = class`, 同一行 sibling / statement-prefixed public class, 複数行 / parenthesized クラス式, type | -- | interface | enum, const enum | -- | -- | import...from | yes | -| C# | メソッド, コンストラクタ, explicit-interface 実装(`IMap.GetCount` のような多引数 generic 修飾子、`IFoo.NullableArg` のような nullable 型引数修飾子、`IFoo.ArrayArg` のような配列型引数修飾子、さらに `Task<(int, string)>`、`Dictionary`、`IEnumerable<(string Key, int Value)>`、`List<(int, int)> IFoo.GetList()` のような generic-over-tuple 戻り値型にも対応), インデクサ(`ref` / `ref readonly` 戻り値、`int*` / `void**` / `delegate*` / `int*[]` のようなポインタ / 関数ポインタ戻り値、`(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]` のような末尾サフィックス付き tuple 戻り値にも対応。qualified call expression の直前にある named-argument label だけを除外し、`global::` / alias-qualified な戻り値型と、スペースを含む generic 型トークンを許可。属性ストリッパは `[Obsolete, Conditional("DEBUG")]` や `[Fact, Trait("cat","io")]` のような複数セクション属性もブランク化して、2 つ目以降の属性名が phantom `function` シンボルとして漏れないようにする。さらに `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` のような LINQ 式 contextual keyword は戻り値型位置から除外し、`where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` のような continuation 行が qualified member 名を phantom `function` シンボルとして出さないようにする。加えて `const` / `static readonly` の field-like `function` 行は実際の type body にだけ列単位で許可するため、`const string content = "hello";` のようなメソッド内宣言はシンボルへ漏れない。qualified member 行も `elapsed <` のような演算子 / contextual suffix で終わる戻り値断片を拒否し、`TimeSpan.FromSeconds(...)` のような call-site 引数から phantom `function` が出ないようにする。さらに修飾子順序は自由で、visibility を修飾子列の任意位置に置いてよい。型宣言(`abstract public class`、`sealed public class`、`readonly public struct`、`ref public struct`、`partial public interface`、`abstract public record class`)、`const` フィールド(`new public const int X = 1;`、`public new const int X = 1;`)、`static readonly` フィールド(`readonly public static int E = 6;`)、メソッド(`static public int F() => 0;`)、プロパティ(`static public int P { get; set; }`)、インデクサ(`static public int this[int i] => 0;`)、イベント、デリゲート、演算子 / 変換演算子オーバーロード(C# 11 の `static abstract` / `abstract static` interface 演算子メンバも含む。例: `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);`。両方の修飾子順を二項 / 単項演算子行と変換演算子行の双方で受けるため、`System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` のような generic-math interface も捕捉される)、コンストラクタ(`unsafe public S(int* p) {}`、`extern public S(int x);`。コンストラクタ regex の開き括弧直後に位置検査の否定先読みを入れ、「対応する `)` のあとに識別子 + `{` / `(` / `=>`(間に `?` / `[]` / `[,]` / 空白混じりの tuple サフィックスを許容。例: `) []` / `) ?` / `) ?`。サフィックスのバリエーションは共有定数 `CSharpTupleSuffixPattern` に切り出し、`CSharpTypePattern` と ctor lookahead の双方から参照することで両者の整合を保つ)が続く行」——modifier 付き property や式本体メソッドそのものの形——を ctor 候補から弾く。これにより、上流の method / property regex が失敗した行——たとえば `public required (int, int) R1 { get; init; }` や `public required (int, int) [] R4 { get; init; }` が以前 phantom `function required` を出していたケース——でも、`partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` のような modifier キーワードが ctor 名として拾われない。複数行 ctor シグネチャ、`;` で終わる `extern` ctor、式本体 ctor、`: base(...)` / `: this(...)` 初期化子、tuple パラメータ ctor はすべて影響を受けず引き続き抽出される)、静的コンストラクタ(`unsafe static S()`)のすべてが対象で、`unsafe` / `extern` もプロパティ / インデクサ / イベント / コンストラクタ / 静的コンストラクタ行の自由順序な修飾子として受け付ける。`static` / `readonly` も `new` と任意順に並べられる(例: `readonly new static`、`new readonly static`)。C# の継承修飾子 `virtual` / `override` / `abstract` / `sealed` / `new` も event 宣言行の自由順序修飾子として受け付け(`abstract public event E;`、`sealed public override event E;`)、`partial` は event / indexer 宣言行の自由順序修飾子として受け付けるので、C# 13 の partial indexer(`public partial int this[int i] { get; }`、partial class 断片をまたぐ宣言側と実装側、式本体 / ブロック本体)と C# 14 の field-like / accessor-based partial event(`public partial event System.Action X;`、`public partial event System.Action OnLog { add { } remove { } }`)が silent drop されず捕捉される。file スコープ型修飾子 `file` とネスト隠蔽の `new` は `interface` / `delegate` 行でも受け付ける(`file interface I {}`、`file delegate int D(int x);`)), `operator +` / `operator checked +` / `operator checked -` 形式で保持する演算子(C# 11 のユーザー定義 `operator checked` を単項・二項とも含む), `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` 形式で保持する変換演算子(`unsafe` / `extern` 付きと function-pointer target type を含む), `Item` に正規化するインデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record(折り返されたヘッダでは base list と `where` 句を `symbols.signature` に保持、C# 12 primary constructor パラメータリストも含む) | struct, record struct, ref struct(折り返されたヘッダでは base list と `where` 句を signature に保持) | interface(折り返されたヘッダでは base list と `where` 句を signature に保持) | enum(折り返されたヘッダでは `: underlyingType` を signature に保持) | property, partial property, 式本体, `ref` / `ref readonly` property, ポインタ property, tuple-array / nullable-tuple property, 明示的インターフェースプロパティ実装(ブレース本体の `int IThing.Value { get; set; }` と式本体の `string IThing.Name => "x";` の両形、`IBucket.Items` のような generic interface 修飾子、`IMap.PairCount` のような多引数 generic 修飾子も含む), record primary component, 通常フィールド(instance / readonly / volatile / 通常 static、初期化子の有無を問わず、`;` 前で折り返す multi-line 宣言 — `= new(\n () => 42);` のような括弧付き / コンストラクタ呼び出し初期化子や `= new() { ... };` / `= new Dictionary<...> { ... };` のようなオブジェクト / コレクション初期化子を含む — `int _x, _y;` のような declarator list — declarator ごとに 1 シンボル — および `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;` のような function-pointer フィールド) | event, 明示的インターフェース event 実装(`event EventHandler IFoo.Evt { add { } remove { } }` 形。同一行 / 次行 accessor block と `IMap.Evt3` のような generic qualifier を含む), delegate(generic 型引数内スペースを含む形と、ポインタ戻り値の delegate 宣言も対応) | using, using alias, extern alias | yes | +| C# | メソッド, コンストラクタ, explicit-interface 実装(`IMap.GetCount` のような多引数 generic 修飾子、`IFoo.NullableArg` のような nullable 型引数修飾子、`IFoo.ArrayArg` のような配列型引数修飾子、さらに `Task<(int, string)>`、`Dictionary`、`IEnumerable<(string Key, int Value)>`、`List<(int, int)> IFoo.GetList()` のような generic-over-tuple 戻り値型にも対応), インデクサ(`ref` / `ref readonly` 戻り値、`int*` / `void**` / `delegate*` / `int*[]` のようなポインタ / 関数ポインタ戻り値、`(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]` のような末尾サフィックス付き tuple 戻り値にも対応。qualified call expression の直前にある named-argument label だけを除外し、`global::` / alias-qualified な戻り値型と、スペースを含む generic 型トークンを許可。属性ストリッパは `[Obsolete, Conditional("DEBUG")]` や `[Fact, Trait("cat","io")]` のような複数セクション属性もブランク化して、2 つ目以降の属性名が phantom `function` シンボルとして漏れないようにする。さらに `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` のような LINQ 式 contextual keyword は戻り値型位置から除外し、`where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` のような continuation 行が qualified member 名を phantom `function` シンボルとして出さないようにする。加えて `const` / `static readonly` の field-like `function` 行は実際の type body にだけ列単位で許可するため、`const string content = "hello";` のようなメソッド内宣言はシンボルへ漏れない。qualified member 行も `elapsed <` のような演算子 / contextual suffix で終わる戻り値断片を拒否しつつ、`public @new Make()` のような本物の verbatim identifier 戻り値型は保持するため、`TimeSpan.FromSeconds(...)` のような call-site 引数から phantom `function` が出ないまま合法宣言は抽出される。さらに修飾子順序は自由で、visibility を修飾子列の任意位置に置いてよい。型宣言(`abstract public class`、`sealed public class`、`readonly public struct`、`ref public struct`、`partial public interface`、`abstract public record class`)、`const` フィールド(`new public const int X = 1;`、`public new const int X = 1;`)、`static readonly` フィールド(`readonly public static int E = 6;`)、メソッド(`static public int F() => 0;`)、プロパティ(`static public int P { get; set; }`)、インデクサ(`static public int this[int i] => 0;`)、イベント、デリゲート、演算子 / 変換演算子オーバーロード(C# 11 の `static abstract` / `abstract static` interface 演算子メンバも含む。例: `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);`。両方の修飾子順を二項 / 単項演算子行と変換演算子行の双方で受けるため、`System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` のような generic-math interface も捕捉される)、コンストラクタ(`unsafe public S(int* p) {}`、`extern public S(int x);`。コンストラクタ regex の開き括弧直後に位置検査の否定先読みを入れ、「対応する `)` のあとに識別子 + `{` / `(` / `=>`(間に `?` / `[]` / `[,]` / 空白混じりの tuple サフィックスを許容。例: `) []` / `) ?` / `) ?`。サフィックスのバリエーションは共有定数 `CSharpTupleSuffixPattern` に切り出し、`CSharpTypePattern` と ctor lookahead の双方から参照することで両者の整合を保つ)が続く行」——modifier 付き property や式本体メソッドそのものの形——を ctor 候補から弾く。これにより、上流の method / property regex が失敗した行——たとえば `public required (int, int) R1 { get; init; }` や `public required (int, int) [] R4 { get; init; }` が以前 phantom `function required` を出していたケース——でも、`partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` のような modifier キーワードが ctor 名として拾われない。複数行 ctor シグネチャ、`;` で終わる `extern` ctor、式本体 ctor、`: base(...)` / `: this(...)` 初期化子、tuple パラメータ ctor はすべて影響を受けず引き続き抽出される)、静的コンストラクタ(`unsafe static S()`)のすべてが対象で、`unsafe` / `extern` もプロパティ / インデクサ / イベント / コンストラクタ / 静的コンストラクタ行の自由順序な修飾子として受け付ける。`static` / `readonly` も `new` と任意順に並べられる(例: `readonly new static`、`new readonly static`)。C# の継承修飾子 `virtual` / `override` / `abstract` / `sealed` / `new` も event 宣言行の自由順序修飾子として受け付け(`abstract public event E;`、`sealed public override event E;`)、`partial` は event / indexer 宣言行の自由順序修飾子として受け付けるので、C# 13 の partial indexer(`public partial int this[int i] { get; }`、partial class 断片をまたぐ宣言側と実装側、式本体 / ブロック本体)と C# 14 の field-like / accessor-based partial event(`public partial event System.Action X;`、`public partial event System.Action OnLog { add { } remove { } }`)が silent drop されず捕捉される。file スコープ型修飾子 `file` とネスト隠蔽の `new` は `interface` / `delegate` 行でも受け付ける(`file interface I {}`、`file delegate int D(int x);`)), `operator +` / `operator checked +` / `operator checked -` 形式で保持する演算子(C# 11 のユーザー定義 `operator checked` を単項・二項とも含む), `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` 形式で保持する変換演算子(`unsafe` / `extern` 付きと function-pointer target type を含む), `Item` に正規化するインデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record(折り返されたヘッダでは base list と `where` 句を `symbols.signature` に保持、C# 12 primary constructor パラメータリストも含む) | struct, record struct, ref struct(折り返されたヘッダでは base list と `where` 句を signature に保持) | interface(折り返されたヘッダでは base list と `where` 句を signature に保持) | enum(折り返されたヘッダでは `: underlyingType` を signature に保持) | property, partial property, 式本体, `ref` / `ref readonly` property, ポインタ property, tuple-array / nullable-tuple property, 明示的インターフェースプロパティ実装(ブレース本体の `int IThing.Value { get; set; }` と式本体の `string IThing.Name => "x";` の両形、`IBucket.Items` のような generic interface 修飾子、`IMap.PairCount` のような多引数 generic 修飾子も含む), record primary component, 通常フィールド(instance / readonly / volatile / 通常 static、初期化子の有無を問わず、`;` 前で折り返す multi-line 宣言 — `= new(\n () => 42);` のような括弧付き / コンストラクタ呼び出し初期化子や `= new() { ... };` / `= new Dictionary<...> { ... };` のようなオブジェクト / コレクション初期化子を含む — `int _x, _y;` のような declarator list — declarator ごとに 1 シンボル — および `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;` のような function-pointer フィールド) | event, 明示的インターフェース event 実装(`event EventHandler IFoo.Evt { add { } remove { } }` 形。同一行 / 次行 accessor block と `IMap.Evt3` のような generic qualifier を含む), delegate(generic 型引数内スペースを含む形と、ポインタ戻り値の delegate 宣言も対応) | using, using alias, extern alias | yes | | Go | func, メソッド | 型エイリアス | struct | interface | -- | -- | -- | import | yes | | Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes | | Java | メソッド(同一行の先頭アノテーション付き、`@Label(")")` や `@SuppressWarnings({"unchecked"})` のような annotation 引数、同一行と Allman スタイル両方の brace 配置を拾う compact constructor、同一行 brace-body sibling、same-line enum 定数 body 内メソッドを含む), static final, enum メンバー(文字列・char・コメント・text block を追跡する body-scoped scanner で抽出し、最初の top-level `;` で停止するため、enum 本体外の `\tRED();` のようなメソッド呼び出しを誤検出しない。匿名 body を持つ enum 定数は body range も保持し、入れ子の override や same-line body-local method が enum 定数コンテナにぶら下がる) | class, record, sealed, @interface | -- | interface | enum | record primary component | -- | import | yes | diff --git a/src/CodeIndex/Indexer/SymbolExtractor.cs b/src/CodeIndex/Indexer/SymbolExtractor.cs index dd422de9bd..546b31b033 100644 --- a/src/CodeIndex/Indexer/SymbolExtractor.cs +++ b/src/CodeIndex/Indexer/SymbolExtractor.cs @@ -9815,6 +9815,13 @@ private static bool HasInvalidCSharpReturnTypeSuffix(string? returnType) tokenStart--; } + if (tokenStart > 0 + && trimmed[tokenStart - 1] == '@' + && IsCSharpVerbatimIdentifierPrefix(trimmed, tokenStart - 1)) + { + return false; + } + var lastToken = trimmed[tokenStart..]; return lastToken is "as" or "is" or "await" or "return" or "throw" or "yield" or "new"; } diff --git a/tests/CodeIndex.Tests/SymbolExtractorTests.cs b/tests/CodeIndex.Tests/SymbolExtractorTests.cs index 8fba951d57..7b5ec27bef 100644 --- a/tests/CodeIndex.Tests/SymbolExtractorTests.cs +++ b/tests/CodeIndex.Tests/SymbolExtractorTests.cs @@ -8968,6 +8968,27 @@ public static void True(bool condition, string message) { } Assert.DoesNotContain(symbols, s => s.Kind == "function" && s.Name == "FromSeconds"); } + [Fact] + public void Extract_CSharp_VerbatimReturnTypeIdentifiers_AreNotRejectedBySuffixGuard() + { + var content = """ + namespace Demo; + + public class @new {} + + public class UsesVerbatim + { + public @new Make() => new @new(); + } + """; + var symbols = SymbolExtractor.Extract(1, "csharp", content); + + Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "new"); + + var method = Assert.Single(symbols.Where(s => s.Kind == "function" && s.Name == "Make")); + Assert.Equal("@new", method.ReturnType); + } + [Fact] public void Extract_CSharp_DetectsMultiLineFieldDeclaration() { From 99608cccc4bc5911ba475e4c1b0f4b5e35995ed5 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Thu, 23 Apr 2026 23:22:17 +0900 Subject: [PATCH 4/4] Fix #785: keep contextual-keyword C# return types --- CHANGELOG.md | 2 ++ DEVELOPER_GUIDE.md | 4 +-- src/CodeIndex/Indexer/SymbolExtractor.cs | 2 +- tests/CodeIndex.Tests/SymbolExtractorTests.cs | 27 +++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a39443558e..dd74341946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), #### Fixed - **C# phantom local `const` and qualified call-site function symbols are now filtered from symbol extraction (#496)** — `SymbolExtractor` now applies the existing column-aware C# type-body gate to field-like `function` rows (`const` / `static readonly`) so local declarations such as `const string content = "hello";` no longer leak into `symbols`, `definition`, or `outline`. It also rejects C# declaration candidates whose captured return-type fragment ends in an operator/contextual suffix, preventing qualified call arguments like `elapsed < TimeSpan.FromSeconds(10)` from surfacing phantom `function FromSeconds` rows. Added a focused regression that locks both repros. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #496. - **C# verbatim-identifier return types no longer get dropped by the #496 suffix guard (#782)** — `HasInvalidCSharpReturnTypeSuffix` now recognizes real C# verbatim identifiers before rejecting contextual-keyword tails, so declarations such as `public @new Make() => new @new();` remain visible in `symbols` and `outline` while the `elapsed < TimeSpan.FromSeconds(...)` phantom-call-site filter stays intact. Added a focused regression for the verbatim return-type repro. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #782. +- **C# contextual-keyword type names like `await` / `yield` no longer get dropped by the #496 suffix guard (#785)** — `HasInvalidCSharpReturnTypeSuffix` now keeps legal contextual-identifier type names while still rejecting truly invalid statement/operator tails, so members such as `public await MakeAwait()` and `public yield MakeYield()` remain visible in `symbols`, `definition`, and `outline`. Added a focused regression covering both contextual-keyword return types. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #785. - **C# parenthesized LINQ clause guard no longer mistakes local `const` identifiers for cast targets (#624)** — `ReferenceExtractor` now treats typed local `const` declarations as in-scope value names for the cast-close disambiguation path, so parenthesized `orderby` expressions such as `(READY)` and `(Select)` no longer suppress the real trailing `select(...)` clause solely because the identifier casing looks type-like. Real casts like `(CustomType)select(items)` still keep the clause blocked. Added focused extractor and CLI `references --exact-name --json` regressions for the uppercase-constant repro, and the existing keyword-named-constant regression now passes against the built binary again. Affected: `src/CodeIndex/Indexer/ReferenceExtractor.cs`, `tests/CodeIndex.Tests/ReferenceExtractorTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `CHANGELOG.md`. Closes #624. - **Installer jq-version detection test now uses a real PATH stub instead of a bash function shim (#774)** — `InstallScriptTests.ResolveVersion_UsesJqWhenAvailable` now creates an executable `jq` stub under a temporary directory and prepends that directory to `PATH`, so CI reliably exercises the `command -v jq` branch without depending on bash function lookup details or the runner's preinstalled tools. This keeps the installer's jq-preferring path covered while avoiding spurious stderr on GitHub Actions. Affected: `tests/CodeIndex.Tests/InstallScriptTests.cs`, `CHANGELOG.md`. Closes #774. - **Java same-line annotations, enum-constant body overrides, and record compact constructors now extract correctly (#221, #751, #755)** — `SymbolExtractor` now strips leading same-line Java annotations with the existing lexer-aware scanner instead of a flat `[^)]*` regex prefix, so declarations such as `@Label(")") public int broken()`, `@SuppressWarnings({"unchecked"}) public int first() { ... }`, and annotated `record` headers survive string/comment/paren/brace edge cases. The extractor also emits record compact constructors in both same-line (`public Range { ... }`) and Allman-style (`public Range` followed by next-line `{`) layouts, preserves anonymous enum-member body ranges so overrides like `ADD { @Override public int apply(...) { ... } }` attach to the enum constant container, keeps same-line Java brace-bodied siblings separate (`int first() { ... } int second() { ... }`) instead of swallowing later methods into the first signature, and now restarts same-line Java scanning after enum constants so methods inside `A { @Override int f() { ... } int g() { ... } }` also reach the symbol table. Added focused Java regressions for the reported repros plus the enum-member body range contract. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`. Closes #221. Closes #751. Closes #755. @@ -823,6 +824,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), #### 修正 - **C# の phantom なローカル `const` と qualified call-site `function` シンボルが symbol extraction から除外されるよう修正 (#496)** — `SymbolExtractor` は既存の列単位 C# type-body gate を field-like な `function` 行(`const` / `static readonly`)にも適用するようになり、`const string content = "hello";` のようなローカル宣言が `symbols` / `definition` / `outline` へ漏れなくなった。さらに、捕捉した戻り値断片が演算子または contextual suffix で終わる C# 宣言候補も拒否するため、`elapsed < TimeSpan.FromSeconds(10)` のような qualified call 引数から phantom な `function FromSeconds` 行が出なくなる。両 repro を固定する focused regression test も追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #496。 - **C# の verbatim identifier 戻り値型が #496 の suffix guard で落ちなくなるよう修正 (#782)** — `HasInvalidCSharpReturnTypeSuffix` は contextual keyword の末尾を拒否する前に本物の C# verbatim identifier を認識するようになり、`public @new Make() => new @new();` のような宣言は `symbols` / `outline` に残りつつ、`elapsed < TimeSpan.FromSeconds(...)` の phantom call-site 抑止は維持される。verbatim 戻り値型 repro を固定する focused regression test も追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #782。 +- **C# の `await` / `yield` のような contextual keyword 型名が #496 の suffix guard で落ちなくなるよう修正 (#785)** — `HasInvalidCSharpReturnTypeSuffix` は合法な contextual identifier 型名を保持しつつ、本当に無効な statement / operator 断片だけを拒否するようになったため、`public await MakeAwait()` や `public yield MakeYield()` のようなメンバーが `symbols` / `definition` / `outline` に再び現れる。両方の contextual-keyword 戻り値型を固定する focused regression test も追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #785。 - **C# の括弧付き LINQ clause guard が、ローカル `const` 識別子を cast target と誤認しないよう修正 (#624)** — `ReferenceExtractor` は cast-close の曖昧性解消で、型付きローカル `const` 宣言も in-scope の値名として扱うようになった。これにより `(READY)` や `(Select)` のような括弧付き `orderby` 式が、識別子の見た目だけで型名扱いされて本物の後続 `select(...)` clause を潰すことがなくなる。一方で `(CustomType)select(items)` のような実際の cast は従来どおり clause をブロックする。uppercase constant repro を固定する focused な extractor / CLI `references --exact-name --json` 回帰を追加し、既存の keyword-named constant 回帰も built binary に対して再び通るようになった。対象: `src/CodeIndex/Indexer/ReferenceExtractor.cs`, `tests/CodeIndex.Tests/ReferenceExtractorTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `CHANGELOG.md`。Closes #624。 - **installer の jq 版数取得テストが bash 関数 shim ではなく実 PATH スタブを使うよう修正 (#774)** — `InstallScriptTests.ResolveVersion_UsesJqWhenAvailable` は、一時ディレクトリ配下に実行可能な `jq` スタブを作成し、そのディレクトリを `PATH` の先頭へ差し込むようになった。これにより CI でも `command -v jq` 分岐を bash 関数解決やランナーのプリインストールツールに依存せず確実に通せる。installer 本体の jq 優先経路のカバレッジを維持しつつ、GitHub Actions 上の偽陽性 stderr を防ぐ。対象: `tests/CodeIndex.Tests/InstallScriptTests.cs`, `CHANGELOG.md`。Closes #774。 - **Java の同一行アノテーション、enum 定数 body override、record compact constructor が正しく抽出されるよう修正 (#221, #751, #755)** — `SymbolExtractor` は Java 宣言の先頭アノテーション処理を平坦な `[^)]*` regex prefix ではなく既存の lexer-aware scanner に寄せ、`@Label(")") public int broken()`、`@SuppressWarnings({"unchecked"}) public int first() { ... }`、注釈付き `record` ヘッダのような文字列/コメント/括弧/波括弧入りケースでも宣言を落とさなくなった。`public Range { ... }` のような record compact constructor も `function` として出し、匿名 enum 定数 body の範囲を保持することで `ADD { @Override public int apply(...) { ... } }` のような override が enum 定数コンテナへぶら下がる。さらに `int first() { ... } int second() { ... }` のような同一行 Java brace-body sibling も最初の signature に飲み込まれず個別に抽出され、`A { @Override int f() { ... } int g() { ... } }` のような same-line enum 定数 body 内メソッドも symbol table まで到達するようになった。issue の repro 群と enum 定数 body range 契約を固定する focused Java 回帰テストも追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `CHANGELOG.md`, `DEVELOPER_GUIDE.md`。Closes #221。Closes #751。Closes #755。 diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 3e27eeef58..8a3f7a38da 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -378,7 +378,7 @@ Supported symbol kinds by language (33 languages with symbol extraction): | Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes | | JavaScript | function, arrow, methods (including same-line keyword/modifier-named methods, default arguments, computed names, `#private`, generator, `async *`) | class, export default class, same-line sibling/statement-prefixed public classes, class expressions, multiline/parenthesized/CommonJS class exports | -- | -- | -- | -- | -- | import...from | yes | | TypeScript | function, arrow, methods (including generic and same-line object/conditional/function-return methods, default arguments, computed names, `#private`, generator, `async *`) | class, export default class, anonymous default `abstract class`, `export = class`, same-line sibling/statement-prefixed public classes, class expressions, multiline/parenthesized class expressions, type | -- | interface | enum, const enum | -- | -- | import...from | yes | -| C# | methods, ctors, explicit-interface impls (including qualifiers with multi-argument generics like `IMap.GetCount`, nullable type arguments like `IFoo.NullableArg`, array type arguments like `IFoo.ArrayArg`, and generic-over-tuple return types such as `Task<(int, string)>`, `Dictionary`, `IEnumerable<(string Key, int Value)>`, and `List<(int, int)> IFoo.GetList()`), and indexers (including `ref` / `ref readonly` returns, pointer / function-pointer returns such as `int*` / `void**` / `delegate*` / `int*[]`, and tuple returns with trailing `[]` / `?` / `[,]` / `[][]` suffixes such as `(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]`; guards named-argument labels only before qualified call expressions; allows `global::` / alias-qualified return types and spaced generic type tokens; attribute-stripper blanks out multi-section attributes such as `[Obsolete, Conditional("DEBUG")]` and `[Fact, Trait("cat","io")]` so trailing attribute names are not leaked as phantom `function` symbols; LINQ query-expression contextual keywords such as `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` are excluded from the return-type position so continuation lines like `where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` do not emit phantom `function` symbols for the qualified member name; field-like `function` rows for `const` / `static readonly` are column-gated to real type bodies so method-local declarations such as `const string content = "hello";` do not leak as symbols, and qualified-member rows also reject operator/contextual return-type tails such as `elapsed <` before call-site expressions like `TimeSpan.FromSeconds(...)` while preserving real verbatim-identifier return types such as `public @new Make()`; modifier order is free, so visibility may appear at any position in the modifier sequence on every C# row — type declarations (`abstract public class`, `sealed public class`, `readonly public struct`, `ref public struct`, `partial public interface`, `abstract public record class`), `const` fields (`new public const int X = 1;`, `public new const int X = 1;`), `static readonly` fields (`readonly public static int E = 6;`), methods (`static public int F() => 0;`), properties (`static public int P { get; set; }`), indexers (`static public int this[int i] => 0;`), events, delegates, operator / conversion-operator overloads (including C# 11 `static abstract` / `abstract static` interface operator members such as `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);` — both modifier orders are accepted on both binary/unary operator rows and conversion operator rows so generic-math interfaces such as `System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` are captured), constructors (`unsafe public S(int* p) {}`, `extern public S(int x);`, with a positional negative lookahead that rejects lines whose matching `)` is followed by an identifier + `{` / `(` / `=>` (with optional `?` / `[]` / `[,]` / whitespaced tuple suffixes such as `) []` / `) ?` / `) ?` in between, factored into a shared `CSharpTupleSuffixPattern` constant consumed by both `CSharpTypePattern` and the ctor lookahead so the two stay in lock-step) — the exact shape of a property or expression-bodied method with a modifier keyword, so contextual / reserved keywords like `partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` cannot be captured as the constructor name when the method / property regex upstream fails — e.g. `public required (int, int) R1 { get; init; }` and `public required (int, int) [] R4 { get; init; }` previously emitted a phantom `function required` row; multi-line ctor signatures, `extern` ctors ending in `;`, expression-bodied ctors, `: base(...)` / `: this(...)` initializers, and tuple-parameter ctors are all unaffected), and static constructors (`unsafe static S()`) — `unsafe` / `extern` are also accepted as free-order modifiers on the property / indexer / event / constructor / static-constructor rows, `static` / `readonly` may be interleaved with `new` in any order (e.g. `readonly new static`, `new readonly static`), C# inheritance modifiers `virtual` / `override` / `abstract` / `sealed` / `new` are accepted as free-order modifiers on event declarations (`abstract public event E;`, `sealed public override event E;`), and `partial` is accepted as a free-order modifier on event and indexer declarations so C# 13 partial indexer members (`public partial int this[int i] { get; }`, expression-body and block-body declaration / implementation pairs across partial class fragments) and C# 14 field-like / accessor-based partial events (`public partial event System.Action X;`, `public partial event System.Action OnLog { add { } remove { } }`) are captured instead of silently dropped, and the `file` file-scoped type modifier plus nested `new` are accepted on `interface` and `delegate` rows (`file interface I {}`, `file delegate int D(int x);`)), operators stored as `operator +` / `operator checked +` / `operator checked -` (including unary, binary, and other C# 11 user-defined checked operators), conversion operators stored as `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` (including `unsafe` / `extern` forms and function-pointer target types), indexers normalized to `Item`, const, static readonly, enum members, #region, finalizers | class, record (wrapped headers preserve base list and `where` clauses in `symbols.signature`, including C# 12 primary constructor parameter lists) | struct, record struct, ref struct (wrapped headers preserve base list and `where` clauses in signature) | interface (wrapped headers preserve base list and `where` clauses in signature) | enum (wrapped headers preserve `: underlyingType` in signature) | property, partial property, expression-bodied, `ref` / `ref readonly` properties, pointer properties, tuple-array / nullable-tuple properties, explicit-interface property implementations (both brace-body `int IThing.Value { get; set; }` and expression-body `string IThing.Name => "x";`, including generic interface qualifiers like `IBucket.Items` and multi-argument generic qualifiers like `IMap.PairCount`), record primary components, plain fields (instance, readonly, volatile, plain static, with or without initializer, multi-line declarations that wrap before `;` — including parenthesized / constructor-call initializers such as `= new(\n () => 42);` and object / collection initializers such as `= new() { ... };` / `= new Dictionary<...> { ... };` — declarator lists such as `int _x, _y;` — one symbol per declarator — and function-pointer fields such as `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;`) | event, explicit-interface event implementations (`event EventHandler IFoo.Evt { add { } remove { } }`, including same-line / next-line accessor blocks and generic qualifiers such as `IMap.Evt3`), delegate (including spaced generic type tokens and pointer returns) | using, using alias, extern alias | yes | +| C# | methods, ctors, explicit-interface impls (including qualifiers with multi-argument generics like `IMap.GetCount`, nullable type arguments like `IFoo.NullableArg`, array type arguments like `IFoo.ArrayArg`, and generic-over-tuple return types such as `Task<(int, string)>`, `Dictionary`, `IEnumerable<(string Key, int Value)>`, and `List<(int, int)> IFoo.GetList()`), and indexers (including `ref` / `ref readonly` returns, pointer / function-pointer returns such as `int*` / `void**` / `delegate*` / `int*[]`, and tuple returns with trailing `[]` / `?` / `[,]` / `[][]` suffixes such as `(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]`; guards named-argument labels only before qualified call expressions; allows `global::` / alias-qualified return types and spaced generic type tokens; attribute-stripper blanks out multi-section attributes such as `[Obsolete, Conditional("DEBUG")]` and `[Fact, Trait("cat","io")]` so trailing attribute names are not leaked as phantom `function` symbols; LINQ query-expression contextual keywords such as `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` are excluded from the return-type position so continuation lines like `where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` do not emit phantom `function` symbols for the qualified member name; field-like `function` rows for `const` / `static readonly` are column-gated to real type bodies so method-local declarations such as `const string content = "hello";` do not leak as symbols, and qualified-member rows also reject operator/contextual return-type tails such as `elapsed <` before call-site expressions like `TimeSpan.FromSeconds(...)` while preserving real contextual-identifier return types such as `public await MakeAwait()` / `public yield MakeYield()` and verbatim-identifier return types such as `public @new Make()`; modifier order is free, so visibility may appear at any position in the modifier sequence on every C# row — type declarations (`abstract public class`, `sealed public class`, `readonly public struct`, `ref public struct`, `partial public interface`, `abstract public record class`), `const` fields (`new public const int X = 1;`, `public new const int X = 1;`), `static readonly` fields (`readonly public static int E = 6;`), methods (`static public int F() => 0;`), properties (`static public int P { get; set; }`), indexers (`static public int this[int i] => 0;`), events, delegates, operator / conversion-operator overloads (including C# 11 `static abstract` / `abstract static` interface operator members such as `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);` — both modifier orders are accepted on both binary/unary operator rows and conversion operator rows so generic-math interfaces such as `System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` are captured), constructors (`unsafe public S(int* p) {}`, `extern public S(int x);`, with a positional negative lookahead that rejects lines whose matching `)` is followed by an identifier + `{` / `(` / `=>` (with optional `?` / `[]` / `[,]` / whitespaced tuple suffixes such as `) []` / `) ?` / `) ?` in between, factored into a shared `CSharpTupleSuffixPattern` constant consumed by both `CSharpTypePattern` and the ctor lookahead so the two stay in lock-step) — the exact shape of a property or expression-bodied method with a modifier keyword, so contextual / reserved keywords like `partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` cannot be captured as the constructor name when the method / property regex upstream fails — e.g. `public required (int, int) R1 { get; init; }` and `public required (int, int) [] R4 { get; init; }` previously emitted a phantom `function required` row; multi-line ctor signatures, `extern` ctors ending in `;`, expression-bodied ctors, `: base(...)` / `: this(...)` initializers, and tuple-parameter ctors are all unaffected), and static constructors (`unsafe static S()`) — `unsafe` / `extern` are also accepted as free-order modifiers on the property / indexer / event / constructor / static-constructor rows, `static` / `readonly` may be interleaved with `new` in any order (e.g. `readonly new static`, `new readonly static`), C# inheritance modifiers `virtual` / `override` / `abstract` / `sealed` / `new` are accepted as free-order modifiers on event declarations (`abstract public event E;`, `sealed public override event E;`), and `partial` is accepted as a free-order modifier on event and indexer declarations so C# 13 partial indexer members (`public partial int this[int i] { get; }`, expression-body and block-body declaration / implementation pairs across partial class fragments) and C# 14 field-like / accessor-based partial events (`public partial event System.Action X;`, `public partial event System.Action OnLog { add { } remove { } }`) are captured instead of silently dropped, and the `file` file-scoped type modifier plus nested `new` are accepted on `interface` and `delegate` rows (`file interface I {}`, `file delegate int D(int x);`)), operators stored as `operator +` / `operator checked +` / `operator checked -` (including unary, binary, and other C# 11 user-defined checked operators), conversion operators stored as `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` (including `unsafe` / `extern` forms and function-pointer target types), indexers normalized to `Item`, const, static readonly, enum members, #region, finalizers | class, record (wrapped headers preserve base list and `where` clauses in `symbols.signature`, including C# 12 primary constructor parameter lists) | struct, record struct, ref struct (wrapped headers preserve base list and `where` clauses in signature) | interface (wrapped headers preserve base list and `where` clauses in signature) | enum (wrapped headers preserve `: underlyingType` in signature) | property, partial property, expression-bodied, `ref` / `ref readonly` properties, pointer properties, tuple-array / nullable-tuple properties, explicit-interface property implementations (both brace-body `int IThing.Value { get; set; }` and expression-body `string IThing.Name => "x";`, including generic interface qualifiers like `IBucket.Items` and multi-argument generic qualifiers like `IMap.PairCount`), record primary components, plain fields (instance, readonly, volatile, plain static, with or without initializer, multi-line declarations that wrap before `;` — including parenthesized / constructor-call initializers such as `= new(\n () => 42);` and object / collection initializers such as `= new() { ... };` / `= new Dictionary<...> { ... };` — declarator lists such as `int _x, _y;` — one symbol per declarator — and function-pointer fields such as `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;`) | event, explicit-interface event implementations (`event EventHandler IFoo.Evt { add { } remove { } }`, including same-line / next-line accessor blocks and generic qualifiers such as `IMap.Evt3`), delegate (including spaced generic type tokens and pointer returns) | using, using alias, extern alias | yes | | Go | func, methods | type alias | struct | interface | -- | -- | -- | import | yes | | Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes | | Java | methods (including same-line leading annotations, lexer-aware annotation arguments such as `@Label(")")` and `@SuppressWarnings({"unchecked"})`, compact constructors in both same-line and Allman-style brace layouts, same-line brace-bodied siblings, and same-line enum-constant-body methods), static final, enum members (body-scoped scanner that tracks strings/chars/comments/text blocks and stops at the first top-level `;`, so method calls like `\tRED();` outside the enum body are not captured; enum constants with anonymous bodies retain body ranges so nested overrides and same-line body-local methods attach to the enum-member container) | class, record, sealed, @interface | -- | interface | enum | record primary components | -- | import | yes | @@ -1454,7 +1454,7 @@ LIMIT 20; | Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes | | JavaScript | function, アロー, メソッド(同一行の keyword / modifier 名、default 引数、computed、`#private`、generator、`async *` を含む) | class, export default class, 同一行 sibling / statement-prefixed public class, クラス式, 複数行 / parenthesized / CommonJS クラス export | -- | -- | -- | -- | -- | import...from | yes | | TypeScript | function, アロー, メソッド(generic / 同一行 object-return / conditional / function-return、default 引数、computed、`#private`、generator、`async *` を含む) | class, export default class, 匿名 default `abstract class`, `export = class`, 同一行 sibling / statement-prefixed public class, 複数行 / parenthesized クラス式, type | -- | interface | enum, const enum | -- | -- | import...from | yes | -| C# | メソッド, コンストラクタ, explicit-interface 実装(`IMap.GetCount` のような多引数 generic 修飾子、`IFoo.NullableArg` のような nullable 型引数修飾子、`IFoo.ArrayArg` のような配列型引数修飾子、さらに `Task<(int, string)>`、`Dictionary`、`IEnumerable<(string Key, int Value)>`、`List<(int, int)> IFoo.GetList()` のような generic-over-tuple 戻り値型にも対応), インデクサ(`ref` / `ref readonly` 戻り値、`int*` / `void**` / `delegate*` / `int*[]` のようなポインタ / 関数ポインタ戻り値、`(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]` のような末尾サフィックス付き tuple 戻り値にも対応。qualified call expression の直前にある named-argument label だけを除外し、`global::` / alias-qualified な戻り値型と、スペースを含む generic 型トークンを許可。属性ストリッパは `[Obsolete, Conditional("DEBUG")]` や `[Fact, Trait("cat","io")]` のような複数セクション属性もブランク化して、2 つ目以降の属性名が phantom `function` シンボルとして漏れないようにする。さらに `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` のような LINQ 式 contextual keyword は戻り値型位置から除外し、`where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` のような continuation 行が qualified member 名を phantom `function` シンボルとして出さないようにする。加えて `const` / `static readonly` の field-like `function` 行は実際の type body にだけ列単位で許可するため、`const string content = "hello";` のようなメソッド内宣言はシンボルへ漏れない。qualified member 行も `elapsed <` のような演算子 / contextual suffix で終わる戻り値断片を拒否しつつ、`public @new Make()` のような本物の verbatim identifier 戻り値型は保持するため、`TimeSpan.FromSeconds(...)` のような call-site 引数から phantom `function` が出ないまま合法宣言は抽出される。さらに修飾子順序は自由で、visibility を修飾子列の任意位置に置いてよい。型宣言(`abstract public class`、`sealed public class`、`readonly public struct`、`ref public struct`、`partial public interface`、`abstract public record class`)、`const` フィールド(`new public const int X = 1;`、`public new const int X = 1;`)、`static readonly` フィールド(`readonly public static int E = 6;`)、メソッド(`static public int F() => 0;`)、プロパティ(`static public int P { get; set; }`)、インデクサ(`static public int this[int i] => 0;`)、イベント、デリゲート、演算子 / 変換演算子オーバーロード(C# 11 の `static abstract` / `abstract static` interface 演算子メンバも含む。例: `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);`。両方の修飾子順を二項 / 単項演算子行と変換演算子行の双方で受けるため、`System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` のような generic-math interface も捕捉される)、コンストラクタ(`unsafe public S(int* p) {}`、`extern public S(int x);`。コンストラクタ regex の開き括弧直後に位置検査の否定先読みを入れ、「対応する `)` のあとに識別子 + `{` / `(` / `=>`(間に `?` / `[]` / `[,]` / 空白混じりの tuple サフィックスを許容。例: `) []` / `) ?` / `) ?`。サフィックスのバリエーションは共有定数 `CSharpTupleSuffixPattern` に切り出し、`CSharpTypePattern` と ctor lookahead の双方から参照することで両者の整合を保つ)が続く行」——modifier 付き property や式本体メソッドそのものの形——を ctor 候補から弾く。これにより、上流の method / property regex が失敗した行——たとえば `public required (int, int) R1 { get; init; }` や `public required (int, int) [] R4 { get; init; }` が以前 phantom `function required` を出していたケース——でも、`partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` のような modifier キーワードが ctor 名として拾われない。複数行 ctor シグネチャ、`;` で終わる `extern` ctor、式本体 ctor、`: base(...)` / `: this(...)` 初期化子、tuple パラメータ ctor はすべて影響を受けず引き続き抽出される)、静的コンストラクタ(`unsafe static S()`)のすべてが対象で、`unsafe` / `extern` もプロパティ / インデクサ / イベント / コンストラクタ / 静的コンストラクタ行の自由順序な修飾子として受け付ける。`static` / `readonly` も `new` と任意順に並べられる(例: `readonly new static`、`new readonly static`)。C# の継承修飾子 `virtual` / `override` / `abstract` / `sealed` / `new` も event 宣言行の自由順序修飾子として受け付け(`abstract public event E;`、`sealed public override event E;`)、`partial` は event / indexer 宣言行の自由順序修飾子として受け付けるので、C# 13 の partial indexer(`public partial int this[int i] { get; }`、partial class 断片をまたぐ宣言側と実装側、式本体 / ブロック本体)と C# 14 の field-like / accessor-based partial event(`public partial event System.Action X;`、`public partial event System.Action OnLog { add { } remove { } }`)が silent drop されず捕捉される。file スコープ型修飾子 `file` とネスト隠蔽の `new` は `interface` / `delegate` 行でも受け付ける(`file interface I {}`、`file delegate int D(int x);`)), `operator +` / `operator checked +` / `operator checked -` 形式で保持する演算子(C# 11 のユーザー定義 `operator checked` を単項・二項とも含む), `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` 形式で保持する変換演算子(`unsafe` / `extern` 付きと function-pointer target type を含む), `Item` に正規化するインデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record(折り返されたヘッダでは base list と `where` 句を `symbols.signature` に保持、C# 12 primary constructor パラメータリストも含む) | struct, record struct, ref struct(折り返されたヘッダでは base list と `where` 句を signature に保持) | interface(折り返されたヘッダでは base list と `where` 句を signature に保持) | enum(折り返されたヘッダでは `: underlyingType` を signature に保持) | property, partial property, 式本体, `ref` / `ref readonly` property, ポインタ property, tuple-array / nullable-tuple property, 明示的インターフェースプロパティ実装(ブレース本体の `int IThing.Value { get; set; }` と式本体の `string IThing.Name => "x";` の両形、`IBucket.Items` のような generic interface 修飾子、`IMap.PairCount` のような多引数 generic 修飾子も含む), record primary component, 通常フィールド(instance / readonly / volatile / 通常 static、初期化子の有無を問わず、`;` 前で折り返す multi-line 宣言 — `= new(\n () => 42);` のような括弧付き / コンストラクタ呼び出し初期化子や `= new() { ... };` / `= new Dictionary<...> { ... };` のようなオブジェクト / コレクション初期化子を含む — `int _x, _y;` のような declarator list — declarator ごとに 1 シンボル — および `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;` のような function-pointer フィールド) | event, 明示的インターフェース event 実装(`event EventHandler IFoo.Evt { add { } remove { } }` 形。同一行 / 次行 accessor block と `IMap.Evt3` のような generic qualifier を含む), delegate(generic 型引数内スペースを含む形と、ポインタ戻り値の delegate 宣言も対応) | using, using alias, extern alias | yes | +| C# | メソッド, コンストラクタ, explicit-interface 実装(`IMap.GetCount` のような多引数 generic 修飾子、`IFoo.NullableArg` のような nullable 型引数修飾子、`IFoo.ArrayArg` のような配列型引数修飾子、さらに `Task<(int, string)>`、`Dictionary`、`IEnumerable<(string Key, int Value)>`、`List<(int, int)> IFoo.GetList()` のような generic-over-tuple 戻り値型にも対応), インデクサ(`ref` / `ref readonly` 戻り値、`int*` / `void**` / `delegate*` / `int*[]` のようなポインタ / 関数ポインタ戻り値、`(int, int)[]` / `(int, int)?` / `(int, int)[][]` / `(int, int)[,]` のような末尾サフィックス付き tuple 戻り値にも対応。qualified call expression の直前にある named-argument label だけを除外し、`global::` / alias-qualified な戻り値型と、スペースを含む generic 型トークンを許可。属性ストリッパは `[Obsolete, Conditional("DEBUG")]` や `[Fact, Trait("cat","io")]` のような複数セクション属性もブランク化して、2 つ目以降の属性名が phantom `function` シンボルとして漏れないようにする。さらに `from` / `where` / `select` / `orderby` / `group` / `join` / `let` / `into` / `on` / `equals` / `ascending` / `descending` / `by` のような LINQ 式 contextual keyword は戻り値型位置から除外し、`where Validator.Check(x)` / `select Mapper.Convert(x)` / `orderby Math.Abs(x)` のような continuation 行が qualified member 名を phantom `function` シンボルとして出さないようにする。加えて `const` / `static readonly` の field-like `function` 行は実際の type body にだけ列単位で許可するため、`const string content = "hello";` のようなメソッド内宣言はシンボルへ漏れない。qualified member 行も `elapsed <` のような演算子 / contextual suffix で終わる戻り値断片を拒否しつつ、`public await MakeAwait()` / `public yield MakeYield()` のような本物の contextual identifier 戻り値型と、`public @new Make()` のような verbatim identifier 戻り値型は保持するため、`TimeSpan.FromSeconds(...)` のような call-site 引数から phantom `function` が出ないまま合法宣言は抽出される。さらに修飾子順序は自由で、visibility を修飾子列の任意位置に置いてよい。型宣言(`abstract public class`、`sealed public class`、`readonly public struct`、`ref public struct`、`partial public interface`、`abstract public record class`)、`const` フィールド(`new public const int X = 1;`、`public new const int X = 1;`)、`static readonly` フィールド(`readonly public static int E = 6;`)、メソッド(`static public int F() => 0;`)、プロパティ(`static public int P { get; set; }`)、インデクサ(`static public int this[int i] => 0;`)、イベント、デリゲート、演算子 / 変換演算子オーバーロード(C# 11 の `static abstract` / `abstract static` interface 演算子メンバも含む。例: `static abstract T operator +(T a, T b);` / `abstract static T operator -(T a, T b);` / `static abstract implicit operator T(int x);` / `abstract static explicit operator int(T t);`。両方の修飾子順を二項 / 単項演算子行と変換演算子行の双方で受けるため、`System.Numerics.INumber` / `IAdditionOperators` / `IComparisonOperators` のような generic-math interface も捕捉される)、コンストラクタ(`unsafe public S(int* p) {}`、`extern public S(int x);`。コンストラクタ regex の開き括弧直後に位置検査の否定先読みを入れ、「対応する `)` のあとに識別子 + `{` / `(` / `=>`(間に `?` / `[]` / `[,]` / 空白混じりの tuple サフィックスを許容。例: `) []` / `) ?` / `) ?`。サフィックスのバリエーションは共有定数 `CSharpTupleSuffixPattern` に切り出し、`CSharpTypePattern` と ctor lookahead の双方から参照することで両者の整合を保つ)が続く行」——modifier 付き property や式本体メソッドそのものの形——を ctor 候補から弾く。これにより、上流の method / property regex が失敗した行——たとえば `public required (int, int) R1 { get; init; }` や `public required (int, int) [] R4 { get; init; }` が以前 phantom `function required` を出していたケース——でも、`partial` / `required` / `readonly` / `async` / `sealed` / `virtual` / `override` / `abstract` / `new` / `file` / `static` のような modifier キーワードが ctor 名として拾われない。複数行 ctor シグネチャ、`;` で終わる `extern` ctor、式本体 ctor、`: base(...)` / `: this(...)` 初期化子、tuple パラメータ ctor はすべて影響を受けず引き続き抽出される)、静的コンストラクタ(`unsafe static S()`)のすべてが対象で、`unsafe` / `extern` もプロパティ / インデクサ / イベント / コンストラクタ / 静的コンストラクタ行の自由順序な修飾子として受け付ける。`static` / `readonly` も `new` と任意順に並べられる(例: `readonly new static`、`new readonly static`)。C# の継承修飾子 `virtual` / `override` / `abstract` / `sealed` / `new` も event 宣言行の自由順序修飾子として受け付け(`abstract public event E;`、`sealed public override event E;`)、`partial` は event / indexer 宣言行の自由順序修飾子として受け付けるので、C# 13 の partial indexer(`public partial int this[int i] { get; }`、partial class 断片をまたぐ宣言側と実装側、式本体 / ブロック本体)と C# 14 の field-like / accessor-based partial event(`public partial event System.Action X;`、`public partial event System.Action OnLog { add { } remove { } }`)が silent drop されず捕捉される。file スコープ型修飾子 `file` とネスト隠蔽の `new` は `interface` / `delegate` 行でも受け付ける(`file interface I {}`、`file delegate int D(int x);`)), `operator +` / `operator checked +` / `operator checked -` 形式で保持する演算子(C# 11 のユーザー定義 `operator checked` を単項・二項とも含む), `implicit operator decimal` / `explicit operator Money` / `explicit operator checked int` 形式で保持する変換演算子(`unsafe` / `extern` 付きと function-pointer target type を含む), `Item` に正規化するインデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record(折り返されたヘッダでは base list と `where` 句を `symbols.signature` に保持、C# 12 primary constructor パラメータリストも含む) | struct, record struct, ref struct(折り返されたヘッダでは base list と `where` 句を signature に保持) | interface(折り返されたヘッダでは base list と `where` 句を signature に保持) | enum(折り返されたヘッダでは `: underlyingType` を signature に保持) | property, partial property, 式本体, `ref` / `ref readonly` property, ポインタ property, tuple-array / nullable-tuple property, 明示的インターフェースプロパティ実装(ブレース本体の `int IThing.Value { get; set; }` と式本体の `string IThing.Name => "x";` の両形、`IBucket.Items` のような generic interface 修飾子、`IMap.PairCount` のような多引数 generic 修飾子も含む), record primary component, 通常フィールド(instance / readonly / volatile / 通常 static、初期化子の有無を問わず、`;` 前で折り返す multi-line 宣言 — `= new(\n () => 42);` のような括弧付き / コンストラクタ呼び出し初期化子や `= new() { ... };` / `= new Dictionary<...> { ... };` のようなオブジェクト / コレクション初期化子を含む — `int _x, _y;` のような declarator list — declarator ごとに 1 シンボル — および `delegate* Callback;` / `delegate* unmanaged[Cdecl] _op;` のような function-pointer フィールド) | event, 明示的インターフェース event 実装(`event EventHandler IFoo.Evt { add { } remove { } }` 形。同一行 / 次行 accessor block と `IMap.Evt3` のような generic qualifier を含む), delegate(generic 型引数内スペースを含む形と、ポインタ戻り値の delegate 宣言も対応) | using, using alias, extern alias | yes | | Go | func, メソッド | 型エイリアス | struct | interface | -- | -- | -- | import | yes | | Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes | | Java | メソッド(同一行の先頭アノテーション付き、`@Label(")")` や `@SuppressWarnings({"unchecked"})` のような annotation 引数、同一行と Allman スタイル両方の brace 配置を拾う compact constructor、同一行 brace-body sibling、same-line enum 定数 body 内メソッドを含む), static final, enum メンバー(文字列・char・コメント・text block を追跡する body-scoped scanner で抽出し、最初の top-level `;` で停止するため、enum 本体外の `\tRED();` のようなメソッド呼び出しを誤検出しない。匿名 body を持つ enum 定数は body range も保持し、入れ子の override や same-line body-local method が enum 定数コンテナにぶら下がる) | class, record, sealed, @interface | -- | interface | enum | record primary component | -- | import | yes | diff --git a/src/CodeIndex/Indexer/SymbolExtractor.cs b/src/CodeIndex/Indexer/SymbolExtractor.cs index 546b31b033..8559ba5f1b 100644 --- a/src/CodeIndex/Indexer/SymbolExtractor.cs +++ b/src/CodeIndex/Indexer/SymbolExtractor.cs @@ -9823,7 +9823,7 @@ private static bool HasInvalidCSharpReturnTypeSuffix(string? returnType) } var lastToken = trimmed[tokenStart..]; - return lastToken is "as" or "is" or "await" or "return" or "throw" or "yield" or "new"; + return lastToken is "as" or "is" or "return" or "throw" or "new"; } private static int FindNextSameLineBraceStatementStart(string matchLine, int startIndex, string? lang) diff --git a/tests/CodeIndex.Tests/SymbolExtractorTests.cs b/tests/CodeIndex.Tests/SymbolExtractorTests.cs index 7b5ec27bef..814ee4cf71 100644 --- a/tests/CodeIndex.Tests/SymbolExtractorTests.cs +++ b/tests/CodeIndex.Tests/SymbolExtractorTests.cs @@ -8989,6 +8989,33 @@ public class UsesVerbatim Assert.Equal("@new", method.ReturnType); } + [Fact] + public void Extract_CSharp_ContextualKeywordReturnTypeIdentifiers_AreNotRejectedBySuffixGuard() + { + var content = """ + namespace Demo; + + public class await {} + public class yield {} + + public class Uses + { + public await MakeAwait() => new await(); + public yield MakeYield() => new yield(); + } + """; + var symbols = SymbolExtractor.Extract(1, "csharp", content); + + Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "await"); + Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "yield"); + + var awaitMethod = Assert.Single(symbols.Where(s => s.Kind == "function" && s.Name == "MakeAwait")); + Assert.Equal("await", awaitMethod.ReturnType); + + var yieldMethod = Assert.Single(symbols.Where(s => s.Kind == "function" && s.Name == "MakeYield")); + Assert.Equal("yield", yieldMethod.ReturnType); + } + [Fact] public void Extract_CSharp_DetectsMultiLineFieldDeclaration() {