Skip to content

Commit 84b35f0

Browse files
authored
C# language support
2 parents 33a0be9 + 0a6c38b commit 84b35f0

File tree

8 files changed

+257
-22
lines changed

8 files changed

+257
-22
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ dependencies = [
2727
# Compiling issue on manylinux aarch64 image, so install from git instead of Pypi
2828
# https://github.com/tree-sitter/py-tree-sitter/issues/386#issuecomment-3101430799
2929
"tree-sitter~=0.25.1",
30-
30+
"tree-sitter-c-sharp>=0.23.1",
3131
]
3232

3333
[build-system]

src/sphinx_codelinks/analyse/analyse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ def extract_marked_content(self) -> None:
322322
if not filepath:
323323
continue
324324
tagged_scope: TreeSitterNode | None = utils.find_associated_scope(
325-
src_comment.node
325+
src_comment.node, self.analyse_config.comment_type
326326
)
327327
if self.analyse_config.get_need_id_refs:
328328
anchors = self.extract_anchors(

src/sphinx_codelinks/analyse/utils.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
from sphinx_codelinks.config import UNIX_NEWLINE, CommentCategory
1313
from sphinx_codelinks.source_discover.config import CommentType
1414

15+
# Language-specific node types for scope detection
16+
SCOPE_NODE_TYPES = {
17+
CommentType.python: {"function_definition", "class_definition"},
18+
CommentType.cpp: {"function_definition", "class_definition"},
19+
CommentType.cs: {"method_declaration", "class_declaration", "property_declaration"},
20+
}
21+
1522
# initialize logger
1623
logger = logging.getLogger(__name__)
1724
logger.setLevel(logging.INFO)
@@ -35,6 +42,7 @@
3542
(class_definition (block (expression_statement (string)) @comment))
3643
"""
3744
CPP_QUERY = """(comment) @comment"""
45+
C_SHARP_QUERY = """(comment) @comment"""
3846

3947

4048
def is_text_file(filepath: Path, sample_size: int = 2048) -> bool:
@@ -63,6 +71,11 @@ def init_tree_sitter(comment_type: CommentType) -> tuple[Parser, Query]:
6371

6472
parsed_language = Language(tree_sitter_python.language())
6573
query = Query(parsed_language, PYTHON_QUERY)
74+
elif comment_type == CommentType.cs:
75+
import tree_sitter_c_sharp # noqa: PLC0415
76+
77+
parsed_language = Language(tree_sitter_c_sharp.language())
78+
query = Query(parsed_language, C_SHARP_QUERY)
6679
else:
6780
raise ValueError(f"Unsupported comment style: {comment_type}")
6881
parser = Parser(parsed_language)
@@ -90,39 +103,47 @@ def extract_comments(
90103
return captures.get("comment")
91104

92105

93-
def find_enclosing_scope(node: TreeSitterNode) -> TreeSitterNode | None:
106+
def find_enclosing_scope(
107+
node: TreeSitterNode, comment_type: CommentType = CommentType.cpp
108+
) -> TreeSitterNode | None:
94109
"""Find the enclosing scope of a comment."""
110+
scope_types = SCOPE_NODE_TYPES.get(comment_type, SCOPE_NODE_TYPES[CommentType.cpp])
95111
current: TreeSitterNode = node
96112
while current:
97-
if current.type in {"function_definition", "class_definition"}:
113+
if current.type in scope_types:
98114
return current
99115
current: TreeSitterNode | None = current.parent # type: ignore[no-redef] # required for node traversal
100116
return None
101117

102118

103-
def find_next_scope(node: TreeSitterNode) -> TreeSitterNode | None:
119+
def find_next_scope(
120+
node: TreeSitterNode, comment_type: CommentType = CommentType.cpp
121+
) -> TreeSitterNode | None:
104122
"""Find the next scope of a comment."""
123+
scope_types = SCOPE_NODE_TYPES.get(comment_type, SCOPE_NODE_TYPES[CommentType.cpp])
105124
current: TreeSitterNode = node
106125
while current:
107-
if current.type in {"function_definition", "class_definition"}:
126+
if current.type in scope_types:
108127
return current
109128
current: TreeSitterNode | None = current.next_named_sibling # type: ignore[no-redef] # required for node traversal
110129
if current and current.type == "block":
111130
for child in current.named_children:
112-
if child.type in {"function_definition", "class_definition"}:
131+
if child.type in scope_types:
113132
return child
114133
return None
115134

116135

117-
def find_associated_scope(node: TreeSitterNode) -> TreeSitterNode | None:
136+
def find_associated_scope(
137+
node: TreeSitterNode, comment_type: CommentType = CommentType.cpp
138+
) -> TreeSitterNode | None:
118139
"""Find the associated scope of a comment."""
119140
if node.type == CommentCategory.docstring:
120141
# Only for python's docstring
121-
return find_enclosing_scope(node)
142+
return find_enclosing_scope(node, comment_type)
122143
# General comments regardless of comment types
123-
associated_scope = find_next_scope(node)
144+
associated_scope = find_next_scope(node, comment_type)
124145
if not associated_scope:
125-
associated_scope = find_enclosing_scope(node)
146+
associated_scope = find_enclosing_scope(node, comment_type)
126147
return associated_scope
127148

128149

src/sphinx_codelinks/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
UNIX_NEWLINE = "\n"
1919

2020

21-
COMMENT_MARKERS = {CommentType.cpp: ["//", "/*"], CommentType.python: ["#"]}
21+
COMMENT_MARKERS = {
22+
CommentType.cpp: ["//", "/*"],
23+
CommentType.python: ["#"],
24+
CommentType.cs: ["//", "/*", "///"],
25+
}
2226
ESCAPE = "\\"
2327

2428

src/sphinx_codelinks/source_discover/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55

66
from jsonschema import ValidationError, validate
77

8-
# TODO: COMMENT_FILETYPE is probably not a good name, as C# uses the same comment style as C++, but it's grammatically different.
9-
# Therefore, C# requires a different parser of tree-sitter to analyse its AST
108
COMMENT_FILETYPE = {
119
"cpp": ["c", "ci", "cpp", "cc", "cxx", "h", "hpp", "hxx", "hh", "ihl"],
1210
"python": ["py"],
11+
"cs": ["cs"],
1312
}
1413

1514

1615
class CommentType(str, Enum):
1716
python = "python"
1817
cpp = "cpp"
18+
cs = "cs"
1919

2020

2121
class SourceDiscoverSectionConfigType(TypedDict, total=False):

0 commit comments

Comments
 (0)