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
4 changes: 2 additions & 2 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ For JavaScript / TypeScript, reference extraction also captures tagged template

SQL also emits `namespace` symbols for `CREATE SCHEMA`, but the summary table above does not have a dedicated namespace column. SQL graph extraction emits `reference` edges for named source/target forms such as `FROM`, `JOIN`, `INSERT INTO`, `UPDATE`, `TRUNCATE TABLE`, `DELETE FROM`, `DELETE ... USING`, and `MERGE ... USING`; procedure and table-valued-function calls stay on the `call` path.

Additionally, 21 languages are detected and indexed as raw text without symbol extraction: cmake, clojure, crystal, dockerignore, d, editorconfig, erlang, gitignore, json, justfile, julia, markdown, nim, ocaml, solidity, svelte, tcl, toml, vue, xml, yaml.
Additionally, 20 languages are detected and indexed as raw text without symbol extraction: cmake, clojure, crystal, dockerignore, d, editorconfig, erlang, gitignore, json, justfile, julia, markdown, nim, ocaml, svelte, tcl, toml, vue, xml, yaml.

VB.NET container patterns use `RegexOptions.IgnoreCase` plus `VisualBasicEnd`-based range tracking, so `Partial` spelling differences and multi-file type families still receive stable definition ranges and hotspot-family metadata.

Expand Down Expand Up @@ -3164,7 +3164,7 @@ JavaScript / TypeScript では、reference extraction が `` gql`...` ``、`` st

SQL は `CREATE SCHEMA` から `namespace` シンボルも出力するが、上の要約表には namespace 専用列はない。SQL graph extraction は `FROM`、`JOIN`、`INSERT INTO`、`UPDATE`、`TRUNCATE TABLE`、`DELETE FROM`、`DELETE ... USING`、`MERGE ... USING` のような source/target 形を `reference` edge として出力し、procedure call と table-valued function 使用は `call` 経路に残す。

他に21言語がテキスト検索用に検出されるがシンボル抽出パターンは未対応: cmake, clojure, crystal, dockerignore, d, editorconfig, erlang, gitignore, json, justfile, julia, markdown, nim, ocaml, solidity, svelte, tcl, toml, vue, xml, yaml。
他に20言語がテキスト検索用に検出されるがシンボル抽出パターンは未対応: cmake, clojure, crystal, dockerignore, d, editorconfig, erlang, gitignore, json, justfile, julia, markdown, nim, ocaml, svelte, tcl, toml, vue, xml, yaml。

正規表現ベースの抽出は意図的にシンプルです。AST精度よりも速度とポータビリティを優先しています。

Expand Down
4 changes: 2 additions & 2 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1749,7 +1749,7 @@ All indexed languages are searchable through FTS5. Rows with **Symbols = yes** a
| Nim | `.nim`, `.nims` | -- |
| OCaml | `.ml`, `.mli` | -- |
| Perl | `.pl`, `.pm`, `.t`, `.pod` | -- |
| Solidity | `.sol` | -- |
| Solidity | `.sol` | yes |
| Tcl | `.tcl`, `.tk` | -- |
| R | `.r`, `.R` | yes |
| Haskell | `.hs`, `.lhs` | yes |
Expand Down Expand Up @@ -4195,7 +4195,7 @@ indexing はファイル単位の SQLite transaction を commit します。長
| Nim | `.nim`, `.nims` | -- |
| OCaml | `.ml`, `.mli` | -- |
| Perl | `.pl`, `.pm`, `.t`, `.pod` | -- |
| Solidity | `.sol` | -- |
| Solidity | `.sol` | yes |
| Tcl | `.tcl`, `.tk` | -- |
| R | `.r`, `.R` | yes |
| Haskell | `.hs`, `.lhs` | yes |
Expand Down
25 changes: 25 additions & 0 deletions changelog.d/unreleased/3531.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
category: added
issues:
- 3531
affected:
- src/CodeIndex/Indexer/SolidityLanguageSupport.cs
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.Solidity.cs
- src/CodeIndex/Indexer/References/ReferenceExtractor.Core.cs
- src/CodeIndex/Indexer/References/ReferenceExtractor.State.cs
- src/CodeIndex/Indexer/References/ReferenceExtractor.Solidity.cs
- tests/CodeIndex.Tests/SymbolExtractorSolidityTests.cs
- tests/CodeIndex.Tests/ReferenceExtractorSolidityTests.cs
- tests/CodeIndex.Tests/QueryCommandRunnerTests.cs
- USER_GUIDE.md
- DEVELOPER_GUIDE.md
---

## English

- **Solidity files now expose symbols and graph references (#3531)** - `.sol` files index contracts, interfaces, libraries, constructors, functions, events, errors, structs, enums, modifiers, inheritance, library usage, modifier applications, event emissions, and interface-style calls for navigation and graph queries.

## 日本語

- **Solidity ファイルでシンボルと graph 参照を抽出するようになりました (#3531)** - `.sol` ファイルで contract、interface、library、constructor、function、event、error、struct、enum、modifier、継承、library 使用、modifier 適用、event emit、interface 形式の call を index し、navigation と graph query に利用できるようにしました。
3 changes: 3 additions & 0 deletions src/CodeIndex/Indexer/References/ReferenceExtractor.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ internal static List<ReferenceRecord> ExtractCore(ReferenceExtractionContext req
// プロパティ自身に帰属させる (issue #233 参照)。
var containerCandidates = BuildReferenceContainerCandidates(symbols);
var containerResolver = new InnermostContainerResolver(containerCandidates);
if (language == "solidity")
return ExtractSolidityReferences(fileId, lines, preparedLines, containerResolver);

var csharpXmlDocAttachmentScopeCandidates = BuildCSharpXmlDocAttachmentScopeCandidates(language, symbols);
// Enclosing-type candidates for constructor-chain rewrites (class/struct/record; namespace excluded).
// Ordered innermost-first via ascending body range. Java enums can declare constructors and
Expand Down
204 changes: 204 additions & 0 deletions src/CodeIndex/Indexer/References/ReferenceExtractor.Solidity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
using System.Text.RegularExpressions;
using CodeIndex.Models;
using Regex = CodeIndex.Indexer.BoundedRegex;

namespace CodeIndex.Indexer;

public static partial class ReferenceExtractor
{
private static readonly Regex SolidityInheritanceRegex = new(
@"^\s*(?:abstract\s+)?(?:contract|interface)\s+" + SolidityLanguageSupport.IdentifierPattern + @"\s+is\s+(?<bases>[^{;]+)",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SolidityBaseIdentifierRegex = new(
@"(?<name>" + SolidityLanguageSupport.IdentifierPattern + @")(?:\s*\([^)]*\))?",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SolidityUsingLibraryRegex = new(
@"^\s*using\s+(?<name>" + SolidityLanguageSupport.IdentifierPattern + @")\s+for\b",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SolidityEmitRegex = new(
@"\bemit\s+(?<name>" + SolidityLanguageSupport.IdentifierPattern + @")\s*(?=\()",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SolidityInterfaceCastCallRegex = new(
@"(?<![A-Za-z0-9_$])(?<type>[A-Z][A-Za-z0-9_$]*)\s*\([^;\r\n]*?\)\s*\.\s*(?<method>" + SolidityLanguageSupport.IdentifierPattern + @")\s*\(",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SolidityCallableHeaderRegex = new(
@"^\s*(?:function\s+(?:" + SolidityLanguageSupport.IdentifierPattern + @")|constructor|fallback|receive)\s*\([^)]*\)(?<tail>[^{;]*)",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SolidityTailIdentifierRegex = new(
@"(?<name>" + SolidityLanguageSupport.IdentifierPattern + @")(?:\s*\([^)]*\))?",
RegexOptions.Compiled | RegexOptions.CultureInvariant);

private static readonly HashSet<string> SolidityModifierTailKeywords = new(StringComparer.Ordinal)
{
"public", "private", "internal", "external", "view", "pure", "payable", "virtual", "override",
"returns", "memory", "calldata", "storage", "immutable", "constant",
};

private static List<ReferenceRecord> ExtractSolidityReferences(
long fileId,
string[] rawLines,
string[] preparedLines,
InnermostContainerResolver containerResolver)
{
var matchLines = SolidityLanguageSupport.MaskCommentsAndStrings(preparedLines);
var references = new List<ReferenceRecord>();
var seen = new HashSet<string>(StringComparer.Ordinal);

for (var i = 0; i < matchLines.Length; i++)
{
var lineNumber = i + 1;
var line = matchLines[i];
var context = rawLines[i].Trim();

AddSolidityInheritanceReferences(references, seen, fileId, line, context, lineNumber, containerResolver);
AddSolidityLibraryReferences(references, seen, fileId, line, context, lineNumber, containerResolver);
AddSolidityModifierReferences(references, seen, fileId, line, context, lineNumber, containerResolver);
AddSolidityEventReferences(references, seen, fileId, line, context, lineNumber, containerResolver);
AddSolidityInterfaceCallReferences(references, seen, fileId, line, context, lineNumber, containerResolver);
}

return references;
}

private static void AddSolidityInheritanceReferences(
List<ReferenceRecord> references,
HashSet<string> seen,
long fileId,
string line,
string context,
int lineNumber,
InnermostContainerResolver containerResolver)
{
var match = SolidityInheritanceRegex.Match(line);
if (!match.Success)
return;

var bases = match.Groups["bases"];
foreach (Match baseMatch in SolidityBaseIdentifierRegex.Matches(bases.Value))
{
var name = baseMatch.Groups["name"].Value;
if (string.IsNullOrWhiteSpace(name))
continue;

AddSolidityReference(
references,
seen,
fileId,
name,
bases.Index + baseMatch.Groups["name"].Index,
"extends",
context,
lineNumber,
containerResolver);
}
}

private static void AddSolidityLibraryReferences(
List<ReferenceRecord> references,
HashSet<string> seen,
long fileId,
string line,
string context,
int lineNumber,
InnermostContainerResolver containerResolver)
{
var match = SolidityUsingLibraryRegex.Match(line);
if (!match.Success)
return;

var name = match.Groups["name"];
AddSolidityReference(references, seen, fileId, name.Value, name.Index, "use", context, lineNumber, containerResolver);
}

private static void AddSolidityModifierReferences(
List<ReferenceRecord> references,
HashSet<string> seen,
long fileId,
string line,
string context,
int lineNumber,
InnermostContainerResolver containerResolver)
{
var header = SolidityCallableHeaderRegex.Match(line);
if (!header.Success)
return;

var tail = header.Groups["tail"];
foreach (Match modifier in SolidityTailIdentifierRegex.Matches(tail.Value))
{
var name = modifier.Groups["name"];
if (SolidityModifierTailKeywords.Contains(name.Value))
continue;

AddSolidityReference(
references,
seen,
fileId,
name.Value,
tail.Index + name.Index,
"call",
context,
lineNumber,
containerResolver);
}
}

private static void AddSolidityEventReferences(
List<ReferenceRecord> references,
HashSet<string> seen,
long fileId,
string line,
string context,
int lineNumber,
InnermostContainerResolver containerResolver)
{
foreach (Match match in SolidityEmitRegex.Matches(line))
{
var name = match.Groups["name"];
AddSolidityReference(references, seen, fileId, name.Value, name.Index, "call", context, lineNumber, containerResolver);
}
}

private static void AddSolidityInterfaceCallReferences(
List<ReferenceRecord> references,
HashSet<string> seen,
long fileId,
string line,
string context,
int lineNumber,
InnermostContainerResolver containerResolver)
{
foreach (Match match in SolidityInterfaceCastCallRegex.Matches(line))
{
var type = match.Groups["type"];
AddSolidityReference(references, seen, fileId, type.Value, type.Index, "type_reference", context, lineNumber, containerResolver);

var method = match.Groups["method"];
AddSolidityReference(references, seen, fileId, method.Value, method.Index, "call", context, lineNumber, containerResolver);
}
}

private static void AddSolidityReference(
List<ReferenceRecord> references,
HashSet<string> seen,
long fileId,
string name,
int nameIndex,
string referenceKind,
string context,
int lineNumber,
InnermostContainerResolver containerResolver)
{
AddReference(
references,
seen,
fileId,
name,
nameIndex,
referenceKind,
context,
lineNumber,
containerResolver.Find(lineNumber),
"solidity");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal sealed class CSharpWhereConstraintState
"java", "kotlin", "ruby", "perl", "c", "cpp", "php", "swift",
"dart", "scala", "elixir", "lua", "commonlisp", "racket", "vb", "fsharp", "sql", "cobol", "batch",
"assembly",
"r", "powershell", "shell", "haskell",
"r", "powershell", "shell", "haskell", "solidity",
"gradle", "terraform", "protobuf", "dockerfile", "makefile", "cmake", "justfile", "msbuild",
"zig", "css", "graphql", "html", "markdown", "fortran", "pascal", "objc", "smalltalk"
];
Expand Down
88 changes: 88 additions & 0 deletions src/CodeIndex/Indexer/SolidityLanguageSupport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Text;

namespace CodeIndex.Indexer;

internal static class SolidityLanguageSupport
{
internal const string IdentifierPattern = @"[A-Za-z_$][A-Za-z0-9_$]*";

internal static string[] MaskCommentsAndStrings(string[] lines)
{
var masked = new string[lines.Length];
var inBlockComment = false;

for (var lineIndex = 0; lineIndex < lines.Length; lineIndex++)
{
var line = lines[lineIndex];
var builder = new StringBuilder(line.Length);
var i = 0;

while (i < line.Length)
{
if (inBlockComment)
{
if (i + 1 < line.Length && line[i] == '*' && line[i + 1] == '/')
{
builder.Append(" ");
i += 2;
inBlockComment = false;
}
else
{
builder.Append(' ');
i++;
}

continue;
}

if (i + 1 < line.Length && line[i] == '/' && line[i + 1] == '/')
{
builder.Append(' ', line.Length - i);
break;
}

if (i + 1 < line.Length && line[i] == '/' && line[i + 1] == '*')
{
builder.Append(" ");
i += 2;
inBlockComment = true;
continue;
}

if (line[i] is '"' or '\'')
{
var quote = line[i];
builder.Append(' ');
i++;

while (i < line.Length)
{
var current = line[i];
builder.Append(' ');
i++;

if (current == '\\' && i < line.Length)
{
builder.Append(' ');
i++;
continue;
}

if (current == quote)
break;
}

continue;
}

builder.Append(line[i]);
i++;
}

masked[lineIndex] = builder.ToString();
}

return masked;
}
}
Loading
Loading