diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 4e0c6579c9..f0f23c733f 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -1757,17 +1757,17 @@ All indexed languages are searchable through FTS5. Rows with **Symbols = yes** a | Scala | `.scala`, `.sc` | yes | | Elixir | `.ex`, `.exs` | yes | | Lua | `.lua` | yes | -| Groovy | `.groovy`, `.gvy`, `.gy`, `.gsh` | -- | -| Crystal | `.cr` | -- | -| Clojure | `.clj`, `.cljs`, `.cljc`, `.edn` | -- | -| D | `.d` | -- | -| Erlang | `.erl`, `.hrl` | -- | -| Julia | `.jl` | -- | -| Nim | `.nim`, `.nims` | -- | -| OCaml | `.ml`, `.mli` | -- | +| Groovy | `.groovy`, `.gvy`, `.gy`, `.gsh` | yes | +| Crystal | `.cr` | yes | +| Clojure | `.clj`, `.cljs`, `.cljc`, `.edn` | yes | +| D | `.d` | yes | +| Erlang | `.erl`, `.hrl` | yes | +| Julia | `.jl` | yes | +| Nim | `.nim`, `.nims` | yes | +| OCaml | `.ml`, `.mli` | yes | | Perl | `.pl`, `.pm`, `.t`, `.pod` | -- | | Solidity | `.sol` | yes | -| Tcl | `.tcl`, `.tk` | -- | +| Tcl | `.tcl`, `.tk` | yes | | R | `.r`, `.R` | yes | | Haskell | `.hs`, `.lhs` | yes | | F# | `.fs`, `.fsx`, `.fsi` | yes | @@ -1792,9 +1792,9 @@ All indexed languages are searchable through FTS5. Rows with **Symbols = yes** a | Common Lisp | `.lisp`, `.lsp`, `.cl` | yes | | Racket | `.rkt` | yes | | Pascal | `.pas`, `.pp`, `.dpr` | -- | -| Ada | `.ada`, `.adb`, `.ads` | -- | +| Ada | `.ada`, `.adb`, `.ads` | yes | | Fortran | `.f`, `.f77`, `.f90`, `.f95`, `.f03`, `.f08`, `.for`, `.ftn` | -- | -| Raku | `.raku`, `.rakumod`, `.rakutest` | -- | +| Raku | `.raku`, `.rakumod`, `.rakutest` | yes | | Perl test | `.t` | -- | | Zig | `.zig` | yes | | XAML | `.xaml`, `.axaml` | yes | @@ -1825,6 +1825,9 @@ All indexed languages are searchable through FTS5. Rows with **Symbols = yes** a - HDL: Verilog, SystemVerilog, and VHDL module/package/type/function/resource declarations are indexed as symbols. References and graph queries are not advertised for HDL yet. - SQL: query-time `--lang tsql` is accepted as a SQL alias, and T-SQL aggregate, assembly, and XML schema collection declarations are searchable. - R: function assignments, S4/R6 class declarations, validity/generic/method declarations, inherit vectors, public/private/active methods, and `library` / `require` imports are indexed. +- Functional symbol-only languages: Clojure, Erlang, OCaml, and Raku expose conservative declarations as symbols. References and graph queries are not advertised for these languages yet. +- Dynamic symbol-only languages: Crystal, Groovy, Julia, and Tcl expose conservative declarations as symbols. References and graph queries are not advertised for these languages yet. +- Systems symbol-only languages: Ada, D, and Nim expose conservative declarations as symbols. References and graph queries are not advertised for these languages yet. - Markdown, JSON/YAML, and CSS: Markdown heading and local-anchor symbols are indexed; JSON/YAML configuration keys are indexed as structural key paths; CSS variables, placeholders, and `@extend` references are indexed. - Dockerfile, Assembly, Common Lisp, and Racket: `ARG` build args, labels/PROC/MACRO blocks, package/module forms, definitions, classes/structs, requires, and provides are surfaced as symbols where applicable. - Shell, PowerShell, and Batch: command-style function calls, functions/filters, classes/enums, imports, labels, `goto` / `call` targets, and inline control-flow forms are indexed where the language supports them. @@ -4239,17 +4242,17 @@ indexing はファイル単位の SQLite transaction を commit します。長 | Scala | `.scala`, `.sc` | yes | | Elixir | `.ex`, `.exs` | yes | | Lua | `.lua` | yes | -| Groovy | `.groovy`, `.gvy`, `.gy`, `.gsh` | -- | -| Crystal | `.cr` | -- | -| Clojure | `.clj`, `.cljs`, `.cljc`, `.edn` | -- | -| D | `.d` | -- | -| Erlang | `.erl`, `.hrl` | -- | -| Julia | `.jl` | -- | -| Nim | `.nim`, `.nims` | -- | -| OCaml | `.ml`, `.mli` | -- | +| Groovy | `.groovy`, `.gvy`, `.gy`, `.gsh` | yes | +| Crystal | `.cr` | yes | +| Clojure | `.clj`, `.cljs`, `.cljc`, `.edn` | yes | +| D | `.d` | yes | +| Erlang | `.erl`, `.hrl` | yes | +| Julia | `.jl` | yes | +| Nim | `.nim`, `.nims` | yes | +| OCaml | `.ml`, `.mli` | yes | | Perl | `.pl`, `.pm`, `.t`, `.pod` | -- | | Solidity | `.sol` | yes | -| Tcl | `.tcl`, `.tk` | -- | +| Tcl | `.tcl`, `.tk` | yes | | R | `.r`, `.R` | yes | | Haskell | `.hs`, `.lhs` | yes | | F# | `.fs`, `.fsx`, `.fsi` | yes | @@ -4274,9 +4277,9 @@ indexing はファイル単位の SQLite transaction を commit します。長 | Common Lisp | `.lisp`, `.lsp`, `.cl` | yes | | Racket | `.rkt` | yes | | Pascal | `.pas`, `.pp`, `.dpr` | -- | -| Ada | `.ada`, `.adb`, `.ads` | -- | +| Ada | `.ada`, `.adb`, `.ads` | yes | | Fortran | `.f`, `.f77`, `.f90`, `.f95`, `.f03`, `.f08`, `.for`, `.ftn` | -- | -| Raku | `.raku`, `.rakumod`, `.rakutest` | -- | +| Raku | `.raku`, `.rakumod`, `.rakutest` | yes | | Perl test | `.t` | -- | | Zig | `.zig` | yes | | XAML | `.xaml`, `.axaml` | yes | @@ -4307,6 +4310,9 @@ indexing はファイル単位の SQLite transaction を commit します。長 - HDL: Verilog、SystemVerilog、VHDL の module / package / type / function / resource 宣言をシンボルとして索引します。HDL の references と graph queries はまだ対応として広告しません。 - SQL: クエリ時の `--lang tsql` は SQL の別名です。T-SQL の aggregate、assembly、XML schema collection 宣言も検索対象です。 - R: 関数代入、S4/R6 class 宣言、validity/generic/method 宣言、inherit vector、public/private/active method、`library` / `require` import を索引します。 +- 関数型言語のシンボル専用対応: Clojure、Erlang、OCaml、Raku は保守的な宣言をシンボルとして公開します。これらの言語では references と graph queries はまだ対応として広告しません。 +- 動的言語のシンボル専用対応: Crystal、Groovy、Julia、Tcl は保守的な宣言をシンボルとして公開します。これらの言語では references と graph queries はまだ対応として広告しません。 +- システム系言語のシンボル専用対応: Ada、D、Nim は保守的な宣言をシンボルとして公開します。これらの言語では references と graph queries はまだ対応として広告しません。 - Markdown、JSON/YAML、CSS: Markdown の heading / local anchor、JSON/YAML の configuration key path、CSS の variable、placeholder、`@extend` をシンボルとして扱います。 - Dockerfile、Assembly、Common Lisp、Racket: `ARG` build arg、label、PROC/MACRO、package/module form、definition、class/struct、require/provide を必要に応じて表面化します。 - Shell、PowerShell、Batch: command-style function call、function/filter、class/enum、import、label、`goto` / `call` target、inline control-flow を言語仕様に合わせて索引します。 diff --git a/changelog.d/unreleased/3527.added.md b/changelog.d/unreleased/3527.added.md new file mode 100644 index 0000000000..4da9583421 --- /dev/null +++ b/changelog.d/unreleased/3527.added.md @@ -0,0 +1,17 @@ +--- +category: added +issues: + - 3527 +affected: + - src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs + - tests/CodeIndex.Tests/SymbolExtractorTests.cs + - tests/CodeIndex.Tests/QueryCommandRunnerTests.cs +--- + +## English + +- **Functional language symbol extraction now covers Clojure, Erlang, OCaml, and Raku (#3527)** — `cdidx` now indexes conservative namespace/module, type, function, and binding symbols for these languages while keeping graph/reference support disabled until dedicated reference extractors are added. + +## 日本語 + +- **関数型言語のシンボル抽出が Clojure、Erlang、OCaml、Raku を扱うようになりました (#3527)** — `cdidx` はこれらの言語で保守的な namespace/module、type、function、binding シンボルをインデックスし、専用の参照抽出器が追加されるまでは graph/reference 対応を無効のままにします。 diff --git a/changelog.d/unreleased/3528.added.md b/changelog.d/unreleased/3528.added.md new file mode 100644 index 0000000000..3d99328016 --- /dev/null +++ b/changelog.d/unreleased/3528.added.md @@ -0,0 +1,17 @@ +--- +category: added +issues: + - 3528 +affected: + - src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs + - tests/CodeIndex.Tests/SymbolExtractorTests.cs + - tests/CodeIndex.Tests/QueryCommandRunnerTests.cs +--- + +## English + +- **Dynamic language symbol extraction now covers Crystal, Groovy, Julia, and Tcl (#3528)** — `cdidx` now indexes conservative package/module, type, function, import, and binding symbols for these languages while keeping graph/reference support disabled until dedicated reference extractors are added. + +## 日本語 + +- **動的言語のシンボル抽出が Crystal、Groovy、Julia、Tcl を扱うようになりました (#3528)** — `cdidx` はこれらの言語で保守的な package/module、type、function、import、binding シンボルをインデックスし、専用の参照抽出器が追加されるまでは graph/reference 対応を無効のままにします。 diff --git a/changelog.d/unreleased/3529.added.md b/changelog.d/unreleased/3529.added.md new file mode 100644 index 0000000000..d55836738d --- /dev/null +++ b/changelog.d/unreleased/3529.added.md @@ -0,0 +1,17 @@ +--- +category: added +issues: + - 3529 +affected: + - src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs + - tests/CodeIndex.Tests/SymbolExtractorTests.cs + - tests/CodeIndex.Tests/QueryCommandRunnerTests.cs +--- + +## English + +- **Systems language symbol extraction now covers Ada, D, and Nim (#3529)** — `cdidx` now indexes conservative package/module, type, function, import, and binding symbols for these languages while keeping graph/reference support disabled until dedicated reference extractors are added. + +## 日本語 + +- **システム系言語のシンボル抽出が Ada、D、Nim を扱うようになりました (#3529)** — `cdidx` はこれらの言語で保守的な package/module、type、function、import、binding シンボルをインデックスし、専用の参照抽出器が追加されるまでは graph/reference 対応を無効のままにします。 diff --git a/src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs b/src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs index 0b96798cad..5decd153c5 100644 --- a/src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs +++ b/src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs @@ -19,6 +19,9 @@ public static partial class SymbolExtractor public const int CSharpContractVersion = 2; public const int DockerfileContractVersion = 2; public const int StyleAndXamlContractVersion = 2; + public const int FunctionalLanguageContractVersion = 2; + public const int DynamicLanguageContractVersion = 2; + public const int SystemsLanguageContractVersion = 2; private static readonly Regex GraphQLInputBlockRegex = new( @"^\s*(?:extend\s+)?input\s+(?\w+)[^{]*\{(?.*?)^\s*\}", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Multiline | RegexOptions.Singleline); @@ -55,6 +58,9 @@ public static int GetContractVersion(string? lang) "csharp" => CSharpContractVersion, "dockerfile" => DockerfileContractVersion, "sass" or "stylus" or "xml" => StyleAndXamlContractVersion, + "clojure" or "erlang" or "ocaml" or "raku" => FunctionalLanguageContractVersion, + "crystal" or "groovy" or "julia" or "tcl" => DynamicLanguageContractVersion, + "ada" or "d" or "nim" => SystemsLanguageContractVersion, "cmake" or "graphql" or "html" or "json" or "justfile" or "markdown" or "msbuild" or "yaml" => ExpandedLanguageContractVersion, _ => DefaultContractVersion, }; @@ -1490,6 +1496,75 @@ private enum JavaScriptTypeScriptFunctionHeaderConsumeResult new("class", new Regex(@"^\s*module\s+(?[A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)", RegexOptions.Compiled), BodyStyle.RubyEnd), new("import", new Regex(@"^\s*require(?:_relative)?\s+(?.+)", RegexOptions.Compiled), BodyStyle.None), ], + ["crystal"] = + [ + new("namespace", new Regex(@"^\s*module\s+(?[A-Z]\w*(?:::[A-Z]\w*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("class", new Regex(@"^\s*(?:abstract\s+)?class\s+(?[A-Z]\w*(?:::[A-Z]\w*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("struct", new Regex(@"^\s*struct\s+(?[A-Z]\w*(?:::[A-Z]\w*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("enum", new Regex(@"^\s*enum\s+(?[A-Z]\w*(?:::[A-Z]\w*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("function", new Regex(@"^\s*(?:private\s+|protected\s+)?def\s+(?:self\.)?(?[A-Za-z_]\w*[?!=]?)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("function", new Regex(@"^\s*macro\s+(?[A-Za-z_]\w*[?!=]?)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("typealias", new Regex(@"^\s*alias\s+(?[A-Z]\w*)\s*=", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("import", new Regex(@"^\s*require\s+(?.+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["groovy"] = + [ + new("namespace", new Regex(@"^\s*package\s+(?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("interface", new Regex(@"^\s*(?:(?:public|private|protected|static|abstract)\s+)*(?:interface|trait)\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("enum", new Regex(@"^\s*(?:(?:public|private|protected|static)\s+)*enum\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("class", new Regex(@"^\s*(?:(?:public|private|protected|static|abstract|final)\s+)*class\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("function", new Regex(@"^\s*(?!(?:if|for|while|switch|catch|return|throw|new)\b)(?:(?:public|private|protected|static|final|abstract|synchronized|native)\s+)*(?def|void|boolean|byte|char|short|int|long|float|double|BigDecimal|BigInteger|String|[\w.$<>?\[\]]+)\s+(?[A-Za-z_]\w*)\s*\(", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace, ReturnTypeGroup: "returnType"), + new("lambda", new Regex(@"^\s*(?:def\s+)?(?[A-Za-z_]\w*)\s*=\s*\{", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("import", new Regex(@"^\s*import\s+(?:static\s+)?(?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?:\.\*)?)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["julia"] = + [ + new("namespace", new Regex(@"^\s*(?:baremodule|module)\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.ElixirEnd), + new("struct", new Regex(@"^\s*(?:mutable\s+)?struct\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.ElixirEnd), + new("type", new Regex(@"^\s*(?:abstract|primitive)\s+type\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.ElixirEnd), + new("function", new Regex(@"^\s*function\s+(?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)?)\s*(?:\(|\{)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.ElixirEnd), + new("function", new Regex(@"^\s*macro\s+(?[A-Za-z_]\w*)\s*(?:\(|$)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.ElixirEnd), + new("function", new Regex(@"^\s*(?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)?)\s*\([^)\r\n]*\)\s*=", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("property", new Regex(@"^\s*const\s+(?[A-Z_]\w*)\s*=", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("import", new Regex(@"^\s*(?:using|import)\s+(?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["tcl"] = + [ + new("namespace", new Regex(@"^\s*namespace\s+eval\s+(?[A-Za-z_:][\w:.-]*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("class", new Regex(@"^\s*oo::class\s+create\s+(?[A-Za-z_:][\w:.-]*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("function", new Regex(@"^\s*proc\s+(?[A-Za-z_:][\w:.-]*)\s+", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("property", new Regex(@"^\s*(?:variable|set)\s+(?[A-Za-z_:][\w:.-]*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("import", new Regex(@"^\s*package\s+(?:require|provide)\s+(?[A-Za-z_:][\w:.-]*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["ada"] = + [ + new("namespace", new Regex(@"^\s*package\s+(?:body\s+)?(?[A-Za-z]\w*(?:\.[A-Za-z]\w*)*)\s+is\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), BodyStyle.PascalEnd), + new("type", new Regex(@"^\s*(?:subtype|type)\s+(?[A-Za-z]\w*)\s+is\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), BodyStyle.None), + new("type", new Regex(@"^\s*(?:task|protected)\s+(?:type\s+)?(?[A-Za-z]\w*)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), BodyStyle.PascalEnd), + new("function", new Regex(@"^\s*(?:(?:overriding|not\s+overriding)\s+)?(?:function|procedure)\s+(?:(?:[A-Za-z]\w*)\.)*(?[A-Za-z]\w*)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), BodyStyle.PascalEnd), + new("import", new Regex(@"^\s*with\s+(?[A-Za-z]\w*(?:\.[A-Za-z]\w*)*)\s*;", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["d"] = + [ + new("namespace", new Regex(@"^\s*module\s+(?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\s*;", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("interface", new Regex(@"^\s*(?:(?:public|private|protected|package|static|abstract|extern)\s+)*interface\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("class", new Regex(@"^\s*(?:(?:public|private|protected|package|static|abstract|final|extern)\s+)*class\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("struct", new Regex(@"^\s*(?:(?:public|private|protected|package|static|extern)\s+)*struct\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("union", new Regex(@"^\s*(?:(?:public|private|protected|package|static|extern)\s+)*union\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("enum", new Regex(@"^\s*(?:(?:public|private|protected|package|static)\s+)*enum\s+(?[A-Za-z_]\w*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace), + new("typealias", new Regex(@"^\s*(?:alias|typedef)\s+(?[A-Za-z_]\w*)\s*=", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("function", new Regex(@"^\s*(?!(?:if|for|while|switch|catch|return|throw|new|assert|version|debug)\b)(?:(?:public|private|protected|package|static|extern|export|final|abstract|override|synchronized|pure|nothrow|@safe|@trusted|@system)\s+)*(?(?:auto|void|bool|byte|ubyte|short|ushort|int|uint|long|ulong|cent|ucent|float|double|real|char|wchar|dchar|string|[A-Za-z_][\w.]*)(?:\s*[*\[\]])*)\s+(?[A-Za-z_]\w*)\s*\(", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Brace, ReturnTypeGroup: "returnType"), + new("import", new Regex(@"^\s*import\s+(?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["nim"] = + [ + new("type", new Regex(@"^\s*type\s+(?[A-Za-z_]\w*)\*?\s*=\s*(?:ref\s+)?(?:object|enum|tuple|distinct)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Indent), + new("type", new Regex(@"^\s+(?[A-Za-z_]\w*)\*?\s*=\s*(?:ref\s+)?(?:object|enum|tuple|distinct)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Indent), + new("function", new Regex(@"^\s*(?:proc|func|method|iterator|template|macro|converter)\s+(?`[^`\r\n]+`|[A-Za-z_]\w*)\*?", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.Indent), + new("property", new Regex(@"^\s*(?:const|let|var)\s+(?[A-Za-z_]\w*)\*?\s*(?::|=)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("import", new Regex(@"^\s*(?:import|include)\s+(?[A-Za-z_][\w./]*(?:\s*,\s*[A-Za-z_][\w./]*)*)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("import", new Regex(@"^\s*from\s+(?[A-Za-z_][\w./]*)\s+import\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], ["perl"] = [ // Perl package declarations / Perl の package 宣言 @@ -1741,6 +1816,42 @@ private enum JavaScriptTypeScriptFunctionHeaderConsumeResult new("protocol_impl", new Regex(@"^\s*defimpl\s+(?[\w.]+(?:\s*,\s*for:\s*(?:\[[^\]]+\]|[\w.{}]+))?)", RegexOptions.Compiled), BodyStyle.ElixirEnd), new("import", new Regex(@"^\s*(?:import|alias|use|require)\s+(?[\w.]+)", RegexOptions.Compiled), BodyStyle.None), ], + ["clojure"] = + [ + // Clojure forms are parenthesized, so use conservative line anchors. + // Clojure の form は括弧ベースなので、保守的な行アンカーだけを拾う。 + new("namespace", new Regex(@"^\s*\(\s*ns\s+(?[^\s\)\[\{]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("class", new Regex(@"^\s*\(\s*(?:defrecord|deftype)\s+(?[^\s\)\[\{]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("protocol", new Regex(@"^\s*\(\s*defprotocol\s+(?[^\s\)\[\{]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("function", new Regex(@"^\s*\(\s*(?:defn-?|defmacro|defmulti|defmethod)\s+(?[^\s\)\[\{]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("property", new Regex(@"^\s*\(\s*(?:def|defonce)\s+(?[^\s\)\[\{]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["erlang"] = + [ + new("namespace", new Regex(@"^\s*-module\s*\(\s*(?[a-z][\w@]*|'[^'\r\n]+')\s*\)\s*\.", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("struct", new Regex(@"^\s*-record\s*\(\s*(?[a-z][\w@]*|'[^'\r\n]+')\s*,", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("type", new Regex(@"^\s*-(?:type|opaque)\s+(?[a-z][\w@]*|'[^'\r\n]+')\s*(?:\(|::)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("function", new Regex(@"^\s*(?[a-z][\w@]*|'[^'\r\n]+')\s*\([^)\r\n]*\)\s*(?:when\b[^-\r\n]*)?->", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("import", new Regex(@"^\s*-(?:import|include(?:_lib)?)\s*\(\s*(?[^)\r\n]+)\)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["ocaml"] = + [ + new("namespace", new Regex(@"^\s*module\s+(?:type\s+)?(?[A-Z][A-Za-z0-9_']*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("class", new Regex(@"^\s*class(?:\s+type)?\s+(?[A-Za-z_][A-Za-z0-9_']*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("type", new Regex(@"^\s*type\s+(?:nonrec\s+)?(?:'[\w]+\s+)*(?[A-Za-z_][A-Za-z0-9_']*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("function", new Regex(@"^\s*let\s+(?:rec\s+)?(?[A-Za-z_][A-Za-z0-9_']*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("function", new Regex(@"^\s*val\s+(?[A-Za-z_][A-Za-z0-9_']*)\s*:", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("import", new Regex(@"^\s*open\s+(?[A-Z][\w.']*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], + ["raku"] = + [ + new("namespace", new Regex(@"^\s*(?:unit\s+)?(?:module|package)\s+(?[\w:.-]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("interface", new Regex(@"^\s*(?:unit\s+)?role\s+(?[\w:.-]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("class", new Regex(@"^\s*(?:unit\s+)?(?:class|grammar)\s+(?[\w:.-]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("enum", new Regex(@"^\s*enum\s+(?[\w:.-]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + new("function", new Regex(@"^\s*(?:(?:my|our|multi|proto|only)\s+)*(?:sub|method|submethod|macro)\s+(?[\w:!?.-]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.RubyEnd), + new("property", new Regex(@"^\s*(?:(?:my|our|state|constant)\s+)*(?[$@%&]\w[\w-]*)\s*=", RegexOptions.Compiled | RegexOptions.CultureInvariant), BodyStyle.None), + ], ["dart"] = [ new("function", new Regex(@"^\s*(?!return\b|await\b|const\b|new\b|throw\b|yield\b|if\b|else\b|for\b|while\b|switch\b|case\b|catch\b|do\b|try\b|finally\b|class\b|enum\b|mixin\b|extension\b|typedef\b|library\b|part\b|import\b|export\b)(?:(?:static|abstract|override|external)\s+)*(?\w[\w<>,\s\?]*?)\s+(?(?!if\b|else\b|for\b|while\b|switch\b|case\b|class\b|enum\b|mixin\b|extension\b|typedef\b|library\b|part\b|import\b|export\b|abstract\b|void\b|var\b|final\b|late\b|const\b|new\b|return\b|throw\b|yield\b|await\b|extends\b|implements\b|with\b|on\b|is\b|as\b|in\b|of\b|super\b|this\b)\w+)\s*\(", RegexOptions.Compiled), BodyStyle.Brace, ReturnTypeGroup: "rt"), diff --git a/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs b/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs index 8e2e71bc18..d3131c09f1 100644 --- a/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs +++ b/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs @@ -2132,9 +2132,10 @@ public void RunLanguages_JsonCapabilitySearchOnlyFiltersAllExtractionGaps() using var document = ParseJsonOutput(stdout); var languages = document.RootElement.GetProperty("languages").EnumerateArray().ToList(); + var names = languages.Select(lang => lang.GetProperty("lang").GetString()).ToList(); Assert.NotEmpty(languages); - Assert.Contains(languages, lang => lang.GetProperty("lang").GetString() == "groovy"); + Assert.DoesNotContain("groovy", names); Assert.All(languages, lang => { Assert.False(lang.GetProperty("symbol_extraction").GetBoolean()); @@ -2326,12 +2327,11 @@ public void RunLanguages_JsonListsCSharpRazorAliases() [Fact] public void RunLanguages_Json_SearchOnlyBucketsAdvertiseZeroSymbolAndGraphSupport() { - // Search-only languages that intentionally live outside richer extractors - // must advertise symbol_extraction=false / graph_queries=false so AI clients can tell the difference - // between "indexed with symbols" and "indexed for search only". - // 意図的に richer な抽出器の対象外になっている search-only 言語 - // symbol_extraction=false / graph_queries=false で広告しなければならない。 - // こうしないと、AI クライアントが「シンボル付きインデックス」と「検索のみインデックス」を区別できない。 + // Languages that have conservative symbol extractors but no dedicated reference + // extractors must advertise symbol_extraction=true while keeping graph/reference + // support disabled. + // 保守的な symbol extractor はあるが専用の reference extractor がない言語は、 + // symbol_extraction=true としつつ graph/reference 対応を無効のまま広告する。 var (exitCode, stdout, stderr) = CaptureConsole(() => QueryCommandRunner.RunLanguages(["--json"], _jsonOptions)); Assert.Equal(CommandExitCodes.Success, exitCode); Assert.Equal(string.Empty, stderr); @@ -2340,16 +2340,21 @@ public void RunLanguages_Json_SearchOnlyBucketsAdvertiseZeroSymbolAndGraphSuppor var languages = document.RootElement.GetProperty("languages").EnumerateArray() .ToDictionary(entry => entry.GetProperty("lang").GetString()!, entry => entry); - foreach (var searchOnly in new[] { "crystal", "clojure", "d", "erlang", "julia", "nim", "ocaml", "tcl" }) + foreach (var symbolOnly in new[] { "ada", "clojure", "crystal", "d", "erlang", "groovy", "julia", "nim", "ocaml", "raku", "tcl" }) { - Assert.True(languages.ContainsKey(searchOnly), $"expected '{searchOnly}' to be listed"); - var entry = languages[searchOnly]; - Assert.False(entry.GetProperty("symbol_extraction").GetBoolean(), - $"{searchOnly} must advertise symbol_extraction=false"); + Assert.True(languages.ContainsKey(symbolOnly), $"expected '{symbolOnly}' to be listed"); + var entry = languages[symbolOnly]; + Assert.True(entry.GetProperty("symbol_extraction").GetBoolean(), + $"{symbolOnly} must advertise symbol_extraction=true"); Assert.False(entry.GetProperty("reference_extraction").GetBoolean(), - $"{searchOnly} must advertise reference_extraction=false"); + $"{symbolOnly} must advertise reference_extraction=false"); Assert.False(entry.GetProperty("graph_queries").GetBoolean(), - $"{searchOnly} must advertise graph_queries=false"); + $"{symbolOnly} must advertise graph_queries=false"); + + var gaps = entry.GetProperty("capability_gaps").EnumerateArray().Select(gap => gap.GetString()).ToList(); + Assert.DoesNotContain("missing-symbols", gaps); + Assert.Contains("missing-references", gaps); + Assert.Contains("missing-graph", gaps); } var yamlAliases = languages["yaml"].GetProperty("aliases").EnumerateArray() diff --git a/tests/CodeIndex.Tests/SymbolExtractorTests.cs b/tests/CodeIndex.Tests/SymbolExtractorTests.cs index 690428544d..5f5621333c 100644 --- a/tests/CodeIndex.Tests/SymbolExtractorTests.cs +++ b/tests/CodeIndex.Tests/SymbolExtractorTests.cs @@ -511,9 +511,209 @@ public void Extract_Yaml_IndexesIndentedConfigurationKeyPaths() Assert.DoesNotContain(symbols, symbol => symbol.Name.Contains("jobs.fake", StringComparison.Ordinal)); } + [Fact] + public void Extract_FunctionalLanguages_IndexConservativeDeclarations_Issue3527() + { + const string clojure = """ + (ns demo.core) + (defrecord User [id name]) + (defprotocol Store + (save! [this value])) + (defn load-user [id] + id) + (defonce cache (atom {})) + """; + + var clojureSymbols = SymbolExtractor.Extract(1, "clojure", clojure); + Assert.Contains(clojureSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "demo.core"); + Assert.Contains(clojureSymbols, symbol => symbol.Kind == "class" && symbol.Name == "User"); + Assert.Contains(clojureSymbols, symbol => symbol.Kind == "protocol" && symbol.Name == "Store"); + Assert.Contains(clojureSymbols, symbol => symbol.Kind == "function" && symbol.Name == "load-user"); + Assert.Contains(clojureSymbols, symbol => symbol.Kind == "property" && symbol.Name == "cache"); + + const string erlang = """ + -module(sample_app). + -record(user, {id, name}). + -type user_id() :: integer(). + load_user(Id) -> + Id. + """; + + var erlangSymbols = SymbolExtractor.Extract(2, "erlang", erlang); + Assert.Contains(erlangSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "sample_app"); + Assert.Contains(erlangSymbols, symbol => symbol.Kind == "struct" && symbol.Name == "user"); + Assert.Contains(erlangSymbols, symbol => symbol.Kind == "type" && symbol.Name == "user_id"); + Assert.Contains(erlangSymbols, symbol => symbol.Kind == "function" && symbol.Name == "load_user"); + + const string ocaml = """ + module Store = struct + type user = { id : int } + let rec find_user id = id + val save_user : user -> unit + open Core + """; + + var ocamlSymbols = SymbolExtractor.Extract(3, "ocaml", ocaml); + Assert.Contains(ocamlSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "Store"); + Assert.Contains(ocamlSymbols, symbol => symbol.Kind == "type" && symbol.Name == "user"); + Assert.Contains(ocamlSymbols, symbol => symbol.Kind == "function" && symbol.Name == "find_user"); + Assert.Contains(ocamlSymbols, symbol => symbol.Kind == "function" && symbol.Name == "save_user"); + Assert.Contains(ocamlSymbols, symbol => symbol.Kind == "import" && symbol.Name == "Core"); + + const string raku = """ + unit module Demo::Store; + role Persistable { } + class User { } + sub load-user($id) { $id } + my $cache = {}; + """; + + var rakuSymbols = SymbolExtractor.Extract(4, "raku", raku); + Assert.Contains(rakuSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "Demo::Store"); + Assert.Contains(rakuSymbols, symbol => symbol.Kind == "interface" && symbol.Name == "Persistable"); + Assert.Contains(rakuSymbols, symbol => symbol.Kind == "class" && symbol.Name == "User"); + Assert.Contains(rakuSymbols, symbol => symbol.Kind == "function" && symbol.Name == "load-user"); + Assert.Contains(rakuSymbols, symbol => symbol.Kind == "property" && symbol.Name == "$cache"); + } + + [Fact] + public void Extract_DynamicLanguages_IndexConservativeDeclarations_Issue3528() + { + const string crystal = """ + require "json" + module Demo + class User + def load_user(id) + id + end + end + struct Point + end + end + """; + + var crystalSymbols = SymbolExtractor.Extract(1, "crystal", crystal); + Assert.Contains(crystalSymbols, symbol => symbol.Kind == "import" && symbol.Name == "\"json\""); + Assert.Contains(crystalSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "Demo"); + Assert.Contains(crystalSymbols, symbol => symbol.Kind == "class" && symbol.Name == "User"); + Assert.Contains(crystalSymbols, symbol => symbol.Kind == "struct" && symbol.Name == "Point"); + Assert.Contains(crystalSymbols, symbol => symbol.Kind == "function" && symbol.Name == "load_user"); + + const string groovy = """ + package demo.app + import groovy.transform.CompileStatic + trait Persistable {} + class UserService { + def loadUser(id) { id } + handler = { event -> event } + } + """; + var groovySymbols = SymbolExtractor.Extract(2, "groovy", groovy); + Assert.Contains(groovySymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "demo.app"); + Assert.Contains(groovySymbols, symbol => symbol.Kind == "import" && symbol.Name == "groovy.transform.CompileStatic"); + Assert.Contains(groovySymbols, symbol => symbol.Kind == "interface" && symbol.Name == "Persistable"); + Assert.Contains(groovySymbols, symbol => symbol.Kind == "class" && symbol.Name == "UserService"); + Assert.Contains(groovySymbols, symbol => symbol.Kind == "function" && symbol.Name == "loadUser"); + Assert.Contains(groovySymbols, symbol => symbol.Kind == "lambda" && symbol.Name == "handler"); + const string julia = """ + module DemoStore + struct User + id::Int + end + function load_user(id) + id + end + macro timed(ex) + ex + end + const CACHE = Dict() + end + """; + + var juliaSymbols = SymbolExtractor.Extract(3, "julia", julia); + Assert.Contains(juliaSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "DemoStore"); + Assert.Contains(juliaSymbols, symbol => symbol.Kind == "struct" && symbol.Name == "User"); + Assert.Contains(juliaSymbols, symbol => symbol.Kind == "function" && symbol.Name == "load_user"); + Assert.Contains(juliaSymbols, symbol => symbol.Kind == "function" && symbol.Name == "timed"); + Assert.Contains(juliaSymbols, symbol => symbol.Kind == "property" && symbol.Name == "CACHE"); + + const string tcl = """ + package require Tcl 8.6 + namespace eval demo {} + oo::class create User {} + proc load_user {id} { return $id } + variable cache + """; + var tclSymbols = SymbolExtractor.Extract(4, "tcl", tcl); + Assert.Contains(tclSymbols, symbol => symbol.Kind == "import" && symbol.Name == "Tcl"); + Assert.Contains(tclSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "demo"); + Assert.Contains(tclSymbols, symbol => symbol.Kind == "class" && symbol.Name == "User"); + Assert.Contains(tclSymbols, symbol => symbol.Kind == "function" && symbol.Name == "load_user"); + Assert.Contains(tclSymbols, symbol => symbol.Kind == "property" && symbol.Name == "cache"); + } + + [Fact] + public void Extract_SystemsLanguages_IndexConservativeDeclarations_Issue3529() + { + const string ada = """ + with Ada.Text_IO; + package body Demo.Store is + type User_Id is new Integer; + protected type Cache is + end Cache; + procedure Load_User(Id : User_Id) is + begin + null; + end Load_User; + end Demo.Store; + """; + + var adaSymbols = SymbolExtractor.Extract(1, "ada", ada); + Assert.Contains(adaSymbols, symbol => symbol.Kind == "import" && symbol.Name == "Ada.Text_IO"); + Assert.Contains(adaSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "Demo.Store"); + Assert.Contains(adaSymbols, symbol => symbol.Kind == "type" && symbol.Name == "User_Id"); + Assert.Contains(adaSymbols, symbol => symbol.Kind == "type" && symbol.Name == "Cache"); + Assert.Contains(adaSymbols, symbol => symbol.Kind == "function" && symbol.Name == "Load_User"); + + const string d = """ + module demo.store; + import std.json; + class UserService { + } + struct Point { + } + alias UserId = int; + int loadUser(int id) { + return id; + } + """; + + var dSymbols = SymbolExtractor.Extract(2, "d", d); + Assert.Contains(dSymbols, symbol => symbol.Kind == "namespace" && symbol.Name == "demo.store"); + Assert.Contains(dSymbols, symbol => symbol.Kind == "import" && symbol.Name == "std.json"); + Assert.Contains(dSymbols, symbol => symbol.Kind == "class" && symbol.Name == "UserService"); + Assert.Contains(dSymbols, symbol => symbol.Kind == "struct" && symbol.Name == "Point"); + Assert.Contains(dSymbols, symbol => symbol.Kind == "typealias" && symbol.Name == "UserId"); + Assert.Contains(dSymbols, symbol => symbol.Kind == "function" && symbol.Name == "loadUser"); + + const string nim = """ + import std/json + type User* = object + id*: int + proc loadUser*(id: int): int = + id + const Cache* = 1 + """; + + var nimSymbols = SymbolExtractor.Extract(3, "nim", nim); + Assert.Contains(nimSymbols, symbol => symbol.Kind == "import" && symbol.Name == "std/json"); + Assert.Contains(nimSymbols, symbol => symbol.Kind == "type" && symbol.Name == "User"); + Assert.Contains(nimSymbols, symbol => symbol.Kind == "function" && symbol.Name == "loadUser"); + Assert.Contains(nimSymbols, symbol => symbol.Kind == "property" && symbol.Name == "Cache"); + } [Theory] [InlineData("csharp", "Pages/Product.razor")] @@ -603,36 +803,45 @@ public void Extract_AllPatternLanguages_IsDeterministicUnderParallelCalls() { var samples = new Dictionary(StringComparer.Ordinal) { + ["ada"] = "with Ada.Text_IO;\npackage Demo is\nprocedure Run;\nend Demo;\n", ["assembly"] = "Start:\n call Target\nTarget:\n ret\n", ["batch"] = ":run\necho ok\n", ["c"] = "int answer(void) { return 42; }\n", + ["clojure"] = "(ns demo.core)\n(defn run [x] x)\n", ["cmake"] = "add_library(core core.cpp)\noption(ENABLE_TESTS \"tests\" ON)\n", ["cobol"] = " IDENTIFICATION DIVISION.\n PROGRAM-ID. HELLO.\n", ["cpp"] = "namespace demo { int answer() { return 42; } }\n", ["csharp"] = "namespace Demo; public class Service { public int Run() => 1; }\n", + ["crystal"] = "module Demo\n def run\n 1\n end\nend\n", ["css"] = ".card { color: red; }\n@keyframes fade { from { opacity: 0; } }\n", ["cython"] = "cdef class Service:\n cpdef int run(self):\n return 1\n", + ["d"] = "module demo;\nint run() { return 1; }\n", ["dart"] = "class Service { int run() => 1; }\n", ["dockerfile"] = "FROM alpine AS build\nARG VERSION=1\n", ["elixir"] = "defmodule Demo do\n def run do\n :ok\n end\nend\n", + ["erlang"] = "-module(demo).\nrun() -> ok.\n", ["fortran"] = "module demo\ncontains\nsubroutine run()\nend subroutine\nend module\n", ["fsharp"] = "module Demo\nlet run x = x + 1\n", ["go"] = "package demo\nfunc Run() int { return 1 }\n", ["gradle"] = "task buildDocs {\n}\n", ["glsl"] = "uniform mat4 model;\nvec4 run(vec4 value) { return model * value; }\n", ["graphql"] = "type Query { answer: Int }\nquery GetAnswer { answer }\n", + ["groovy"] = "package demo\nclass Service { def run() { 1 } }\n", ["haskell"] = "module Demo where\nrun :: Int -> Int\nrun x = x + 1\n", ["html"] = "
\n\n", ["hlsl"] = "cbuffer Params { float4 color; }\nfloat4 run(float4 value) { return value * color; }\n", ["java"] = "package demo; public class Service { int run() { return 1; } }\n", ["javascript"] = "export function run() { return 1; }\n", + ["julia"] = "module Demo\nfunction run()\n 1\nend\nend\n", ["justfile"] = "build:\n echo build\n", ["kotlin"] = "package demo\nclass Service { fun run(): Int = 1 }\n", ["lua"] = "local function run()\n return 1\nend\n", ["makefile"] = "build:\n\t@echo build\n", ["metal"] = "struct VertexOut { float4 position; };\nvertex VertexOut run(uint id) { return VertexOut(); }\n", ["msbuild"] = "\n", + ["nim"] = "proc run*(): int =\n 1\n", ["objc"] = "@interface Service\n- (void)run;\n@end\n", + ["ocaml"] = "module Demo = struct\nlet run x = x\nend\n", ["pascal"] = "unit Demo;\ninterface\nprocedure Run;\nimplementation\nprocedure Run; begin end;\nend.\n", ["perl"] = "package Demo;\nsub run { return 1; }\n", ["php"] = " i32 { 1 }\n", ["sass"] = "$primary: #3366cc\n@mixin rounded($radius)\n.button\n +rounded(4px)\n", @@ -652,6 +862,7 @@ public void Extract_AllPatternLanguages_IsDeterministicUnderParallelCalls() ["svelte"] = "\n", ["swift"] = "class Service { func run() -> Int { 1 } }\n", ["systemverilog"] = "module demo #(parameter int WIDTH = 8) (input logic clk);\nfunction int run(); return WIDTH; endfunction\nendmodule\n", + ["tcl"] = "namespace eval demo {}\nproc run {} { return 1 }\n", ["terraform"] = "resource \"local_file\" \"demo\" {\n filename = \"demo.txt\"\n}\n", ["typescript"] = "export class Service { run(): number { return 1; } }\n", ["vb"] = "Public Class Service\n Public Sub Run()\n End Sub\nEnd Class\n",