diff --git a/libs/solc-references/Cargo.toml b/libs/solc-references/Cargo.toml new file mode 100644 index 00000000..f6aab5c7 --- /dev/null +++ b/libs/solc-references/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "solc-references" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +solc-ast-rs-types = { version = "0.1.1", features = ["visit"]} +solc-wrapper = { path="../solc-wrapper" } +thiserror = "1.0.56" diff --git a/libs/solc-references/src/definitions.rs b/libs/solc-references/src/definitions.rs new file mode 100644 index 00000000..b1e30ccd --- /dev/null +++ b/libs/solc-references/src/definitions.rs @@ -0,0 +1,78 @@ + +use solc_ast_rs_types::types::*; +use solc_ast_rs_types::visit; +use solc_ast_rs_types::visit::*; +use crate::types::InteractableNode; + +struct DefinitionFinder { + id: i64, + token: String, + node: Option, + to_find: Option, +} + +impl <'ast> Visit<'ast> for DefinitionFinder { + fn visit_contract_definition(&mut self, contract: &'ast ContractDefinition) { + if contract.id == self.id { + self.node = Some(InteractableNode::ContractDefinition(contract.clone())); + } + visit::visit_contract_definition(self, contract); + } + fn visit_function_definition(&mut self, function: &'ast FunctionDefinition) { + if function.id == self.id { + self.node = Some(InteractableNode::FunctionDefinition(function.clone())); + } + visit::visit_function_definition(self, function); + } + fn visit_modifier_definition(&mut self, modifier: &'ast ModifierDefinition) { + if modifier.id == self.id { + self.node = Some(InteractableNode::ModifierDefinition(modifier.clone())); + } + visit::visit_modifier_definition(self, modifier); + } + fn visit_struct_definition(&mut self, struct_def: &'ast StructDefinition) { + if struct_def.id == self.id { + self.node = Some(InteractableNode::StructDefinition(struct_def.clone())); + } + visit::visit_struct_definition(self, struct_def); + } + fn visit_enum_definition(&mut self, enum_def: &'ast EnumDefinition) { + if enum_def.id == self.id { + self.node = Some(InteractableNode::EnumDefinition(enum_def.clone())); + } + visit::visit_enum_definition(self, enum_def); + } + fn visit_variable_declaration(&mut self, variable: &'ast VariableDeclaration) { + if variable.id == self.id { + self.node = Some(InteractableNode::VariableDeclaration(variable.clone())); + } + visit::visit_variable_declaration(self, variable); + } + fn visit_event_definition(&mut self, event: &'ast EventDefinition) { + if event.id == self.id { + self.node = Some(InteractableNode::EventDefinition(event.clone())); + } + visit::visit_event_definition(self, event); + } + fn visit_enum_value(&mut self, enum_value: &'ast EnumValue) { + if enum_value.id == self.id { + self.node = Some(InteractableNode::EnumValue(enum_value.clone())); + } + visit::visit_enum_value(self, enum_value); + } +} + +impl DefinitionFinder { + pub fn new(id: i64, token: String) -> Self { + DefinitionFinder { + id, + token, + node: None, + to_find: None, + } + } + pub fn find(&mut self, src: SourceUnit) -> Option { + self.visit_source_unit(&src); + self.node.clone() + } +} \ No newline at end of file diff --git a/libs/solc-references/src/error.rs b/libs/solc-references/src/error.rs new file mode 100644 index 00000000..6ea9c4ef --- /dev/null +++ b/libs/solc-references/src/error.rs @@ -0,0 +1,8 @@ +use thiserror::Error; +use solc_wrapper::SolcWrapperError; + +#[derive(Error, Debug)] +pub enum ReferencesError { + #[error("Solc error: {0}")] + Solc(#[from] SolcWrapperError) +} \ No newline at end of file diff --git a/libs/solc-references/src/lib.rs b/libs/solc-references/src/lib.rs new file mode 100644 index 00000000..d3e229fc --- /dev/null +++ b/libs/solc-references/src/lib.rs @@ -0,0 +1,67 @@ +#[macro_use] +mod types; +mod error; +mod definitions; +mod usages; +mod node_finder; +mod utils; + +use error::ReferencesError; +use node_finder::NodeVisitor; +use solc_wrapper::{command::SolcCommand, get_ast_from_solc_output, SolcAstFile}; +pub use solc_ast_rs_types::types::*; +use types::{InteractableNode, Position}; +use usages::UsagesFinder; + + +pub struct ReferencesProvider { + files: Vec +} + +impl ReferencesProvider { + pub fn update_file_content(&mut self, filepath: String, content: String) -> Result<(), ReferencesError> { + let solc = SolcCommand::new(filepath); + let out = solc.execute_with_input(&content)?; + let out = String::from_utf8_lossy(&out.stdout); + let ast_files = get_ast_from_solc_output(&out)?; + for res_file in &ast_files { + let mut found = false; + for file in &mut self.files { + if file.file == res_file.file { + found = true; + file.ast = res_file.ast.clone(); + break; + } + } + if found == false { + self.files.push(res_file.clone()); + } + } + Ok(()) + } + + pub fn get_references(&self, position: Position) -> Vec<(Position, Position)> { + let mut references: Vec<(Position, Position)> = Vec::new(); + let mut node: InteractableNode; + let mut node_finder = NodeVisitor::new(position); + for file in &self.files { + node_finder.find(file.ast); + if let Some(node) = node_finder.node { + node = node.clone(); + break; + } + } + let mut usages_finder = UsagesFinder { + id: get_id!(node), + token: get_name!(node), + to_find: references, + }; + for file in &self.files { + let nodes = usages_finder.find(&file.ast); + for node in nodes { + references.push(node); + } + } + references + } +} \ No newline at end of file diff --git a/libs/solc-references/src/node_finder.rs b/libs/solc-references/src/node_finder.rs new file mode 100644 index 00000000..af625823 --- /dev/null +++ b/libs/solc-references/src/node_finder.rs @@ -0,0 +1,173 @@ +use solc_ast_rs_types::types::*; +use solc_ast_rs_types::visit; +use solc_ast_rs_types::visit::*; +use crate::types::{InteractableNode, Position}; +use crate::utils::*; + +pub struct NodeVisitor { + position: Position, + pub node: Option, + above_node: Option +} + +impl <'ast> Visit<'ast> for NodeVisitor { + + fn visit_contract_definition(&mut self,contract: &'ast ContractDefinition) { + if is_node_in_range(&contract.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::ContractDefinition(contract.clone())); + } + visit::visit_contract_definition(self, contract); + } + + fn visit_function_definition(&mut self, function: &'ast FunctionDefinition) { + if is_node_in_range(&function.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::FunctionDefinition(function.clone())); + } + visit::visit_function_definition(self, function); + } + + fn visit_modifier_definition(&mut self, modifier: &'ast ModifierDefinition) { + if is_node_in_range(&modifier.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::ModifierDefinition(modifier.clone())); + } + visit::visit_modifier_definition(self, modifier); + } + + fn visit_struct_definition(&mut self, struct_def: &'ast StructDefinition) { + if is_node_in_range(&struct_def.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::StructDefinition(struct_def.clone())); + } + visit::visit_struct_definition(self, struct_def); + } + + fn visit_enum_definition(&mut self, enum_def: &'ast EnumDefinition) { + if is_node_in_range(&enum_def.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::EnumDefinition(enum_def.clone())); + } + visit::visit_enum_definition(self, enum_def); + } + + fn visit_variable_declaration(&mut self, variable: &'ast VariableDeclaration) { + if is_node_in_range(&variable.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::VariableDeclaration(variable.clone())); + } + visit::visit_variable_declaration(self, variable); + } + + fn visit_event_definition(&mut self, event: &'ast EventDefinition) { + if is_node_in_range(&event.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::EventDefinition(event.clone())); + } + visit::visit_event_definition(self, event); + } + + fn visit_enum_value(&mut self, enum_value: &'ast EnumValue) { + if is_node_in_range(&enum_value.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::EnumValue(enum_value.clone())); + } + visit::visit_enum_value(self, enum_value); + } + + fn visit_using_for_directive(&mut self, using_for: &'ast UsingForDirective) { + if is_node_in_range(&using_for.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::UsingForDirective(using_for.clone())); + } + visit::visit_using_for_directive(self, using_for); + } + + fn visit_import_directive(&mut self, import: &'ast ImportDirective) { + if is_node_in_range(&import.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::ImportDirective(import.clone())); + } + visit::visit_import_directive(self, import); + } + + fn visit_error_definition(&mut self, error: &'ast ErrorDefinition) { + if is_node_in_range(&error.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::ErrorDefinition(error.clone())); + } + visit::visit_error_definition(self, error); + } + + fn visit_function_call(&mut self, function_call: &'ast FunctionCall) { + if is_node_in_range(&function_call.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::FunctionCall(function_call.clone())); + } + visit::visit_function_call(self, function_call); + } + + fn visit_modifier_invocation(&mut self, modifier_invocation: &'ast ModifierInvocation) { + if is_node_in_range(&modifier_invocation.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::ModifierInvocation(modifier_invocation.clone())); + } + visit::visit_modifier_invocation(self, modifier_invocation); + } + + fn visit_inheritance_specifier(&mut self, inheritance_specifier: &'ast InheritanceSpecifier) { + if is_node_in_range(&inheritance_specifier.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::InheritanceSpecifier(inheritance_specifier.clone())); + } + visit::visit_inheritance_specifier(self, inheritance_specifier); + } + + fn visit_variable_declaration_statement(&mut self, variable_declaration_statement: &'ast VariableDeclarationStatement) { + if is_node_in_range(&variable_declaration_statement.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::VariableDeclarationStatement(variable_declaration_statement.clone())); + } + visit::visit_variable_declaration_statement(self, variable_declaration_statement); + } + + fn visit_identifier(&mut self, identifier: &'ast Identifier) { + if is_node_in_range(&identifier.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::Identifier(identifier.clone(), Box::new(self.above_node.clone().unwrap()))); + } + visit::visit_identifier(self, identifier); + } + + fn visit_member_access(&mut self, member_access: &'ast MemberAccess) { + if is_node_in_range(&member_access.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::MemberAccess(member_access.clone())); + } + visit::visit_member_access(self, member_access); + } + + fn visit_new(&mut self, new_expression: &'ast NewExpression) { + if is_node_in_range(&new_expression.src, &self.position) { + self.above_node = self.node.clone(); + self.node = Some(InteractableNode::NewExpression(new_expression.clone(), Box::new(self.above_node.clone().unwrap()))); + } + visit::visit_new(self, new_expression); + } +} + + +impl NodeVisitor { + pub fn new(position: Position) -> Self { + NodeVisitor { + position, + node: None, + above_node: None + } + } + pub fn find(&mut self, src: SourceUnit) -> Option { + self.visit_source_unit(&src); + self.node.clone() + } +} \ No newline at end of file diff --git a/libs/solc-references/src/types.rs b/libs/solc-references/src/types.rs new file mode 100644 index 00000000..f100a75f --- /dev/null +++ b/libs/solc-references/src/types.rs @@ -0,0 +1,85 @@ +use solc_ast_rs_types::types::*; + +pub struct Position { + pub line: u32, + pub column: u32 +} + +pub struct Range { + pub position: Position, + pub length: u32 +} + +#[derive(Debug, Clone)] +pub enum InteractableNode { + ContractDefinition(ContractDefinition), + FunctionDefinition(FunctionDefinition), + ModifierDefinition(ModifierDefinition), + StructDefinition(StructDefinition), + EnumDefinition(EnumDefinition), + VariableDeclaration(VariableDeclaration), + EventDefinition(EventDefinition), + EnumValue(EnumValue), + UsingForDirective(UsingForDirective), + ImportDirective(ImportDirective), + ErrorDefinition(ErrorDefinition), + FunctionCall(FunctionCall), + ModifierInvocation(ModifierInvocation), + InheritanceSpecifier(InheritanceSpecifier), + VariableDeclarationStatement(VariableDeclarationStatement), + Identifier(Identifier, Box), + MemberAccess(MemberAccess), + NewExpression(NewExpression, Box), +} + +//macro to get the .id field of an interactable node +macro_rules! get_id { + ($node:expr) => { + match $node { + InteractableNode::ContractDefinition(node) => node.id, + InteractableNode::FunctionDefinition(node) => node.id, + InteractableNode::ModifierDefinition(node) => node.id, + InteractableNode::StructDefinition(node) => node.id, + InteractableNode::EnumDefinition(node) => node.id, + InteractableNode::VariableDeclaration(node) => node.id, + InteractableNode::EventDefinition(node) => node.id, + InteractableNode::EnumValue(node) => node.id, + InteractableNode::UsingForDirective(node) => node.id, + InteractableNode::ImportDirective(node) => node.id, + InteractableNode::ErrorDefinition(node) => node.id, + InteractableNode::FunctionCall(node) => node.id, + InteractableNode::ModifierInvocation(node) => node.id, + InteractableNode::InheritanceSpecifier(node) => node.id, + InteractableNode::VariableDeclarationStatement(node) => node.id, + InteractableNode::Identifier(node, _) => node.id, + InteractableNode::MemberAccess(node) => node.id, + InteractableNode::NewExpression(node, _) => node.id, + } + }; +} + +//macro to get the .name field of an interactable node +macro_rules! get_name { + ($node:expr) => { + match $node { + InteractableNode::ContractDefinition(node) => node.name.clone(), + InteractableNode::FunctionDefinition(node) => node.name.clone(), + InteractableNode::ModifierDefinition(node) => node.name.clone(), + InteractableNode::StructDefinition(node) => node.name.clone(), + InteractableNode::EnumDefinition(node) => node.name.clone(), + InteractableNode::VariableDeclaration(node) => node.name.clone(), + InteractableNode::EventDefinition(node) => node.name.clone(), + InteractableNode::EnumValue(node) => node.name.clone(), + InteractableNode::UsingForDirective(node) => node.name.clone(), + InteractableNode::ImportDirective(node) => node.name.clone(), + InteractableNode::ErrorDefinition(node) => node.name.clone(), + InteractableNode::FunctionCall(node) => node.name.clone(), + InteractableNode::ModifierInvocation(node) => node.name.clone(), + InteractableNode::InheritanceSpecifier(node) => node.name.clone(), + InteractableNode::VariableDeclarationStatement(node) => node.name.clone(), + InteractableNode::Identifier(node, _) => node.name.clone(), + InteractableNode::MemberAccess(node) => node.name.clone(), + InteractableNode::NewExpression(node, _) => node.name.clone(), + } + }; +} \ No newline at end of file diff --git a/libs/solc-references/src/usages.rs b/libs/solc-references/src/usages.rs new file mode 100644 index 00000000..fbd06c91 --- /dev/null +++ b/libs/solc-references/src/usages.rs @@ -0,0 +1,81 @@ + +use solc_ast_rs_types::types::*; +use solc_ast_rs_types::visit; +use solc_ast_rs_types::visit::*; +use crate::types::InteractableNode; + +pub struct UsagesFinder { + pub id: i64, + pub token: String, + pub to_find: Vec, +} + +impl <'ast> Visit<'ast> for UsagesFinder { + fn visit_contract_definition(&mut self, contract: &'ast ContractDefinition) { + if contract.id == self.id { + self.to_find.push(InteractableNode::ContractDefinition(contract.clone())); + } + visit::visit_contract_definition(self, contract); + } + fn visit_function_definition(&mut self, function: &'ast FunctionDefinition) { + if function.id == self.id { + self.to_find.push(InteractableNode::FunctionDefinition(function.clone())); + } + visit::visit_function_definition(self, function); + } + fn visit_modifier_definition(&mut self, modifier: &'ast ModifierDefinition) { + if modifier.id == self.id { + self.to_find.push(InteractableNode::ModifierDefinition(modifier.clone())); + } + visit::visit_modifier_definition(self, modifier); + } + fn visit_struct_definition(&mut self, struct_def: &'ast StructDefinition) { + if struct_def.id == self.id { + self.to_find.push(InteractableNode::StructDefinition(struct_def.clone())); + } + visit::visit_struct_definition(self, struct_def); + } + fn visit_enum_definition(&mut self, enum_def: &'ast EnumDefinition) { + if enum_def.id == self.id { + self.to_find.push(InteractableNode::EnumDefinition(enum_def.clone())); + } + visit::visit_enum_definition(self, enum_def); + } + fn visit_variable_declaration(&mut self, variable: &'ast VariableDeclaration) { + if variable.id == self.id { + self.to_find.push(InteractableNode::VariableDeclaration(variable.clone())); + } + visit::visit_variable_declaration(self, variable); + } + fn visit_event_definition(&mut self, event: &'ast EventDefinition) { + if event.id == self.id { + self.to_find.push(InteractableNode::EventDefinition(event.clone())); + } + visit::visit_event_definition(self, event); + } + fn visit_enum_value(&mut self, enum_value: &'ast EnumValue) { + if enum_value.id == self.id { + self.to_find.push(InteractableNode::EnumValue(enum_value.clone())); + } + visit::visit_enum_value(self, enum_value); + } + fn visit_using_for_directive(&mut self, using_for: &'ast UsingForDirective) { + if using_for.id == self.id { + self.to_find.push(InteractableNode::UsingForDirective(using_for.clone())); + } + visit::visit_using_for_directive(self, using_for); + } + fn visit_import_directive(&mut self, import: &'ast ImportDirective) { + if import.unit_alias == self.token { + self.to_find.push(InteractableNode::ImportDirective(import.clone())); + } + visit::visit_import_directive(self, import); + } +} + +impl UsagesFinder { + pub fn find(&mut self, ast: &SourceUnit) -> Vec { + self.visit_source_unit(ast); + self.to_find.clone() + } +} \ No newline at end of file diff --git a/libs/solc-references/src/utils.rs b/libs/solc-references/src/utils.rs new file mode 100644 index 00000000..691c0cb7 --- /dev/null +++ b/libs/solc-references/src/utils.rs @@ -0,0 +1,24 @@ + +use crate::types::{Position, Range}; +use solc_ast_rs_types::types::SourceLocation; + +pub fn is_node_in_range(node: &SourceLocation, position: &Position) -> bool { + let range = source_location_to_range(node); + + //check if the position is within the range of the node + if range.position.line <= position.line && + range.position.column <= position.column && + range.position.line + range.position.column + range.length >= position.line + position.column { + return true; + } + false +} + + +pub fn source_location_to_range(location: &SourceLocation) -> Range { + let src = location.split(":").collect::>(); + let line = src[0].parse::().unwrap(); + let column = src[1].parse::().unwrap(); + let length = src[3].parse::().unwrap(); + Range { position: Position { line, column }, length} +} diff --git a/libs/solc-wrapper/src/error.rs b/libs/solc-wrapper/src/error.rs new file mode 100644 index 00000000..62b23538 --- /dev/null +++ b/libs/solc-wrapper/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +use crate::solc::error::CommandError; + +#[derive(Error, Debug)] +pub enum SolcWrapperError { + #[error("Solc error: {0}")] + Solc(#[from] CommandError), + #[error("JSON parsing error: {0}")] + Json(#[from] serde_json::Error) +} \ No newline at end of file diff --git a/libs/solc-wrapper/src/lib.rs b/libs/solc-wrapper/src/lib.rs index 91ce6044..abfffd4a 100644 --- a/libs/solc-wrapper/src/lib.rs +++ b/libs/solc-wrapper/src/lib.rs @@ -1,5 +1,8 @@ mod solc; +mod error; +pub use error::SolcWrapperError; +pub use solc::*; use solc_ast_rs_types::types::SourceUnit; #[derive(Debug)] @@ -7,13 +10,13 @@ pub struct SolcJsonFile { pub json: serde_json::Value, pub file: String, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SolcAstFile { pub file: String, pub ast: SourceUnit, } -pub fn get_ast_from_solc_output(output: &str) -> Result, serde_json::Error> { +pub fn get_ast_from_solc_output(output: &str) -> Result, SolcWrapperError> { let files = get_files_from_solc_output(output)?; let mut ast_files = Vec::new(); for file in files { diff --git a/toolchains/solidity/core/Cargo.lock b/toolchains/solidity/core/Cargo.lock index 95fa11e5..f0b77d2a 100644 --- a/toolchains/solidity/core/Cargo.lock +++ b/toolchains/solidity/core/Cargo.lock @@ -696,6 +696,14 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "references-server" +version = "0.3.0" +dependencies = [ + "tokio", + "tower-lsp", +] + [[package]] name = "regex" version = "1.10.2" @@ -962,9 +970,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", diff --git a/toolchains/solidity/core/Cargo.toml b/toolchains/solidity/core/Cargo.toml index eaf3e245..12a609d3 100644 --- a/toolchains/solidity/core/Cargo.toml +++ b/toolchains/solidity/core/Cargo.toml @@ -19,4 +19,7 @@ extension = { version = "0.1.0", path = "crates/extension", default-features = f solidhunter = { version = "0.2.1", path = "crates/solidhunter", default-features = false } linter-cli = { version = "0.2.0", path = "crates/linter-cli", default-features = false } linter-server = { version = "0.1.0", path = "crates/linter-server", default-features = false } -foundry-compiler-server = { version = "0.1.0", path = "crates/foundry-compiler-server", default-features = false } \ No newline at end of file +slither-server = { version = "0.1.0", path = "crates/slither-server", default-features = false } +tests-positions-server = { version = "0.1.0", path = "crates/tests-positions-server", default-features = false } +references-server = { version = "0.1.0", path = "crates/references-server", default-features = false } +foundry-compiler-server = { version = "0.1.0", path = "crates/foundry-compiler-server", default-features = false } diff --git a/toolchains/solidity/core/crates/references-server/Cargo.toml b/toolchains/solidity/core/crates/references-server/Cargo.toml new file mode 100644 index 00000000..eb79b9f7 --- /dev/null +++ b/toolchains/solidity/core/crates/references-server/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "references-server" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +exclude.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = "1.36.0" +tower-lsp = "0.20.0" diff --git a/toolchains/solidity/core/crates/references-server/src/main.rs b/toolchains/solidity/core/crates/references-server/src/main.rs new file mode 100644 index 00000000..8ed3425f --- /dev/null +++ b/toolchains/solidity/core/crates/references-server/src/main.rs @@ -0,0 +1,75 @@ +use tower_lsp::jsonrpc::Result; +use tower_lsp::lsp_types::*; +use tower_lsp::{Client, LanguageServer, LspService, Server}; + +#[derive(Debug)] +struct Backend { + client: Client, +} + +#[tower_lsp::async_trait] +impl LanguageServer for Backend { + async fn initialize(&self, _: InitializeParams) -> Result { + Ok(InitializeResult { + server_info: None, + capabilities: ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Kind( + TextDocumentSyncKind::INCREMENTAL, + )), + workspace: Some(WorkspaceServerCapabilities { + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), + file_operations: None, + }), + definition_provider: Some(OneOf::Left(true)), + references_provider: Some(OneOf::Left(true)), + implementation_provider: Some(ImplementationProviderCapability::Simple(true)), + type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), + ..ServerCapabilities::default() + }, + }) + } + + async fn initialized(&self, _: InitializedParams) { + self.client + .log_message(MessageType::INFO, "osmium-solidity-references initialized!") + .await; + } + + async fn goto_definition(&self, params: GotoDefinitionParams) -> Result> { + let location = Location { + uri: params.text_document_position_params.text_document.uri, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + }; + Ok(Some(GotoDefinitionResponse::Scalar(location))) + } + + async fn shutdown(&self) -> Result<()> { + Ok(()) + } + +} + +impl Backend { + +} + +#[tokio::main] +async fn main() { + let stdin = tokio::io::stdin(); + let stdout = tokio::io::stdout(); + + let (service, socket) = LspService::new(|client| Backend { client }); + Server::new(stdin, stdout, socket).serve(service).await; +} diff --git a/toolchains/solidity/extension/src/extension.ts b/toolchains/solidity/extension/src/extension.ts index f6e1193d..6ed8e9c8 100644 --- a/toolchains/solidity/extension/src/extension.ts +++ b/toolchains/solidity/extension/src/extension.ts @@ -8,22 +8,25 @@ import { createSlitherClient } from './slither'; import { createTestsPositionsClient } from './tests-positions'; import registerForgeFmtLinter from "./fmt-wrapper"; import { TestManager } from './tests/test-manager'; +import { createReferencesClient } from './references'; let slitherClient: LanguageClient; let linterClient: LanguageClient; let foundryCompilerClient: LanguageClient; let testsPositionsClient: LanguageClient; +let referencesClient: LanguageClient; let testManager: TestManager; export async function activate(context: ExtensionContext) { linterClient = await createLinterClient(context); foundryCompilerClient = createFoundryCompilerClient(context); slitherClient = createSlitherClient(context); + referencesClient = await createReferencesClient(context); testsPositionsClient = await createTestsPositionsClient(context); if (workspace.workspaceFolders?.length) testManager = new TestManager(testsPositionsClient, workspace.workspaceFolders[0].uri.fsPath); - context.subscriptions.push(linterClient, foundryCompilerClient, slitherClient, testsPositionsClient, testManager.testController); + context.subscriptions.push(linterClient, foundryCompilerClient, slitherClient, testsPositionsClient, testManager.testController, referencesClient); registerForgeFmtLinter(context); diff --git a/toolchains/solidity/extension/src/references.ts b/toolchains/solidity/extension/src/references.ts new file mode 100644 index 00000000..ef888e4b --- /dev/null +++ b/toolchains/solidity/extension/src/references.ts @@ -0,0 +1,53 @@ +import * as path from 'path'; +import * as os from 'os'; +import { workspace, ExtensionContext, Uri } from "vscode"; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind +} from 'vscode-languageclient/node'; +import { TextDecoder } from 'util'; + +export async function createReferencesClient(context: ExtensionContext): Promise { + // The server is implemented in node + const serverBinary = context.asAbsolutePath( + path.join( + 'dist', + os.platform().startsWith("win") ? 'references-server.exe' : 'references-server' + ) + ); + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { command: serverBinary, transport: TransportKind.stdio }, + debug: { + command: serverBinary, + transport: TransportKind.stdio, + } + }; + + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{ scheme: 'file', language: 'solidity' }], + synchronize: { + // Notify the server about file changes to '.clientrc files contained in the workspace + fileEvents: workspace.createFileSystemWatcher('**/.solidhunter.json') + } + }; + + // Create the language client and start the client. + const client = new LanguageClient( + 'osmium-solidity-references', + 'Osmium Solidity References Language Server', + serverOptions, + clientOptions + ); + + // Start the client. This will also launch the server + await client.start(); + + return client; +} \ No newline at end of file