Skip to content

feat: CSS/SCSS/LESS parsing with cross-language STYLES edges and conflict detection#61

Open
CodeBlackwell wants to merge 1 commit intotirth8205:mainfrom
CodeBlackwell:pr/css-scss-support
Open

feat: CSS/SCSS/LESS parsing with cross-language STYLES edges and conflict detection#61
CodeBlackwell wants to merge 1 commit intotirth8205:mainfrom
CodeBlackwell:pr/css-scss-support

Conversation

@CodeBlackwell
Copy link
Copy Markdown

Summary

  • CSS/SCSS parsing with Tree-sitter: selector extraction, specificity computation [a,b,c], single-file override detection (specificity, !important, BEM, source order), custom properties, @media/@keyframes/@import, SCSS $variables/@mixin/@include/& nesting, Vue <style> block delegation
  • Cross-language STYLES edges: extract className from JSX/TSX and class from Vue <template>, post-build linking pass connects components to CSS selectors, CSS Modules resolution (styles.foo.foo with camelCase-to-kebab)
  • Cross-file conflict detection: heuristic flagging of same-name selectors across files via POTENTIAL_CONFLICT edges with specificity comparison and confidence metadata
  • LESS support: regex-based parser (no tree-sitter grammar available) for selectors, @variables, .mixins(), @import
  • 3 new MCP query patterns: styles_of, styled_by, conflicts_of
  • Visualization: STYLES (blue) and POTENTIAL_CONFLICT (red) edge styling with legend entries

New Edge Types

Edge Source Target Purpose
OVERRIDES Winning CSS selector Losing CSS selector Single-file specificity/cascade override
STYLES JSX component / Vue file CSS selector Cross-language class reference
POTENTIAL_CONFLICT CSS selector CSS selector Cross-file same-name selector conflict

Architecture

Two-phase approach keeps the parser stateless:

  1. Per-file: Extract class references during parsing, store in node extra metadata
  2. Post-build: link_css_styles() and detect_cross_file_conflicts() query the graph and create cross-file edges

Both phases run automatically in full_build() and incremental_update().

Test Plan

  • 229 tests passing (47 new)
  • ruff check clean
  • mypy clean
  • TestJSXClassExtraction — static className, CSS Module refs, dynamic skip, function attachment
  • TestVueTemplateClassExtraction — static class, dynamic :class skip, style+template coexistence
  • TestLESSParsing — selectors, @variables, @import, .mixins, language tagging
  • TestCSSStylesLinking — cross-file STYLES edges, Vue same-file linking, missing selector no-op
  • TestCrossFileConflicts — same-selector conflict, unique selector no-conflict, metadata validation
  • TestCamelToKebab — camelCase conversion correctness
  • Full graph rebuild exercises linking pass end-to-end

…lict detection

Add comprehensive CSS ecosystem support to the code knowledge graph:

**CSS/SCSS Parsing (v1)**
- Tree-sitter based parsing for CSS and SCSS files
- Selector extraction with specificity computation (a, b, c tuples)
- Single-file override detection via specificity, !important, BEM refinement, and source order
- OVERRIDES edges with mechanism metadata
- Custom properties (CSS variables), @media, @Keyframes, @import/@use
- SCSS: $variables, @mixin/@include, & nesting resolution
- Vue SFC <style> block delegation to CSS/SCSS parser

**Cross-language STYLES Edges (v2)**
- Extract className from JSX/TSX (static strings, JSX expressions, template literals)
- Extract class attributes from Vue <template> sections
- Post-build linking pass creates STYLES edges from components to CSS selectors
- CSS Modules resolution: import styles from './x.module.css' → styles.foo → .foo
  with camelCase-to-kebab conversion

**Cross-file Conflict Detection (v2)**
- Heuristic detection of same-name selectors across different CSS files
- POTENTIAL_CONFLICT edges with specificity comparison and confidence metadata
- Capped at 50 pairwise comparisons per class name

**LESS Support (v2)**
- Regex-based parser (no tree-sitter grammar available)
- Selectors, @variables, .mixins(), @import extraction
- Regex-based specificity computation
- Override detection via existing _detect_css_overrides()

**MCP Tools**
- 3 new query patterns: styles_of, styled_by, conflicts_of
- 2 existing patterns: overrides_of, overridden_by

**Visualization**
- STYLES edges (blue, dashed) and POTENTIAL_CONFLICT edges (red, dashed)
- Legend entries and arrow markers

**Graph Infrastructure**
- GraphStore.delete_edges_by_kind() for linking pass cleanup
- GraphStore.get_nodes_by_extra_pattern() for CSS node queries
- find_dependents() extended to traverse STYLES edges
- link_css_styles() and detect_cross_file_conflicts() integrated into
  full_build() and incremental_update()

**Tests**
- 47 new tests (229 total), all passing
- 6 new fixture files (TSX, Vue, CSS, LESS, CSS Modules, conflict pairs)
- TestJSXClassExtraction, TestVueTemplateClassExtraction, TestLESSParsing
- TestCSSStylesLinking, TestCrossFileConflicts, TestCamelToKebab

Supported languages: 16 (Python, JS, TS, TSX, Go, Rust, Java, C, C++, C#,
Ruby, PHP, Solidity, Vue, CSS, SCSS) + LESS via regex fallback.
@tirth8205
Copy link
Copy Markdown
Owner

Closing — CSS/SCSS support scope is too broad for the current architecture. Tree-sitter CSS parsing doesn't produce meaningful function/class nodes for the knowledge graph.

@tirth8205
Copy link
Copy Markdown
Owner

CSS/SCSS/LESS parsing with cross-language STYLES edges is not in main — parser.py has no CSS/SCSS/LESS support, no STYLES edge type, and no styles_of/styled_by/conflicts_of query patterns. This is a substantial PR (2135 additions) with well-defined scope. Worth reviving — it would be valuable for frontend-heavy codebases. The two-phase approach (per-file extraction + post-build linking) is architecturally sound.

@tirth8205 tirth8205 reopened this Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants