fix rust
This commit is contained in:
+54
-114
@@ -1,4 +1,3 @@
|
|||||||
// tools/src/main.rs
|
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -7,7 +6,7 @@ use std::fs;
|
|||||||
use std::process;
|
use std::process;
|
||||||
use syn::{
|
use syn::{
|
||||||
spanned::Spanned, Attribute, FnArg, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct,
|
spanned::Spanned, Attribute, FnArg, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct,
|
||||||
ItemTrait, Pat, Type, UseTree, UseTreeKind, Visibility,
|
ItemTrait, Pat, Type, UseTree, Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -26,8 +25,8 @@ struct RustDecl {
|
|||||||
return_type: Option<String>,
|
return_type: Option<String>,
|
||||||
parameters: Vec<String>,
|
parameters: Vec<String>,
|
||||||
docstring: Option<String>,
|
docstring: Option<String>,
|
||||||
imports: Vec<String>, // Track imports for cross-file relationships
|
imports: Vec<String>,
|
||||||
calls: Vec<String>, // Track function calls
|
calls: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -55,7 +54,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut decls = Vec::new();
|
let mut decls = Vec::new();
|
||||||
let mut imports = Vec::new(); // Collect imports separately
|
let mut imports = Vec::new();
|
||||||
|
|
||||||
// Process imports first
|
// Process imports first
|
||||||
for item in &syntax.items {
|
for item in &syntax.items {
|
||||||
@@ -63,7 +62,7 @@ fn main() {
|
|||||||
let import_paths = extract_use_paths(&item_use.tree);
|
let import_paths = extract_use_paths(&item_use.tree);
|
||||||
imports.extend(import_paths.clone());
|
imports.extend(import_paths.clone());
|
||||||
|
|
||||||
for path in &import_paths {
|
for path in import_paths {
|
||||||
decls.push(RustDecl {
|
decls.push(RustDecl {
|
||||||
name: path.clone(),
|
name: path.clone(),
|
||||||
type_: "import".to_string(),
|
type_: "import".to_string(),
|
||||||
@@ -86,12 +85,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process other declarations with import context
|
// Process other declarations
|
||||||
for item in &syntax.items {
|
for item in &syntax.items {
|
||||||
match item {
|
match item {
|
||||||
Item::Fn(item_fn) => {
|
Item::Fn(item_fn) => {
|
||||||
if let Some(mut decl) = parse_function(&item_fn, &content) {
|
if let Some(mut decl) = parse_function(&item_fn, &content) {
|
||||||
decl.imports = imports.clone(); // Add imports to function context
|
decl.imports = imports.clone();
|
||||||
decls.push(decl);
|
decls.push(decl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +113,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item::Impl(item_impl) => {
|
Item::Impl(item_impl) => {
|
||||||
if let Some(mut decl) = parse_impl(&item_impl, &content) {
|
if let Some(mut decl) = parse_impl(&item_impl) {
|
||||||
decl.imports = imports.clone();
|
decl.imports = imports.clone();
|
||||||
decls.push(decl);
|
decls.push(decl);
|
||||||
}
|
}
|
||||||
@@ -133,7 +132,7 @@ fn main() {
|
|||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Better span handling (requires proc-macro2 "span-locations" feature)
|
// Fixed span handling
|
||||||
fn get_span_start(span: &proc_macro2::Span) -> usize {
|
fn get_span_start(span: &proc_macro2::Span) -> usize {
|
||||||
span.start().line
|
span.start().line
|
||||||
}
|
}
|
||||||
@@ -142,7 +141,7 @@ fn get_span_end(span: &proc_macro2::Span) -> usize {
|
|||||||
span.end().line
|
span.end().line
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced import extraction
|
// Fixed import extraction
|
||||||
fn extract_use_paths(tree: &UseTree) -> Vec<String> {
|
fn extract_use_paths(tree: &UseTree) -> Vec<String> {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
extract_use_paths_recursive(tree, "", &mut paths);
|
extract_use_paths_recursive(tree, "", &mut paths);
|
||||||
@@ -178,9 +177,6 @@ fn extract_use_paths_recursive(tree: &UseTree, current_path: &str, paths: &mut V
|
|||||||
UseTree::Glob(_) => {
|
UseTree::Glob(_) => {
|
||||||
if !current_path.is_empty() {
|
if !current_path.is_empty() {
|
||||||
paths.push(format!("{}::*", current_path));
|
paths.push(format!("{}::*", current_path));
|
||||||
} else {
|
|
||||||
// top-level glob — represent as "*"
|
|
||||||
paths.push("*".to_string());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UseTree::Group(group) => {
|
UseTree::Group(group) => {
|
||||||
@@ -188,12 +184,10 @@ fn extract_use_paths_recursive(tree: &UseTree, current_path: &str, paths: &mut V
|
|||||||
extract_use_paths_recursive(item, current_path, paths);
|
extract_use_paths_recursive(item, current_path, paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the AST contains other nodes, ignore them (keeps function exhaustive)
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced function parsing with call detection
|
// Fixed function parsing
|
||||||
fn parse_function(item_fn: &ItemFn, file_content: &str) -> Option<RustDecl> {
|
fn parse_function(item_fn: &ItemFn, file_content: &str) -> Option<RustDecl> {
|
||||||
let start_line = get_span_start(&item_fn.span());
|
let start_line = get_span_start(&item_fn.span());
|
||||||
let end_line = get_span_end(&item_fn.span());
|
let end_line = get_span_end(&item_fn.span());
|
||||||
@@ -204,7 +198,7 @@ fn parse_function(item_fn: &ItemFn, file_content: &str) -> Option<RustDecl> {
|
|||||||
let name = if let Pat::Ident(pat_ident) = &*pat_type.pat {
|
let name = if let Pat::Ident(pat_ident) = &*pat_type.pat {
|
||||||
pat_ident.ident.to_string()
|
pat_ident.ident.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{}", quote::ToTokens::to_token_stream(&pat_type.pat))
|
quote::ToTokens::to_token_stream(&pat_type.pat).to_string()
|
||||||
};
|
};
|
||||||
let ty = type_to_string(&*pat_type.ty);
|
let ty = type_to_string(&*pat_type.ty);
|
||||||
parameters.push(format!("{}: {}", name, ty));
|
parameters.push(format!("{}: {}", name, ty));
|
||||||
@@ -218,7 +212,6 @@ fn parse_function(item_fn: &ItemFn, file_content: &str) -> Option<RustDecl> {
|
|||||||
syn::ReturnType::Type(_, ty) => Some(type_to_string(&*ty)),
|
syn::ReturnType::Type(_, ty) => Some(type_to_string(&*ty)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract function calls from body
|
|
||||||
let calls = extract_function_calls(&item_fn.block, file_content);
|
let calls = extract_function_calls(&item_fn.block, file_content);
|
||||||
|
|
||||||
Some(RustDecl {
|
Some(RustDecl {
|
||||||
@@ -242,36 +235,28 @@ fn parse_function(item_fn: &ItemFn, file_content: &str) -> Option<RustDecl> {
|
|||||||
return_type,
|
return_type,
|
||||||
parameters,
|
parameters,
|
||||||
docstring: extract_docstring(&item_fn.attrs),
|
docstring: extract_docstring(&item_fn.attrs),
|
||||||
imports: Vec::new(), // Filled by caller
|
imports: Vec::new(),
|
||||||
calls,
|
calls,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple function call extraction using regex on tokenized block
|
// Fixed function call extraction
|
||||||
fn extract_function_calls(block: &syn::Block, _file_content: &str) -> Vec<String> {
|
fn extract_function_calls(block: &syn::Block, _file_content: &str) -> Vec<String> {
|
||||||
let mut calls = Vec::new();
|
let mut calls = Vec::new();
|
||||||
let content = quote::ToTokens::to_token_stream(block).to_string();
|
let content = quote::ToTokens::to_token_stream(block).to_string();
|
||||||
|
|
||||||
// Basic regex to find function-like identifiers followed by '('
|
|
||||||
let re = Regex::new(r"\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(").unwrap();
|
let re = Regex::new(r"\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(").unwrap();
|
||||||
for cap in re.captures_iter(&content) {
|
for cap in re.captures_iter(&content) {
|
||||||
if let Some(name) = cap.get(1) {
|
if let Some(name) = cap.get(1) {
|
||||||
let call = name.as_str().to_string();
|
let call = name.as_str().to_string();
|
||||||
// Filter out common keywords and types, and constructors (capitalized)
|
|
||||||
if !is_rust_keyword(&call) && !call.chars().next().map_or(false, |c| c.is_uppercase()) {
|
if !is_rust_keyword(&call) && !call.chars().next().map_or(false, |c| c.is_uppercase()) {
|
||||||
calls.push(call);
|
calls.push(call);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate while preserving order
|
calls.dedup();
|
||||||
let mut deduped = Vec::new();
|
calls
|
||||||
for c in calls {
|
|
||||||
if !deduped.contains(&c) {
|
|
||||||
deduped.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deduped
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_rust_keyword(word: &str) -> bool {
|
fn is_rust_keyword(word: &str) -> bool {
|
||||||
@@ -313,14 +298,10 @@ fn is_rust_keyword(word: &str) -> bool {
|
|||||||
| "box"
|
| "box"
|
||||||
| "true"
|
| "true"
|
||||||
| "false"
|
| "false"
|
||||||
| "Some"
|
|
||||||
| "None"
|
|
||||||
| "Ok"
|
|
||||||
| "Err"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update other parse functions to include the new fields...
|
// Fixed struct parsing
|
||||||
fn parse_struct(item_struct: &ItemStruct) -> Option<RustDecl> {
|
fn parse_struct(item_struct: &ItemStruct) -> Option<RustDecl> {
|
||||||
let start_line = get_span_start(&item_struct.span());
|
let start_line = get_span_start(&item_struct.span());
|
||||||
let end_line = get_span_end(&item_struct.span());
|
let end_line = get_span_end(&item_struct.span());
|
||||||
@@ -329,7 +310,6 @@ fn parse_struct(item_struct: &ItemStruct) -> Option<RustDecl> {
|
|||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|field| {
|
.filter_map(|field| {
|
||||||
// field.ident is Option<Ident> for named fields; for tuple structs it's None
|
|
||||||
field.ident.as_ref().map(|ident| {
|
field.ident.as_ref().map(|ident| {
|
||||||
let field_name = ident.to_string();
|
let field_name = ident.to_string();
|
||||||
let field_type = type_to_string(&field.ty);
|
let field_type = type_to_string(&field.ty);
|
||||||
@@ -358,11 +338,12 @@ fn parse_struct(item_struct: &ItemStruct) -> Option<RustDecl> {
|
|||||||
return_type: None,
|
return_type: None,
|
||||||
parameters: Vec::new(),
|
parameters: Vec::new(),
|
||||||
docstring: extract_docstring(&item_struct.attrs),
|
docstring: extract_docstring(&item_struct.attrs),
|
||||||
imports: Vec::new(), // Filled by caller
|
imports: Vec::new(),
|
||||||
calls: Vec::new(),
|
calls: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed enum parsing
|
||||||
fn parse_enum(item_enum: &ItemEnum) -> Option<RustDecl> {
|
fn parse_enum(item_enum: &ItemEnum) -> Option<RustDecl> {
|
||||||
let start_line = get_span_start(&item_enum.span());
|
let start_line = get_span_start(&item_enum.span());
|
||||||
let end_line = get_span_end(&item_enum.span());
|
let end_line = get_span_end(&item_enum.span());
|
||||||
@@ -398,6 +379,7 @@ fn parse_enum(item_enum: &ItemEnum) -> Option<RustDecl> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed trait parsing
|
||||||
fn parse_trait(item_trait: &ItemTrait) -> Option<RustDecl> {
|
fn parse_trait(item_trait: &ItemTrait) -> Option<RustDecl> {
|
||||||
let start_line = get_span_start(&item_trait.span());
|
let start_line = get_span_start(&item_trait.span());
|
||||||
let end_line = get_span_end(&item_trait.span());
|
let end_line = get_span_end(&item_trait.span());
|
||||||
@@ -439,7 +421,8 @@ fn parse_trait(item_trait: &ItemTrait) -> Option<RustDecl> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_impl(item_impl: &ItemImpl, file_content: &str) -> Option<RustDecl> {
|
// Fixed impl parsing
|
||||||
|
fn parse_impl(item_impl: &ItemImpl) -> Option<RustDecl> {
|
||||||
let start_line = get_span_start(&item_impl.span());
|
let start_line = get_span_start(&item_impl.span());
|
||||||
let end_line = get_span_end(&item_impl.span());
|
let end_line = get_span_end(&item_impl.span());
|
||||||
|
|
||||||
@@ -457,34 +440,19 @@ fn parse_impl(item_impl: &ItemImpl, file_content: &str) -> Option<RustDecl> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// If this impl implements a trait, extract its path
|
let implements = item_impl
|
||||||
let implements: Vec<String> = item_impl
|
|
||||||
.trait_
|
.trait_
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|(_, path, _)| path_to_string(path))
|
.map(|(_, path, _)| path_to_string(path))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Prefer the first trait name (if any) as the identifier; otherwise fall back to the target type
|
|
||||||
let name = if !implements.is_empty() {
|
let name = if !implements.is_empty() {
|
||||||
implements[0].clone()
|
implements[0].clone()
|
||||||
} else {
|
} else {
|
||||||
target.clone()
|
target
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract calls across all method bodies in the impl (basic)
|
|
||||||
let mut calls = Vec::new();
|
|
||||||
for item in &item_impl.items {
|
|
||||||
if let syn::ImplItem::Fn(method) = item {
|
|
||||||
let body_calls = extract_function_calls(&method.block, file_content);
|
|
||||||
for c in body_calls {
|
|
||||||
if !calls.contains(&c) {
|
|
||||||
calls.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let docstring = extract_docstring(&item_impl.attrs);
|
let docstring = extract_docstring(&item_impl.attrs);
|
||||||
|
|
||||||
Some(RustDecl {
|
Some(RustDecl {
|
||||||
@@ -508,10 +476,11 @@ fn parse_impl(item_impl: &ItemImpl, file_content: &str) -> Option<RustDecl> {
|
|||||||
parameters: Vec::new(),
|
parameters: Vec::new(),
|
||||||
docstring,
|
docstring,
|
||||||
imports: Vec::new(),
|
imports: Vec::new(),
|
||||||
calls,
|
calls: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed mod parsing
|
||||||
fn parse_mod(item_mod: &ItemMod) -> Option<RustDecl> {
|
fn parse_mod(item_mod: &ItemMod) -> Option<RustDecl> {
|
||||||
let start_line = get_span_start(&item_mod.span());
|
let start_line = get_span_start(&item_mod.span());
|
||||||
let end_line = get_span_end(&item_mod.span());
|
let end_line = get_span_end(&item_mod.span());
|
||||||
@@ -536,13 +505,36 @@ fn parse_mod(item_mod: &ItemMod) -> Option<RustDecl> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed docstring extraction
|
||||||
|
fn extract_docstring(attrs: &[Attribute]) -> Option<String> {
|
||||||
|
let text = attrs
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.to_token_stream().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let re = Regex::new(r#"(?m)^\s*(?:///|#\[\s*doc\s*=\s*")(.+?)(?:")?$"#).unwrap();
|
||||||
|
|
||||||
|
let mut doc_lines = Vec::new();
|
||||||
|
for line in text.lines() {
|
||||||
|
if let Some(caps) = re.captures(line) {
|
||||||
|
if let Some(m) = caps.get(1) {
|
||||||
|
doc_lines.push(m.as_str().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if doc_lines.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(doc_lines.join("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn visibility_to_string(vis: &Visibility) -> String {
|
fn visibility_to_string(vis: &Visibility) -> String {
|
||||||
match vis {
|
match vis {
|
||||||
Visibility::Public(_) => "pub".to_string(),
|
Visibility::Public(_) => "pub".to_string(),
|
||||||
Visibility::Restricted(r) => {
|
Visibility::Restricted(_) => "pub(restricted)".to_string(),
|
||||||
// pub(crate) or pub(in ...)
|
|
||||||
quote::ToTokens::to_token_stream(r).to_string()
|
|
||||||
}
|
|
||||||
_ => "private".to_string(),
|
_ => "private".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,10 +551,6 @@ fn type_to_string(ty: &Type) -> String {
|
|||||||
Type::Reference(type_ref) => {
|
Type::Reference(type_ref) => {
|
||||||
format!("&{}", type_to_string(&*type_ref.elem))
|
format!("&{}", type_to_string(&*type_ref.elem))
|
||||||
}
|
}
|
||||||
Type::Tuple(tuple) => {
|
|
||||||
let elems: Vec<String> = tuple.elems.iter().map(type_to_string).collect();
|
|
||||||
format!("({})", elems.join(", "))
|
|
||||||
}
|
|
||||||
_ => quote::ToTokens::to_token_stream(ty).to_string(),
|
_ => quote::ToTokens::to_token_stream(ty).to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,51 +570,3 @@ fn generic_param_to_string(param: &syn::GenericParam) -> String {
|
|||||||
syn::GenericParam::Const(cnst) => cnst.ident.to_string(),
|
syn::GenericParam::Const(cnst) => cnst.ident.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pull a concatenated doc-string from a list of attributes.
|
|
||||||
/// Handles both `///` comments (converted to `#[doc = "..."]` by syn)
|
|
||||||
/// and explicit `#[doc = "..."]` attributes.
|
|
||||||
fn extract_docstring(attrs: &[Attribute]) -> Option<String> {
|
|
||||||
// Prefer parsing doc attributes explicitly
|
|
||||||
let mut lines: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
for attr in attrs {
|
|
||||||
// If attribute path is "doc", try to parse the meta and extract the literal
|
|
||||||
if attr.path().is_ident("doc") {
|
|
||||||
if let Ok(syn::Meta::NameValue(nv)) = attr.parse_meta() {
|
|
||||||
if let syn::Lit::Str(litstr) = nv.lit {
|
|
||||||
lines.push(litstr.value());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback: stringify the tokens and try regex extraction
|
|
||||||
let tok = attr.to_token_stream().to_string();
|
|
||||||
let re = Regex::new(r#"(?m)#\s*\[\s*doc\s*=\s*"(.+?)"\s*\]"#).ok();
|
|
||||||
if let Some(re) = re {
|
|
||||||
if let Some(cap) = re.captures(&tok) {
|
|
||||||
if let Some(m) = cap.get(1) {
|
|
||||||
lines.push(m.as_str().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// capture leading "///" style comments as attributes may not always be "doc"
|
|
||||||
let tok = attr.to_token_stream().to_string();
|
|
||||||
// crude fallback to capture /// "..." inside tokens
|
|
||||||
let re_line = Regex::new(r#"(?m)///\s*(.+)$"#).ok();
|
|
||||||
if let Some(re_line) = re_line {
|
|
||||||
for cap in re_line.captures_iter(&tok) {
|
|
||||||
if let Some(m) = cap.get(1) {
|
|
||||||
lines.push(m.as_str().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if lines.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(lines.join("\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user