From f92c07ed71e53124b3ed7431bd0271680b0f01e5 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Sun, 24 May 2026 17:59:40 +0900 Subject: [PATCH] Fix HTML class reference extraction (#2103) --- changelog.d/unreleased/2103.fixed.md | 16 +++++++++++ .../Indexer/Symbols/SymbolExtractor.Markup.cs | 11 ++++++++ tests/CodeIndex.Tests/SymbolExtractorTests.cs | 27 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 changelog.d/unreleased/2103.fixed.md diff --git a/changelog.d/unreleased/2103.fixed.md b/changelog.d/unreleased/2103.fixed.md new file mode 100644 index 0000000000..0a55b46962 --- /dev/null +++ b/changelog.d/unreleased/2103.fixed.md @@ -0,0 +1,16 @@ +--- +category: fixed +issues: + - 2103 +affected: + - src/CodeIndex/Indexer/Symbols/SymbolExtractor.Markup.cs + - tests/CodeIndex.Tests/SymbolExtractorTests.cs +--- + +## English + +- **HTML class attributes are indexed as individual references (#2103)** — `class` and `className` values now split on whitespace so utility classes such as `btn`, `mx-2`, and Tailwind variants can be searched individually. + +## 日本語 + +- **HTML の class 属性を個別の reference としてインデックスするようになりました (#2103)** — `class` と `className` の値を空白で分割し、`btn`、`mx-2`、Tailwind の variant などを個別に検索できるようにしました。 diff --git a/src/CodeIndex/Indexer/Symbols/SymbolExtractor.Markup.cs b/src/CodeIndex/Indexer/Symbols/SymbolExtractor.Markup.cs index 13b8a09544..cedbe09484 100644 --- a/src/CodeIndex/Indexer/Symbols/SymbolExtractor.Markup.cs +++ b/src/CodeIndex/Indexer/Symbols/SymbolExtractor.Markup.cs @@ -221,6 +221,11 @@ private static List ExtractHtmlSymbols(long fileId, string[] lines emitKind = "property"; emittedNames = [attrValue.Trim()]; } + else if (attrNameLower is "class" or "classname") + { + emitKind = "reference"; + emittedNames = EnumerateHtmlClassNames(attrValue).ToList(); + } if (emitKind == null || emittedNames == null || emittedNames.Count == 0) continue; @@ -256,6 +261,12 @@ private static List ExtractHtmlSymbols(long fileId, string[] lines return symbols; } + private static IEnumerable EnumerateHtmlClassNames(string value) + { + foreach (var token in value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)) + yield return token; + } + private static List ExtractMarkdownSymbols(long fileId, string[] lines) { // Markdown headings are the closest thing to navigable symbols in docs files. diff --git a/tests/CodeIndex.Tests/SymbolExtractorTests.cs b/tests/CodeIndex.Tests/SymbolExtractorTests.cs index ec6bbd3704..ca5a575407 100644 --- a/tests/CodeIndex.Tests/SymbolExtractorTests.cs +++ b/tests/CodeIndex.Tests/SymbolExtractorTests.cs @@ -81,6 +81,33 @@ private void Load() { } Assert.Contains(symbols, symbol => symbol.Kind == "function" && symbol.Name == "Load"); } + [Fact] + public void Extract_HtmlClassAttributes_IndexesIndividualClassReferences() + { + const string content = """ +
+ +
+ """; + + var symbols = SymbolExtractor.Extract(1, "html", content); + var classReferences = symbols + .Where(symbol => symbol.Kind == "reference") + .Select(symbol => (symbol.Name, symbol.Line)) + .ToArray(); + + Assert.Contains(("btn", 1), classReferences); + Assert.Contains(("btn-primary", 1), classReferences); + Assert.Contains(("mx-2", 1), classReferences); + Assert.Contains(("md:flex", 1), classReferences); + Assert.Contains(("hover:bg-red-500", 1), classReferences); + Assert.Contains(("[&>*]:mt-2", 1), classReferences); + Assert.Contains(("inline-flex", 2), classReferences); + Assert.Contains(("items-center", 2), classReferences); + Assert.DoesNotContain(classReferences, symbol => string.IsNullOrWhiteSpace(symbol.Name)); + Assert.DoesNotContain(("btn btn-primary mx-2 md:flex hover:bg-red-500 [&>*]:mt-2", 1), classReferences); + } + [Theory] [InlineData("javascript")] [InlineData("typescript")]