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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions tests/CodeIndex.Tests/QueryCommandRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3733,6 +3733,88 @@ private Dictionary<string, int>
}
}

[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<string>()
.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()
{
Expand Down
62 changes: 62 additions & 0 deletions tests/CodeIndex.Tests/SymbolExtractorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading