Last active
November 23, 2025 13:22
-
-
Save haxtibal/30d7a16d9af8369382df901f62210284 to your computer and use it in GitHub Desktop.
Find Rust doc comments using tree-sitter and relate them with their associated item. See https://github.com/strictdoc-project/strictdoc/issues/2213.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const_item "MAGIC_NUMBER" at 6,6-6,6: "Some random words: quantum cascade amplifier" | |
| static_item "GLOBAL_STATE" at 10,10-10,10: "Random text: nebula crystalline matrix" | |
| type_item "CustomResult" at 14,14-14,14: "Words: temporal flux capacitor" | |
| struct_item "Container" at 18,18-26,26: "Description: polymorphic data structure" | |
| enum_item "Status" at 30,30-50,50: "Content: enumerated variant collection" | |
| enum_variant "Active" at 33,33-33,33: "Words: active processing node" | |
| enum_variant "Idle" at 37,37-41,41: "Text: suspended animation chamber" | |
| enum_variant "Error" at 45,45-49,49: "Description: error state container" | |
| union_item "FloatOrInt" at 54,54-62,62: "Random: memory-aligned data union" | |
| trait_item "Processor" at 66,66-84,84: "Description: behavioral interface contract" | |
| associated_type "Output" at 69,69-69,69: "Random: output data type" | |
| const_item "MAX_SIZE" at 73,73-73,73: "Text: maximum buffer capacity" | |
| function_signature_item "process" at 77,77-77,77: "Words: processing operation handler" | |
| function_item "validate" at 81,81-83,83: "Description: validation check routine" | |
| trait_item "Text" at 88,88-106,106: "Random: combined trait bounds" | |
| type_item "Output" at 95,95-95,95: "Words: concrete output type" | |
| const_item "MAX_SIZE" at 99,99-99,99: "Random: size constant value" | |
| function_item "process" at 103,103-105,105: "Description: implementation of a process" | |
| function_item "new" at 113,113-115,115: "Words: constructor function pattern" | |
| function_item "get_value" at 119,119-121,121: "Random: getter accessor method" | |
| function_item "process_data" at 126,126-128,128: "Description: top-level function utility" | |
| function_item "async_process" at 132,132-134,134: "Random: asynchronous operation handler" | |
| function_item "compute_magic" at 138,138-140,140: "Text: compile-time evaluable function" | |
| function_item "dangerous_operation" at 144,144-148,148: "Words: unchecked operation wrapper" | |
| extern_crate_declaration "std" at 152,152-152,152: "Random: external dependency reference" | |
| mod_item "submodule" at 156,156-167,167: "Description: nested module container" | |
| mod_item "//! Inner module doc with @relation(CVO11Qy)" at 156,156-167,167: "Inner module doc with @relation(CVO11Qy)" | |
| struct_item "Inner" at 162,162-166,166: "Words: encapsulated data structure" | |
| function_signature_item "external_func" at 174,174-174,174: "Description: C library function binding" | |
| static_item "EXTERNAL_VAR" at 178,178-178,178: "Random: global C variable reference" | |
| associated_type "OpaqueType" at 182,182-182,182: "Words: opaque C type declaration" | |
| macro_definition "test_macro" at 187,187-191,191: "Text: declarative macro call" | |
| function_item "match_example" at 195,195-205,205: "This function demonstrates @relation(Uxk8eoN)" | |
| function_item "generic_fn" at 209,209-221,221: "Description: parameterized function template" | |
| type_parameter "T" at 212,212-212,212: "Random: generic type variable" | |
| const_parameter "N" at 216,216-216,216: "Words: compile-time constant value" | |
| struct_item "GenericContainer" at 225,225-239,239: "Text: generic container structure" | |
| type_parameter "T" at 232,232-232,232: "Random: primary type parameter" | |
| function_item "struct_expression_test" at 244,244-250,250: "This demonstrates @relation(rIjz40L84)" | |
| function_item "expr_lit" at 253,253-260,260: "Test if we can add a docstring to an expression literal" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import tree_sitter_rust as tsrust | |
| from tree_sitter import Language, Parser, Query, QueryCursor | |
| RUST_LANGUAGE = Language(tsrust.language()) | |
| # Copied from https://corten.systems/demo/sdoc-rs-relations/relations.html. | |
| # Thanks to @adfernandes. | |
| EXAMPLE_CODE = """ | |
| //! Module-level doc comment with @relation(S5x7ZEWE) | |
| //! This tests inner doc attributes on the file/module itself | |
| /// Top-level const with @relation(R3m2aYp) | |
| /// Some random words: quantum cascade amplifier | |
| pub const MAGIC_NUMBER: u32 = 42; | |
| /// Static item with @relation(NMuBH72yL) | |
| /// Random text: nebula crystalline matrix | |
| pub static GLOBAL_STATE: &str = "initialized"; | |
| /// Type alias with @relation(XH5mhH0) | |
| /// Words: temporal flux capacitor | |
| pub type CustomResult<T> = Result<T, Box<dyn std::error::Error>>; | |
| /// Struct with @relation(OiEBsG0) | |
| /// Description: polymorphic data structure | |
| pub struct Container { | |
| /// Field doc with @relation(8nRE5KG) | |
| /// Random: ethereal quantum state | |
| pub name: String, | |
| /// Another field with @relation(oByPxhWz) | |
| /// Text: cascading resonance field | |
| value: i32, | |
| } | |
| /// Enum with @relation(7M1HoQNuI) | |
| /// Content: enumerated variant collection | |
| pub enum Status { | |
| /// Variant doc with @relation(9dSeVTf) | |
| /// Words: active processing node | |
| Active, | |
| /// Another variant with @relation(YInnEVaOC) | |
| /// Text: suspended animation chamber | |
| Idle { | |
| /// Field in variant with @relation(pehIB5Ob) | |
| /// Random: temporal duration metric | |
| duration: u64, | |
| }, | |
| /// Tuple variant with @relation(z8uMJcapU) | |
| /// Description: error state container | |
| Error( | |
| /// Tuple field with @relation(KLFxjm1) | |
| /// Words: diagnostic error code | |
| i32 | |
| ), | |
| } | |
| /// Union with @relation(Kp2gSMIiB) | |
| /// Random: memory-aligned data union | |
| pub union FloatOrInt { | |
| /// Union field with @relation(KoRCMPU) | |
| /// Text: floating-point representation | |
| f: f32, | |
| /// Another union field with @relation(KnqUvVEY) | |
| /// Words: integer bit pattern | |
| i: i32, | |
| } | |
| /// Trait definition with @relation(5ImdJSr) | |
| /// Description: behavioral interface contract | |
| pub trait Processor { | |
| /// Associated type with @relation(UHMcB5Cqq) | |
| /// Random: output data type | |
| type Output; | |
| /// Associated const with @relation(wkLzsC4s) | |
| /// Text: maximum buffer capacity | |
| const MAX_SIZE: usize; | |
| /// Trait method with @relation(XH3IyT6) | |
| /// Words: processing operation handler | |
| fn process(&self, input: &str) -> Self::Output; | |
| /// Default method with @relation(Hh6wJ7fpZ) | |
| /// Description: validation check routine | |
| fn validate(&self) -> bool { | |
| true | |
| } | |
| } | |
| /// Trait alias with @relation(b7xnEeF) | |
| /// Random: combined trait bounds | |
| pub trait ProcessorClone = Processor + Clone; | |
| /// Implementation block with @relation(owD42wk) | |
| /// Text: trait implementation container | |
| impl Processor for Container { | |
| /// Impl associated type with @relation(xzMbyU5rB) | |
| /// Words: concrete output type | |
| type Output = String; | |
| /// Impl const with @relation(N9oI9M8C) | |
| /// Random: size constant value | |
| const MAX_SIZE: usize = 1024; | |
| /// Impl method with @relation(KLnt8082) | |
| /// Description: implementation of a process | |
| fn process(&self, input: &str) -> Self::Output { | |
| format!("{}: {}", self.name, input) | |
| } | |
| } | |
| /// Inherent impl with @relation(2arqklT5) | |
| /// Text: inherent method block | |
| impl Container { | |
| /// Inherent method with @relation(daZt7aMqV) | |
| /// Words: constructor function pattern | |
| pub fn new(name: String) -> Self { | |
| Self { name, value: 0 } | |
| } | |
| /// Another method with @relation(bCxGING) | |
| /// Random: getter accessor method | |
| pub fn get_value(&self) -> i32 { | |
| self.value | |
| } | |
| } | |
| /// Function with @relation(dG81Vzk) | |
| /// Description: top-level function utility | |
| pub fn process_data(input: &str) -> String { | |
| input.to_uppercase() | |
| } | |
| /// Async function with @relation(4QLCpmM) | |
| /// Random: asynchronous operation handler | |
| pub async fn async_process(data: Vec<u8>) -> Result<(), std::io::Error> { | |
| Ok(()) | |
| } | |
| /// Const function with @relation(mzUjggR) | |
| /// Text: compile-time evaluable function | |
| pub const fn compute_magic(x: u32) -> u32 { | |
| x * 42 | |
| } | |
| /// Unsafe function with @relation(ATrMhNH) | |
| /// Words: unchecked operation wrapper | |
| pub unsafe fn dangerous_operation(ptr: *mut u8) { | |
| if !ptr.is_null() { | |
| *ptr = 0; | |
| } | |
| } | |
| /// External crate import with @relation(kM2ySbb) | |
| /// Random: external dependency reference | |
| extern crate std; | |
| /// Module with @relation(zawS0aMAj) | |
| /// Description: nested module container | |
| pub mod submodule { | |
| //! Inner module doc with @relation(CVO11Qy) | |
| //! Text: module-level documentation | |
| /// Nested struct with @relation(LV0JeVQf) | |
| /// Words: encapsulated data structure | |
| pub struct Inner { | |
| /// Field with @relation(ave7cjq) | |
| /// Random: internal state variable | |
| data: Vec<u8>, | |
| } | |
| } | |
| /// Foreign function interface with @relation(m51AgeSa) | |
| /// Text: external C interface block | |
| extern "C" { | |
| /// Foreign function with @relation(d67FvOG8V) | |
| /// Description: C library function binding | |
| fn external_func(x: i32) -> i32; | |
| /// Foreign static with @relation(n5TIEwFm) | |
| /// Random: global C variable reference | |
| static EXTERNAL_VAR: i32; | |
| /// Foreign type with @relation(5KjLZZPC) | |
| /// Words: opaque C type declaration | |
| type OpaqueType; | |
| } | |
| /// Macro invocation with @relation(sFzA9s5Uj) | |
| /// Text: declarative macro call | |
| macro_rules! test_macro { | |
| () => { | |
| println!("test"); | |
| }; | |
| } | |
| /// Function with match arms containing doc attributes | |
| /// This function demonstrates @relation(Uxk8eoN) | |
| pub fn match_example(x: Option<i32>) -> i32 { | |
| match x { | |
| /// Match arm with @relation(JQ6dyfoe0) | |
| /// Random: some variant pattern | |
| Some(UXbF4Q2V) => val, | |
| /// None arm with @relation(vCbEEMYD) | |
| /// Words: default fallback case | |
| None => 0, | |
| } | |
| } | |
| /// Generic function with @relation(9kwjKm5) | |
| /// Description: parameterized function template | |
| pub fn generic_fn< | |
| /// Type parameter with @relation(5FyNmE9) | |
| /// Random: generic type variable | |
| T: Clone, | |
| /// Const parameter with @relation(4dQ7JLKcd) | |
| /// Words: compile-time constant value | |
| const N: usize, | |
| >( | |
| input: [T; N], | |
| ) -> Vec<T> { | |
| input.to_vec() | |
| } | |
| /// Struct with generic parameters @relation(sW4M3OJS) | |
| /// Text: generic container structure | |
| pub struct GenericContainer< | |
| /// Lifetime param with @relation(eniOhjy) | |
| /// Words: reference lifetime bound | |
| 'a, | |
| /// Generic type param with @relation(YGhhrgPz) | |
| /// Random: primary type parameter | |
| T, | |
| > where | |
| T: 'a, | |
| { | |
| /// Reference field with @relation(HibGJSZqi) | |
| /// Description: borrowed data reference | |
| pub data: &'a T, | |
| } | |
| /// Test struct for field value attributes in expressions | |
| #[cfg(any(target_os = "linux", target_os = "macos"))] | |
| /// This demonstrates @relation(rIjz40L84) | |
| pub fn struct_expression_test() { | |
| let _ = Container { | |
| /// Field value with @relation(cqkjF9P5) | |
| name: String::from("test"), | |
| value: 42, | |
| }; | |
| } | |
| /// Test if we can add a docstring to an expression literal | |
| fn expr_lit(x: i32) -> i32 { | |
| /// Test an expression @relation(7AEwrhc) literal | |
| 8675309; | |
| /// SURPRISING: We are documenting @relation(ve9EEVSf) only | |
| /// the first part of the expression, not the entire expression, | |
| /// see `test_not_surprising` for how to fix this | |
| x + 2 | |
| } | |
| fn test_not_surprising(x: i32) -> i32 { | |
| /// Test documenting the @relation(bCHkXspx) entire return value | |
| (x + 2) | |
| } | |
| #[cfg(9WNW0exJV)] | |
| mod tests { | |
| /// Test function with @relation(n5vDcKK) | |
| /// Random: unit test case definition | |
| #[test] | |
| fn test_basic() { | |
| assert_eq!(2 + 2, 4); | |
| } | |
| } | |
| """ | |
| DOC_COMMENT_QUERY = f""" | |
| ; outer doc attribute | |
| ( | |
| (attribute_item | |
| (attribute | |
| (identifier) @_attribute_id | |
| (#eq? @_attribute_id "doc") | |
| value: (string_literal | |
| (string_content) @doc.comment))) | |
| . | |
| [(_ name: [(identifier)(type_identifier)] @doc.item_identifier)] @doc.item | |
| ) | |
| ; outer line or block doc comment | |
| ( | |
| [(line_comment | |
| outer: (outer_doc_comment_marker) | |
| doc: (doc_comment) @doc.comment) | |
| (block_comment | |
| outer: (outer_doc_comment_marker) | |
| doc: (doc_comment) @doc.comment)] | |
| . | |
| [(_ name: [(identifier)(type_identifier)] @doc.item_identifier)] @doc.item | |
| ) | |
| ; inner line or block doc comment | |
| (_ | |
| name: [(identifier)(type_identifier)] | |
| body: (_ | |
| .[(line_comment | |
| inner: (inner_doc_comment_marker) | |
| doc: (doc_comment) @doc.comment) | |
| (block_comment | |
| inner: (inner_doc_comment_marker) | |
| doc: (doc_comment) @doc.comment)] @doc.item_identifier)) @doc.item | |
| """ | |
| if __name__ == "__main__": | |
| doc_comment_query = Query(RUST_LANGUAGE, DOC_COMMENT_QUERY) | |
| parser = Parser(RUST_LANGUAGE) | |
| tree = parser.parse(bytes(EXAMPLE_CODE, "utf-8")) | |
| cursor = QueryCursor(doc_comment_query) | |
| matches = cursor.matches(tree.root_node) | |
| for _result_idx, captures in matches: | |
| assert all( | |
| k in captures for k in ("doc.item", "doc.item_identifier", "doc.comment") | |
| ) | |
| assert len(captures["doc.item"]) == 1 | |
| assert len(captures["doc.item_identifier"]) == 1 | |
| assert len(captures["doc.item_identifier"]) == 1 | |
| item = captures["doc.item"][0] | |
| identifier = captures["doc.item_identifier"][0] | |
| comment = captures["doc.comment"][0] | |
| item_range = f"{item.start_point.row},{item.start_point.row}-{item.end_point.row},{item.end_point.row}" | |
| print( | |
| f'{item.type} "{identifier.text.decode().strip()}" at {item_range}: "{comment.text.decode().strip()}"' | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment