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
22 changes: 22 additions & 0 deletions changelog.d/unreleased/3391.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
category: fixed
issues:
- 3391
affected:
- src/CodeIndex/Cli/ConsoleUi.cs
- src/CodeIndex/Cli/QueryCommandRunner.cs
- src/CodeIndex/Indexer/References/Languages/CSharpReferenceExtractor.Support.cs
- src/CodeIndex/Indexer/References/ReferenceExtractor.Core.cs
- src/CodeIndex/Indexer/References/ReferenceExtractor.TypeReferences.cs
- src/CodeIndex/Indexer/References/ReferenceExtractor.cs
- src/CodeIndex/Mcp/McpToolDefinitions.cs
- src/CodeIndex/Models/SymbolKindCatalog.cs
---

## English

- **C# constructor reference search now resolves using-alias targets (#3391)** — `references BoundedRegex --kind instantiate` can now distinguish `using Regex = CodeIndex.Indexer.BoundedRegex; new Regex(...)` from direct `System.Text.RegularExpressions.Regex` construction, and `references Regex --kind bcl_regex_without_timeout --lang csharp` reports direct BCL Regex construction without a timeout argument.

## 日本語

- **C# constructor reference search が using alias の参照先を解決するようになりました (#3391)** — `references BoundedRegex --kind instantiate` で、`using Regex = CodeIndex.Indexer.BoundedRegex; new Regex(...)` と直接の `System.Text.RegularExpressions.Regex` 生成を区別できるようになり、`references Regex --kind bcl_regex_without_timeout --lang csharp` で timeout 引数なしの直接 BCL Regex 生成を報告します。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/3396.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 3396
affected:
- src/CodeIndex/Database/DbSymbolReader.cs
---

## English

- **Unused-symbol analysis now recognizes more serialization and reflection contract annotations (#3396)** — public C# members, including properties, DTO fields, constructors, and methods annotated with System.Text.Json contract attributes or trimming/reflection preservation attributes, are classified under `reflection_or_config_suspect` instead of the general public no-reference bucket.

## 日本語

- **unused-symbol analysis が serialization / reflection contract annotation をより多く認識するようになりました (#3396)** — System.Text.Json の contract 属性や trimming / reflection preservation 属性が付いた C# public member(property、DTO field、constructor、method を含む)を、一般の public no-reference bucket ではなく `reflection_or_config_suspect` に分類するようになりました。
2 changes: 1 addition & 1 deletion src/CodeIndex/Cli/ConsoleUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,7 +1061,7 @@ private static void PrintFlagReference(Action<string> WriteHelpLine)
Console.WriteLine(" Uses NFKC + Unicode CaseFold when ready.");
Console.WriteLine(" Legacy/stale-fold DBs fall back to ASCII NOCASE;");
Console.WriteLine(" run `cdidx backfill-fold` or check fold_ready.");
WriteHelpLine(" --kind <kind> definition/symbols/hotspots/unused: symbol kind; references: reference kind (call/instantiate/subscribe/attribute/annotation); callers/callees: call-graph kinds only (call/instantiate/subscribe — metadata kinds rejected, use references instead); validate: issue kind");
WriteHelpLine(" --kind <kind> definition/symbols/hotspots/unused: symbol kind; references: reference kind (call/instantiate/subscribe/attribute/annotation/bcl_regex_without_timeout); callers/callees: call-graph kinds only (call/instantiate/subscribe — metadata kinds rejected, use references instead); validate: issue kind");
Console.WriteLine(" --severity <s> validate only: filter issues by severity: info, warning, error");
Console.WriteLine(" --visibility <v[,v]> Filter symbols/definitions/unused/hotspots by visibility: public, protected, internal, private");
WriteHelpLine(" --exclude-visibility <v[,v]> Exclude symbols/definitions/unused/hotspots by visibility");
Expand Down
2 changes: 1 addition & 1 deletion src/CodeIndex/Cli/QueryCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11140,7 +11140,7 @@ private static void WriteSymbolExtractionCapabilityHint(string? lang, DbReader r
// compile-time な `type_reference` エッジを含む。C++ の `friend` 宣言も extractor が出す
// dependency edge として受け付け、graph query にも参加させる。
private static readonly string[] AllValidReferenceKinds =
["annotation", "attribute", "augmentation", "call", "consumes_hook", "friend", "import", "instantiate", "razor_event_binding", "subscribe", "type_reference", "unsubscribe"];
["annotation", "attribute", "augmentation", "bcl_regex_without_timeout", "call", "consumes_hook", "friend", "import", "instantiate", "razor_event_binding", "subscribe", "type_reference", "unsubscribe"];
// Reference kinds that `callers` / `callees` can legitimately return. Metadata kinds
// (`attribute` / `annotation`) and type-position edges (`type_reference`) are structurally
// not call-graph edges, so those queries are rejected at the CLI / MCP boundary. C++ `friend`
Expand Down
41 changes: 40 additions & 1 deletion src/CodeIndex/Database/DbSymbolReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public partial class DbReader
"jsonpropertyname",
"jsonproperty",
"jsoninclude",
"jsonextensiondata",
"jsonconverter",
"jsonrequired",
"jsonpropertyorder",
"jsonnumberhandling",
"jsonobjectcreationhandling",
"datamember",
"bsonelement",
"bsonid",
Expand All @@ -37,20 +43,50 @@ public partial class DbReader
"parameter",
"inject",
"bindnever",
"dynamicallyaccessedmembers",
"dynamicdependency",
"preserve",
"usedimplicitly",
"publicapi",
};
private static readonly HashSet<string> ReflectionTypeAttributeNames = new(StringComparer.Ordinal)
{
"serializable",
"jsonserializable",
"jsonsourcegenerationoptions",
"jsonconverter",
"jsonderivedtype",
"jsonpolymorphic",
"datacontract",
"xmlroot",
"xmltype",
"xmlinclude",
"knowntype",
"protocontract",
"messagepackobject",
"table",
"complextype",
"owned",
"keyless",
"attributeusage",
"dynamicallyaccessedmembers",
"dynamicdependency",
"preserve",
"usedimplicitly",
"publicapi",
};
private static readonly HashSet<string> ReflectionFunctionAttributeNames = new(StringComparer.Ordinal)
{
"jsonconstructor",
"onserializing",
"onserialized",
"ondeserializing",
"ondeserialized",
"dynamicdependency",
"dynamicallyaccessedmembers",
"preserve",
"usedimplicitly",
"publicapi",
};
private static readonly HashSet<string> ReflectionIgnoreAttributeNames = new(StringComparer.Ordinal)
{
Expand Down Expand Up @@ -4157,12 +4193,15 @@ private bool HasReflectionAttributeContext(string kind, string path, int startLi

private static HashSet<string>? GetReflectionAttributeNamesForKind(string kind)
{
if (kind == "property")
if (kind is "property" or "field")
return ReflectionPropertyAttributeNames;

if (kind is "class" or "struct" or "interface" or "enum")
return ReflectionTypeAttributeNames;

if (kind == "function")
return ReflectionFunctionAttributeNames;

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ private sealed record CSharpNamespaceScope(string QualifiedName, int ScopeStartL
private sealed record CSharpUsingNamespaceScope(string TargetQualifiedName, int Line, int ScopeStartLine, int ScopeEndLine);
private sealed record CSharpContainingTypeScope(string QualifiedName, int ScopeStartLine, int ScopeEndLine);
internal sealed record CSharpUsingAliasRecord(string AliasName, string TargetQualifiedName, int Line, int ScopeStartLine, int ScopeEndLine, bool TargetsType);
internal sealed record CSharpUsingNamespaceRecord(string TargetQualifiedName, int Line, int ScopeStartLine, int ScopeEndLine);
internal sealed record CSharpUsingStaticRecord(string TargetQualifiedName, int Line, int ScopeStartLine, int ScopeEndLine);
private sealed record CSharpCastTypeShape(IReadOnlyList<string> IdentifierSegments, string? SimpleQualifiedName, bool HasTypeOnlySyntax, bool AllIdentifiersTypeLike);
internal sealed record CSharpContainingTypeValueReceiverNames(HashSet<string> InstanceNames, HashSet<string> StaticNames);
internal sealed record CSharpFunctionValueReceiverNameRecord(string Name, int ScopeStartLine, int ScopeStartColumn, int ScopeEndLine, int ScopeEndColumn);

private static List<CSharpUsingAliasRecord> BuildCSharpUsingAliases(string language, IReadOnlyList<SymbolRecord> symbols, IReadOnlySet<string> csharpKnownTypeNames)
private static List<CSharpUsingAliasRecord> BuildCSharpUsingAliases(
string language,
IReadOnlyList<SymbolRecord> symbols,
IReadOnlySet<string> csharpKnownTypeNames,
IReadOnlyList<string>? lines = null,
IReadOnlyList<string>? aliasScanLines = null)
{
var aliases = new List<CSharpUsingAliasRecord>();
if (language != "csharp")
Expand All @@ -41,9 +47,112 @@ private static List<CSharpUsingAliasRecord> BuildCSharpUsingAliases(string langu
if (!match.Success)
continue;

var alias = NormalizeCSharpIdentifier(match.Groups["alias"].Value);
var target = TryNormalizeCSharpQualifiedName(match.Groups["target"].Value);
if (string.IsNullOrWhiteSpace(alias) || string.IsNullOrWhiteSpace(target))
AddCSharpUsingAliasRecord(aliases, namespaceScopes, symbol.Line, match, csharpKnownTypeNames);
}

if (lines != null)
{
for (var i = 0; i < lines.Count; i++)
{
var scanLine = aliasScanLines != null && i < aliasScanLines.Count
? aliasScanLines[i]
: lines[i];
if (!CSharpUsingAliasRegex.IsMatch(scanLine))
continue;

var match = CSharpUsingAliasRegex.Match(lines[i]);
if (!match.Success)
continue;

var lineNumber = i + 1;
if (aliases.Any(existing => existing.Line == lineNumber
&& string.Equals(existing.AliasName, NormalizeCSharpIdentifier(match.Groups["alias"].Value), StringComparison.Ordinal)))
{
continue;
}

AddCSharpUsingAliasRecord(aliases, namespaceScopes, lineNumber, match, csharpKnownTypeNames);
}
}

aliases.Sort(static (left, right) => left.Line.CompareTo(right.Line));
return aliases;
}

private static void AddCSharpUsingAliasRecord(
List<CSharpUsingAliasRecord> aliases,
IReadOnlyList<(int StartLine, int EndLine)> namespaceScopes,
int lineNumber,
Match match,
IReadOnlySet<string> csharpKnownTypeNames)
{
var alias = NormalizeCSharpIdentifier(match.Groups["alias"].Value);
var target = TryNormalizeCSharpQualifiedName(match.Groups["target"].Value)
?? NormalizeCSharpUsingAliasRawTarget(match.Groups["target"].Value);
if (string.IsNullOrWhiteSpace(alias) || string.IsNullOrWhiteSpace(target))
return;

var scopeStartLine = 1;
var scopeEndLine = int.MaxValue;
var scopeWidth = int.MaxValue;
foreach (var (startLine, endLine) in namespaceScopes)
{
if (lineNumber < startLine || lineNumber > endLine)
continue;

var width = endLine - startLine;
if (width > scopeWidth)
continue;

scopeStartLine = startLine;
scopeEndLine = endLine;
scopeWidth = width;
}

aliases.Add(new CSharpUsingAliasRecord(
alias,
target,
lineNumber,
scopeStartLine,
scopeEndLine,
IsCSharpUsingAliasTypeTarget(target, csharpKnownTypeNames)));
}

private static string NormalizeCSharpUsingAliasRawTarget(string target)
{
var trimmed = target.Trim();
var genericStart = trimmed.IndexOf('<');
if (genericStart >= 0)
trimmed = trimmed[..genericStart].TrimEnd();
return trimmed;
}

private static List<CSharpUsingNamespaceRecord> BuildCSharpUsingNamespaces(string language, IReadOnlyList<SymbolRecord> symbols)
{
var imports = new List<CSharpUsingNamespaceRecord>();
if (language != "csharp")
return imports;

var namespaceScopes = symbols
.Where(symbol => symbol.Kind == "namespace")
.Select(symbol => (
StartLine: symbol.BodyStartLine ?? symbol.StartLine,
EndLine: symbol.BodyEndLine ?? symbol.EndLine))
.Where(scope => scope.StartLine > 0 && scope.EndLine >= scope.StartLine)
.ToList();

foreach (var symbol in symbols)
{
if (symbol.Kind != "import" || string.IsNullOrWhiteSpace(symbol.Signature))
continue;

var match = CSharpUsingNamespaceRegex.Match(symbol.Signature!);
if (!match.Success)
continue;

var target = TryNormalizeCSharpQualifiedName(match.Groups["target"].Value)
?? NormalizeCSharpUsingAliasRawTarget(match.Groups["target"].Value);
if (string.IsNullOrWhiteSpace(target))
continue;

var scopeStartLine = 1;
Expand All @@ -63,17 +172,11 @@ private static List<CSharpUsingAliasRecord> BuildCSharpUsingAliases(string langu
scopeWidth = width;
}

aliases.Add(new CSharpUsingAliasRecord(
alias,
target,
symbol.Line,
scopeStartLine,
scopeEndLine,
IsCSharpUsingAliasTypeTarget(target, csharpKnownTypeNames)));
imports.Add(new CSharpUsingNamespaceRecord(target, symbol.Line, scopeStartLine, scopeEndLine));
}

aliases.Sort(static (left, right) => left.Line.CompareTo(right.Line));
return aliases;
imports.Sort(static (left, right) => left.Line.CompareTo(right.Line));
return imports;
}

private static List<CSharpUsingStaticRecord> BuildCSharpUsingStatics(string language, IReadOnlyList<SymbolRecord> symbols)
Expand Down
Loading
Loading