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
17 changes: 17 additions & 0 deletions changelog.d/unreleased/2105.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: fixed
issues:
- 2105
affected:
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.Css.cs
- tests/CodeIndex.Tests/SymbolExtractorTests.cs
---

## English

- **CSS media features are now searchable as symbols (#2105)** — `@media` feature names such as `min-width`, `prefers-color-scheme`, `orientation`, range-style `width`, and custom media feature names are indexed as `property` symbols while literal values and boolean operators are skipped.

## 日本語

- **CSS media feature を symbol として検索できるようにしました (#2105)** — `@media` 内の `min-width`、`prefers-color-scheme`、`orientation`、range 形式の `width`、custom media feature 名を `property` symbol として index し、値リテラルや boolean operator は除外します。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/2611.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 2611
affected:
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs
- tests/CodeIndex.Tests/SymbolExtractorTests.cs
---

## English

- **Rust associated type defaults are indexed under trait containers again (#2611)** — the post-processing pass now handles Rust traits stored as `protocol` symbols, so defaults such as `type Output = ();` produce property symbols with the correct trait container.

## 日本語

- **Rust associated type default を trait container 配下で再び index するようにしました (#2611)** — 後処理が `protocol` symbol として保存された Rust trait も対象にするようになり、`type Output = ();` のような default が正しい trait container を持つ property symbol になります。
74 changes: 74 additions & 0 deletions src/CodeIndex/Indexer/Symbols/SymbolExtractor.Css.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ private static int FindCssSameLineBraceEndColumn(string line, int startColumn)

private static readonly Regex CssFontFaceDeclarationRegex = new(@"(?:^|[;{])\s*font-family\s*:", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly Regex CssInlineCustomPropertyRegex = new(@"(?<name>--[\w-]+)\s*:", RegexOptions.Compiled);
private static readonly Regex CssMediaFeatureNameRegex = new(@"^\s*(?:not\s+)?(?<name>--[\w-]+|[A-Za-z_][\w-]*)(?=\s*(?::|[<>]=?|=|$))|[<>]=?\s*(?<name>--[\w-]+|[A-Za-z_][\w-]*)(?=\s*(?:[<>]=?|$))", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

private static string ResolveCssSymbolName(string matchLine, string name, string[] lines, int startIndex, int endLine)
{
Expand All @@ -287,6 +288,79 @@ private static string ResolveCssSymbolName(string matchLine, string name, string
: string.Empty;
}

private static void TryAddCssMediaFeatureSymbols(
long fileId,
string rawLine,
string maskedLine,
int lineIndex,
List<SymbolRecord> symbols,
HashSet<string>? cssSeenSymbols)
{
var trimmedMaskedLine = maskedLine.TrimStart();
if (!trimmedMaskedLine.StartsWith("@media", StringComparison.OrdinalIgnoreCase))
return;

var blockStart = maskedLine.IndexOf('{');
if (blockStart < 0)
blockStart = maskedLine.Length;

var query = maskedLine[..blockStart];
for (var index = 0; index < query.Length; index++)
{
if (query[index] != '(')
continue;

var featureStart = index + 1;
var depth = 1;
index++;
while (index < query.Length && depth > 0)
{
if (query[index] == '(')
depth++;
else if (query[index] == ')')
depth--;

index++;
}

if (depth != 0)
break;

var featureText = query[featureStart..(index - 1)];
if (string.IsNullOrWhiteSpace(featureText))
continue;

var match = CssMediaFeatureNameRegex.Match(featureText);
if (match.Success)
{
var name = match.Groups["name"].Value;
if (string.Equals(name, "and", StringComparison.OrdinalIgnoreCase)
|| string.Equals(name, "or", StringComparison.OrdinalIgnoreCase)
|| string.Equals(name, "not", StringComparison.OrdinalIgnoreCase))
{
continue;
}

var featureColumn = featureStart + match.Groups["name"].Index;
AddSymbolRecord(
symbols,
cssSeenSymbols,
lineIndex + 1,
new SymbolRecord
{
FileId = fileId,
Kind = "property",
Name = name,
Line = lineIndex + 1,
StartLine = lineIndex + 1,
StartColumn = featureColumn,
EndLine = lineIndex + 1,
Signature = rawLine.Trim(),
});
}
}
}

private static bool TryGetCssFontFaceFamilyName(string[] lines, int startIndex, int endLine, out string fontFamily)
{
fontFamily = string.Empty;
Expand Down
16 changes: 15 additions & 1 deletion src/CodeIndex/Indexer/Symbols/SymbolExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3460,6 +3460,20 @@ public static List<SymbolRecord> Extract(long fileId, string? lang, string conte
}
}

if (lang == "css"
&& pattern.Kind == "namespace"
&& pattern.BodyStyle == BodyStyle.Brace
&& cssScannerLines != null)
{
TryAddCssMediaFeatureSymbols(
fileId,
line,
cssScannerLines[i],
i,
symbols,
cssSeenSymbols);
}

if (lang == "css"
&& pattern.Kind == "class"
&& pattern.BodyStyle == BodyStyle.Brace
Expand Down Expand Up @@ -10038,7 +10052,7 @@ private static bool IsCppTemplateSpecializationSymbol(
private static void ExtractRustAssociatedTypeDefaultSymbols(long fileId, string[] lines, string[] structuralLines, List<SymbolRecord> symbols)
{
var traits = symbols
.Where(symbol => symbol.Kind == "interface"
.Where(symbol => symbol.Kind is "interface" or "protocol"
&& symbol.BodyStartLine is > 0
&& symbol.BodyEndLine is > 0)
.OrderBy(symbol => symbol.StartLine)
Expand Down
35 changes: 33 additions & 2 deletions tests/CodeIndex.Tests/SymbolExtractorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14382,13 +14382,13 @@ fn build(&self) {
Assert.Contains(symbols, s =>
s.Kind == "property"
&& s.Name == "Output"
&& s.ContainerKind == "interface"
&& s.ContainerKind == "protocol"
&& s.ContainerName == "Builder"
&& s.ReturnType == "()");
Assert.Contains(symbols, s =>
s.Kind == "property"
&& s.Name == "Error"
&& s.ContainerKind == "interface"
&& s.ContainerKind == "protocol"
&& s.ContainerName == "Builder"
&& s.ReturnType == "String");
Assert.DoesNotContain(symbols, s => s.Kind == "property" && s.Name == "Pending");
Expand Down Expand Up @@ -21508,6 +21508,37 @@ @media screen {
Assert.DoesNotContain(symbols, s => s.Kind == "class" && s.Name == ".nested-child");
}

[Fact]
public void Extract_CSS_CapturesMediaFeatureNamesButNotValuesOrOperators()
{
var content = """
@media (min-width: 768px) and (prefers-color-scheme: dark), not screen and (orientation: landscape) {
.responsive {
color: red;
}
}

@supports (display: grid) {
@media (width >= 40rem) and (--narrow) {
.nested-media {
display: grid;
}
}
}
""";

var symbols = SymbolExtractor.Extract(1, "css", content);

Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "min-width");
Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "prefers-color-scheme");
Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "orientation");
Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "width");
Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "--narrow");
Assert.DoesNotContain(symbols, s =>
s.Kind == "property"
&& s.Name is "768px" or "dark" or "landscape" or "and" or "not" or "or");
}

[Fact]
public void Extract_CSS_DoesNotLeakNestedSelectorsAfterSameLineGroupingAndQualifiedRule()
{
Expand Down
Loading