Skip to content
Open
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
19 changes: 13 additions & 6 deletions src/arcade_agent/parsers/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,11 +443,24 @@ def visit_container(
pending_attributes.append(node)
continue

# Comments between an attribute and its item must not
# clear the pending attribute list (tree-sitter emits
# line_comment/block_comment as named children).
if node.type in {"line_comment", "block_comment"}:
continue

is_cfg_test = any(
_is_cfg_test_attribute(attribute) for attribute in pending_attributes
)
pending_attributes.clear()

# Rust unit tests live inline, so path-based test
# exclusion in ``ingest`` cannot see them. Drop
# ``#[cfg(test)]`` items only when the caller asked
# for test code to be excluded.
if is_cfg_test and self.exclude_tests:
continue

if node.type in _TYPE_ITEMS:
name_node = node.child_by_field_name("name")
if name_node is None:
Expand Down Expand Up @@ -548,12 +561,6 @@ def visit_container(
)
)
elif node.type == "mod_item":
# Rust unit tests live inline, so path-based test
# exclusion in ``ingest`` cannot see them. Drop
# ``#[cfg(test)]`` modules only when the caller asked
# for test code to be excluded.
if is_cfg_test and self.exclude_tests:
continue
name_node = node.child_by_field_name("name")
body = node.child_by_field_name("body")
if name_node is not None and body is not None:
Expand Down
56 changes: 56 additions & 0 deletions tests/test_parsers/test_rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,62 @@ def test_rust_parser_does_not_silently_drop_large_files(tmp_path):
assert "Tail" in graph.entities


def test_rust_parser_skips_cfg_test_with_comment_between_attribute_and_item(tmp_path):
"""A comment between #[cfg(test)] and the item must not break exclusion."""
source = tmp_path / "lib.rs"
source.write_text(
"pub struct Production;\n"
"#[cfg(test)]\n"
"// unit tests for this module\n"
"mod tests {\n"
" struct Fixture;\n"
" fn helper() {}\n"
"}\n"
"#[cfg(test)]\n"
"/// Doc comment should also not break exclusion\n"
"mod doc_tests {\n"
" struct DocFixture;\n"
"}\n"
)

graph = RustParser().parse([source], tmp_path)
assert "Production" in graph.entities
assert all(not fqn.startswith("tests") for fqn in graph.entities)
assert all(not fqn.startswith("doc_tests") for fqn in graph.entities)


def test_rust_parser_skips_cfg_test_on_non_mod_items(tmp_path):
"""#[cfg(test)] on functions, structs, and impls must also be excluded."""
source = tmp_path / "lib.rs"
source.write_text(
"pub struct Production;\n"
"#[cfg(test)]\n"
"fn test_only_helper() {}\n"
"#[cfg(test)]\n"
"struct TestFixture { x: u64 }\n"
"#[cfg(test)]\n"
"impl TestFixture { fn setup(&self) {} }\n"
"pub fn real_function() {}\n"
)

graph = RustParser().parse([source], tmp_path)
assert "Production" in graph.entities
assert "real_function" in graph.entities
assert "test_only_helper" not in graph.entities
assert all("TestFixture" not in fqn for fqn in graph.entities)


def test_rust_parser_handles_large_files_without_cap(tmp_path):
"""Files larger than 1MB must still be parsed (no silent cap)."""
source = tmp_path / "large.rs"
# Generate a file > 1MB with valid Rust content
padding = "// padding\n" * 100_000 # ~1.1MB of comments
source.write_text(padding + "pub struct LargeFile;\n")

graph = RustParser().parse([source], tmp_path)
assert "large.LargeFile" in graph.entities


def test_rust_parser_tolerates_invalid_cargo_manifest_encoding(tmp_path):
(tmp_path / "Cargo.toml").write_bytes(b"\xff\xfe")
source = tmp_path / "lib.rs"
Expand Down
Loading