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

Commit 18772ce

Browse files
refactor(solidity/core/foundry-compiler-server): cleaned up the server codebase
1 parent d0d7c3c commit 18772ce

File tree

4 files changed

+178
-87
lines changed

4 files changed

+178
-87
lines changed

toolchains/solidity/core/crates/foundry-compiler-server/src/affected_files_store.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,31 @@ impl AffectedFilesStore {
1313
}
1414

1515
pub fn add_project_file(&mut self, project_path: String, file: String) {
16-
if !self.projects_files.contains_key(&project_path) {
17-
self.projects_files.insert(project_path.clone(), vec![]);
18-
} else {
19-
let files = self.projects_files.get_mut(&project_path).unwrap();
16+
if let Some(files) = self.projects_files.get_mut(&project_path) {
2017
if !files.contains(&file) {
2118
files.push(file);
2219
}
20+
} else {
21+
self.projects_files.insert(project_path, vec![file]);
2322
}
2423
}
2524

26-
pub fn get_affected_files(&self, project_path: &str) -> Vec<String> {
27-
self.projects_files.get(project_path).unwrap().clone()
25+
/**
26+
* This function returns the list of files that previously raised an error and are not raising it anymore.
27+
* It also updates the list of files that are raising an error.
28+
* @param {Vec<String>} raised_files List of files that are raising an error
29+
* @param {String} project_path Project path
30+
* @returns {Vec<String>} List of files that are not raising an error anymore
31+
*/
32+
pub fn fill_affected_files(&mut self, raised_files: Vec<String>, project_path: &str) -> Vec<String> {
33+
let mut affected_files = Vec::new();
34+
if let Some(project_files) = self.projects_files.get_mut(project_path) {
35+
project_files.retain(|file| !raised_files.contains(&file));
36+
affected_files = project_files.clone();
37+
project_files.extend(raised_files);
38+
} else {
39+
self.projects_files.insert(project_path.to_string(), raised_files);
40+
}
41+
affected_files
2842
}
2943
}

toolchains/solidity/core/crates/foundry-compiler-server/src/main.rs

Lines changed: 126 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use tower_lsp::jsonrpc::Result;
77
use tower_lsp::lsp_types::*;
88
use tower_lsp::{Client, LanguageServer, LspService, Server};
99
mod utils;
10-
use utils::{convert_severity, get_root_path, normalize_path, slashify_path};
10+
use utils::{convert_severity, get_root_path, slashify_path, normalized_slash_path};
1111
mod affected_files_store;
1212
use affected_files_store::AffectedFilesStore;
1313

@@ -30,18 +30,17 @@ impl LanguageServer for Backend {
3030
self.client
3131
.log_message(MessageType::INFO, "Foundry server initializing!")
3232
.await;
33-
let opt_path = get_root_path(params.clone());
34-
if let Some(path) = opt_path {
33+
if let Some(root_path) = get_root_path(params.clone()) {
3534
self.client
3635
.log_message(
3736
MessageType::INFO,
3837
&format!(
3938
"Foundry server initializing with workspace path: {:?}",
40-
path
39+
root_path
4140
),
4241
)
4342
.await;
44-
self.load_workspace(normalize_path(path.as_str())).await;
43+
let _ = self.load_workspace(root_path).await;
4544
} else {
4645
self.client
4746
.log_message(
@@ -63,7 +62,7 @@ impl LanguageServer for Backend {
6362

6463
async fn initialized(&self, _: InitializedParams) {
6564
self.client
66-
.log_message(MessageType::INFO, "foundryserver initialized!")
65+
.log_message(MessageType::INFO, "Foundry server initialized!")
6766
.await;
6867
}
6968

@@ -74,10 +73,7 @@ impl LanguageServer for Backend {
7473
format!("file opened!: {:}", params.text_document.uri),
7574
)
7675
.await;
77-
if params.text_document.uri.path().contains("forge-std") {
78-
return;
79-
}
80-
self.compile(slashify_path(&normalize_path(params.text_document.uri.path())))
76+
let _ = self.compile(normalized_slash_path(params.text_document.uri.path()))
8177
.await;
8278
}
8379

@@ -88,7 +84,7 @@ impl LanguageServer for Backend {
8884
format!("file changed!: {:}", params.text_document.uri),
8985
)
9086
.await;
91-
self.compile(slashify_path(&normalize_path(params.text_document.uri.path())))
87+
let _ = self.compile(normalized_slash_path(params.text_document.uri.path()))
9288
.await;
9389
}
9490

@@ -98,21 +94,21 @@ impl LanguageServer for Backend {
9894
}
9995

10096
impl Backend {
101-
pub async fn load_workspace(&self, path: String) {
97+
pub async fn load_workspace(&self, path: String) -> std::result::Result<(), ()> {
10298
let mut state = self.state.lock().await;
10399
match Compiler::new_with_executable_check() {
104100
Ok(compiler) => state.compiler = Some(compiler),
105101
Err(Error::FoundryExecutableNotFound) => {
106102
self.client
107103
.show_message(MessageType::WARNING, "Foundry executable not found. Please install foundry and restart the extension.")
108104
.await;
109-
return;
105+
return Err(());
110106
}
111107
Err(Error::InvalidFoundryVersion) => {
112108
self.client
113109
.show_message(MessageType::WARNING, "Foundry executable version is not compatible with this extension. Please update foundry and restart the extension.")
114110
.await;
115-
return;
111+
return Err(());
116112
}
117113
Err(err) => {
118114
self.client
@@ -121,7 +117,7 @@ impl Backend {
121117
&format!("Foundry server failed to initialize: {:?}", err),
122118
)
123119
.await;
124-
return;
120+
return Err(());
125121
}
126122
}
127123
if let Err(err) = state.compiler.as_mut().unwrap().load_workspace(path) {
@@ -131,37 +127,50 @@ impl Backend {
131127
&format!("Foundry server failed to initialize: {:?}", err),
132128
)
133129
.await;
130+
return Err(());
134131
} else {
135132
state.initialized = true;
136133
self.client
137134
.log_message(MessageType::INFO, "Foundry server initialized!")
138135
.await;
139136
}
140-
drop(state);
137+
Ok(())
141138
}
142139

143-
pub async fn compile(&self, filepath: String) {
144-
let mut state = self.state.lock().await;
140+
/**
141+
* This function initializes the workspace if it is not already initialized.
142+
* @param {&str} filepath Filepath to compile
143+
* @returns {Result<(), ()>} Result of the initialization
144+
*/
145+
async fn initialize_if_not(&self, filepath: &str) -> std::result::Result<(), ()> {
146+
let state = self.state.lock().await;
147+
145148
if !state.initialized {
146-
// unlock the mutex before calling load_workspace
147-
drop(state);
149+
drop(state); // unlock the mutex before calling load_workspace
150+
148151
self.client
149152
.log_message(MessageType::INFO, "Foundry server initializing!")
150153
.await;
151-
let folder_path = Path::new(&filepath)
154+
let folder_path = Path::new(filepath)
152155
.parent()
153156
.unwrap()
154157
.to_str()
155158
.unwrap()
156159
.to_string();
157-
self.load_workspace(folder_path).await;
158-
state = self.state.lock().await;
159-
}
160+
self.load_workspace(folder_path).await?
161+
}
162+
Ok(())
163+
}
164+
165+
pub async fn compile(&self, filepath: String) -> std::result::Result<(), ()> {
166+
self.initialize_if_not(&filepath).await?;
167+
let mut state = self.state.lock().await;
168+
160169
self.client
161170
.log_message(MessageType::INFO, "Foundry server compiling!")
162171
.await;
163-
let output = state.compiler.as_mut().unwrap().compile(&filepath);
164-
match output {
172+
173+
match state.compiler.as_mut().unwrap().compile(&filepath) {
165174
Ok((project_path, output)) => {
166175
/*self.client
167176
.log_message(MessageType::INFO, format!("Compile errors: {:?}", output.get_errors()))
@@ -179,66 +188,99 @@ impl Backend {
179188
.await;
180189
}
181190
}
191+
Ok(())
182192
}
183193

194+
/**
195+
* Generate and publish diagnostics from compilation errors
196+
* @param {String} project_path Project path
197+
* @param {String} filepath Filepath to compile
198+
* @param {ProjectCompileOutput} output Compilation output
199+
*/
184200
pub async fn publish_errors_diagnostics(
185201
&self,
186202
project_path: String,
187203
filepath: String,
188204
output: ProjectCompileOutput,
189205
) {
190-
let mut diagnostics = HashMap::<String, Vec<Diagnostic>>::new();
206+
let mut raised_diagnostics = HashMap::<String, Vec<Diagnostic>>::new();
207+
191208
for error in output.get_errors() {
192-
eprintln!("error: {:?}", error);
193-
let (source_content_filepath, range) =
194-
match self.extract_diagnostic_range(&project_path, error).await {
195-
Some((source_content_filepath, range)) => (source_content_filepath, range),
196-
None => continue,
197-
};
198-
let diagnostic = Diagnostic {
199-
range: Range {
200-
start: Position {
201-
line: range.start.line,
202-
character: range.start.column,
203-
},
204-
end: Position {
205-
line: range.end.line,
206-
character: range.end.column,
207-
},
208-
},
209-
severity: Some(convert_severity(error.get_severity())),
210-
code: None,
211-
code_description: None,
212-
source: Some("osmium-solidity-foundry-compiler".to_string()),
213-
message: error.get_message(),
214-
related_information: None,
215-
tags: None,
216-
data: None,
209+
// Generate diagnostic from compilation error
210+
let (affected_file, diagnostic) = match self.extract_diagnostic(&error, &project_path).await {
211+
Some(diagnostic) => diagnostic,
212+
None => continue,
217213
};
218-
let url = match source_content_filepath.to_str() {
214+
215+
// Add diagnostic to the hashmap
216+
let url = match affected_file.to_str() {
219217
Some(source_path) => slashify_path(source_path),
220218
None => continue,
221219
};
222-
if !diagnostics.contains_key(&url) {
223-
diagnostics.insert(url.clone(), vec![diagnostic]);
220+
if !raised_diagnostics.contains_key(&url) {
221+
raised_diagnostics.insert(url.clone(), vec![diagnostic]);
224222
} else {
225-
diagnostics.get_mut(&url).unwrap().push(diagnostic);
223+
raised_diagnostics.get_mut(&url).unwrap().push(diagnostic);
226224
}
227225
}
228226

229-
self.add_not_affected_files(project_path, filepath, &mut diagnostics)
230-
.await;
231-
for (uri, diags) in diagnostics.iter() {
227+
self.reset_not_affected_files(project_path, filepath, &raised_diagnostics).await;
228+
for (uri, diags) in raised_diagnostics.iter() {
232229
if let Ok(url) = Url::parse(&format!("file://{}", &uri)) {
233230
self.client
234231
.publish_diagnostics(url, diags.clone(), None)
235232
.await;
236233
} else {
237-
self.client.log_message(MessageType::ERROR, "error, cannot parse file uri").await;
234+
self.client.log_message(MessageType::ERROR, format!("error, cannot parse file uri : {}", uri)).await;
238235
}
239236
}
240237
}
241238

239+
/**
240+
* Extract diagnostic from compilation error
241+
* @param {CompilationError} compilation_error Compilation error
242+
* @param {String} project_path Project path
243+
* @returns {Option<(PathBuf, Diagnostic)>} Diagnostic
244+
* @returns {None} If the diagnostic cannot be extracted
245+
*/
246+
async fn extract_diagnostic(&self, compilation_error: &CompilationError, project_path: &str) -> Option<(PathBuf, Diagnostic)> {
247+
eprintln!("Compilation error: {:?}", compilation_error);
248+
let (source_content_filepath, range) =
249+
match self.extract_diagnostic_range(&project_path, compilation_error).await {
250+
Some((source_content_filepath, range)) => (source_content_filepath, range),
251+
None => return None,
252+
};
253+
let diagnostic = Diagnostic {
254+
range: Range {
255+
start: Position {
256+
line: range.start.line,
257+
character: range.start.column,
258+
},
259+
end: Position {
260+
line: range.end.line,
261+
character: range.end.column,
262+
},
263+
},
264+
severity: Some(convert_severity(compilation_error.get_severity())),
265+
code: None,
266+
code_description: None,
267+
source: Some("osmium-solidity-foundry-compiler".to_string()),
268+
message: compilation_error.get_message(),
269+
related_information: None,
270+
tags: None,
271+
data: None,
272+
};
273+
Some((source_content_filepath, diagnostic))
274+
}
275+
276+
/**
277+
* Extract diagnostic range from compilation error's source location
278+
* Open the file and get the range from the source location
279+
* @param {String} project_path Project path
280+
* @param {CompilationError} error Compilation error
281+
* @returns {Option<(PathBuf, osmium_libs_foundry_wrapper::Range)>} Diagnostic range
282+
* @returns {None} If the diagnostic range cannot be extracted
283+
*/
242284
async fn extract_diagnostic_range(
243285
&self,
244286
project_path: &str,
@@ -251,9 +293,9 @@ impl Backend {
251293
complete_path
252294
}
253295
None => {
254-
/*self.client
296+
self.client
255297
.log_message(MessageType::ERROR, format!("error, cannot get filepath: {:?}", error))
256-
.await;*/
298+
.await;
257299
return None;
258300
}
259301
};
@@ -287,36 +329,44 @@ impl Backend {
287329
Some((source_content_filepath, range))
288330
}
289331

290-
async fn add_not_affected_files(
332+
/**
333+
* This function resets the diagnostics of the files that are not raising an error anymore.
334+
* @param {String} project_path Project path
335+
* @param {String} filepath Filepath to compile
336+
* @param {HashMap<String, Vec<Diagnostic>>} raised_diagnostics Raised diagnostics
337+
*/
338+
async fn reset_not_affected_files(
291339
&self,
292340
project_path: String,
293341
filepath: String,
294-
raised_diagnostics: &mut HashMap<String, Vec<Diagnostic>>,
342+
raised_diagnostics: &HashMap<String, Vec<Diagnostic>>,
295343
) {
296344
let mut state = self.state.lock().await;
297345

298346
state
299347
.affected_files
300348
.add_project_file(project_path.clone(), filepath.clone());
301-
302-
let affected_files = state.affected_files.get_affected_files(&project_path);
303-
drop(state);
304-
let mut without_diagnostics = vec![];
305-
306-
for file in affected_files {
307-
if !raised_diagnostics.contains_key(&file) { // if not potential not affected file is not in raised diags
308-
if let std::collections::hash_map::Entry::Vacant(e) = files.entry(url) {
309-
raised_diagnostics.insert(file.clone(), vec![]);
310-
without_diagnostics.push(file);
311-
}
312-
}
349+
let raised_files = raised_diagnostics.keys().cloned().collect::<Vec<String>>();
350+
let without_diagnostics = state.affected_files.fill_affected_files(raised_files, &project_path);
313351

314352
self.client
315353
.log_message(
316354
MessageType::INFO,
317355
format!("files without diagnostic: {:?}", without_diagnostics),
318356
)
319357
.await;
358+
359+
for file in without_diagnostics.iter() {
360+
if let Ok(url) = Url::parse(&format!("file://{}", &file)) {
361+
self.client
362+
.publish_diagnostics(url, vec![], None)
363+
.await;
364+
} else {
365+
self.client.log_message(MessageType::ERROR, format!("error, cannot parse file uri : {}", file)).await;
366+
}
367+
}
368+
369+
320370
}
321371
}
322372

0 commit comments

Comments
 (0)