From 03edfcd936ab4473ac430ea5d9cce28fcd2cd5ae Mon Sep 17 00:00:00 2001 From: Alejandro Giacometti Date: Thu, 2 Jul 2026 21:51:30 +0100 Subject: [PATCH] Add SCSS and Less support Register the scss and less plugins, reusing the CSS nesting scope helper, with sample projects and golden snapshots. --- packages/rbtr/pyproject.toml | 2 + packages/rbtr/src/rbtr/languages/__init__.py | 4 + packages/rbtr/src/rbtr/languages/less.py | 68 ++++ packages/rbtr/src/rbtr/languages/scss.py | 78 +++++ ...est_sample_edges_match_snapshot[less].json | 3 + ...est_sample_edges_match_snapshot[scss].json | 5 + ...ple_extraction_matches_snapshot[less].json | 182 +++++++++++ ...ple_extraction_matches_snapshot[scss].json | 308 ++++++++++++++++++ .../src/rbtr/tests/languages/cases_samples.py | 35 ++ .../tests/languages/samples/less/base.less | 3 + .../tests/languages/samples/less/less.less | 38 +++ .../tests/languages/samples/scss/config.scss | 3 + .../tests/languages/samples/scss/reset.scss | 6 + .../tests/languages/samples/scss/scss.scss | 53 +++ .../tests/languages/samples/scss/theme.scss | 3 + .../src/rbtr/tests/languages/test_manager.py | 6 + uv.lock | 33 ++ 17 files changed, 830 insertions(+) create mode 100644 packages/rbtr/src/rbtr/languages/less.py create mode 100644 packages/rbtr/src/rbtr/languages/scss.py create mode 100644 packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[less].json create mode 100644 packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[scss].json create mode 100644 packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[less].json create mode 100644 packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[scss].json create mode 100644 packages/rbtr/src/rbtr/tests/languages/samples/less/base.less create mode 100644 packages/rbtr/src/rbtr/tests/languages/samples/less/less.less create mode 100644 packages/rbtr/src/rbtr/tests/languages/samples/scss/config.scss create mode 100644 packages/rbtr/src/rbtr/tests/languages/samples/scss/reset.scss create mode 100644 packages/rbtr/src/rbtr/tests/languages/samples/scss/scss.scss create mode 100644 packages/rbtr/src/rbtr/tests/languages/samples/scss/theme.scss diff --git a/packages/rbtr/pyproject.toml b/packages/rbtr/pyproject.toml index 2195a25f..d66d8ac3 100644 --- a/packages/rbtr/pyproject.toml +++ b/packages/rbtr/pyproject.toml @@ -40,11 +40,13 @@ dependencies = [ "tree-sitter-java", "tree-sitter-javascript", "tree-sitter-json", + "tree-sitter-less", "tree-sitter-markdown", "tree-sitter-python", "tree-sitter-rst", "tree-sitter-ruby", "tree-sitter-rust", + "tree-sitter-scss", "tree-sitter-sql", "tree-sitter-toml", "tree-sitter-typescript", diff --git a/packages/rbtr/src/rbtr/languages/__init__.py b/packages/rbtr/src/rbtr/languages/__init__.py index 3e492079..a588e907 100644 --- a/packages/rbtr/src/rbtr/languages/__init__.py +++ b/packages/rbtr/src/rbtr/languages/__init__.py @@ -52,11 +52,13 @@ from rbtr.languages.java import JavaPlugin from rbtr.languages.javascript import JavaScriptPlugin from rbtr.languages.json import JsonPlugin +from rbtr.languages.less import LessPlugin from rbtr.languages.markdown import MarkdownPlugin from rbtr.languages.python import PythonPlugin from rbtr.languages.rst import RstPlugin from rbtr.languages.ruby import RubyPlugin from rbtr.languages.rust import RustPlugin +from rbtr.languages.scss import ScssPlugin from rbtr.languages.sql import SqlPlugin from rbtr.languages.toml import TomlPlugin from rbtr.languages.yaml import YamlPlugin @@ -112,6 +114,7 @@ def _register_builtins(self) -> None: HclPlugin, HtmlPlugin, JavaPlugin, + LessPlugin, JavaScriptPlugin, JsonPlugin, MarkdownPlugin, @@ -119,6 +122,7 @@ def _register_builtins(self) -> None: RstPlugin, RubyPlugin, RustPlugin, + ScssPlugin, SqlPlugin, TomlPlugin, YamlPlugin, diff --git a/packages/rbtr/src/rbtr/languages/less.py b/packages/rbtr/src/rbtr/languages/less.py new file mode 100644 index 00000000..71e7ce50 --- /dev/null +++ b/packages/rbtr/src/rbtr/languages/less.py @@ -0,0 +1,68 @@ +"""Less language plugin. + +Extends CSS extraction with the Less preprocessor constructs, +via a tree-sitter query. Rule sets and `@media`/`@charset`/ +`@keyframes` are captured as doc sections; `@`-prefixed +declarations as variables; `.mixin()` definitions as functions; +`@import` as imports for cross-file edges. Mixin *calls* +(`mixin_statement`) are references, not definitions, and are +skipped. + +Extracted chunks:: + + @primary: #333; → variable "@primary" + .rounded(@r) { ... } → function "rounded" + .btn { ... } → doc_section ".btn" + @keyframes slide { ... } → doc_section "slide" + @import "reset"; → import, metadata {module: "reset"} +""" + +from __future__ import annotations + +from rbtr.languages.css import css_nesting_scope +from rbtr.languages.hookspec import ( + LanguageRegistration, + build_quoted_import, + hookimpl, +) + +_QUERY = r""" +(declaration + (property_name) @_var_name + (#match? @_var_name "^[@]")) @variable + +(mixin_definition + (class_name) @_fn_name) @function + +(rule_set + (selectors) @_section_name) @doc_section + +(media_statement) @doc_section + +(charset_statement) @doc_section + +(keyframes_statement + (keyframes_name) @_section_name) @doc_section + +(import_statement + (string_value) @_import_module) @import +""" + + +class LessPlugin: + """Less language support — Less constructs + @import edges.""" + + @hookimpl + def rbtr_register_languages(self) -> list[LanguageRegistration]: + return [ + LanguageRegistration( + id="less", + extensions=frozenset({".less"}), + grammar_module="tree_sitter_less", + query=_QUERY, + scope_extractor=css_nesting_scope, + import_extractor=build_quoted_import, + import_targets=frozenset({"css", "scss", "less"}), + language_plugin_version=1, + ), + ] diff --git a/packages/rbtr/src/rbtr/languages/scss.py b/packages/rbtr/src/rbtr/languages/scss.py new file mode 100644 index 00000000..b5be4dee --- /dev/null +++ b/packages/rbtr/src/rbtr/languages/scss.py @@ -0,0 +1,78 @@ +"""SCSS / Sass language plugin. + +Extends CSS extraction with the Sass preprocessor constructs, +via a tree-sitter query. Rule sets (incl. `%placeholder` +selectors), `@media`/`@charset`/`@keyframes` are captured as +doc sections; `$`-prefixed declarations as variables; `@mixin` +and `@function` definitions as functions; `@use`/`@forward`/ +`@import` as imports for cross-file edges. + +Extracted chunks:: + + $primary: #333; → variable "$primary" + @mixin flex($dir) { ... } → function "flex" + @function rem($px) { ... } → function "rem" + %card { ... } → doc_section "%card" + .btn { ... } → doc_section ".btn" + @keyframes slide { ... } → doc_section "slide" + @use "config"; → import, metadata {module: "config"} +""" + +from __future__ import annotations + +from rbtr.languages.css import css_nesting_scope +from rbtr.languages.hookspec import ( + LanguageRegistration, + build_quoted_import, + hookimpl, +) + +_QUERY = r""" +(declaration + (property_name) @_var_name + (#match? @_var_name "^[$]")) @variable + +(mixin_statement + (identifier) @_fn_name) @function + +(function_statement + (identifier) @_fn_name) @function + +(rule_set + (selectors) @_section_name) @doc_section + +(media_statement) @doc_section + +(charset_statement) @doc_section + +(keyframes_statement + (keyframes_name) @_section_name) @doc_section + +(use_statement + (string_value) @_import_module) @import + +(forward_statement + (string_value) @_import_module) @import + +(import_statement + (string_value) @_import_module) @import +""" + + +class ScssPlugin: + """SCSS language support — Sass constructs + @use/@forward edges.""" + + @hookimpl + def rbtr_register_languages(self) -> list[LanguageRegistration]: + return [ + LanguageRegistration( + id="scss", + extensions=frozenset({".scss"}), + grammar_module="tree_sitter_scss", + query=_QUERY, + scope_extractor=css_nesting_scope, + import_extractor=build_quoted_import, + import_targets=frozenset({"css", "scss", "less"}), + language_plugin_version=1, + ), + ] diff --git a/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[less].json b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[less].json new file mode 100644 index 00000000..d06ffdbd --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[less].json @@ -0,0 +1,3 @@ +[ + "less.less::@import \"base\"; -> base.less::@bg [imports]" +] diff --git a/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[scss].json b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[scss].json new file mode 100644 index 00000000..1bc49d3e --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_edges_match_snapshot[scss].json @@ -0,0 +1,5 @@ +[ + "scss.scss::@forward \"theme\"; -> theme.scss::$radius [imports]", + "scss.scss::@import \"reset\"; -> reset.scss::* [imports]", + "scss.scss::@use \"config\"; -> config.scss::$brand [imports]" +] diff --git a/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[less].json b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[less].json new file mode 100644 index 00000000..a0465f4e --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[less].json @@ -0,0 +1,182 @@ +[ + { + "id": "8f10aa0b97fda5ea", + "blob_sha": "sha1", + "file_path": "base.less", + "kind": "variable", + "name": "@bg", + "scope": "", + "language": "less", + "content": "@bg: #ffffff;", + "line_start": 3, + "line_end": 3, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "14b448311d1139b8", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "import", + "name": "@import \"base\";", + "scope": "", + "language": "less", + "content": "@import \"base\";", + "line_start": 7, + "line_end": 7, + "metadata": { + "module": "base", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "8d9441ee2a311c25", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "variable", + "name": "@primary", + "scope": "", + "language": "less", + "content": "@primary: #333;", + "line_start": 9, + "line_end": 9, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "7c7b0d1e3f708b87", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "variable", + "name": "@spacing", + "scope": "", + "language": "less", + "content": "@spacing: 8px;", + "line_start": 10, + "line_end": 10, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "70cc768af8829886", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "function", + "name": "rounded", + "scope": "", + "language": "less", + "content": ".rounded(@radius: 4px) {\n border-radius: @radius;\n}", + "line_start": 12, + "line_end": 14, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "787412e930624b62", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "doc_section", + "name": ".greeter", + "scope": "", + "language": "less", + "content": ".greeter {\n color: @primary;\n padding: @spacing;\n .rounded(8px);\n .greeting-label {\n text-transform: uppercase;\n }\n}", + "line_start": 16, + "line_end": 23, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "a21acf50441166ff", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "doc_section", + "name": ".greeting-label", + "scope": ".greeter", + "language": "less", + "content": ".greeting-label {\n text-transform: uppercase;\n }", + "line_start": 20, + "line_end": 22, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "289a7c82bd01f03c", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "doc_section", + "name": "", + "scope": "", + "language": "less", + "content": "@media (max-width: 600px) {\n .greeter {\n font-size: 14px;\n }\n}", + "line_start": 25, + "line_end": 29, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "a4d6c82c63beab1c", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "doc_section", + "name": ".greeter", + "scope": "", + "language": "less", + "content": ".greeter {\n font-size: 14px;\n }", + "line_start": 26, + "line_end": 28, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "4d89902ee8dcd396", + "blob_sha": "sha1", + "file_path": "less.less", + "kind": "doc_section", + "name": "greeting-fade", + "scope": "", + "language": "less", + "content": "@keyframes greeting-fade {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}", + "line_start": 31, + "line_end": 38, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + } +] diff --git a/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[scss].json b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[scss].json new file mode 100644 index 00000000..a75912b1 --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/__snapshots__/test_samples/test_sample_extraction_matches_snapshot[scss].json @@ -0,0 +1,308 @@ +[ + { + "id": "39312074cb776ca1", + "blob_sha": "sha1", + "file_path": "config.scss", + "kind": "variable", + "name": "$brand", + "scope": "", + "language": "scss", + "content": "$brand: #00aa77;", + "line_start": 3, + "line_end": 3, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "ca618b519a5d17c6", + "blob_sha": "sha1", + "file_path": "reset.scss", + "kind": "doc_section", + "name": "*", + "scope": "", + "language": "scss", + "content": "* {\n margin: 0;\n padding: 0;\n}", + "line_start": 3, + "line_end": 6, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "9c79559af6a0cb62", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "doc_section", + "name": "", + "scope": "", + "language": "scss", + "content": "@charset \"UTF-8\";", + "line_start": 7, + "line_end": 7, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "1f0198d6bc929161", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "import", + "name": "@use \"config\";", + "scope": "", + "language": "scss", + "content": "@use \"config\";", + "line_start": 9, + "line_end": 9, + "metadata": { + "module": "config", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "cf7865de50f3fafd", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "import", + "name": "@forward \"theme\";", + "scope": "", + "language": "scss", + "content": "@forward \"theme\";", + "line_start": 10, + "line_end": 10, + "metadata": { + "module": "theme", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "c1d6fcb5e3fac8a5", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "import", + "name": "@import \"reset\";", + "scope": "", + "language": "scss", + "content": "@import \"reset\";", + "line_start": 11, + "line_end": 11, + "metadata": { + "module": "reset", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "faa218eb01099bf6", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "variable", + "name": "$primary", + "scope": "", + "language": "scss", + "content": "$primary: #333;", + "line_start": 13, + "line_end": 13, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "5d75324f09c4352f", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "variable", + "name": "$spacing", + "scope": "", + "language": "scss", + "content": "$spacing: 8px;", + "line_start": 14, + "line_end": 14, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "6facb9e386edb697", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "function", + "name": "flex-center", + "scope": "", + "language": "scss", + "content": "@mixin flex-center($direction: row) {\n display: flex;\n flex-direction: $direction;\n align-items: center;\n}", + "line_start": 16, + "line_end": 20, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "6516223d5f986f51", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "function", + "name": "rem", + "scope": "", + "language": "scss", + "content": "@function rem($px) {\n @return $px * 1rem;\n}", + "line_start": 22, + "line_end": 24, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "3205387087aed863", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "doc_section", + "name": "%card-base", + "scope": "", + "language": "scss", + "content": "%card-base {\n border-radius: 4px;\n padding: $spacing;\n}", + "line_start": 26, + "line_end": 29, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "bbd96a4892c6537a", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "doc_section", + "name": ".greeter", + "scope": "", + "language": "scss", + "content": ".greeter {\n color: $primary;\n @include flex-center(column);\n\n &:hover {\n color: blue;\n }\n}", + "line_start": 31, + "line_end": 38, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "0db402fd896394ea", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "doc_section", + "name": "&:hover", + "scope": ".greeter", + "language": "scss", + "content": "&:hover {\n color: blue;\n }", + "line_start": 35, + "line_end": 37, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "9d1da3a16c28d4ba", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "doc_section", + "name": "", + "scope": "", + "language": "scss", + "content": "@media (max-width: 600px) {\n .greeter {\n font-size: rem(14);\n }\n}", + "line_start": 40, + "line_end": 44, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "2c28098a955d9ab2", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "doc_section", + "name": ".greeter", + "scope": "", + "language": "scss", + "content": ".greeter {\n font-size: rem(14);\n }", + "line_start": 41, + "line_end": 43, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "f3d13cefdaa9e70c", + "blob_sha": "sha1", + "file_path": "scss.scss", + "kind": "doc_section", + "name": "greeting-fade", + "scope": "", + "language": "scss", + "content": "@keyframes greeting-fade {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}", + "line_start": 46, + "line_end": 53, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + }, + { + "id": "262ba62fce660d22", + "blob_sha": "sha1", + "file_path": "theme.scss", + "kind": "variable", + "name": "$radius", + "scope": "", + "language": "scss", + "content": "$radius: 6px;", + "line_start": 3, + "line_end": 3, + "metadata": { + "module": "", + "names": "", + "dots": "", + "language_hint": "" + } + } +] diff --git a/packages/rbtr/src/rbtr/tests/languages/cases_samples.py b/packages/rbtr/src/rbtr/tests/languages/cases_samples.py index 78c9edd9..3517a43e 100644 --- a/packages/rbtr/src/rbtr/tests/languages/cases_samples.py +++ b/packages/rbtr/src/rbtr/tests/languages/cases_samples.py @@ -178,6 +178,41 @@ def case_css() -> SampleCase: ) +@case(id="scss", tags=["sample"]) +def case_scss() -> SampleCase: + """SCSS: `$`-variables, @mixin and @function definitions (as functions), + rule sets incl. `%placeholders` and nested rules (as doc sections), + @media/@charset/@keyframes (as doc sections), and @use/@forward/@import + (as imports). + """ + return ( + "scss", + { + ChunkKind.VARIABLE, + ChunkKind.FUNCTION, + ChunkKind.DOC_SECTION, + ChunkKind.IMPORT, + }, + ) + + +@case(id="less", tags=["sample"]) +def case_less() -> SampleCase: + """Less: `@`-variables, `.mixin()` definitions (as functions), rule sets + and @media/@charset/@keyframes (as doc sections), and @import (as + imports). Mixin calls are references and are not captured. + """ + return ( + "less", + { + ChunkKind.VARIABLE, + ChunkKind.FUNCTION, + ChunkKind.DOC_SECTION, + ChunkKind.IMPORT, + }, + ) + + @case(id="sql", tags=["sample"]) def case_sql() -> SampleCase: """SQL: one chunk per top-level statement — CREATE TABLE/VIEW (as diff --git a/packages/rbtr/src/rbtr/tests/languages/samples/less/base.less b/packages/rbtr/src/rbtr/tests/languages/samples/less/base.less new file mode 100644 index 00000000..46262f2f --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/samples/less/base.less @@ -0,0 +1,3 @@ +// Imported by less.less via `@import "base"`. + +@bg: #ffffff; diff --git a/packages/rbtr/src/rbtr/tests/languages/samples/less/less.less b/packages/rbtr/src/rbtr/tests/languages/samples/less/less.less new file mode 100644 index 00000000..8dea834a --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/samples/less/less.less @@ -0,0 +1,38 @@ +// Greeter theme (Less). +// +// The Less plugin extracts @-variables, .mixin() definitions, rule sets +// and @media/@charset/@keyframes as doc sections, and @import as imports. +// Mixin calls (e.g. `.rounded(8px)`) are references, not definitions. + +@import "base"; + +@primary: #333; +@spacing: 8px; + +.rounded(@radius: 4px) { + border-radius: @radius; +} + +.greeter { + color: @primary; + padding: @spacing; + .rounded(8px); + .greeting-label { + text-transform: uppercase; + } +} + +@media (max-width: 600px) { + .greeter { + font-size: 14px; + } +} + +@keyframes greeting-fade { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/packages/rbtr/src/rbtr/tests/languages/samples/scss/config.scss b/packages/rbtr/src/rbtr/tests/languages/samples/scss/config.scss new file mode 100644 index 00000000..c540dc84 --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/samples/scss/config.scss @@ -0,0 +1,3 @@ +// Imported by scss.scss via `@use "config"`. + +$brand: #00aa77; diff --git a/packages/rbtr/src/rbtr/tests/languages/samples/scss/reset.scss b/packages/rbtr/src/rbtr/tests/languages/samples/scss/reset.scss new file mode 100644 index 00000000..e92f25fb --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/samples/scss/reset.scss @@ -0,0 +1,6 @@ +// Imported by scss.scss via `@import "reset"`. + +* { + margin: 0; + padding: 0; +} diff --git a/packages/rbtr/src/rbtr/tests/languages/samples/scss/scss.scss b/packages/rbtr/src/rbtr/tests/languages/samples/scss/scss.scss new file mode 100644 index 00000000..7e18f705 --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/samples/scss/scss.scss @@ -0,0 +1,53 @@ +// Greeter theme (SCSS). +// +// The SCSS plugin extracts $-variables, @mixin and @function definitions, +// rule sets (including %placeholders and nested rules), @media/@charset/ +// @keyframes as doc sections, and @use/@forward/@import as imports. + +@charset "UTF-8"; + +@use "config"; +@forward "theme"; +@import "reset"; + +$primary: #333; +$spacing: 8px; + +@mixin flex-center($direction: row) { + display: flex; + flex-direction: $direction; + align-items: center; +} + +@function rem($px) { + @return $px * 1rem; +} + +%card-base { + border-radius: 4px; + padding: $spacing; +} + +.greeter { + color: $primary; + @include flex-center(column); + + &:hover { + color: blue; + } +} + +@media (max-width: 600px) { + .greeter { + font-size: rem(14); + } +} + +@keyframes greeting-fade { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/packages/rbtr/src/rbtr/tests/languages/samples/scss/theme.scss b/packages/rbtr/src/rbtr/tests/languages/samples/scss/theme.scss new file mode 100644 index 00000000..7e239fc0 --- /dev/null +++ b/packages/rbtr/src/rbtr/tests/languages/samples/scss/theme.scss @@ -0,0 +1,3 @@ +// Re-exported by scss.scss via `@forward "theme"`. + +$radius: 6px; diff --git a/packages/rbtr/src/rbtr/tests/languages/test_manager.py b/packages/rbtr/src/rbtr/tests/languages/test_manager.py index 860f45f7..c255aead 100644 --- a/packages/rbtr/src/rbtr/tests/languages/test_manager.py +++ b/packages/rbtr/src/rbtr/tests/languages/test_manager.py @@ -37,6 +37,8 @@ ("config.yaml", "yaml"), ("ci.yml", "yaml"), ("pyproject.toml", "toml"), + ("theme.scss", "scss"), + ("theme.less", "less"), ("README.md", "markdown"), ("src/rbtr/index/store.py", "python"), ], @@ -59,6 +61,8 @@ "yaml", "yml", "toml", + "scss", + "less", "markdown", "nested-path", ], @@ -263,6 +267,8 @@ def test_all_language_ids_not_empty(language_manager: LanguageManager) -> None: "markdown", "css", "html", + "scss", + "less", ], ) def test_all_language_ids_contains(lang_id: str, language_manager: LanguageManager) -> None: diff --git a/uv.lock b/uv.lock index d86af033..d2bd09cc 100644 --- a/uv.lock +++ b/uv.lock @@ -3233,11 +3233,13 @@ dependencies = [ { name = "tree-sitter-java" }, { name = "tree-sitter-javascript" }, { name = "tree-sitter-json" }, + { name = "tree-sitter-less" }, { name = "tree-sitter-markdown" }, { name = "tree-sitter-python" }, { name = "tree-sitter-rst" }, { name = "tree-sitter-ruby" }, { name = "tree-sitter-rust" }, + { name = "tree-sitter-scss" }, { name = "tree-sitter-sql" }, { name = "tree-sitter-toml" }, { name = "tree-sitter-typescript" }, @@ -3282,11 +3284,13 @@ requires-dist = [ { name = "tree-sitter-java" }, { name = "tree-sitter-javascript" }, { name = "tree-sitter-json" }, + { name = "tree-sitter-less" }, { name = "tree-sitter-markdown" }, { name = "tree-sitter-python" }, { name = "tree-sitter-rst" }, { name = "tree-sitter-ruby" }, { name = "tree-sitter-rust" }, + { name = "tree-sitter-scss" }, { name = "tree-sitter-sql" }, { name = "tree-sitter-toml" }, { name = "tree-sitter-typescript" }, @@ -4174,6 +4178,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/3d/902d2f3125b6b90cebf404b63ca775bc6d82071ccc76c0d10fabfeb2febe/tree_sitter_json-0.24.8-cp39-abi3-win_arm64.whl", hash = "sha256:591e0096c882d12668b88f30d3ca6f85b9db3406910eaaab6afb6b17d65367dd", size = 10174, upload-time = "2024-11-11T06:04:59.309Z" }, ] +[[package]] +name = "tree-sitter-less" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/2c/d230df63641efde982282d475663e6e25467123a55cddaff2849349d8bef/tree-sitter-less-1.0.0.tar.gz", hash = "sha256:ddb96ba5ebb374df38d7ccc4eb51cb137a5ad6c96c4d8b94b5ea56e59ec870c7", size = 45581, upload-time = "2024-07-11T15:40:23.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/c9/a501c69c3d663324c64738201d10fcf6fa8e7f18009be71b462e25170a74/tree_sitter_less-1.0.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7fc614cf487f9d9d65ccce29eb44939541c70d7b05493b2e94135830774aa042", size = 33937, upload-time = "2024-07-11T15:40:13.86Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/d9030a53711717c7c34f1445ee317e0ffad3030e61318bd630ebacbeae5a/tree_sitter_less-1.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:67786bc032527f32926fb00e35c074df2add8d37cad8b107d35d9394668a3fc4", size = 35485, upload-time = "2024-07-11T15:40:15.418Z" }, + { url = "https://files.pythonhosted.org/packages/f4/41/9c34959e86aa1b6716fb5fb08c641e4c060628e38ea685bbc5a49c1c50cb/tree_sitter_less-1.0.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a03a523ddef3de915b5a90ccd058e6a14ae050074a423ca83878305b61f7dbb", size = 65622, upload-time = "2024-07-11T15:40:16.966Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/3376c0af3db4a0329f59d22c86efd420ae64c7295f8639bb8bd91c845e85/tree_sitter_less-1.0.0-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e70c521aa8582ab92f21a5a037576b2590eca43d97e2156ed7c13d9a1c06f40", size = 48181, upload-time = "2024-07-11T15:40:18.594Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1b/c91639bcdb4729ed42958b4817ecef34bf62a2a3d5582b6d1a649f22e823/tree_sitter_less-1.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0101b1a847f3ff89bdcba149d0f405f5f06f8b37ba85e7c7944a7f58423978c9", size = 45257, upload-time = "2024-07-11T15:40:19.736Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/088cdcfb22d6299537291cf2c641ca4c50ec11a6643dbc8423ff96c9c509/tree_sitter_less-1.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1ccffef2542ca66b6cc740e1b28b5ecbcf88b738780effdb74caeae186d1037b", size = 44733, upload-time = "2024-07-11T15:40:21.152Z" }, + { url = "https://files.pythonhosted.org/packages/44/43/a513bfd76b28aeabc894977242e0e78bb473039d03c0976216a52ab8c3b8/tree_sitter_less-1.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:d5e6af5fdedb15e98a92b2f10347add43619d1940eda59d567f180b50624c972", size = 37469, upload-time = "2024-07-11T15:40:22.237Z" }, +] + [[package]] name = "tree-sitter-markdown" version = "0.5.1" @@ -4252,6 +4271,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/d8/050a781172745bc345f98abb7c56e72022ea0790f8e793de981c83c2ef15/tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227", size = 128245, upload-time = "2026-03-27T21:08:54.803Z" }, ] +[[package]] +name = "tree-sitter-scss" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/46/f9efaa05a3fb5fde4d3b5648a3b71d243b96226338d1acfa5b0598f3a70e/tree_sitter_scss-1.0.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4b4e25f1ff5196883db694aab9bb3bce23d0c309d1cea3bc4edfd244eca6d453", size = 34533, upload-time = "2024-04-09T02:09:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/88/1a17995dbd04b93c0572b7d92a6f041a8477a758e59a7808935a985be79b/tree_sitter_scss-1.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:529ad1573aed8802e73ac9c3fcdeacf999296f941893401a96f513e4d2e793e7", size = 36237, upload-time = "2024-04-09T02:09:53.204Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/5bae36d4119c12e376cf55c038e0b711400517922ee21c02e9364995eb87/tree_sitter_scss-1.0.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4d5e8ff127dc0530bd33dc185682fbd41f339c2dd263519b496c71039900e71", size = 65705, upload-time = "2024-04-09T02:09:54.839Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a9/75e8e7a80083c68bf9e6d471374e74fb9e3ac3df92cca12ceb7a90d63abe/tree_sitter_scss-1.0.0-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcbde4c22c231bce4f979722df9b110e994760b6aee12a4d1ea099387eaad5f", size = 49080, upload-time = "2024-04-09T02:09:55.847Z" }, + { url = "https://files.pythonhosted.org/packages/68/3d/c14c0fe5497af925124c51e9cbbb08068a5a0853bbf5ad10fc3c2e229433/tree_sitter_scss-1.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c66bc0cfa54d854c2d12d9ee4be1bb519a2545f6ad6b69f855714971ac82d354", size = 67840, upload-time = "2024-04-09T02:09:57.432Z" }, + { url = "https://files.pythonhosted.org/packages/bb/39/32c103b373ae432a25307d74e76ec0a33a90a7d19502ee6bfe664fab00cd/tree_sitter_scss-1.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd236bfdf0d0f0db3ac3d1294797df9ec54c495248a6e17fce77426b7e01d5d9", size = 51978, upload-time = "2024-04-09T02:09:59.075Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bd/207c85c734e63dfd69e054a0c81f6633cdb0760f2a72510a33b1f7935cba/tree_sitter_scss-1.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:254265570e4385cac76e34261278a6b0e74fb1eb98065f74cff86272da8e48a7", size = 38068, upload-time = "2024-04-09T02:10:00.841Z" }, +] + [[package]] name = "tree-sitter-sql" version = "0.3.11"