Skip to content

Commit 45c232e

Browse files
committed
wip
1 parent cc232b5 commit 45c232e

File tree

7 files changed

+217
-20
lines changed

7 files changed

+217
-20
lines changed

server/error_code.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ For compute, search, inverse arguments, this error is shown when the method is n
266266
The compute method is set to modify a certain field(s).
267267
Consider marking the modified field with the compute method
268268

269+
### OLS30329
270+
"Unknown XML ID"
271+
The XML ID you referenced has not been found in any XML in this module or its dependencies
272+
273+
### OLS30330
274+
"Unspecified module. Add the module name before the XML ID: 'module.xml_id'"
275+
Your provided an XML ID that has no module specified. Specify the module which XML_ID belong to with 'module.xml_id'
276+
277+
### OLS30331
278+
"Unknown module"
279+
The given module is unknown
280+
269281
### OLS30400
270282
"Invalid attribute"
271283
odoo, openerp and data nodes can not contain this attribute.
@@ -304,3 +316,11 @@ This attribute is not valid in a menuitem node
304316

305317
### OLS30445
306318
"Data file should be an XML or a CSV file"
319+
320+
### OLS30446
321+
"XML ID already exists in module"
322+
You have duplicated XML ID in the same module
323+
324+
### OLS30447
325+
"XML ID should not contain more than one dot"
326+
An XML_ID should be in the format 'xml_id' or 'module.xml_id', but can't contains more dots

server/src/core/evaluation.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -931,12 +931,9 @@ impl Evaluation {
931931
if base_sym.borrow().evaluations().is_some() {
932932
let parent_file_or_func = parent.clone().borrow().parent_file_or_function().as_ref().unwrap().upgrade().unwrap();
933933
let is_in_validation = match parent_file_or_func.borrow().typ().clone() {
934-
SymType::FILE | SymType::PACKAGE(_) => {
934+
SymType::FILE | SymType::PACKAGE(_) | SymType::FUNCTION => {
935935
parent_file_or_func.borrow().build_status(BuildSteps::VALIDATION) == BuildStatus::IN_PROGRESS
936936
},
937-
SymType::FUNCTION => {
938-
true //functions are always evaluated at validation step
939-
}
940937
_ => {false}
941938
};
942939
let call_parent = match base_sym_weak_eval.context.get(&S!("base_attr")){
@@ -959,6 +956,8 @@ impl Evaluation {
959956
));
960957
}
961958
context.as_mut().unwrap().insert(S!("base_call"), ContextValue::SYMBOL(call_parent));
959+
context.as_mut().unwrap().insert(S!("parameters"), ContextValue::ARGUMENTS(expr.arguments.clone()));
960+
context.as_mut().unwrap().insert(S!("is_in_validation"), ContextValue::BOOLEAN(is_in_validation));
962961
for eval in base_sym.borrow().evaluations().unwrap().iter() {
963962
let eval_ptr = eval.symbol.get_symbol_weak_transformed(session, context, &mut diagnostics, Some(parent.borrow().get_file().unwrap().upgrade().unwrap().clone()));
964963
evals.push(Evaluation{
@@ -971,6 +970,8 @@ impl Evaluation {
971970
});
972971
}
973972
context.as_mut().unwrap().remove(&S!("base_call"));
973+
context.as_mut().unwrap().remove(&S!("parameters"));
974+
context.as_mut().unwrap().remove(&S!("is_in_validation"));
974975
}
975976
}
976977
}

server/src/core/python_arch_eval_hooks.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,13 @@ static arch_eval_function_hooks: Lazy<Vec<PythonArchEvalFunctionHook>> = Lazy::n
432432
func: |odoo: &mut SyncOdoo, entry_point: &Rc<RefCell<EntryPoint>>, symbol: Rc<RefCell<Symbol>>| {
433433
PythonArchEvalHooks::_update_get_eval_func_relational(symbol.clone());
434434
}},
435+
PythonArchEvalFunctionHook {
436+
odoo_entry: true,
437+
tree: (vec![Sy!("odoo"), Sy!("api")], vec![Sy!("Environment"), Sy!("ref")]),
438+
if_exist_only: true,
439+
func: |odoo: &mut SyncOdoo, entry_point: &Rc<RefCell<EntryPoint>>, symbol: Rc<RefCell<Symbol>>| {
440+
PythonArchEvalHooks::_validation_env_ref(symbol.clone());
441+
}},
435442
]});
436443

437444

@@ -1007,4 +1014,92 @@ impl PythonArchEvalHooks {
10071014
diagnostics
10081015
}
10091016

1017+
fn eval_env_ref(session: &mut SessionInfo, evaluation_sym: &EvaluationSymbol, context: &mut Option<Context>, diagnostics: &mut Vec<Diagnostic>, scope: Option<Rc<RefCell<Symbol>>>) -> Option<EvaluationSymbolPtr> {
1018+
let Some(context) = context else {return None};
1019+
let in_validation = context.get(&S!("is_in_validation")).unwrap_or(&ContextValue::BOOLEAN(false)).as_bool();
1020+
let Some(parameters) = context.get(&S!("parameters")).map(|ps| ps.as_arguments()) else {return None};
1021+
if parameters.args.is_empty() {
1022+
return None; // No arguments to process
1023+
}
1024+
if !parameters.args[0].is_string_literal_expr() {
1025+
return None;
1026+
}
1027+
if parameters.keywords.len() == 1 {
1028+
if parameters.keywords[0].value.as_boolean_literal_expr().unwrap().value == false {
1029+
return None; // No need to process if the second argument (raise_if_not_found) is false
1030+
}
1031+
}
1032+
let xml_id_expr = parameters.args[0].as_string_literal_expr().unwrap();
1033+
let xml_id_str = xml_id_expr.value.to_str();
1034+
let mut xml_id_split = xml_id_str.split('.');
1035+
let module_name = xml_id_split.next().unwrap();
1036+
let mut xml_id = xml_id_split.collect::<Vec<&str>>().join(".");
1037+
let mut module = session.sync_odoo.modules.get(module_name).cloned();
1038+
if module.is_none() {
1039+
if in_validation {
1040+
if xml_id.len() == 0 {
1041+
diagnostics.push(Diagnostic::new(
1042+
FileMgr::textRange_to_temporary_Range(&xml_id_expr.range()),
1043+
Some(DiagnosticSeverity::ERROR),
1044+
Some(NumberOrString::String(S!("OLS30330"))),
1045+
Some(EXTENSION_NAME.to_string()),
1046+
S!("Unspecified module. Add the module name before the XML ID: 'module.xml_id'"),
1047+
None,
1048+
None
1049+
));
1050+
} else {
1051+
diagnostics.push(Diagnostic::new(
1052+
FileMgr::textRange_to_temporary_Range(&xml_id_expr.range()),
1053+
Some(DiagnosticSeverity::ERROR),
1054+
Some(NumberOrString::String(S!("OLS30331"))),
1055+
Some(EXTENSION_NAME.to_string()),
1056+
S!("Unknown module"),
1057+
None,
1058+
None
1059+
));
1060+
}
1061+
}
1062+
return None;
1063+
}
1064+
let Some(module_rc) = module.unwrap().upgrade() else {
1065+
return None;
1066+
};
1067+
let module_rc_bw = module_rc.borrow();
1068+
let Some(symbol) = module_rc_bw.as_module_package().xml_ids.get(xml_id.as_str()) else {
1069+
if in_validation {
1070+
diagnostics.push(Diagnostic::new(
1071+
FileMgr::textRange_to_temporary_Range(&xml_id_expr.range()),
1072+
Some(DiagnosticSeverity::ERROR),
1073+
Some(NumberOrString::String(S!("OLS30329"))),
1074+
Some(EXTENSION_NAME.to_string()),
1075+
S!("Unknown XML ID"),
1076+
None,
1077+
None
1078+
));
1079+
}
1080+
return None;
1081+
};
1082+
//TODO => csv xml_id
1083+
//TODO check module dependencies
1084+
//TODO in xml, ref can omit the 'module.' before the xml_id
1085+
//TODO implement base.module_'nameofmodule'
1086+
return None; //TODO implement returned value
1087+
}
1088+
1089+
fn _validation_env_ref(func_sym: Rc<RefCell<Symbol>>) -> Vec<Diagnostic> {
1090+
let mut diagnostics = vec![];
1091+
func_sym.borrow_mut().set_evaluations(vec![Evaluation {
1092+
symbol: EvaluationSymbol::new_with_symbol(
1093+
Rc::downgrade(&func_sym),
1094+
Some(true),
1095+
HashMap::new(),
1096+
Some(PythonArchEvalHooks::eval_env_ref)
1097+
),
1098+
value: None,
1099+
range: None
1100+
}]);
1101+
1102+
diagnostics
1103+
}
1104+
10101105
}

server/src/core/python_odoo_builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ impl PythonOdooBuilder {
4949
Some(model) => model.borrow_mut().add_symbol(session, sym.clone()),
5050
None => {
5151
let model = Model::new(model_name.clone(), sym.clone());
52+
sym.borrow().find_module().map(|module| {
53+
let xml_id_model_name = oyarn!("model_{}", model_name.replace(".", "_").as_str());
54+
module.borrow_mut().as_module_package_mut().xml_ids.insert(xml_id_model_name, Rc::downgrade(&sym));
55+
});
5256
session.sync_odoo.models.insert(model_name.clone(), Rc::new(RefCell::new(model)));
5357
}
5458
}

server/src/core/symbols/module_symbol.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub struct ModuleSymbol {
4141
all_depends: HashSet<OYarn>, //computed all depends to avoid too many recomputations
4242
data: Vec<(String, TextRange)>, // TODO
4343
pub module_symbols: HashMap<OYarn, Rc<RefCell<Symbol>>>,
44+
pub xml_ids: HashMap<OYarn, Weak<RefCell<Symbol>>>,
4445
pub arch_status: BuildStatus,
4546
pub arch_eval_status: BuildStatus,
4647
pub odoo_status: BuildStatus,
@@ -79,6 +80,7 @@ impl ModuleSymbol {
7980
root_path: dir_path.sanitize(),
8081
loaded: false,
8182
module_name: OYarn::from(""),
83+
xml_ids: HashMap::new(),
8284
dir_name: OYarn::from(""),
8385
depends: vec!((OYarn::from("base"), TextRange::default())),
8486
all_depends: HashSet::new(),
@@ -358,8 +360,8 @@ impl ModuleSymbol {
358360
if let Ok(document) = document {
359361
file_info.replace_diagnostics(BuildSteps::SYNTAX, vec![]);
360362
let root = document.root_element();
361-
let mut xml_builder = XmlArchBuilder::new();
362-
xml_builder.load_arch(session, xml_sym, &mut file_info, &root);
363+
let mut xml_builder = XmlArchBuilder::new(xml_sym);
364+
xml_builder.load_arch(session, &mut file_info, &root);
363365
file_info.publish_diagnostics(session); //TODO do it only if diagnostics are not empty, else in validation
364366
} else if data.len() > 0 {
365367
let mut diagnostics = vec![];

server/src/core/xml_arch_builder.rs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,90 @@ use regex::Regex;
55
use roxmltree::Node;
66
use tracing::{error, warn};
77

8-
use crate::{constants::{BuildStatus, BuildSteps, OYarn, EXTENSION_NAME}, oyarn, threads::SessionInfo, S};
8+
use crate::{constants::{BuildStatus, BuildSteps, OYarn, EXTENSION_NAME}, core::entry_point::EntryPointType, oyarn, threads::SessionInfo, Sy, S};
99

1010
use super::{file_mgr::FileInfo, odoo::SyncOdoo, symbols::{symbol::Symbol, xml_file_symbol::XmlFileSymbol}};
1111

1212
/*
1313
Struct made to load RelaxNG Odoo schemas and add hooks and specific OdooLS behavior on particular nodes.
1414
*/
1515
pub struct XmlArchBuilder {
16+
pub is_in_main_ep: bool,
17+
pub xml_symbol: Rc<RefCell<Symbol>>,
1618
}
1719

1820
impl XmlArchBuilder {
1921

20-
pub fn new() -> Self {
22+
pub fn new(xml_symbol: Rc<RefCell<Symbol>>) -> Self {
2123
Self {
24+
is_in_main_ep: false,
25+
xml_symbol
2226
}
2327
}
2428

25-
pub fn load_arch(&mut self, session: &mut SessionInfo, xml_symbol: Rc<RefCell<Symbol>>, file_info: &mut FileInfo, node: &Node) {
29+
pub fn load_arch(&mut self, session: &mut SessionInfo, file_info: &mut FileInfo, node: &Node) {
2630
let mut diagnostics = vec![];
27-
xml_symbol.borrow_mut().set_build_status(BuildSteps::ARCH, BuildStatus::IN_PROGRESS);
31+
self.xml_symbol.borrow_mut().set_build_status(BuildSteps::ARCH, BuildStatus::IN_PROGRESS);
32+
let ep = self.xml_symbol.borrow().get_entry();
33+
if let Some(ep) = ep {
34+
self.is_in_main_ep = ep.borrow().typ == EntryPointType::MAIN || ep.borrow().typ == EntryPointType::ADDON;
35+
}
2836
self.load_odoo_openerp_data(session, node, &mut diagnostics);
29-
xml_symbol.borrow_mut().set_build_status(BuildSteps::ARCH, BuildStatus::DONE);
37+
self.xml_symbol.borrow_mut().set_build_status(BuildSteps::ARCH, BuildStatus::DONE);
3038
file_info.replace_diagnostics(BuildSteps::ARCH, diagnostics);
3139
}
40+
41+
pub fn on_operation_creation(
42+
&self,
43+
session: &mut SessionInfo,
44+
id: Option<String>,
45+
node: &Node,
46+
diagnostics: &mut Vec<Diagnostic>,
47+
) {
48+
if !self.is_in_main_ep {
49+
return;
50+
}
51+
if let Some(id) = id {
52+
let module = self.xml_symbol.borrow().find_module();
53+
if module.is_none() {
54+
warn!("Module not found for id: {}", id);
55+
return;
56+
}
57+
let module = module.unwrap();
58+
let id_split = id.split(".").collect::<Vec<&str>>();
59+
if id_split.len() > 2 {
60+
diagnostics.push(Diagnostic::new(
61+
Range {
62+
start: Position::new(node.range().start as u32, 0),
63+
end: Position::new(node.range().end as u32, 0),
64+
},
65+
Some(DiagnosticSeverity::ERROR),
66+
Some(lsp_types::NumberOrString::String(S!("OLS30445"))),
67+
Some(EXTENSION_NAME.to_string()),
68+
format!("Invalid XML ID '{}'. It should not contain more than one dot", id),
69+
None,
70+
None
71+
));
72+
return;
73+
}
74+
let id = id_split.last().unwrap().to_string();
75+
let already_exists = module.borrow().as_module_package().xml_ids.contains_key(&Sy!(id.clone()));
76+
if already_exists {
77+
diagnostics.push(Diagnostic::new(
78+
Range {
79+
start: Position::new(node.range().start as u32, 0),
80+
end: Position::new(node.range().end as u32, 0),
81+
},
82+
Some(DiagnosticSeverity::ERROR),
83+
Some(lsp_types::NumberOrString::String(S!("OLS30446"))),
84+
Some(EXTENSION_NAME.to_string()),
85+
format!("XML ID '{}' already exists in module '{}'.", id, module.borrow().as_module_package().name),
86+
None,
87+
None
88+
));
89+
return;
90+
}
91+
module.borrow_mut().as_module_package_mut().xml_ids.insert(Sy!(id), Rc::downgrade(&self.xml_symbol));
92+
}
93+
}
3294
}

0 commit comments

Comments
 (0)