Skip to content
Closed
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
4 changes: 4 additions & 0 deletions docs/src/content/docs/core-concepts/plugin-system.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ kit comes with built-in support for 12+ programming languages:
- **Dart** (`.dart`) - Classes, functions, mixins, enums, extensions
- **HCL/Terraform** (`.hcl`, `.tf`) - Resources, variables, modules
- **Haskell** (`.hs`) - Module header, functions (including lambda-binds), common type-level declarations
- **Swift** (`.swift`) - Classes, structs, enums, protocols, actors, extensions, functions
- **Bash** (`.sh`, `.bash`) - Function definitions
- **YAML** (`.yaml`, `.yml`) - Top-level mapping keys
- **TOML** (`.toml`) - Tables, array tables

Each language supports comprehensive symbol extraction including:
- **Classes and interfaces** with inheritance relationships
Expand Down
5 changes: 5 additions & 0 deletions src/kit/queries/bash/tags.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
;; tags.scm for Bash symbol extraction (tree-sitter-bash)

; Function definitions (covers both "function name()" and "name()" syntax)
(function_definition
name: (word) @name) @definition.function
42 changes: 42 additions & 0 deletions src/kit/queries/swift/tags.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
;; tags.scm for Swift symbol extraction (tree-sitter-swift)

; Function declarations
(function_declaration
name: (simple_identifier) @name) @definition.function

; Class declarations (keyword-differentiated from struct/enum/extension)
(class_declaration
"class"
name: (type_identifier) @name) @definition.class

; Actor declarations
(class_declaration
"actor"
name: (type_identifier) @name) @definition.actor

; Struct declarations
(class_declaration
"struct"
name: (type_identifier) @name) @definition.struct

; Enum declarations
(class_declaration
"enum"
name: (type_identifier) @name) @definition.enum

; Extension declarations (name field is user_type, not type_identifier)
(class_declaration
"extension"
name: (user_type) @name) @definition.extension

; Protocol declarations
(protocol_declaration
name: (type_identifier) @name) @definition.protocol

; Type alias declarations
(typealias_declaration
name: (type_identifier) @name) @definition.typealias

; Initializer declarations
(init_declaration
"init" @name) @definition.initializer
19 changes: 19 additions & 0 deletions src/kit/queries/toml/tags.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
;; tags.scm for TOML symbol extraction (tree-sitter-toml)

; Table headers with bare key: [section]
(table (bare_key) @name) @definition.table

; Table headers with dotted key: [section.subsection]
(table (dotted_key) @name) @definition.table

; Table headers with quoted key: ["section.name"]
(table (quoted_key) @name) @definition.table

; Array table headers with bare key: [[array]]
(table_array_element (bare_key) @name) @definition.table_array

; Array table headers with dotted key: [[parent.array]]
(table_array_element (dotted_key) @name) @definition.table_array

; Array table headers with quoted key: [["array.name"]]
(table_array_element (quoted_key) @name) @definition.table_array
10 changes: 10 additions & 0 deletions src/kit/queries/yaml/tags.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
;; tags.scm for YAML symbol extraction (tree-sitter-yaml)
;; Only captures top-level mapping keys (direct children of document root).
;; Use the full mapping pair as the definition so symbol spans/code include values.

(stream
(document
(block_node
(block_mapping
(block_mapping_pair
key: (flow_node (_) @name)) @definition.key))))
29 changes: 25 additions & 4 deletions src/kit/tree_sitter_symbol_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
".hxx": "cpp",
".zig": "zig",
".cs": "csharp",
".swift": "swift",
".sh": "bash",
".bash": "bash",
".yaml": "yaml",
".yml": "yaml",
".toml": "toml",
}


Expand Down Expand Up @@ -350,11 +356,23 @@ def reset_plugins(cls) -> None:
".hxx": "cpp",
".zig": "zig",
".cs": "csharp",
".swift": "swift",
".sh": "bash",
".bash": "bash",
".yaml": "yaml",
".yml": "yaml",
".toml": "toml",
}
LANGUAGES.clear()
LANGUAGES.update(original_languages)
cls.LANGUAGES = set(LANGUAGES.keys())

@staticmethod
def _strip_wrapping_quotes(text: str) -> str:
if len(text) >= 2 and text[0] == text[-1] and text[0] in {'"', "'"}:
return text[1:-1]
return text

@staticmethod
def extract_symbols(ext: str, source_code: str) -> List[Dict[str, Any]]:
"""Extracts symbols from source code using tree-sitter queries."""
Expand Down Expand Up @@ -454,10 +472,13 @@ def extract_symbols(ext: str, source_code: str) -> List[Dict[str, Any]]:
if hasattr(actual_name_node, "text") and actual_name_node.text
else str(actual_name_node)
)
# HCL: Strip quotes from string literals
if ext == ".tf" and hasattr(actual_name_node, "type") and actual_name_node.type == "string_lit":
if len(symbol_name) >= 2 and symbol_name.startswith('"') and symbol_name.endswith('"'):
symbol_name = symbol_name[1:-1]
node_type = actual_name_node.type if hasattr(actual_name_node, "type") else None
if (
(ext == ".tf" and node_type == "string_lit")
or (ext == ".toml" and node_type == "quoted_key")
or (ext in {".yaml", ".yml"} and node_type in {"double_quote_scalar", "single_quote_scalar"})
):
symbol_name = TreeSitterSymbolExtractor._strip_wrapping_quotes(symbol_name)

definition_capture = next(
((name, node) for name, node in captures.items() if name.startswith("definition.")), None
Expand Down
57 changes: 57 additions & 0 deletions tests/test_bash_symbols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import pytest

from kit.tree_sitter_symbol_extractor import TreeSitterSymbolExtractor

BASH_SAMPLE = """\
function greet() {
echo "Hello, $1!"
}

say_hi() {
echo "Hi there"
}
"""


def test_bash_parser_and_query_available():
parser = TreeSitterSymbolExtractor.get_parser(".sh")
query = TreeSitterSymbolExtractor.get_query(".sh")
if not parser or not query:
pytest.skip("Bash parser or query not available in this environment")

tree = parser.parse(BASH_SAMPLE.encode("utf-8"))
assert tree.root_node is not None


def test_bash_symbols():
parser = TreeSitterSymbolExtractor.get_parser(".sh")
query = TreeSitterSymbolExtractor.get_query(".sh")
if not parser or not query:
pytest.skip("Bash parser or query not available in this environment")

symbols = TreeSitterSymbolExtractor.extract_symbols(".sh", BASH_SAMPLE)
names = {s["name"] for s in symbols}

assert "greet" in names
assert "say_hi" in names
assert all(s["type"] == "function" for s in symbols)


def test_bash_extensions():
supported = TreeSitterSymbolExtractor.list_supported_languages()
assert "bash" in supported
assert ".sh" in supported["bash"]
assert ".bash" in supported["bash"]


def test_bash_extension_extracts_symbols():
parser = TreeSitterSymbolExtractor.get_parser(".bash")
query = TreeSitterSymbolExtractor.get_query(".bash")
if not parser or not query:
pytest.skip("Bash parser or query not available in this environment")

symbols = TreeSitterSymbolExtractor.extract_symbols(".bash", BASH_SAMPLE)
names = {s["name"] for s in symbols}

assert "greet" in names
assert "say_hi" in names
92 changes: 92 additions & 0 deletions tests/test_swift_symbols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import pytest

from kit.tree_sitter_symbol_extractor import TreeSitterSymbolExtractor

SWIFT_SAMPLE = """\
class Animal {
var name: String
init(name: String) {
self.name = name
}
}

struct Point {
var x: Int
var y: Int
}

enum Direction {
case north, south, east, west
}

extension Animal {
func speak() -> String {
return name
}
}

actor Worker {
func run() {}
}

protocol Drawable {
func draw()
}

typealias StringMap = [String: String]

func greet(person: String) -> String {
return "Hello, \\(person)!"
}
"""


def test_swift_parser_and_query_available():
parser = TreeSitterSymbolExtractor.get_parser(".swift")
query = TreeSitterSymbolExtractor.get_query(".swift")
if not parser or not query:
pytest.skip("Swift parser or query not available in this environment")

tree = parser.parse(SWIFT_SAMPLE.encode("utf-8"))
assert tree.root_node is not None


def test_swift_symbols():
parser = TreeSitterSymbolExtractor.get_parser(".swift")
query = TreeSitterSymbolExtractor.get_query(".swift")
if not parser or not query:
pytest.skip("Swift parser or query not available in this environment")

symbols = TreeSitterSymbolExtractor.extract_symbols(".swift", SWIFT_SAMPLE)
names = {s["name"] for s in symbols}
types = {s["type"] for s in symbols}
animal_symbols = [s for s in symbols if s["name"] == "Animal"]

# All 9 symbol types
assert "Animal" in names
assert "Point" in names
assert "Direction" in names
assert "Worker" in names
assert "Drawable" in names
assert "StringMap" in names
assert "greet" in names
assert "init" in names

assert len(animal_symbols) == 2
assert {s["type"] for s in animal_symbols} == {"class", "extension"}

assert "class" in types
assert "actor" in types
assert "struct" in types
assert "enum" in types
assert "extension" in types
assert "protocol" in types
assert "typealias" in types
assert "function" in types
assert "initializer" in types


def test_swift_in_supported_languages():
supported = TreeSitterSymbolExtractor.list_supported_languages()
assert "swift" in supported
assert ".swift" in supported["swift"]
7 changes: 5 additions & 2 deletions tests/test_symbol_extraction_multilang.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
".java": "class Bar { void foo() {} }\n",
".rs": "fn foo() {}\nstruct Bar;\n",
".zig": "pub fn foo() void {}\npub const Bar = struct {};\n",
".swift": "func foo() -> Int { return 42 }\nclass Bar {}\n",
".sh": "function foo() { echo hello; }\n",
".yaml": "foo: bar\nbaz: 1\n",
".toml": "[foo]\nbar = 1\n",
}


Expand Down Expand Up @@ -90,8 +94,7 @@ def test_symbol_code_contains_full_body(ext: str, code: str):

# The code field should contain more than just the function name
assert len(func_code) > len(func_name), (
f"Code field for {ext} only contains name '{func_name}', expected full function body. "
f"Got: '{func_code}'"
f"Code field for {ext} only contains name '{func_name}', expected full function body. Got: '{func_code}'"
)

# The code should contain the function keyword or definition
Expand Down
Loading
Loading