From 04e40c936e25a1a4a81fc7361ff1633a4d16d002 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Tue, 21 Apr 2026 12:11:22 +0900 Subject: [PATCH] Fixes #363 Add exact regression coverage for C# phantom string symbols --- .../QueryCommandRunnerTests.cs | 82 +++++++++++++++++++ tests/CodeIndex.Tests/SymbolExtractorTests.cs | 62 ++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs b/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs index 4b2ce9678e..34f209a741 100644 --- a/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs +++ b/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs @@ -3733,6 +3733,88 @@ private Dictionary } } + [Fact] + public void RunSymbols_CSharpIssue363Fixture_DoesNotReturnPhantomSymbols() + { + var projectRoot = TestProjectHelper.CreateTempProject("cdidx_symbols_csharp_issue363"); + try + { + Directory.CreateDirectory(Path.Combine(projectRoot, "src")); + File.WriteAllText( + Path.Combine(projectRoot, "src", "R.cs"), + """"" + namespace CsRawStringPhantom; + + public class Svc + { + public int RealMethod() => 0; + + public string DocsExample() => """ + public void FakeMethod() { } + public int FakeProp { get; set; } + public class FakeClass { } + public interface IFakeIface { } + public delegate int FakeDel(); + public event System.EventHandler FakeEvent; + public Foo() { } + """; + + public string VerbatimExample() => @" + public void VerbatimFake() { } + "; + + public string InterpExample() => $""" + public void InterpFake() { } + """; + + public int AnotherReal() => 1; + } + """""); + + var dbPath = Path.Combine(projectRoot, ".cdidx", "codeindex.db"); + var (indexExitCode, _, indexStderr) = CaptureConsole(() => IndexCommandRunner.Run( + [projectRoot, "--json"], + _jsonOptions)); + var (exitCode, stdout, stderr) = CaptureConsole(() => QueryCommandRunner.RunSymbols( + ["--db", dbPath, "--json", "--lang", "csharp"], + _jsonOptions)); + + var rows = ParseJsonLines(stdout); + var symbols = rows + .Select(row => row.RootElement.GetProperty("name").GetString()) + .OfType() + .ToHashSet(StringComparer.Ordinal); + + Assert.Equal(CommandExitCodes.Success, indexExitCode); + Assert.Equal(string.Empty, indexStderr); + Assert.Equal(CommandExitCodes.Success, exitCode); + Assert.Equal(string.Empty, stderr); + Assert.Equal(7, rows.Count); + + Assert.Contains("CsRawStringPhantom", symbols); + Assert.Contains("Svc", symbols); + Assert.Contains("RealMethod", symbols); + Assert.Contains("DocsExample", symbols); + Assert.Contains("VerbatimExample", symbols); + Assert.Contains("InterpExample", symbols); + Assert.Contains("AnotherReal", symbols); + + Assert.DoesNotContain("FakeMethod", symbols); + Assert.DoesNotContain("FakeProp", symbols); + Assert.DoesNotContain("FakeClass", symbols); + Assert.DoesNotContain("IFakeIface", symbols); + Assert.DoesNotContain("FakeDel", symbols); + Assert.DoesNotContain("FakeEvent", symbols); + Assert.DoesNotContain("Foo", symbols); + Assert.DoesNotContain("VerbatimFake", symbols); + Assert.DoesNotContain("InterpFake", symbols); + } + finally + { + TestProjectHelper.DeleteDirectory(projectRoot); + } + } + [Fact] public void RunSymbols_CssExactNameSeparatesLiteralSelectors() { diff --git a/tests/CodeIndex.Tests/SymbolExtractorTests.cs b/tests/CodeIndex.Tests/SymbolExtractorTests.cs index 1a37a4081c..1fa9d64576 100644 --- a/tests/CodeIndex.Tests/SymbolExtractorTests.cs +++ b/tests/CodeIndex.Tests/SymbolExtractorTests.cs @@ -2986,6 +2986,68 @@ public class Phantom Assert.DoesNotContain(symbols, symbol => symbol.Kind == "class" && symbol.Name == "Phantom"); } + [Fact] + public void Extract_CSharp_Issue363RawInterpolatedAndVerbatimStrings_DoNotLeakPhantomSymbols() + { + // Regression for issue #363 exact repro: code-shaped members inside C# raw, + // interpolated raw, and multi-line verbatim strings must not be indexed as + // real symbols. The current main branch already handles this correctly; this + // test locks the user-reported fixture in place so future refactors cannot + // silently reopen it. + // issue #363 の exact repro 回帰: C# の raw string / 補間付き raw string / + // 複数行 verbatim string 内のコード風メンバーを本物の symbol として + // index してはならない。現行 main では直っているため、このテストで + // ユーザー報告フィクスチャを固定し、将来の refactor での再発を防ぐ。 + var content = """"" + namespace CsRawStringPhantom; + + public class Svc + { + public int RealMethod() => 0; + + public string DocsExample() => """ + public void FakeMethod() { } + public int FakeProp { get; set; } + public class FakeClass { } + public interface IFakeIface { } + public delegate int FakeDel(); + public event System.EventHandler FakeEvent; + public Foo() { } + """; + + public string VerbatimExample() => @" + public void VerbatimFake() { } + "; + + public string InterpExample() => $""" + public void InterpFake() { } + """; + + public int AnotherReal() => 1; + } + """""; + var symbols = SymbolExtractor.Extract(1, "csharp", content); + + Assert.Contains(symbols, symbol => symbol.Kind == "namespace" && symbol.Name == "CsRawStringPhantom"); + Assert.Contains(symbols, symbol => symbol.Kind == "class" && symbol.Name == "Svc"); + Assert.Contains(symbols, symbol => symbol.Kind == "function" && symbol.Name == "RealMethod"); + Assert.Contains(symbols, symbol => symbol.Kind == "function" && symbol.Name == "DocsExample"); + Assert.Contains(symbols, symbol => symbol.Kind == "function" && symbol.Name == "VerbatimExample"); + Assert.Contains(symbols, symbol => symbol.Kind == "function" && symbol.Name == "InterpExample"); + Assert.Contains(symbols, symbol => symbol.Kind == "function" && symbol.Name == "AnotherReal"); + Assert.Equal(7, symbols.Count); + + Assert.DoesNotContain(symbols, symbol => symbol.Name == "FakeMethod"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "FakeProp"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "FakeClass"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "IFakeIface"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "FakeDel"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "FakeEvent"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "Foo"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "VerbatimFake"); + Assert.DoesNotContain(symbols, symbol => symbol.Name == "InterpFake"); + } + [Fact] public void Extract_CSharp_CommentedTripleQuotesDoNotHideFollowingMembers() {