From 2c3a81ecf35ab0dff580077a52409c790372d15c Mon Sep 17 00:00:00 2001 From: Meet Mangukiya Date: Fri, 21 Jun 2024 14:08:13 +0530 Subject: [PATCH] refactor: remove solang and use tree-sitter for document-symbols --- Cargo.lock | 23 +- Cargo.toml | 3 +- src/backend.rs | 369 +++++++++++++++-------------- src/bin/solidity-analyzer-ls.rs | 18 +- src/features/document_symbols.rs | 233 +++++++++++++++++++ src/features/mod.rs | 1 + src/lib.rs | 2 +- src/solang/document_symbol.rs | 388 ------------------------------- src/solang/mod.rs | 1 - 9 files changed, 456 insertions(+), 582 deletions(-) create mode 100644 src/features/document_symbols.rs create mode 100644 src/features/mod.rs delete mode 100644 src/solang/document_symbol.rs delete mode 100644 src/solang/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ee522db..dd8035d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2888,12 +2888,13 @@ dependencies = [ "ropey", "serde_json", "similar", - "solang-parser", "thiserror", "tokio", "tower-lsp", "tracing", "tracing-subscriber", + "tree-sitter", + "tree-sitter-solidity", ] [[package]] @@ -3358,6 +3359,26 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tree-sitter" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-solidity" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa874a7bde4e3f1ef14f51f34a4a73e6d2a1f44856a6429e7d311ab6a476b3c" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 098a549..9544483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,10 @@ anyhow = "1.0.72" similar = "2.2.1" thiserror = "1.0.44" dashmap = "5.5.0" -solang-parser = "0.3.1" serde_json = "1.0.103" tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } foundry-compilers = "0.8.0" ropey = "1.6.1" +tree-sitter-solidity = "1.2.7" +tree-sitter = "0.20.10" diff --git a/src/backend.rs b/src/backend.rs index 2ff272f..1a65715 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -5,7 +5,7 @@ use std::{ io::Read, os::unix::prelude::MetadataExt, path::PathBuf, - sync::OnceLock, + sync::{Arc, OnceLock}, }; use dashmap::DashMap; @@ -19,7 +19,6 @@ use foundry_compilers::{ }; use ropey::Rope; use similar::{DiffOp, TextDiff}; -use solang_parser::pt::SourceUnitPart; use tokio::task::JoinSet; use tower_lsp::{ lsp_types::{ @@ -31,10 +30,9 @@ use tower_lsp::{ Client, }; use tracing::{debug, error, instrument}; +use tree_sitter::{InputEdit, Language, Parser, Point}; use crate::utils; -#[allow(unused_imports)] -use crate::{append_to_file, solang::document_symbol::ToDocumentSymbol}; pub enum Job { ComputeSolcDiagnostics(Url), @@ -53,8 +51,8 @@ impl std::fmt::Debug for Job { #[derive(Debug)] pub struct BackendState { pub documents: DashMap, + pub trees: DashMap, pub document_symbols: DashMap>, - pub document_diagnostics: DashMap>, pub solc_diagnostics: DashMap>, pub project_compilation_output: DashMap, pub jobs_sender: tokio::sync::mpsc::Sender, @@ -72,8 +70,8 @@ impl BackendState { jobs_receiver: tokio::sync::RwLock::new(receiver), jobs_sender: sender, documents: Default::default(), + trees: Default::default(), document_symbols: Default::default(), - document_diagnostics: Default::default(), solc_diagnostics: Default::default(), project_compilation_output: Default::default(), client, @@ -470,11 +468,11 @@ impl BackendState { } } -#[derive(Debug)] pub struct Backend { pub client: Client, pub client_capabilities: OnceLock, pub state: std::sync::Arc, + language: Language, } #[derive(thiserror::Error, Debug)] @@ -487,8 +485,6 @@ pub enum BackendError { ReadError, #[error("Unprocessable url error")] UnprocessableUrlError, - #[error("Solidity parse error")] - SolidityParseError(Vec), #[error("Position not found error")] PositionNotFoundError, #[error("Invalid location error")] @@ -506,6 +502,25 @@ enum FileAction { } impl Backend { + pub fn new(client: Client) -> Self { + let client_clone = client.clone(); + let language = tree_sitter_solidity::language(); + Self { + client, + client_capabilities: Default::default(), + state: Arc::new(BackendState::new(client_clone)), + language, + } + } + + pub fn parser(&self) -> Parser { + let mut parser = Parser::new(); + parser + .set_language(self.language) + .expect("failed to load solidity grammar into tree-sitter parser"); + parser + } + #[instrument(skip_all)] pub async fn get_fmt_textedits(&self, file_path: Url) -> anyhow::Result>> { let config = utils::get_foundry_config(&file_path)?; @@ -600,18 +615,29 @@ impl Backend { } pub async fn add_file(&self, file_path: &Url, file_contents: String) { + let tree = self + .parser() + .parse(&file_contents, None) + .expect("error parsing the file"); self.state .documents .insert(file_path.to_string(), Source::new(file_contents)); + self.state.trees.insert(file_path.to_string(), tree); self.on_file_change(FileAction::Open, file_path).await; } pub async fn remove_file(&self, file_path: &Url) { self.state.documents.remove(&file_path.to_string()); + self.state.trees.remove(&file_path.to_string()); self.on_file_change(FileAction::Close, file_path).await; } pub async fn update_file(&self, file_path: &Url, file_contents: String) { + let tree = self + .parser() + .parse(&file_contents, None) + .expect("error parsing the file"); + self.state.trees.insert(file_path.to_string(), tree); self.state .documents .insert(file_path.to_string(), Source::new(file_contents)); @@ -625,15 +651,50 @@ impl Backend { .documents .get_mut(&file_path.to_string()) .expect("given file doesnt exist in state"); - src.update_range(range, text); + src.update_range(&range, text.as_str()); + debug!("updated file with incremental change"); + } + { + let src = &self + .state + .documents + .get(&file_path.to_string()) + .expect("given file doesnt exist in state"); + let edit = src.change_range_to_input_edit(&range, text.as_str()); + + let write_file = |tree: &tree_sitter::Tree| { + let file = std::fs::File::create("/tmp/currently_parsed_tree.dot"); + if file.is_ok() { + tree.print_dot_graph(&file.unwrap()) + } + }; + let new_tree = { + let mut old_tree = self + .state + .trees + .get_mut(&file_path.to_string()) + .expect("given file tree doesnt exist in state"); + old_tree.edit(&edit); + self.parser() + .parse(src.text.to_string(), Some(&old_tree)) + .expect("error parsing new tree") + }; + write_file(&new_tree); + self.state.trees.insert(file_path.to_string(), new_tree); + // *old_tree = new_tree; + // std::fs::File::open("/tmp/currently_parsed_tree.dot") + // .map(|fd| old_tree.print_dot_graph(&fd)) + // .map_err(|err| debug!(err =?err, "an error occurred")); + + debug!("finished parsing the tree with edits"); } + self.on_file_change(FileAction::Update, file_path).await; } async fn on_file_change(&self, action: FileAction, path: &Url) { if let FileAction::Update = action { self.state.document_symbols.remove(&path.to_string()); - self.state.document_diagnostics.remove(&path.to_string()); } self.update_document_symbols(path).await; @@ -648,96 +709,25 @@ impl Backend { } pub async fn update_document_symbols(&self, path: &Url) -> bool { - match self.get_document_symbols(path) { - Ok(symbols) => { + let tree = self.state.trees.get(&path.to_string()); + if tree.is_some() { + let tree = tree.unwrap(); + let mut cursor = tree.walk(); + + let src = self.state.read_file(path.clone()); + if src.is_ok() { + let src = src.unwrap(); + let symbols = crate::features::document_symbols::get_document_symbols( + &mut cursor, + src.as_bytes(), + ); self.state .document_symbols .insert(path.to_string(), symbols); - self.state.document_diagnostics.remove(&path.to_string()); - self.on_document_diagnostic_update(path).await; - true - } - Err(error) => { - if let BackendError::SolidityParseError(diagnostics) = error { - self.state - .document_diagnostics - .insert(path.to_string(), diagnostics); - self.on_document_diagnostic_update(path).await; - } - false + return true; } } - } - - async fn on_document_diagnostic_update(&self, _path: &Url) { - // if let Some(diagnostics) = self.document_diagnostics.get(&path.to_string()) { - // let diags = diagnostics - // .iter() - // .map(|diagnostic| { - // // let range = diagnostic.range.clone(); - // tower_lsp::lsp_types::Diagnostic { - // range: Default::default(), - // severity: Some(tower_lsp::lsp_types::DiagnosticSeverity::ERROR), - // code: None, - // code_description: None, - // source: Some("solang".to_string()), - // message: diagnostic.message.clone(), - // related_information: None, - // tags: None, - // data: None, - // } - // }) - // .collect(); - - // self.client - // .publish_diagnostics(path.clone(), diags, None) - // .await; - // } - } - - pub fn get_document_symbols( - &self, - file_path: &Url, - ) -> Result, BackendError> { - let file_contents = self - .state - .read_file(file_path.clone()) - .map_err(|_| BackendError::ReadError)?; - let (source_unit, _comments) = - solang_parser::parse(&file_contents, 0).map_err(BackendError::SolidityParseError)?; - let source = Source::new(file_contents); - - Ok(source_unit - .0 - .iter() - .filter_map(|part| match part { - SourceUnitPart::ContractDefinition(contract) => { - Some(contract.to_document_symbol_with_loc(&source)) - } - SourceUnitPart::EnumDefinition(enum_definition) => { - Some(enum_definition.to_document_symbol_with_loc(&source)) - } - SourceUnitPart::StructDefinition(struct_definition) => { - Some(struct_definition.to_document_symbol_with_loc(&source)) - } - SourceUnitPart::EventDefinition(event_definition) => { - Some(event_definition.to_document_symbol_with_loc(&source)) - } - SourceUnitPart::ErrorDefinition(error_definition) => { - Some(error_definition.to_document_symbol_with_loc(&source)) - } - SourceUnitPart::FunctionDefinition(func_definition) => { - Some(func_definition.to_document_symbol_with_loc(&source)) - } - SourceUnitPart::VariableDefinition(variable_definition) => { - Some(variable_definition.to_document_symbol_with_loc(&source)) - } - SourceUnitPart::TypeDefinition(type_definition) => { - Some(type_definition.to_document_symbol_with_loc(&source)) - } - _ => None, - }) - .collect()) + false } } @@ -757,28 +747,66 @@ impl Source { } } - pub fn update_range(&mut self, range: Range, text: String) { - let start = self.text.line_to_char(range.start.line as usize); + pub fn update_range(&mut self, range: &Range, text: &str) { + Self::edit_rope(&mut self.text, range, text); + } + + pub fn position_to_char(&self, position: Position) -> usize { + let start = self.text.line_to_char(position.line as usize); + + start + (position.character as usize) + } + + pub fn position_to_byte(&self, position: Position) -> usize { + self.text.char_to_byte(self.position_to_char(position)) + } + + pub fn char_to_byte(&self, char_idx: usize) -> usize { + self.text.char_to_byte(char_idx) + } + + pub fn edit_rope(rope: &mut Rope, range: &Range, text: &str) { + let start = rope.line_to_char(range.start.line as usize); let start_char_idx = start + (range.start.character as usize); - let end = self.text.line_to_char(range.end.line as usize); + let end = rope.line_to_char(range.end.line as usize); let end_char_idx = end + (range.end.character as usize); - self.text.remove(start_char_idx..end_char_idx); - self.text.insert(start_char_idx, text.as_str()); + rope.remove(start_char_idx..end_char_idx); + rope.insert(start_char_idx, text); } - pub fn loc_to_range( - &self, - loc: &solang_parser::pt::Loc, - ) -> Result { - if let solang_parser::pt::Loc::File(_, start, end) = loc { - let start_pos = self.byte_index_to_position(*start)?; - let end_pos = self.byte_index_to_position(*end)?; - Ok(tower_lsp::lsp_types::Range { - start: start_pos, - end: end_pos, - }) - } else { - Err(BackendError::InvalidLocationError) + pub fn change_range_to_input_edit(&self, range: &Range, text: &str) -> InputEdit { + let start_char = self.position_to_char(range.start); + let start_byte = self.char_to_byte(start_char); + + let end_char = self.position_to_char(range.end); + let end_byte = self.char_to_byte(end_char); + + let n_char_next = text.len(); + let new_end_char = start_char + n_char_next; + + let mut next_rope = self.text.clone(); + Self::edit_rope(&mut next_rope, range, text); + let new_end_byte = next_rope.char_to_byte(new_end_char); + let new_end_line = next_rope.byte_to_line(new_end_byte); + let new_end_line_start_char_idx = next_rope.line_to_char(new_end_line); + let new_column = new_end_char - new_end_line_start_char_idx; + + InputEdit { + start_byte, + old_end_byte: end_byte, + new_end_byte, + start_position: Point { + row: range.start.line as usize, + column: range.start.character as usize, + }, + old_end_position: Point { + row: range.end.line as usize, + column: range.end.character as usize, + }, + new_end_position: Point { + row: new_end_line, + column: new_column, + }, } } @@ -819,14 +847,12 @@ pub fn diagnostic_file_url(diagnostic: &Diagnostic) -> Option { #[cfg(test)] mod tests { use super::*; - use solang_parser::pt::*; mod source_tests { use super::*; - #[test] - fn test_byte_index_to_position() { - let source: &str = r#"contract flipper { + fn source_example_1() -> String { + r#"contract flipper { bool private value; /// Constructor that initializes the `bool` value to the given `init_value`. @@ -851,73 +877,54 @@ mod tests { returns (uint _a) { return 0; } -}"#; - let source_bytes = source.as_bytes(); - let src = Source::new(source.to_string()); - let lines = source.lines().collect::>(); - let (tree, _comments) = solang_parser::parse(source, 0).unwrap(); - - let verify_loc_to_range = |loc: &Loc| { - if let Loc::File(_, loc_start, loc_end) = loc { - let loc_string = String::from_utf8_lossy(&source_bytes[*loc_start..*loc_end]); - let range = src.loc_to_range(loc).unwrap(); - - let range_string = if range.start.line != range.end.line { - let start_line = lines[range.start.line as usize] - [range.start.character as usize..] - .to_string(); - let end_line = lines[range.end.line as usize] - [..range.end.character as usize] - .to_string(); - - let in_between_start = range.start.line as usize + 1; - let in_between_end = range.end.line as usize - 1; - - let mut in_between_lines = None; - if in_between_start <= in_between_end { - let to_join = lines[in_between_start..=in_between_end].to_vec(); - in_between_lines = Some(to_join.join("\n")); - } - (if let Some(in_between_lines) = in_between_lines { - vec![start_line, in_between_lines, end_line] - } else { - vec![start_line, end_line] - }) - .join("\n") - } else { - lines[range.start.line as usize] - [range.start.character as usize..range.end.character as usize] - .to_string() - }; +}"# + .to_string() + } - assert_eq!( - loc_string, range_string, - "loc: {:#?}, range: {:#?}", - loc, range - ); + #[test] + fn test_edit_rope() { + let source_1 = r#" +pragma solidity 0.8.26; - println!( - "loc_string: {:?}, range_string: {:?}", - loc_string, range_string - ); - } +function pureFunction() pure returns (uint) { + return 10; +} + +contract Contract { + uint stateUint = 10; + + constructor() payable {} +} + "#; + + let mut rope = Rope::from_str(source_1); + let range = Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 1, + }, }; + Source::edit_rope(&mut rope, &range, "r"); - for part in &tree.0 { - if let SourceUnitPart::ContractDefinition(def) = part { - for part in &def.parts { - match part { - ContractPart::VariableDefinition(def) => { - verify_loc_to_range(&def.loc); - } - ContractPart::FunctionDefinition(def) => { - verify_loc_to_range(&def.loc); - } - _ => (), - } - } - } - } + assert_eq!( + rope.to_string().as_str(), + r#"rpragma solidity 0.8.26; + +function pureFunction() pure returns (uint) { + return 10; +} + +contract Contract { + uint stateUint = 10; + + constructor() payable {} +} + "# + ) } } } diff --git a/src/bin/solidity-analyzer-ls.rs b/src/bin/solidity-analyzer-ls.rs index 9582d45..bee2078 100644 --- a/src/bin/solidity-analyzer-ls.rs +++ b/src/bin/solidity-analyzer-ls.rs @@ -1,7 +1,7 @@ -use std::sync::Arc; - -use solidity_analyzer::backend::{Backend, BackendState}; +use solidity_analyzer::backend::Backend; use tower_lsp::{LspService, Server}; +use tracing::level_filters::LevelFilter; +use tree_sitter::Parser; #[tokio::main] async fn main() { @@ -26,18 +26,18 @@ async fn main() { ) }) .with_env_filter(env_filter) + .with_max_level(LevelFilter::DEBUG) .finish(); // Set the global subscriber tracing::subscriber::set_global_default(subscriber).unwrap(); let (service, socket) = LspService::new(|client| { - let client_clone = client.clone(); - Backend { - client, - client_capabilities: Default::default(), - state: Arc::new(BackendState::new(client_clone)), - } + let mut parser = Parser::new(); + parser + .set_language(tree_sitter_solidity::language()) + .expect("Error loading solidity grammar"); + Backend::new(client) }); Server::new(stdin, stdout, socket).serve(service).await; } diff --git a/src/features/document_symbols.rs b/src/features/document_symbols.rs new file mode 100644 index 0000000..b7bb4b1 --- /dev/null +++ b/src/features/document_symbols.rs @@ -0,0 +1,233 @@ +use std::str::FromStr; + +use tower_lsp::lsp_types; +use tower_lsp::lsp_types::{DocumentSymbol, SymbolKind}; +use tree_sitter::TreeCursor; + +mod queries { + // SourceUnitPart::ContractDefinition(contract) => { + // Some(contract.to_document_symbol_with_loc(&source)) + // } + // SourceUnitPart::EnumDefinition(enum_definition) => { + // Some(enum_definition.to_document_symbol_with_loc(&source)) + // } + // SourceUnitPart::StructDefinition(struct_definition) => { + // Some(struct_definition.to_document_symbol_with_loc(&source)) + // } + // SourceUnitPart::EventDefinition(event_definition) => { + // Some(event_definition.to_document_symbol_with_loc(&source)) + // } + // SourceUnitPart::ErrorDefinition(error_definition) => { + // Some(error_definition.to_document_symbol_with_loc(&source)) + // } + // SourceUnitPart::FunctionDefinition(func_definition) => { + // Some(func_definition.to_document_symbol_with_loc(&source)) + // } + // SourceUnitPart::VariableDefinition(variable_definition) => { + // Some(variable_definition.to_document_symbol_with_loc(&source)) + // } + // SourceUnitPart::TypeDefinition(type_definition) => { + // Some(type_definition.to_document_symbol_with_loc(&source)) + // } +} + +#[non_exhaustive] +#[derive(PartialEq)] +enum Kind { + ContractDeclaration, + InterfaceDeclaration, + ErrorDeclaration, + LibraryDeclaration, + StructDeclaration, + EnumDeclaration, + FunctionDefinition, + ConstantVariableDeclaration, + UserDefinedTypeDefinition, + EventDefinition, + SourceFile, + ContractBody, +} + +struct KindToEnumFailure; +impl std::str::FromStr for Kind { + type Err = KindToEnumFailure; + + fn from_str(s: &str) -> Result { + match s { + "contract_declaration" => Ok(Kind::ContractDeclaration), + "interface_declaration" => Ok(Kind::InterfaceDeclaration), + "error_declaration" => Ok(Kind::ErrorDeclaration), + "library_declaration" => Ok(Kind::LibraryDeclaration), + "struct_declaration" => Ok(Kind::StructDeclaration), + "enum_declaration" => Ok(Kind::EnumDeclaration), + "function_definition" => Ok(Kind::FunctionDefinition), + "contract_variable_declaration" => Ok(Kind::ConstantVariableDeclaration), + "user_defined_type_definition" => Ok(Kind::UserDefinedTypeDefinition), + "event_definition" => Ok(Kind::EventDefinition), + "source_file" => Ok(Kind::SourceFile), + "contract_body" => Ok(Kind::ContractBody), + _ => Err(KindToEnumFailure), + } + } +} + +#[derive(Debug)] +struct NotImplementedError; +impl Kind { + fn lsp_kind(&self) -> Result { + match self { + Kind::ContractDeclaration => Ok(SymbolKind::CLASS), + Kind::InterfaceDeclaration => Ok(SymbolKind::INTERFACE), + Kind::ErrorDeclaration => Ok(SymbolKind::OBJECT), // TODO: maybe theres a better option + Kind::LibraryDeclaration => Ok(SymbolKind::CLASS), + Kind::StructDeclaration => Ok(SymbolKind::STRUCT), + Kind::EnumDeclaration => Ok(SymbolKind::ENUM), + Kind::FunctionDefinition => Ok(SymbolKind::FUNCTION), + Kind::ConstantVariableDeclaration => Ok(SymbolKind::CONSTANT), + Kind::UserDefinedTypeDefinition => Ok(SymbolKind::OBJECT), // TODO: maybe there's a better option + Kind::EventDefinition => Ok(SymbolKind::EVENT), + Kind::SourceFile | Kind::ContractBody => Err(NotImplementedError), + } + } + + fn name(&self, node: &tree_sitter::Node, src: &[u8]) -> String { + match self { + Kind::ContractDeclaration => get_node_field(node, src, "name", "contract_declaration"), + Kind::InterfaceDeclaration => { + get_node_field(node, src, "name", "interface_declaration") + } + Kind::ErrorDeclaration => get_node_field(node, src, "name", "error_declaration"), + Kind::LibraryDeclaration => get_node_field(node, src, "name", "library_declaration"), + Kind::StructDeclaration => get_node_field(node, src, "name", "struct_declaration"), + Kind::EnumDeclaration => get_node_field(node, src, "name", "enum_declaration"), + Kind::FunctionDefinition => get_node_field(node, src, "name", "function_definition"), + Kind::ConstantVariableDeclaration => { + get_node_field(node, src, "name", "constant_variable_declaration") + } + Kind::UserDefinedTypeDefinition => { + get_node_field(node, src, "name", "user_defined_type_definition") + } + Kind::EventDefinition => get_node_field(node, src, "name", "event_definition"), + Kind::ContractBody | Kind::SourceFile => "".to_string(), + } + } +} + +fn get_node_field( + node: &tree_sitter::Node, + src: &[u8], + field_name: &str, + node_name: &str, +) -> String { + node.child_by_field_name(field_name) + .expect(format!("field name not found on {}", node_name).as_str()) + .utf8_text(src) + .expect("error getting text for node") + .to_string() +} + +fn lsp_position(point: tree_sitter::Point) -> lsp_types::Position { + lsp_types::Position { + line: point.row as u32, + character: point.column as u32, + } +} + +fn lsp_range(node: &tree_sitter::Node) -> lsp_types::Range { + let range = node.range(); + lsp_types::Range { + start: lsp_position(range.start_point), + end: lsp_position(range.end_point), + } +} + +fn get_document_symbol(cursor: &mut TreeCursor, src: &[u8]) -> Option> { + let node = cursor.node(); + Kind::from_str(node.kind()).map_or(None, |kind| { + if kind == Kind::ContractBody || kind == Kind::SourceFile { + return Some(get_document_symbols(cursor, src)); + } + + let has_children = cursor.goto_first_child(); + tracing::debug!("has_children: {}", has_children); + let child_symbols = if has_children { + get_document_symbols(cursor, src) + } else { + vec![] + }; + // if it had children, go back to parent to get back to same position as earlier + if has_children { + tracing::debug!(before = ?cursor.node().kind(), "goto parent before"); + cursor.goto_parent(); + tracing::debug!(after = ?cursor.node().kind(), "gotoparent" ); + } + + Some(vec![ + #[allow(deprecated)] + DocumentSymbol { + name: kind.name(&node, src), + detail: None, + kind: kind.lsp_kind().expect("lsp kind not found"), + tags: None, + deprecated: None, + range: lsp_range(&node), + selection_range: lsp_range(&node), + children: Some(child_symbols), + }, + ]) + }) +} + +pub fn get_document_symbols(cursor: &mut TreeCursor, src: &[u8]) -> Vec { + let mut symbols = vec![]; + let needs_restore = if matches!(cursor.node().kind(), "source_file" | "contract_body") { + tracing::debug!("goto_first_child"); + if cursor.goto_first_child() { + true + } else { + false + } + } else { + false + }; + + loop { + tracing::debug!("node_kind: {}", cursor.node().kind()); + let symbol = get_document_symbol(cursor, src); + if symbol.is_some() { + symbols.extend(symbol.unwrap()) + } + if !cursor.goto_next_sibling() { + break; + } + } + + // restore cursor to original + if needs_restore { + cursor.goto_parent(); + } + return symbols; +} + +mod tests { + #[test] + fn debug_shit() { + let src = r#" + pragma solidity 0.8.26; + contract Contract { + function func() external pure returns (uint) {} + } + "#; + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(tree_sitter_solidity::language()) + .expect("error setting language"); + let tree = parser.parse(src, None); + let tree = tree.expect("parsing failed"); + println!( + "document_symbols: {:?}", + super::get_document_symbols(&mut tree.walk(), src.as_bytes()) + ); + // assert!(false, "kind: {}", tree.walk().node().kind()); + } +} diff --git a/src/features/mod.rs b/src/features/mod.rs new file mode 100644 index 0000000..889f7ed --- /dev/null +++ b/src/features/mod.rs @@ -0,0 +1 @@ +pub mod document_symbols; diff --git a/src/lib.rs b/src/lib.rs index e1dbade..181b875 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![feature(stmt_expr_attributes)] #![warn(clippy::unwrap_used)] pub mod backend; +mod features; pub mod server; -mod solang; mod utils; diff --git a/src/solang/document_symbol.rs b/src/solang/document_symbol.rs deleted file mode 100644 index f86e4a7..0000000 --- a/src/solang/document_symbol.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::default::Default; - -use forge_fmt::solang_ext::pt::TypeDefinition; -use solang_parser::pt::*; -use tower_lsp::lsp_types::{DocumentSymbol, Range, SymbolKind, SymbolTag}; -use tracing::instrument; - -#[allow(unused_imports)] -use crate::append_to_file; -use crate::backend::Source; - -pub trait ToDocumentSymbol: CodeLocation { - fn to_document_symbol(&self, source: &Source) -> DocumentSymbol; - fn try_to_document_symbol(&self, source: &Source) -> Option { - Some(self.to_document_symbol(source)) - } - fn with_loc(&self, source: &Source, document_symbol: DocumentSymbol) -> DocumentSymbol { - let loc = self.loc(); - if let Ok(range) = source.loc_to_range(&loc) { - DocumentSymbol { - range, - selection_range: range, - ..document_symbol - } - } else { - document_symbol - } - } - fn to_document_symbol_with_loc(&self, source: &Source) -> DocumentSymbol { - self.with_loc(source, self.to_document_symbol(source)) - } - // fn try_to_document_symbol_with_loc(&self, source: &Source) -> Option { - // self.try_to_document_symbol(source) - // .map(|document_symbol| self.with_loc(source, document_symbol)) - // } -} - -impl ToDocumentSymbol for Identifier { - #[instrument(skip_all)] - fn to_document_symbol(&self, source: &Source) -> DocumentSymbol { - let mut range = Range::default(); - // trace!("to_document_symbol: {:?}", self); - if matches!(self.loc, Loc::File(_, _, _)) { - if let Ok(range_) = source.loc_to_range(&self.loc) { - range = range_; - } - } - - DocumentSymbolBuilder::new(self.name.clone(), SymbolKind::VARIABLE) - .range(range) - .selection_range(range) - .build() - } -} - -impl ToDocumentSymbol for EnumDefinition { - fn to_document_symbol(&self, source: &Source) -> DocumentSymbol { - DocumentSymbolBuilder::new_with_identifier(&self.name, "", SymbolKind::ENUM) - .children( - self.values - .iter() - .filter(|x| x.is_some()) - .map(|enum_value| { - #[allow(clippy::unwrap_used)] - let doc_symbol = enum_value - .as_ref() - .unwrap() - .to_document_symbol_with_loc(source); - - DocumentSymbol { - kind: SymbolKind::ENUM_MEMBER, - // okay to unwrap because filtered Nones - ..doc_symbol - } - }) - .collect::>(), - ) - .build() - } -} - -impl ToDocumentSymbol for VariableDefinition { - fn to_document_symbol(&self, _source: &Source) -> DocumentSymbol { - let mut builder = - DocumentSymbolBuilder::new_with_identifier(&self.name, "", SymbolKind::VARIABLE); - - if matches!(self.ty, Expression::Type(_, _) | Expression::Variable(_)) { - builder.detail(self.ty.to_string()).build() - } else { - builder.build() - } - } -} - -impl ToDocumentSymbol for VariableDeclaration { - fn to_document_symbol(&self, _source: &Source) -> DocumentSymbol { - let mut builder = - DocumentSymbolBuilder::new_with_identifier(&self.name, "", SymbolKind::VARIABLE); - - if matches!(self.ty, Expression::Type(_, _) | Expression::Variable(_)) { - builder.detail(self.ty.to_string()).build() - } else { - builder.build() - } - } -} - -impl ToDocumentSymbol for StructDefinition { - fn to_document_symbol(&self, source: &Source) -> DocumentSymbol { - DocumentSymbolBuilder::new_with_identifier(&self.name, "", SymbolKind::STRUCT) - .children( - self.fields - .iter() - .map(|field| DocumentSymbol { - kind: SymbolKind::FIELD, - ..field.to_document_symbol_with_loc(source) - }) - .collect::>(), - ) - .build() - } -} - -impl ToDocumentSymbol for FunctionDefinition { - fn to_document_symbol(&self, _source: &Source) -> DocumentSymbol { - let ident_to_string = |name: &Option, default: &str| { - name.as_ref() - .map_or(default.to_string(), |name| name.name.clone()) - }; - - let name = match self.ty { - FunctionTy::Constructor => "constructor".to_string(), - FunctionTy::Function => ident_to_string(&self.name, ""), - FunctionTy::Fallback => "fallback".to_string(), - FunctionTy::Receive => "receive".to_string(), - FunctionTy::Modifier => { - format!("modifier {}", ident_to_string(&self.name, "")) - } - }; - - let mutability = self - .attributes - .iter() - .filter_map(|attr| match &attr { - FunctionAttribute::Mutability(Mutability::View(_)) => Some("view"), - FunctionAttribute::Mutability(Mutability::Pure(_)) => Some("pure"), - _ => None, - }) - .collect::>() - .first() - .map(|x| x.to_string()); - - let visibility = self - .attributes - .iter() - .filter_map(|attr| match &attr { - FunctionAttribute::Visibility(Visibility::External(_)) => Some("external"), - FunctionAttribute::Visibility(Visibility::Public(_)) => Some("public"), - FunctionAttribute::Visibility(Visibility::Internal(_)) => Some("internal"), - FunctionAttribute::Visibility(Visibility::Private(_)) => Some("private"), - _ => None, - }) - .collect::>() - .first() - .map(|x| x.to_string()); - - let payable = self - .attributes - .iter() - .find(|attr| matches!(attr, FunctionAttribute::Mutability(Mutability::Payable(_)))) - .map(|_| "payable".to_string()); - - let detail = vec![visibility, payable, mutability] - .into_iter() - .flatten() - .collect::>() - .join(" "); - - DocumentSymbolBuilder::new_with_identifier(&self.name, "", SymbolKind::FUNCTION) - .name(name) - .detail(detail) - .build() - } -} - -impl ToDocumentSymbol for ErrorDefinition { - fn to_document_symbol(&self, _source: &Source) -> DocumentSymbol { - DocumentSymbolBuilder::new( - format!( - "error {}", - self.name - .as_ref() - .map_or("".to_string(), |ident| ident.name.to_string()) - ), - SymbolKind::OBJECT, - ) - .build() - } -} - -impl ToDocumentSymbol for EventDefinition { - fn to_document_symbol(&self, _source: &Source) -> DocumentSymbol { - DocumentSymbolBuilder::new( - format!( - "event {}", - self.name - .as_ref() - .map_or("".to_string(), |ident| ident.name.to_string()) - ), - SymbolKind::EVENT, - ) - .build() - } -} - -impl ToDocumentSymbol for TypeDefinition { - fn to_document_symbol(&self, _source: &Source) -> DocumentSymbol { - DocumentSymbolBuilder::new(format!("type {}", self.name.name), SymbolKind::OBJECT).build() - } -} - -impl ToDocumentSymbol for ContractPart { - fn to_document_symbol(&self, _source: &Source) -> DocumentSymbol { - unimplemented!() - } - - fn try_to_document_symbol(&self, source: &Source) -> Option { - match self { - Self::StructDefinition(struct_definition) => { - Some(struct_definition.to_document_symbol_with_loc(source)) - } - Self::EnumDefinition(enum_definition) => { - Some(enum_definition.to_document_symbol_with_loc(source)) - } - Self::EventDefinition(event_definition) => { - Some(event_definition.to_document_symbol_with_loc(source)) - } - Self::ErrorDefinition(error_definition) => { - Some(error_definition.to_document_symbol_with_loc(source)) - } - Self::FunctionDefinition(func_definition) => Some(DocumentSymbol { - kind: SymbolKind::METHOD, - ..func_definition.to_document_symbol_with_loc(source) - }), - Self::VariableDefinition(variable_definition) => Some(DocumentSymbol { - kind: SymbolKind::PROPERTY, - ..variable_definition.to_document_symbol_with_loc(source) - }), - Self::TypeDefinition(type_definition) => { - Some(type_definition.to_document_symbol_with_loc(source)) - } - _ => None, - } - } -} - -impl ToDocumentSymbol for ContractDefinition { - fn to_document_symbol(&self, source: &Source) -> DocumentSymbol { - let kind = match self.ty { - ContractTy::Abstract(_) | ContractTy::Contract(_) | ContractTy::Library(_) => { - SymbolKind::CLASS - } - ContractTy::Interface(_) => SymbolKind::INTERFACE, - }; - let detail = match self.ty { - ContractTy::Abstract(_) => "abstract", - ContractTy::Contract(_) => "contract", - ContractTy::Library(_) => "library", - ContractTy::Interface(_) => "interface", - }; - let mut inherits = self - .base - .iter() - .map(|base| { - base.name - .identifiers - .iter() - .map(|ident| ident.name.clone()) - .collect::>() - .join(", ") - }) - .collect::>() - .join(", "); - if !inherits.is_empty() { - inherits = format!("inherits {inherits}"); - } - let detail = format!("{} {}", detail, inherits); - - DocumentSymbolBuilder::new_with_identifier(&self.name, "", kind) - .children( - self.parts - .iter() - .filter_map(|part| part.try_to_document_symbol(source)) - .collect(), - ) - .detail(detail) - .build() - } -} - -struct DocumentSymbolBuilder(DocumentSymbol); - -impl DocumentSymbolBuilder { - #[allow(deprecated)] - fn new_with_identifier( - name: &Option, - default_name: &str, - kind: SymbolKind, - ) -> Self { - Self(DocumentSymbol { - name: name - .as_ref() - .map_or(default_name.to_string(), |name| name.name.clone()), - kind, - detail: Default::default(), - tags: Default::default(), - deprecated: Default::default(), - range: Default::default(), - selection_range: Default::default(), - children: Default::default(), - }) - } - - #[allow(deprecated)] - fn new(name: String, kind: SymbolKind) -> Self { - Self(DocumentSymbol { - name, - kind, - detail: Default::default(), - tags: Default::default(), - deprecated: Default::default(), - range: Default::default(), - selection_range: Default::default(), - children: Default::default(), - }) - } - - #[allow(dead_code)] - fn name(&mut self, name: String) -> &mut Self { - self.0.name = name; - self - } - - #[allow(dead_code)] - fn kind(&mut self, kind: SymbolKind) -> &mut Self { - self.0.kind = kind; - self - } - - fn detail(&mut self, detail: String) -> &mut Self { - self.0.detail = Some(detail); - self - } - - #[allow(dead_code)] - fn tags(&mut self, tags: Vec) -> &mut Self { - self.0.tags = Some(tags); - self - } - - #[allow(dead_code)] - #[allow(deprecated)] - fn deprecated(&mut self, deprecated: bool) -> &mut Self { - self.0.deprecated = Some(deprecated); - self - } - - #[allow(dead_code)] - fn range(&mut self, range: Range) -> &mut Self { - self.0.range = range; - self - } - - #[allow(dead_code)] - fn selection_range(&mut self, selection_range: Range) -> &mut Self { - self.0.selection_range = selection_range; - self - } - - fn children(&mut self, children: Vec) -> &mut Self { - self.0.children = Some(children); - self - } - - fn build(&self) -> DocumentSymbol { - self.0.clone() - } -} diff --git a/src/solang/mod.rs b/src/solang/mod.rs deleted file mode 100644 index ef44469..0000000 --- a/src/solang/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod document_symbol;