Skip to content
This repository has been archived by the owner on Jul 3, 2024. It is now read-only.

Commit

Permalink
refactor(solidity/core/foundry-compiler-server): cleaned up the serve…
Browse files Browse the repository at this point in the history
…r codebase
  • Loading branch information
0xmemorygrinder committed Dec 10, 2023
1 parent d0d7c3c commit 18772ce
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,31 @@ impl AffectedFilesStore {
}

pub fn add_project_file(&mut self, project_path: String, file: String) {
if !self.projects_files.contains_key(&project_path) {
self.projects_files.insert(project_path.clone(), vec![]);
} else {
let files = self.projects_files.get_mut(&project_path).unwrap();
if let Some(files) = self.projects_files.get_mut(&project_path) {
if !files.contains(&file) {
files.push(file);
}
} else {
self.projects_files.insert(project_path, vec![file]);
}
}

pub fn get_affected_files(&self, project_path: &str) -> Vec<String> {
self.projects_files.get(project_path).unwrap().clone()
/**
* This function returns the list of files that previously raised an error and are not raising it anymore.
* It also updates the list of files that are raising an error.
* @param {Vec<String>} raised_files List of files that are raising an error
* @param {String} project_path Project path
* @returns {Vec<String>} List of files that are not raising an error anymore
*/
pub fn fill_affected_files(&mut self, raised_files: Vec<String>, project_path: &str) -> Vec<String> {
let mut affected_files = Vec::new();
if let Some(project_files) = self.projects_files.get_mut(project_path) {
project_files.retain(|file| !raised_files.contains(&file));
affected_files = project_files.clone();
project_files.extend(raised_files);
} else {
self.projects_files.insert(project_path.to_string(), raised_files);
}
affected_files
}
}
202 changes: 126 additions & 76 deletions toolchains/solidity/core/crates/foundry-compiler-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server};
mod utils;
use utils::{convert_severity, get_root_path, normalize_path, slashify_path};
use utils::{convert_severity, get_root_path, slashify_path, normalized_slash_path};
mod affected_files_store;
use affected_files_store::AffectedFilesStore;

Expand All @@ -30,18 +30,17 @@ impl LanguageServer for Backend {
self.client
.log_message(MessageType::INFO, "Foundry server initializing!")
.await;
let opt_path = get_root_path(params.clone());
if let Some(path) = opt_path {
if let Some(root_path) = get_root_path(params.clone()) {
self.client
.log_message(
MessageType::INFO,
&format!(
"Foundry server initializing with workspace path: {:?}",
path
root_path
),
)
.await;
self.load_workspace(normalize_path(path.as_str())).await;
let _ = self.load_workspace(root_path).await;
} else {
self.client
.log_message(
Expand All @@ -63,7 +62,7 @@ impl LanguageServer for Backend {

async fn initialized(&self, _: InitializedParams) {
self.client
.log_message(MessageType::INFO, "foundryserver initialized!")
.log_message(MessageType::INFO, "Foundry server initialized!")
.await;
}

Expand All @@ -74,10 +73,7 @@ impl LanguageServer for Backend {
format!("file opened!: {:}", params.text_document.uri),
)
.await;
if params.text_document.uri.path().contains("forge-std") {
return;
}
self.compile(slashify_path(&normalize_path(params.text_document.uri.path())))
let _ = self.compile(normalized_slash_path(params.text_document.uri.path()))
.await;
}

Expand All @@ -88,7 +84,7 @@ impl LanguageServer for Backend {
format!("file changed!: {:}", params.text_document.uri),
)
.await;
self.compile(slashify_path(&normalize_path(params.text_document.uri.path())))
let _ = self.compile(normalized_slash_path(params.text_document.uri.path()))
.await;
}

Expand All @@ -98,21 +94,21 @@ impl LanguageServer for Backend {
}

impl Backend {
pub async fn load_workspace(&self, path: String) {
pub async fn load_workspace(&self, path: String) -> std::result::Result<(), ()> {
let mut state = self.state.lock().await;
match Compiler::new_with_executable_check() {
Ok(compiler) => state.compiler = Some(compiler),
Err(Error::FoundryExecutableNotFound) => {
self.client
.show_message(MessageType::WARNING, "Foundry executable not found. Please install foundry and restart the extension.")
.await;
return;
return Err(());
}
Err(Error::InvalidFoundryVersion) => {
self.client
.show_message(MessageType::WARNING, "Foundry executable version is not compatible with this extension. Please update foundry and restart the extension.")
.await;
return;
return Err(());
}
Err(err) => {
self.client
Expand All @@ -121,7 +117,7 @@ impl Backend {
&format!("Foundry server failed to initialize: {:?}", err),
)
.await;
return;
return Err(());
}
}
if let Err(err) = state.compiler.as_mut().unwrap().load_workspace(path) {
Expand All @@ -131,37 +127,50 @@ impl Backend {
&format!("Foundry server failed to initialize: {:?}", err),
)
.await;
return Err(());
} else {
state.initialized = true;
self.client
.log_message(MessageType::INFO, "Foundry server initialized!")
.await;
}
drop(state);
Ok(())
}

pub async fn compile(&self, filepath: String) {
let mut state = self.state.lock().await;
/**
* This function initializes the workspace if it is not already initialized.
* @param {&str} filepath Filepath to compile
* @returns {Result<(), ()>} Result of the initialization
*/
async fn initialize_if_not(&self, filepath: &str) -> std::result::Result<(), ()> {
let state = self.state.lock().await;

if !state.initialized {
// unlock the mutex before calling load_workspace
drop(state);
drop(state); // unlock the mutex before calling load_workspace

self.client
.log_message(MessageType::INFO, "Foundry server initializing!")
.await;
let folder_path = Path::new(&filepath)
let folder_path = Path::new(filepath)
.parent()
.unwrap()
.to_str()
.unwrap()
.to_string();
self.load_workspace(folder_path).await;
state = self.state.lock().await;
}
self.load_workspace(folder_path).await?
}
Ok(())
}

pub async fn compile(&self, filepath: String) -> std::result::Result<(), ()> {
self.initialize_if_not(&filepath).await?;
let mut state = self.state.lock().await;

self.client
.log_message(MessageType::INFO, "Foundry server compiling!")
.await;
let output = state.compiler.as_mut().unwrap().compile(&filepath);
match output {

match state.compiler.as_mut().unwrap().compile(&filepath) {
Ok((project_path, output)) => {
/*self.client
.log_message(MessageType::INFO, format!("Compile errors: {:?}", output.get_errors()))
Expand All @@ -179,66 +188,99 @@ impl Backend {
.await;
}
}
Ok(())
}

/**
* Generate and publish diagnostics from compilation errors
* @param {String} project_path Project path
* @param {String} filepath Filepath to compile
* @param {ProjectCompileOutput} output Compilation output
*/
pub async fn publish_errors_diagnostics(
&self,
project_path: String,
filepath: String,
output: ProjectCompileOutput,
) {
let mut diagnostics = HashMap::<String, Vec<Diagnostic>>::new();
let mut raised_diagnostics = HashMap::<String, Vec<Diagnostic>>::new();

for error in output.get_errors() {
eprintln!("error: {:?}", error);
let (source_content_filepath, range) =
match self.extract_diagnostic_range(&project_path, error).await {
Some((source_content_filepath, range)) => (source_content_filepath, range),
None => continue,
};
let diagnostic = Diagnostic {
range: Range {
start: Position {
line: range.start.line,
character: range.start.column,
},
end: Position {
line: range.end.line,
character: range.end.column,
},
},
severity: Some(convert_severity(error.get_severity())),
code: None,
code_description: None,
source: Some("osmium-solidity-foundry-compiler".to_string()),
message: error.get_message(),
related_information: None,
tags: None,
data: None,
// Generate diagnostic from compilation error
let (affected_file, diagnostic) = match self.extract_diagnostic(&error, &project_path).await {
Some(diagnostic) => diagnostic,
None => continue,
};
let url = match source_content_filepath.to_str() {

// Add diagnostic to the hashmap
let url = match affected_file.to_str() {
Some(source_path) => slashify_path(source_path),
None => continue,
};
if !diagnostics.contains_key(&url) {
diagnostics.insert(url.clone(), vec![diagnostic]);
if !raised_diagnostics.contains_key(&url) {
raised_diagnostics.insert(url.clone(), vec![diagnostic]);
} else {
diagnostics.get_mut(&url).unwrap().push(diagnostic);
raised_diagnostics.get_mut(&url).unwrap().push(diagnostic);
}
}

self.add_not_affected_files(project_path, filepath, &mut diagnostics)
.await;
for (uri, diags) in diagnostics.iter() {
self.reset_not_affected_files(project_path, filepath, &raised_diagnostics).await;
for (uri, diags) in raised_diagnostics.iter() {
if let Ok(url) = Url::parse(&format!("file://{}", &uri)) {
self.client
.publish_diagnostics(url, diags.clone(), None)
.await;
} else {
self.client.log_message(MessageType::ERROR, "error, cannot parse file uri").await;
self.client.log_message(MessageType::ERROR, format!("error, cannot parse file uri : {}", uri)).await;
}
}
}

/**
* Extract diagnostic from compilation error
* @param {CompilationError} compilation_error Compilation error
* @param {String} project_path Project path
* @returns {Option<(PathBuf, Diagnostic)>} Diagnostic
* @returns {None} If the diagnostic cannot be extracted
*/
async fn extract_diagnostic(&self, compilation_error: &CompilationError, project_path: &str) -> Option<(PathBuf, Diagnostic)> {
eprintln!("Compilation error: {:?}", compilation_error);
let (source_content_filepath, range) =
match self.extract_diagnostic_range(&project_path, compilation_error).await {
Some((source_content_filepath, range)) => (source_content_filepath, range),
None => return None,
};
let diagnostic = Diagnostic {
range: Range {
start: Position {
line: range.start.line,
character: range.start.column,
},
end: Position {
line: range.end.line,
character: range.end.column,
},
},
severity: Some(convert_severity(compilation_error.get_severity())),
code: None,
code_description: None,
source: Some("osmium-solidity-foundry-compiler".to_string()),
message: compilation_error.get_message(),
related_information: None,
tags: None,
data: None,
};
Some((source_content_filepath, diagnostic))
}

/**
* Extract diagnostic range from compilation error's source location
* Open the file and get the range from the source location
* @param {String} project_path Project path
* @param {CompilationError} error Compilation error
* @returns {Option<(PathBuf, osmium_libs_foundry_wrapper::Range)>} Diagnostic range
* @returns {None} If the diagnostic range cannot be extracted
*/
async fn extract_diagnostic_range(
&self,
project_path: &str,
Expand All @@ -251,9 +293,9 @@ impl Backend {
complete_path
}
None => {
/*self.client
self.client
.log_message(MessageType::ERROR, format!("error, cannot get filepath: {:?}", error))
.await;*/
.await;
return None;
}
};
Expand Down Expand Up @@ -287,36 +329,44 @@ impl Backend {
Some((source_content_filepath, range))
}

async fn add_not_affected_files(
/**
* This function resets the diagnostics of the files that are not raising an error anymore.
* @param {String} project_path Project path
* @param {String} filepath Filepath to compile
* @param {HashMap<String, Vec<Diagnostic>>} raised_diagnostics Raised diagnostics
*/
async fn reset_not_affected_files(
&self,
project_path: String,
filepath: String,
raised_diagnostics: &mut HashMap<String, Vec<Diagnostic>>,
raised_diagnostics: &HashMap<String, Vec<Diagnostic>>,
) {
let mut state = self.state.lock().await;

state
.affected_files
.add_project_file(project_path.clone(), filepath.clone());

let affected_files = state.affected_files.get_affected_files(&project_path);
drop(state);
let mut without_diagnostics = vec![];

for file in affected_files {
if !raised_diagnostics.contains_key(&file) { // if not potential not affected file is not in raised diags
if let std::collections::hash_map::Entry::Vacant(e) = files.entry(url) {
raised_diagnostics.insert(file.clone(), vec![]);
without_diagnostics.push(file);
}
}
let raised_files = raised_diagnostics.keys().cloned().collect::<Vec<String>>();
let without_diagnostics = state.affected_files.fill_affected_files(raised_files, &project_path);

self.client
.log_message(
MessageType::INFO,
format!("files without diagnostic: {:?}", without_diagnostics),
)
.await;

for file in without_diagnostics.iter() {
if let Ok(url) = Url::parse(&format!("file://{}", &file)) {
self.client
.publish_diagnostics(url, vec![], None)
.await;
} else {
self.client.log_message(MessageType::ERROR, format!("error, cannot parse file uri : {}", file)).await;
}
}


}
}

Expand Down
Loading

0 comments on commit 18772ce

Please sign in to comment.