@@ -361,6 +361,18 @@ public static int GetContractVersion(string? lang)
361361 private static readonly Regex SqlCteDefinitionRegex = new(
362362 $@"(?<![\w$])(?:WITH\s+(?:RECURSIVE\s+)?|,\s*)(?<name>{SqlQualifiedIdentifierSegmentPattern})(?:\s*\([^)]*\))?\s+AS\s*\(",
363363 RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
364+ private static readonly Regex SqlAlterTableAddGeneratedColumnRegex = new(
365+ $@"(?<![\w$])ALTER\s+TABLE\s+(?<table>{SqlQualifiedIdentifierPattern})\s+ADD(?:\s+COLUMN)?\s+(?!CONSTRAINT\b)(?<name>{SqlQualifiedIdentifierSegmentPattern})\b(?=[^;]*?\b(?:GENERATED\s+(?:ALWAYS\s+)?AS|AS\s*\(|DEFAULT\s+NEXT\s+VALUE\s+FOR)\b)",
366+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
367+ private static readonly Regex SqlCreateTableBodyRegex = new(
368+ $@"(?<![\w$])CREATE\s+(?:OR\s+(?:REPLACE|ALTER)\s+)?(?:(?:(?:GLOBAL|LOCAL)\s+)?(?:TEMP|TEMPORARY)\s+|UNLOGGED\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?<table>{SqlQualifiedIdentifierPattern})\s*\((?<body>[\s\S]*?)\)\s*;",
369+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
370+ private static readonly Regex SqlGeneratedColumnDefinitionMarkerRegex = new(
371+ @"\b(?:GENERATED\s+(?:ALWAYS\s+)?AS|AS\s*\(|DEFAULT\s+NEXT\s+VALUE\s+FOR)\b",
372+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
373+ private static readonly Regex SqlColumnDefinitionNameRegex = new(
374+ $@"^\s*(?<name>{SqlQualifiedIdentifierSegmentPattern})\b",
375+ RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
364376 private static readonly Regex SqlReturnsTableRegex = new(
365377 @"\bRETURNS\s+TABLE\s*\((?<columns>(?:[^()]|\([^()]*\))*)\)",
366378 RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline);
@@ -1391,6 +1403,8 @@ private enum JavaScriptTypeScriptFunctionHeaderConsumeResult
13911403 // file module declarations and inline modules / ファイルモジュール宣言とインラインモジュール
13921404 new("file_module", new Regex(@"^\s*(?:(?<visibility>pub(?:\([^)]*\))?)\s+)?mod\s+(?<name>(?:r#)?\w+)\s*;", RegexOptions.Compiled), BodyStyle.None, "visibility"),
13931405 new("namespace", new Regex(@"^\s*(?:(?<visibility>pub(?:\([^)]*\))?)\s+)?mod\s+(?<name>(?:r#)?\w+)", RegexOptions.Compiled), BodyStyle.Brace, "visibility"),
1406+ // Trait associated type defaults / trait 関連型のデフォルト
1407+ new("property", new Regex(@"^\s*(?:(?<visibility>pub(?:\([^)]*\))?)\s+)?type\s+(?<name>(?:r#)?\w+)(?:\s*<[^=>]+>)?(?:\s*:\s*[^=;]+)?\s*=\s*(?<returnType>[^;]+)", RegexOptions.Compiled), BodyStyle.None, "visibility", "returnType"),
13941408 // type alias / 型エイリアス
13951409 new("import", new Regex(@"^\s*(?:(?<visibility>pub(?:\([^)]*\))?)\s+)?type\s+(?<name>(?:r#)?\w+)(?:\s*<[^=]+>)?", RegexOptions.Compiled), BodyStyle.None, "visibility"),
13961410 new("import", new Regex(@"^\s*(?:(?<visibility>pub(?:\([^)]*\))?)\s+)?use\s+(?<name>.+);", RegexOptions.Compiled), BodyStyle.None, "visibility"),
@@ -2099,6 +2113,22 @@ public static IReadOnlyCollection<string> GetSupportedLanguages()
20992113 "class", "struct", "interface", "protocol", "namespace", "enum", "object", "heading", "specialization", "class_hook"
21002114 ];
21012115
2116+ private static bool IsRustDirectTraitBodyMember(List<SymbolRecord> symbols, int candidateLine)
2117+ {
2118+ SymbolRecord? innermostContainer = null;
2119+ foreach (var symbol in symbols)
2120+ {
2121+ if (!symbol.BodyStartLine.HasValue || !symbol.BodyEndLine.HasValue)
2122+ continue;
2123+ if (candidateLine < symbol.BodyStartLine.Value || candidateLine > symbol.BodyEndLine.Value)
2124+ continue;
2125+ if (innermostContainer == null || symbol.StartLine >= innermostContainer.StartLine)
2126+ innermostContainer = symbol;
2127+ }
2128+
2129+ return innermostContainer?.Kind == "protocol";
2130+ }
2131+
21022132 /// <summary>
21032133 /// Extract symbols from the given source content.
21042134 /// 指定されたソース内容からシンボルを抽出する。
@@ -2687,6 +2717,14 @@ public static List<SymbolRecord> Extract(long fileId, string? lang, string conte
26872717 lineOffset = FindNextSameLineBraceStatementStart(matchLine, absoluteStartColumn + Math.Max(1, match.Length), lang);
26882718 continue;
26892719 }
2720+ if (lang == "rust"
2721+ && pattern.Kind == "property"
2722+ && pattern.BodyStyle == BodyStyle.None
2723+ && pattern.ReturnTypeGroup != null
2724+ && !IsRustDirectTraitBodyMember(symbols, i + 1))
2725+ {
2726+ break;
2727+ }
26902728 var rawReturnType = NormalizeCSharpImplicitPartialMethodReturnType(
26912729 lang,
26922730 pattern,
@@ -3955,6 +3993,7 @@ public static List<SymbolRecord> Extract(long fileId, string? lang, string conte
39553993 ExtractSqlCteSymbols(fileId, lines, symbols);
39563994 ExtractSqlDefinerSymbols(fileId, lines, sqlSyntheticSymbolLines, symbols);
39573995 ExtractSqlRoutineResultColumnSymbols(fileId, lines, sqlSyntheticSymbolLines, symbols);
3996+ ExtractSqlGeneratedColumnSymbols(fileId, lines, sqlSyntheticSymbolLines, symbols);
39583997 }
39593998 if (IsRazorLanguage(originalLang) || IsRazorFilePath(filePath))
39603999 ExtractRazorDirectiveSymbols(fileId, lines, symbols);
@@ -4062,6 +4101,119 @@ private static int GetLineNumberFromOffset(List<int> lineStarts, int offset)
40624101 return ~index;
40634102 }
40644103
4104+ private static void ExtractSqlGeneratedColumnSymbols(long fileId, string[] lines, string[] structuralLines, List<SymbolRecord> symbols)
4105+ {
4106+ var structuralContent = string.Join('\n', structuralLines);
4107+ if (structuralContent.IndexOf("GENERATED", StringComparison.OrdinalIgnoreCase) < 0
4108+ && structuralContent.IndexOf("NEXT VALUE FOR", StringComparison.OrdinalIgnoreCase) < 0
4109+ && structuralContent.IndexOf(" AS ", StringComparison.OrdinalIgnoreCase) < 0)
4110+ {
4111+ return;
4112+ }
4113+
4114+ var lineStarts = BuildLineStarts(structuralContent);
4115+ foreach (Match match in SqlAlterTableAddGeneratedColumnRegex.Matches(structuralContent))
4116+ {
4117+ var nameGroup = match.Groups["name"];
4118+ AddSqlGeneratedColumnSymbol(
4119+ fileId,
4120+ lines,
4121+ lineStarts,
4122+ new GroupProxy(nameGroup.Value, nameGroup.Index),
4123+ match.Groups["table"].Value,
4124+ symbols);
4125+ }
4126+
4127+ foreach (Match tableMatch in SqlCreateTableBodyRegex.Matches(structuralContent))
4128+ {
4129+ var tableName = tableMatch.Groups["table"].Value;
4130+ var bodyGroup = tableMatch.Groups["body"];
4131+ foreach (var column in EnumerateSqlColumnDefinitions(bodyGroup.Value, bodyGroup.Index))
4132+ {
4133+ if (!SqlGeneratedColumnDefinitionMarkerRegex.IsMatch(column.Text))
4134+ continue;
4135+
4136+ var nameMatch = SqlColumnDefinitionNameRegex.Match(column.Text);
4137+ if (!nameMatch.Success)
4138+ continue;
4139+
4140+ AddSqlGeneratedColumnSymbol(
4141+ fileId,
4142+ lines,
4143+ lineStarts,
4144+ new GroupProxy(nameMatch.Groups["name"].Value, column.StartIndex + nameMatch.Groups["name"].Index),
4145+ tableName,
4146+ symbols);
4147+ }
4148+ }
4149+ }
4150+
4151+ private static void AddSqlGeneratedColumnSymbol(
4152+ long fileId,
4153+ string[] lines,
4154+ List<int> lineStarts,
4155+ IGroupLike nameGroup,
4156+ string rawTableName,
4157+ List<SymbolRecord> symbols)
4158+ {
4159+ var name = NormalizeSqlIdentifierSegment(nameGroup.Value);
4160+ if (string.IsNullOrWhiteSpace(name))
4161+ return;
4162+
4163+ var lineNumber = GetLineNumberFromOffset(lineStarts, nameGroup.Index);
4164+ AddSymbolRecord(
4165+ symbols,
4166+ null,
4167+ lineNumber,
4168+ new SymbolRecord
4169+ {
4170+ FileId = fileId,
4171+ Kind = "property",
4172+ SubKind = "generated_column",
4173+ Name = name,
4174+ Line = lineNumber,
4175+ StartLine = lineNumber,
4176+ StartColumn = nameGroup.Index - lineStarts[lineNumber - 1],
4177+ EndLine = lineNumber,
4178+ Signature = lines[lineNumber - 1].Trim(),
4179+ ContainerKind = "class",
4180+ ContainerName = NormalizeSqlIdentifierSegment(SqlNameResolver.GetLeafName(rawTableName)),
4181+ },
4182+ lines[lineNumber - 1]);
4183+ }
4184+
4185+ private interface IGroupLike
4186+ {
4187+ string Value { get; }
4188+ int Index { get; }
4189+ }
4190+
4191+ private readonly record struct GroupProxy(string Value, int Index) : IGroupLike;
4192+
4193+ private readonly record struct SqlColumnDefinitionSlice(string Text, int StartIndex);
4194+
4195+ private static IEnumerable<SqlColumnDefinitionSlice> EnumerateSqlColumnDefinitions(string body, int bodyStartIndex)
4196+ {
4197+ var start = 0;
4198+ var depth = 0;
4199+ for (var i = 0; i <= body.Length; i++)
4200+ {
4201+ if (i == body.Length || (body[i] == ',' && depth == 0))
4202+ {
4203+ var text = body[start..i].Trim();
4204+ if (text.Length > 0)
4205+ yield return new SqlColumnDefinitionSlice(text, bodyStartIndex + start + body[start..i].Length - body[start..i].TrimStart().Length);
4206+ start = i + 1;
4207+ continue;
4208+ }
4209+
4210+ if (body[i] == '(')
4211+ depth++;
4212+ else if (body[i] == ')' && depth > 0)
4213+ depth--;
4214+ }
4215+ }
4216+
40654217 private static string NormalizeSqlIdentifierSegment(string value)
40664218 {
40674219 if (value.Length >= 2 && value[0] == '[' && value[^1] == ']')
0 commit comments