Skip to content

Commit 1b5aa22

Browse files
authored
Merge pull request #2737 from Widthdom/codex/fix-issue1664-1824
Fix Elixir pipe and defimpl indexing
2 parents 5ebd972 + 98c750d commit 1b5aa22

10 files changed

Lines changed: 434 additions & 1 deletion

File tree

DEVELOPER_GUIDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ Do not add mutable static caches, shared `StringBuilder` instances, reused `Matc
223223
| `procedure` | Procedure declarations in languages such as Fortran | Callable definition |
224224
| `program` | Program block declarations in languages such as Fortran | Definition target and container |
225225
| `protocol` | Protocol declarations in languages that distinguish protocols from interfaces | Definition target and container |
226+
| `protocol_impl` | Elixir `defimpl` protocol implementation declarations | Definition target and container for implementation blocks |
226227
| `reference` | Secondary extracted symbolic references, such as HTML classes or metadata keys | Search/filter symbol |
227228
| `rule` | CSS/SCSS rule container context used by nested references | Container context |
228229
| `route` | Razor route directives | Context/search symbol |
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1664
5+
affected:
6+
- src/CodeIndex/Indexer/References/Languages/ElixirReferenceExtractor.cs
7+
- tests/CodeIndex.Tests/ReferenceExtractorTests.cs
8+
---
9+
10+
## English
11+
12+
- **Elixir pipe chains now preserve call targets (#1664)**`|>` calls to both `Module.function(...)` and local `function(...)` targets are indexed as call references.
13+
14+
## 日本語
15+
16+
- **Elixir pipe chain の呼び出し先を保持するようになりました (#1664)**`|>` による `Module.function(...)` とローカル `function(...)` の両方を call reference として index します。
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1824
5+
affected:
6+
- DEVELOPER_GUIDE.md
7+
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs
8+
- src/CodeIndex/Indexer/References/Languages/ElixirReferenceExtractor.cs
9+
- src/CodeIndex/Models/SymbolKindCatalog.cs
10+
- tests/CodeIndex.Tests/SymbolExtractorTests.cs
11+
- tests/CodeIndex.Tests/ReferenceExtractorTests.cs
12+
---
13+
14+
## English
15+
16+
- **Elixir protocol implementations are now indexed (#1824)**`defimpl Protocol, for: Type` blocks produce `protocol_impl` symbols and type references for both the protocol and implemented type names.
17+
18+
## 日本語
19+
20+
- **Elixir protocol implementation を index するようになりました (#1824)**`defimpl Protocol, for: Type` block は `protocol_impl` symbol と、protocol/type 双方への type reference を生成します。

src/CodeIndex/Database/DbContext.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,7 @@ value TEXT
15681568
EnforceRequiredFileIdConstraints();
15691569
EnforceReferenceLineSetNullConstraint();
15701570
EnsureReferenceLinesContextKey();
1571+
EnsureKindCheckConstraintsCurrent();
15711572

15721573
// Indexes / インデックス
15731574
Execute("CREATE INDEX IF NOT EXISTS idx_files_lang ON files(lang)");
@@ -1952,6 +1953,104 @@ private bool ReferenceLinesHasContextUniqueKey()
19521953
return false;
19531954
}
19541955

1956+
private void EnsureKindCheckConstraintsCurrent()
1957+
{
1958+
var symbolKindCheck = SymbolKindCatalog.ToSqlCheckInList(SymbolKindCatalog.SymbolKinds);
1959+
var referenceKindCheck = SymbolKindCatalog.ToSqlCheckInList(SymbolKindCatalog.ReferenceKinds);
1960+
var symbolsCreateSql =
1961+
$"""
1962+
CREATE TABLE symbols (
1963+
id INTEGER PRIMARY KEY AUTOINCREMENT,
1964+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
1965+
kind TEXT CHECK (kind IN ({symbolKindCheck})),
1966+
sub_kind TEXT,
1967+
name TEXT,
1968+
line INTEGER,
1969+
start_line INTEGER,
1970+
start_column INTEGER,
1971+
end_line INTEGER,
1972+
body_start_line INTEGER,
1973+
body_end_line INTEGER,
1974+
signature TEXT,
1975+
container_kind TEXT CHECK (container_kind IS NULL OR container_kind IN ({symbolKindCheck})),
1976+
container_name TEXT,
1977+
container_qualified_name TEXT,
1978+
family_key TEXT,
1979+
visibility TEXT,
1980+
return_type TEXT,
1981+
is_metadata_target INTEGER,
1982+
name_folded TEXT
1983+
)
1984+
""";
1985+
const string symbolsColumns = "id, file_id, kind, sub_kind, name, line, start_line, start_column, end_line, body_start_line, body_end_line, signature, container_kind, container_name, container_qualified_name, family_key, visibility, return_type, is_metadata_target, name_folded";
1986+
var symbolReferencesCreateSql =
1987+
$"""
1988+
CREATE TABLE symbol_references (
1989+
id INTEGER PRIMARY KEY AUTOINCREMENT,
1990+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
1991+
symbol_name TEXT,
1992+
reference_kind TEXT CHECK (reference_kind IN ({referenceKindCheck})),
1993+
line INTEGER,
1994+
column_number INTEGER,
1995+
context TEXT,
1996+
reference_line_id INTEGER REFERENCES reference_lines(id) ON DELETE SET NULL,
1997+
container_kind TEXT CHECK (container_kind IS NULL OR container_kind IN ({symbolKindCheck})),
1998+
container_name TEXT,
1999+
symbol_name_folded TEXT,
2000+
container_name_folded TEXT,
2001+
is_self_reference INTEGER NOT NULL DEFAULT 0,
2002+
is_mutual_recursion INTEGER NOT NULL DEFAULT 0
2003+
)
2004+
""";
2005+
const string symbolReferencesColumns = "id, file_id, symbol_name, reference_kind, line, column_number, context, reference_line_id, container_kind, container_name, symbol_name_folded, container_name_folded, is_self_reference, is_mutual_recursion";
2006+
2007+
var foreignKeys = ReadPragmaLong("foreign_keys");
2008+
Execute("PRAGMA foreign_keys=OFF");
2009+
try
2010+
{
2011+
if (!TableCheckContainsAll("symbols", SymbolKindCatalog.SymbolKinds))
2012+
RebuildTableWithCurrentKindChecks("symbols", "_symbols_kind_check", symbolsCreateSql, symbolsColumns);
2013+
2014+
if (!TableCheckContainsAll("symbol_references", SymbolKindCatalog.SymbolKinds.Concat(SymbolKindCatalog.ReferenceKinds)))
2015+
RebuildTableWithCurrentKindChecks("symbol_references", "_symbol_references_kind_check", symbolReferencesCreateSql, symbolReferencesColumns);
2016+
}
2017+
finally
2018+
{
2019+
Execute($"PRAGMA foreign_keys={foreignKeys}");
2020+
}
2021+
}
2022+
2023+
private bool TableCheckContainsAll(string tableName, IEnumerable<string> allowedValues)
2024+
{
2025+
var createSql = GetTableCreateSql(tableName);
2026+
if (createSql == null)
2027+
return true;
2028+
2029+
if (!createSql.Contains("CHECK", StringComparison.OrdinalIgnoreCase))
2030+
return true;
2031+
2032+
return allowedValues.All(value => createSql.Contains($"'{value.Replace("'", "''")}'", StringComparison.Ordinal));
2033+
}
2034+
2035+
private string? GetTableCreateSql(string tableName)
2036+
{
2037+
using var cmd = _connection.CreateCommand();
2038+
if (_activeMigrationTransaction != null)
2039+
cmd.Transaction = _activeMigrationTransaction;
2040+
cmd.CommandText = "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = @table";
2041+
cmd.Parameters.AddWithValue("@table", tableName);
2042+
return cmd.ExecuteScalar() as string;
2043+
}
2044+
2045+
private void RebuildTableWithCurrentKindChecks(string tableName, string oldTableName, string createSql, string columns)
2046+
{
2047+
Execute($"DROP TABLE IF EXISTS {oldTableName}");
2048+
Execute($"ALTER TABLE {tableName} RENAME TO {oldTableName}");
2049+
Execute(createSql);
2050+
Execute($"INSERT INTO {tableName} ({columns}) SELECT {columns} FROM {oldTableName}");
2051+
Execute($"DROP TABLE {oldTableName}");
2052+
}
2053+
19552054
private void RebuildTableWithRequiredFileId(string tableName, string createSql, string columns)
19562055
{
19572056
if (ColumnIsNotNull(tableName, "file_id"))

src/CodeIndex/Indexer/References/Languages/ElixirReferenceExtractor.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
using CodeIndex.Models;
2+
using System.Text.RegularExpressions;
23

34
namespace CodeIndex.Indexer;
45

56
internal static class ElixirReferenceExtractor
67
{
8+
private static readonly Regex PipeCallRegex = new(
9+
@"\|>\s*(?:(?:[A-Z_]\w*(?:\.[A-Z_]\w*)*)\.)?(?<name>[a-z_]\w*[!?]?)\s*(?=\(|$)",
10+
RegexOptions.Compiled | RegexOptions.CultureInvariant);
11+
12+
private static readonly Regex DefimplRegex = new(
13+
@"^\s*defimpl\s+(?<protocol>[\w.]+)\s*,\s*for:\s*(?<types>\[[^\]]+\]|[\w.{}]+)",
14+
RegexOptions.Compiled | RegexOptions.CultureInvariant);
15+
16+
private static readonly Regex DefimplTypeRegex = new(
17+
@"[\w.{}]+",
18+
RegexOptions.Compiled | RegexOptions.CultureInvariant);
19+
20+
private static readonly HashSet<string> IgnoredPipeCallNames = new(StringComparer.Ordinal)
21+
{
22+
"and",
23+
"catch",
24+
"do",
25+
"else",
26+
"end",
27+
"fn",
28+
"in",
29+
"not",
30+
"or",
31+
"rescue",
32+
"when",
33+
};
34+
735
public static void EmitTypePositionReferences(
836
string preparedLine,
937
List<ReferenceRecord> references,
@@ -13,6 +41,8 @@ public static void EmitTypePositionReferences(
1341
int lineNumber,
1442
SymbolRecord? container)
1543
{
44+
EmitDefimplReferences(preparedLine, references, seen, fileId, context, lineNumber, container);
45+
1646
LanguageReferenceExtractionSupport.EmitTypePositionReferences(
1747
"elixir",
1848
preparedLine,
@@ -26,11 +56,64 @@ public static void EmitTypePositionReferences(
2656
container);
2757
}
2858

59+
private static void EmitDefimplReferences(
60+
string preparedLine,
61+
List<ReferenceRecord> references,
62+
HashSet<string> seen,
63+
long fileId,
64+
string context,
65+
int lineNumber,
66+
SymbolRecord? container)
67+
{
68+
var match = DefimplRegex.Match(preparedLine);
69+
if (!match.Success)
70+
return;
71+
72+
AddDefimplGroupReference(match.Groups["protocol"]);
73+
74+
var typesGroup = match.Groups["types"];
75+
foreach (Match typeMatch in DefimplTypeRegex.Matches(typesGroup.Value))
76+
AddDefimplTypeReference(typeMatch.Groups[0], typesGroup.Index + typeMatch.Index);
77+
78+
void AddDefimplGroupReference(Group group)
79+
=> AddDefimplTypeReference(group, group.Index);
80+
81+
void AddDefimplTypeReference(Group group, int column)
82+
{
83+
var name = group.Value.Trim();
84+
if (name.Length == 0)
85+
return;
86+
87+
var key = $"type_reference:{name}:{lineNumber}:{column}";
88+
if (!seen.Add(key))
89+
return;
90+
91+
references.Add(new ReferenceRecord
92+
{
93+
FileId = fileId,
94+
SymbolName = name,
95+
ReferenceKind = "type_reference",
96+
Line = lineNumber,
97+
Column = column,
98+
Context = context.Trim(),
99+
ContainerKind = container?.Kind,
100+
ContainerName = container?.Name,
101+
});
102+
}
103+
}
104+
29105
public static void EmitAdditionalCallReferences(
30106
string preparedLine,
31107
Action<string, int> addCallLikeReference,
32108
IReadOnlySet<string>? definitionNames)
33109
{
110+
foreach (Match match in PipeCallRegex.Matches(preparedLine))
111+
{
112+
var name = match.Groups["name"].Value;
113+
if (!IgnoredPipeCallNames.Contains(name))
114+
addCallLikeReference(name, match.Groups["name"].Index);
115+
}
116+
34117
LanguageReferenceExtractionSupport.EmitAdditionalCallReferences(
35118
"elixir",
36119
preparedLine,

src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1743,6 +1743,7 @@ private enum JavaScriptTypeScriptFunctionHeaderConsumeResult
17431743
new("function", new Regex(@"^\s*(?:def|defp|defmacro|defguardp?)\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.ElixirEnd),
17441744
new("class", new Regex(@"^\s*defmodule\s+(?<name>[\w.]+)", RegexOptions.Compiled), BodyStyle.ElixirEnd),
17451745
new("interface", new Regex(@"^\s*defprotocol\s+(?<name>[\w.]+)", RegexOptions.Compiled), BodyStyle.ElixirEnd),
1746+
new("protocol_impl", new Regex(@"^\s*defimpl\s+(?<name>[\w.]+(?:\s*,\s*for:\s*(?:\[[^\]]+\]|[\w.{}]+))?)", RegexOptions.Compiled), BodyStyle.ElixirEnd),
17461747
new("import", new Regex(@"^\s*(?:import|alias|use|require)\s+(?<name>[\w.]+)", RegexOptions.Compiled), BodyStyle.None),
17471748
],
17481749
["dart"] =
@@ -2115,7 +2116,7 @@ public static IReadOnlyCollection<string> GetSupportedLanguages()
21152116

21162117
private static readonly HashSet<string> ContainerKinds =
21172118
[
2118-
"class", "struct", "interface", "protocol", "namespace", "enum", "object", "heading", "specialization", "class_hook"
2119+
"class", "struct", "interface", "protocol", "protocol_impl", "namespace", "enum", "object", "heading", "specialization", "class_hook"
21192120
];
21202121

21212122
private static bool IsRustDirectTraitBodyMember(List<SymbolRecord> symbols, int candidateLine)

src/CodeIndex/Models/SymbolKindCatalog.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public static class SymbolKindCatalog
4242
"procedure",
4343
"program",
4444
"protocol",
45+
"protocol_impl",
4546
"reference",
4647
"rule",
4748
"route",

0 commit comments

Comments
 (0)