Skip to content

Commit fd02324

Browse files
mmahroussfda-odoo
authored andcommitted
[IMP] allow vars in diagnostic filters and log parsing errors
1 parent 381a8eb commit fd02324

File tree

6 files changed

+109
-35
lines changed

6 files changed

+109
-35
lines changed

server/src/core/config.rs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use ruff_python_ast::{Expr, Mod};
1111
use ruff_python_parser::{Mode, ParseOptions};
1212
use serde::{Deserialize, Deserializer, Serialize, Serializer};
1313
use schemars::{JsonSchema, Schema, SchemaGenerator};
14+
use tracing::error;
1415

1516
use crate::constants::{CONFIG_WIKI_URL};
1617
use crate::core::diagnostics::{DiagnosticCode, DiagnosticSetting, SchemaDiagnosticCodes};
@@ -66,13 +67,13 @@ impl Default for MergeMethod {
6667
#[derive(Debug, Deserialize, Serialize, Clone, Copy, JsonSchema)]
6768
#[serde(rename_all = "lowercase")]
6869
pub enum DiagnosticFilterPathType {
69-
IN,
70-
NOT_IN
70+
In,
71+
NotIn
7172
}
7273

7374
impl Default for DiagnosticFilterPathType {
7475
fn default() -> Self {
75-
DiagnosticFilterPathType::IN
76+
DiagnosticFilterPathType::In
7677
}
7778
}
7879

@@ -523,7 +524,7 @@ fn process_version(var: Sourced<String>, ws_folders: &HashMap<String, String>, w
523524
};
524525
let config_dir = config_path.parent().map(PathBuf::from).unwrap_or_else(|| PathBuf::from("."));
525526
match fill_validate_path(ws_folders, workspace_name, var.value(), |p| PathBuf::from(p).exists(), HashMap::new(), &config_dir) {
526-
Some(filled_path) => {
527+
Ok(filled_path) => {
527528
let var_pb = PathBuf::from(&filled_path);
528529
if var_pb.is_file() {
529530
// If it is a file, we can return the file name
@@ -552,8 +553,11 @@ fn process_version(var: Sourced<String>, ws_folders: &HashMap<String, String>, w
552553
};
553554
Sourced { value: S!(suffix.as_os_str().to_string_lossy()), sources: var.sources.clone(), ..Default::default() }
554555
},
555-
// Not a valid path, just return the variable as is
556-
None => var,
556+
// Not a valid path, just return the variable as is, log info
557+
Err(err) => {
558+
error!("Failed to process $version path for variable {:?}: {}", var, err);
559+
var
560+
},
557561
}
558562
}
559563

@@ -766,7 +770,7 @@ pub fn default_profile_name() -> String {
766770
"default".to_string()
767771
}
768772

769-
fn fill_or_canonicalize<F>(sourced_path: &Sourced<String>, ws_folders: &HashMap<String, String>, workspace_name: Option<&String>, predicate: &F, var_map: HashMap<String, String>) -> Option<Sourced<String>>
773+
fn fill_or_canonicalize<F>(sourced_path: &Sourced<String>, ws_folders: &HashMap<String, String>, workspace_name: Option<&String>, predicate: &F, var_map: HashMap<String, String>) -> Result<Sourced<String>, String>
770774
where
771775
F: Fn(&String) -> bool,
772776
{
@@ -776,19 +780,21 @@ F: Fn(&String) -> bool,
776780
let config_dir = config_path.parent().map(PathBuf::from).unwrap_or_else(|| PathBuf::from("."));
777781
if has_template(&sourced_path.value) {
778782
return fill_validate_path(ws_folders, workspace_name, &sourced_path.value, predicate, var_map, &config_dir)
779-
.and_then(|p| std::fs::canonicalize(PathBuf::from(p)).ok())
783+
.and_then(|p| std::fs::canonicalize(PathBuf::from(p)).map_err(|e| e.to_string()))
780784
.map(|p| p.sanitize())
781785
.map(|path| Sourced { value: path, sources: sourced_path.sources.clone(), ..Default::default()});
782786
}
783787
let mut path = PathBuf::from(&sourced_path.value);
784788
if path.is_relative() {
785789
path = config_dir.join(sourced_path.value.clone());
786790
}
787-
std::fs::canonicalize(path)
788-
.map(|p| p.sanitize())
789-
.ok()
790-
.filter(|p| predicate(&p))
791-
.map(|path| Sourced { value: path, sources: sourced_path.sources.clone(), ..Default::default()})
791+
let path = std::fs::canonicalize(path)
792+
.map_err(|e| e.to_string())?
793+
.sanitize();
794+
if !predicate(&path) {
795+
return Err(format!("Path '{}' does not satisfy the required conditions", path));
796+
}
797+
Ok(Sourced { value: path, sources: sourced_path.sources.clone(), ..Default::default() })
792798
}
793799

794800
fn process_paths(
@@ -804,7 +810,10 @@ fn process_paths(
804810
var_map.insert(S!("base"), b.value().clone());
805811
}
806812
entry.odoo_path = entry.odoo_path.as_ref()
807-
.and_then(|p| fill_or_canonicalize(p, ws_folders, workspace_name, &is_odoo_path, var_map.clone()));
813+
.and_then(|p| fill_or_canonicalize(p, ws_folders, workspace_name, &is_odoo_path, var_map.clone())
814+
.map_err(|err| error!("Failed to process odoo path for variable {:?}: {}", p, err))
815+
.ok()
816+
);
808817

809818
let infer = entry.addons_paths.as_mut().map_or(true, |ps| {
810819
let initial_len = ps.len();
@@ -814,6 +823,8 @@ fn process_paths(
814823
entry.addons_paths = entry.addons_paths.as_ref().map(|paths|
815824
paths.iter().filter_map(|sourced| {
816825
fill_or_canonicalize(sourced, ws_folders, workspace_name, &is_addon_path, var_map.clone())
826+
.map_err(|err| error!("Failed to process addons path for variable {:?}: {}", sourced, err))
827+
.ok()
817828
}).collect()
818829
);
819830
if infer {
@@ -834,6 +845,8 @@ fn process_paths(
834845
Some(p.clone())
835846
} else {
836847
fill_or_canonicalize(p, ws_folders, workspace_name, &is_python_path, var_map.clone())
848+
.map_err(|err| error!("Failed to fill or canonicalize python path for variable {:?}: {}", p, err))
849+
.ok()
837850
}
838851
});
839852
entry.stdlib.as_mut().map(|std|{
@@ -848,6 +861,27 @@ fn process_paths(
848861
}
849862
}
850863
});
864+
entry.diagnostic_filters.iter_mut().for_each(|filter| {
865+
let Some(config_path) = filter.sources.iter().next().map(PathBuf::from) else {
866+
unreachable!("Expected at least one source for sourced_path: {:?}", filter);
867+
};
868+
let config_dir = config_path.parent().map(PathBuf::from).unwrap_or_else(|| PathBuf::from("."));
869+
filter.value.paths = filter.value.paths.iter().filter_map(|pattern| {
870+
let pattern_string = pattern.to_string();
871+
let processed_pattern = fill_validate_path(ws_folders, workspace_name, &pattern_string, &|_: &String| true, var_map.clone(), &config_dir)
872+
.and_then(|p| Pattern::new(&p)
873+
.map_err(|e| e.to_string()));
874+
match processed_pattern {
875+
Ok(p) => Some(p),
876+
Err(err) => {
877+
let message = format!("Failed to process pattern '{}': {}", pattern_string, err);
878+
filter.info = filter.info.clone() + &message;
879+
error!(message);
880+
None
881+
}
882+
}
883+
}).collect();
884+
});
851885
}
852886

853887
fn read_config_from_file<P: AsRef<Path>>(path: P) -> Result<HashMap<String, ConfigEntryRaw>, String> {
@@ -1167,7 +1201,7 @@ fn load_config_from_workspace(
11671201
let Some(parent_dir) = version_path.parent() else {
11681202
continue;
11691203
};
1170-
let Some(parent_dir) = fill_or_canonicalize(
1204+
let Ok(parent_dir) = fill_or_canonicalize(
11711205
&{Sourced { value: parent_dir.sanitize(), sources: version_var.sources.clone(), ..Default::default() }},
11721206
ws_folders,
11731207
Some(workspace_name),

server/src/core/file_mgr.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,10 @@ impl FileInfo {
306306
pub fn update_diagnostic_filters(&mut self, session: &SessionInfo) {
307307
self.diagnostic_filters = session.sync_odoo.config.diagnostic_filters.iter().cloned().filter(|filter| {
308308
match filter.path_type {
309-
DiagnosticFilterPathType::IN => {
309+
DiagnosticFilterPathType::In => {
310310
filter.paths.iter().any(|p| p.matches(&self.uri))
311311
}
312-
DiagnosticFilterPathType::NOT_IN => {
312+
DiagnosticFilterPathType::NotIn => {
313313
!filter.paths.iter().any(|p| p.matches(&self.uri))
314314
}
315315
}

server/src/features/features_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ impl FeaturesUtils {
623623
}
624624

625625
/// Finds and returns useful links for an evaluation
626-
fn get_useful_link(session: &mut SessionInfo, typ: &EvaluationSymbolPtr) -> String {
626+
fn get_useful_link(_session: &mut SessionInfo, typ: &EvaluationSymbolPtr) -> String {
627627
// Possibly add more links in the future
628628
let Some(typ) = typ.upgrade_weak() else {
629629
return S!("")

server/src/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl Server {
125125
if let Some(initialize_params) = initialize_params.process_id {
126126
self.client_process_id = initialize_params;
127127
}
128+
#[allow(deprecated)]
128129
if let Some(workspace_folders) = initialize_params.workspace_folders {
129130
let sync_odoo = self.sync_odoo.lock().unwrap();
130131
let file_mgr = sync_odoo.get_file_mgr();

server/src/utils.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,19 +249,22 @@ pub fn has_template(template: &str) -> bool {
249249
TEMPLATE_REGEX.is_match(template)
250250
}
251251

252-
pub fn fill_template(template: &str, vars: &HashMap<String, String>) -> Option<String> {
253-
let mut invalid = false;
252+
pub fn fill_template(template: &str, vars: &HashMap<String, String>) -> Result<String, String> {
253+
let mut invalid = None;
254254

255255
let result = TEMPLATE_REGEX.replace_all(template, |captures: &regex::Captures| -> String{
256256
let key = captures[1].to_string();
257257
if let Some(value) = vars.get(&key) {
258258
value.clone()
259259
} else {
260-
invalid = true;
260+
invalid = Some(format!("Invalid key ({}) in pattern", key));
261261
S!("")
262262
}
263263
});
264-
if invalid {None} else {Some(S!(result))}
264+
match invalid {
265+
Some(err) => Err(err),
266+
None => Ok(S!(result)),
267+
}
265268
}
266269

267270

@@ -282,7 +285,7 @@ pub fn build_pattern_map(ws_folders: &HashMap<String, String>) -> HashMap<String
282285
/// While also checking it with the predicate function.
283286
/// pass `|_| true` to skip the predicate check.
284287
/// Currently, only the workspaceFolder[:workspace_name] and userHome variables are supported.
285-
pub fn fill_validate_path<F, P>(ws_folders: &HashMap<String, String>, workspace_name: Option<&String>, template: &str, predicate: F, var_map: HashMap<String, String>, parent_path: P) -> Option<String>
288+
pub fn fill_validate_path<F, P>(ws_folders: &HashMap<String, String>, workspace_name: Option<&String>, template: &str, predicate: F, var_map: HashMap<String, String>, parent_path: P) -> Result<String, String>
286289
where
287290
F: Fn(&String) -> bool,
288291
P: AsRef<Path>
@@ -291,19 +294,18 @@ where
291294
if let Some(path) = workspace_name.and_then(|name| ws_folders.get(name)) {
292295
pattern_map.insert(S!("workspaceFolder"), path.clone());
293296
}
294-
if let Some(path) = fill_template(template, &pattern_map) {
295-
if predicate(&path) {
296-
return Some(path);
297-
}
298-
// Attempt to convert the path to an absolute path
299-
if let Ok(abs_path) = std::fs::canonicalize(parent_path.as_ref().join(&path)) {
300-
let abs_path = abs_path.sanitize();
301-
if predicate(&abs_path) {
302-
return Some(abs_path);
303-
}
297+
let path = fill_template(template, &pattern_map)?;
298+
if predicate(&path) {
299+
return Ok(path);
300+
}
301+
// Attempt to convert the path to an absolute path
302+
if let Ok(abs_path) = std::fs::canonicalize(parent_path.as_ref().join(&path)) {
303+
let abs_path = abs_path.sanitize();
304+
if predicate(&abs_path) {
305+
return Ok(abs_path);
304306
}
305307
}
306-
None
308+
Err(format!("Failed to fill and validate path: {} from template {}", path, template))
307309
}
308310

309311
fn is_really_module(directory_path: &str, entry: &DirEntry) -> bool {

server/tests/config_tests.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1798,4 +1798,41 @@ fn test_base_and_version_resolve_for_workspace_subpaths() {
17981798
ws_folders.insert(S!("ws_invalid"), vdir.path().sanitize().to_string());
17991799
let result = get_configuration(&ws_folders, &None);
18001800
assert!(result.is_err(), "Expected error when $base is not a valid path");
1801-
}
1801+
}
1802+
1803+
#[test]
1804+
fn test_diagnostic_filter_path_variable_expansion() {
1805+
use dirs;
1806+
use odoo_ls_server::utils::PathSanitizer;
1807+
// Setup temp workspace
1808+
let temp = TempDir::new().unwrap();
1809+
let ws1 = temp.child("ws1");
1810+
ws1.create_dir_all().unwrap();
1811+
let version = "vX.Y.Z";
1812+
// Write odools.toml with $userHome, ${workspaceFolder}, and $version in diagnostic_filters
1813+
let user_home = dirs::home_dir().map(|buf| buf.sanitize()).unwrap();
1814+
let toml_content = format!(r#"
1815+
[[config]]
1816+
name = "default"
1817+
"$version" = "{}"
1818+
[[config.diagnostic_filters]]
1819+
paths = ["${{userHome}}/some/path", "${{workspaceFolder}}/foo", "${{version}}/bar"]
1820+
"#, version);
1821+
ws1.child("odools.toml").write_str(&toml_content).unwrap();
1822+
1823+
let mut ws_folders = HashMap::new();
1824+
ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string());
1825+
1826+
let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap();
1827+
let config = config_map.get("default").unwrap();
1828+
let filters = &config.diagnostic_filters;
1829+
assert!(!filters.is_empty(), "Expected at least one diagnostic filter");
1830+
let filter = &filters[0];
1831+
let patterns: Vec<String> = filter.paths.iter().map(|p| p.as_str().to_string()).collect();
1832+
// Check that $userHome was expanded
1833+
assert!(patterns.iter().any(|p| p.starts_with(&format!("{}/some/path", user_home))), "userHome variable not expanded: {:?}", patterns);
1834+
// Check that ${workspaceFolder} was expanded
1835+
assert!(patterns.iter().any(|p| p.ends_with("/foo") && p.contains(&ws1.path().sanitize())), "workspaceFolder variable not expanded: {:?}", patterns);
1836+
// Check that $version was expanded
1837+
assert!(patterns.iter().any(|p| p.ends_with("/bar") && p.contains(version)), "version variable not expanded: {:?}", patterns);
1838+
}

0 commit comments

Comments
 (0)