diff --git a/server/Cargo.toml b/server/Cargo.toml index 5d0727df..f2d4ff07 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -38,6 +38,7 @@ byteyarn = "0.5.1" roxmltree = "0.20.0" dirs = "5.0" toml = "0.8.22" +# ittapi = "0.3" # profiling with VTune profiler [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] nix = { version = "0.29.0", features = ["process"] } diff --git a/server/error_code.md b/server/error_code.md index f79b8425..08670efd 100644 --- a/server/error_code.md +++ b/server/error_code.md @@ -1,2 +1,2 @@ # OdooLS Error codes -OdooLS errors are now defined in their own modules, with the codes being available in [the diagnostics codes list file](src\core\diagnostic_codes_list.rs) \ No newline at end of file +OdooLS errors are now defined in their own modules, with the codes being available in [the diagnostics codes list file](src\core\diagnostic_codes_list.rs) diff --git a/server/src/cli_backend.rs b/server/src/cli_backend.rs index 2756d331..77cc89d6 100644 --- a/server/src/cli_backend.rs +++ b/server/src/cli_backend.rs @@ -5,11 +5,11 @@ use tracing::{error, info}; use crate::core::config::ConfigEntry; use crate::threads::SessionInfo; -use crate::utils::PathSanitizer; +use crate::utils::{get_python_command, PathSanitizer}; use crate::args::Cli; use std::io::Write; use std::path::PathBuf; -use std::fs::File; +use std::fs::{self, File}; use serde_json::json; use crate::core::{config::{DiagMissingImportsMode}, odoo::SyncOdoo}; use crate::S; @@ -42,17 +42,25 @@ impl CliBackend { info!("Using tracked folders: {:?}", workspace_folders); for (id, tracked_folder) in workspace_folders.into_iter().enumerate() { - session.sync_odoo.get_file_mgr().borrow_mut().add_workspace_folder(format!("{}", id), PathBuf::from(tracked_folder).sanitize()); + let tf = fs::canonicalize(tracked_folder.clone()); + if let Ok(tf) = tf { + let tf = tf.sanitize(); + session.sync_odoo.get_file_mgr().borrow_mut().add_workspace_folder(format!("{}", id), tf); + } else { + error!("Unable to resolve tracked folder: {}", tracked_folder); + } + } let mut config = ConfigEntry::new(); - config.addons_paths = addons_paths.into_iter().collect(); - config.odoo_path = community_path; + config.addons_paths = addons_paths.into_iter().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).collect(); + config.odoo_path = Some(fs::canonicalize(community_path.unwrap_or(S!(""))).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()); config.refresh_mode = crate::core::config::RefreshMode::Off; config.diag_missing_imports = DiagMissingImportsMode::All; config.no_typeshed = self.cli.no_typeshed; - config.additional_stubs = self.cli.stubs.clone().unwrap_or(vec![]).into_iter().collect(); - config.stdlib = self.cli.stdlib.clone().unwrap_or(S!("")); + config.additional_stubs = self.cli.stubs.clone().unwrap_or(vec![]).into_iter().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).collect(); + config.stdlib = self.cli.stdlib.clone().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).unwrap_or(S!("")); + config.python_path = self.cli.python.clone().unwrap_or(get_python_command().unwrap_or(S!(""))); SyncOdoo::init(&mut session, config); let output_path = self.cli.output.clone().unwrap_or(S!("output.json")); diff --git a/server/src/constants.rs b/server/src/constants.rs index 5cc35d29..32613f55 100644 --- a/server/src/constants.rs +++ b/server/src/constants.rs @@ -10,6 +10,7 @@ pub const DEBUG_ODOO_BUILDER: bool = false; pub const DEBUG_MEMORY: bool = false; pub const DEBUG_THREADS: bool = false; pub const DEBUG_STEPS: bool = false; +pub const DEBUG_STEPS_ONLY_INTERNAL: bool = true; pub const DEBUG_REBUILD_NOW: bool = false; pub type Tree = (Vec, Vec); diff --git a/server/src/core/diagnostic_codes_list.rs b/server/src/core/diagnostic_codes_list.rs index f4953e49..42ed0302 100644 --- a/server/src/core/diagnostic_codes_list.rs +++ b/server/src/core/diagnostic_codes_list.rs @@ -400,4 +400,28 @@ OLS05050, DiagnosticSetting::Error, "Data file {0} is not a valid XML or CSV fil * An XML_ID should be in the format 'xml_id' or 'module.xml_id', but can't contains more dots */ OLS05051, DiagnosticSetting::Error, "Invalid XML ID '{0}'. It should not contain more than one dot.", +/** +* The given parent_id does not exists in the dependents modules, or is not a menuitem +*/ +OLS05052, DiagnosticSetting::Error, "Parent menuitem with id '{0}' does not exist", +/** + * A menuitem is specifying an action that has not been declared before the menuitem. + */ +OLS05053, DiagnosticSetting::Error, "Action with id '{0}' does not exist", +/** + * A menuitem is specifying a group that has not been declared before the menuitem + */ +OLS05054, DiagnosticSetting::Error, "Group with id '{0}' does not exist", +/** + * Model not found + */ +OLS05055, DiagnosticSetting::Error, "Model '{0}' not found in module '{1}'", +/** + * Model not found + */ +OLS05056, DiagnosticSetting::Error, "Model '{0}' not found", +/** + * Field not found in model + */ +OLS05057, DiagnosticSetting::Error, "Field '{0}' not found in model '{1}'", } diff --git a/server/src/core/entry_point.rs b/server/src/core/entry_point.rs index 5ad3d968..ef38407f 100644 --- a/server/src/core/entry_point.rs +++ b/server/src/core/entry_point.rs @@ -349,6 +349,10 @@ impl EntryPoint { self.typ == EntryPointType::PUBLIC || self.typ == EntryPointType::BUILTIN } + pub fn is_main(&self) -> bool { + self.typ == EntryPointType::MAIN || self.typ == EntryPointType::ADDON + } + pub fn get_symbol(&self) -> Option>> { let tree = self.addon_to_odoo_tree.as_ref().unwrap_or(&self.tree).clone(); let symbol = self.root.borrow().get_symbol(&(tree, vec![]), u32::MAX); diff --git a/server/src/core/evaluation.rs b/server/src/core/evaluation.rs index 25789141..88d1c41b 100644 --- a/server/src/core/evaluation.rs +++ b/server/src/core/evaluation.rs @@ -564,7 +564,7 @@ impl Evaluation { * should be equal to vec![vec![], vec![]] to be able to get arch and arch_eval deps at index 0 and 1. It means that if validation is * not build but required during the eval_from_ast, it will NOT be built */ - pub fn eval_from_ast(session: &mut SessionInfo, ast: &Expr, parent: Rc>, max_infer: &TextSize, required_dependencies: &mut Vec>>>) -> (Vec, Vec) { + pub fn eval_from_ast(session: &mut SessionInfo, ast: &Expr, parent: Rc>, max_infer: &TextSize, for_annotation: bool, required_dependencies: &mut Vec>>>) -> (Vec, Vec) { let from_module; if let Some(module) = parent.borrow().find_module() { from_module = ContextValue::MODULE(Rc::downgrade(&module)); @@ -575,12 +575,12 @@ impl Evaluation { (S!("module"), from_module), (S!("range"), ContextValue::RANGE(ast.range())) ])); - let analyze_result = Evaluation::analyze_ast(session, &ExprOrIdent::Expr(ast), parent, max_infer, &mut context, required_dependencies); + let analyze_result = Evaluation::analyze_ast(session, &ExprOrIdent::Expr(ast), parent, max_infer, &mut context, for_annotation, required_dependencies); return (analyze_result.evaluations, analyze_result.diagnostics) } /* Given an Expr, try to return the represented String. None if it can't be achieved */ - pub fn expr_to_str(session: &mut SessionInfo, ast: &Expr, parent: Rc>, max_infer: &TextSize, diagnostics: &mut Vec) -> (Option, Vec) { + pub fn expr_to_str(session: &mut SessionInfo, ast: &Expr, parent: Rc>, max_infer: &TextSize, for_annotation: bool, diagnostics: &mut Vec) -> (Option, Vec) { let from_module; if let Some(module) = parent.borrow().find_module() { from_module = ContextValue::MODULE(Rc::downgrade(&module)); @@ -591,7 +591,7 @@ impl Evaluation { (S!("module"), from_module), (S!("range"), ContextValue::RANGE(ast.range())) ])); - let value = Evaluation::analyze_ast(session, &ExprOrIdent::Expr(ast), parent, max_infer, &mut context, &mut vec![]); + let value = Evaluation::analyze_ast(session, &ExprOrIdent::Expr(ast), parent, max_infer, &mut context, for_annotation, &mut vec![]); if value.evaluations.len() == 1 { //only handle strict evaluations let eval = &value.evaluations[0]; let v = eval.follow_ref_and_get_value(session, &mut None, diagnostics); @@ -612,6 +612,39 @@ impl Evaluation { (None, value.diagnostics) } + /* Given an Expr, try to return the represented Boolean. None if it can't be achieved */ + pub fn expr_to_bool(session: &mut SessionInfo, ast: &Expr, parent: Rc>, max_infer: &TextSize, for_annotation: bool, diagnostics: &mut Vec) -> (Option, Vec) { + let from_module; + if let Some(module) = parent.borrow().find_module() { + from_module = ContextValue::MODULE(Rc::downgrade(&module)); + } else { + from_module = ContextValue::BOOLEAN(false); + } + let mut context: Option = Some(HashMap::from([ + (S!("module"), from_module), + (S!("range"), ContextValue::RANGE(ast.range())) + ])); + let value = Evaluation::analyze_ast(session, &ExprOrIdent::Expr(ast), parent, max_infer, &mut context, for_annotation, &mut vec![]); + if value.evaluations.len() == 1 { //only handle strict evaluations + let eval = &value.evaluations[0]; + let v = eval.follow_ref_and_get_value(session, &mut None, diagnostics); + if let Some(v) = v { + match v { + EvaluationValue::CONSTANT(v) => { + match v { + Expr::BooleanLiteral(s) => { + return (Some(s.value), value.diagnostics); + }, + _ => {} + } + }, + _ => {} + } + } + } + (None, value.diagnostics) + } + /** analyze_ast will extract all known information about an ast: @@ -636,7 +669,7 @@ impl Evaluation { context: {} diagnostics: vec![] */ - pub fn analyze_ast(session: &mut SessionInfo, ast: &ExprOrIdent, parent: Rc>, max_infer: &TextSize, context: &mut Option, required_dependencies: &mut Vec>>>) -> AnalyzeAstResult { + pub fn analyze_ast(session: &mut SessionInfo, ast: &ExprOrIdent, parent: Rc>, max_infer: &TextSize, context: &mut Option, for_annotation: bool, required_dependencies: &mut Vec>>>) -> AnalyzeAstResult { let odoo = &mut session.sync_odoo; let mut evals = vec![]; let mut diagnostics = vec![]; @@ -707,7 +740,7 @@ impl Evaluation { evals.push(Evaluation::new_dict(odoo, values, expr.range)); }, ExprOrIdent::Expr(Expr::Call(expr)) => { - let (base_eval, diags) = Evaluation::eval_from_ast(session, &expr.func, parent.clone(), max_infer, required_dependencies); + let (base_eval, diags) = Evaluation::eval_from_ast(session, &expr.func, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); //TODO actually we only evaluate if there is only one function behind the evaluation. // we could evaluate the result of each function and filter results by signature matching. @@ -736,8 +769,8 @@ impl Evaluation { However, other cases should be handled by arch step or syntax? */ return AnalyzeAstResult::from_only_diagnostics(diagnostics); } - let base_sym_weak_eval= base_eval[0].symbol.get_symbol_weak_transformed(session, context, &mut diagnostics, None); - let base_eval_ptrs = Symbol::follow_ref(&base_sym_weak_eval, session, context, true, false, None, &mut diagnostics); + let base_sym_weak_eval_base= base_eval[0].symbol.get_symbol_weak_transformed(session, context, &mut diagnostics, None); + let base_eval_ptrs = Symbol::follow_ref(&base_sym_weak_eval_base, session, context, true, false, None, &mut diagnostics); for base_eval_ptr in base_eval_ptrs.iter() { let EvaluationSymbolPtr::WEAK(base_sym_weak_eval) = base_eval_ptr else {continue}; let Some(base_sym) = base_sym_weak_eval.weak.upgrade() else {continue}; @@ -748,7 +781,7 @@ impl Evaluation { if base_sym.borrow().match_tree_from_any_entry(session, &(vec![Sy!("builtins")], vec![Sy!("super")])){ // - If 1st argument exists, we add that class with symbol_type Super let super_class = if !expr.arguments.is_empty(){ - let (class_eval, diags) = Evaluation::eval_from_ast(session, &expr.arguments.args[0], parent.clone(), max_infer, required_dependencies); + let (class_eval, diags) = Evaluation::eval_from_ast(session, &expr.arguments.args[0], parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); if class_eval.len() != 1 { return AnalyzeAstResult::from_only_diagnostics(diagnostics); @@ -773,18 +806,24 @@ impl Evaluation { None } else { let mut is_instance = None; + let mut default_instance = true; //used if we can't evaluate the instance parameter + if parent.borrow().typ() == SymType::FUNCTION && parent.borrow().as_func().is_class_method { + default_instance = false; + } if expr.arguments.args.len() >= 2 { - let (object_or_type_eval, diags) = Evaluation::eval_from_ast(session, &expr.arguments.args[1], parent.clone(), max_infer, required_dependencies); + let (object_or_type_eval, diags) = Evaluation::eval_from_ast(session, &expr.arguments.args[1], parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); if object_or_type_eval.len() != 1 { - return Some((class_sym_weak_eval.weak.clone(), is_instance)) + return Some((class_sym_weak_eval.weak.clone(), Some(default_instance))) } let object_or_type_weak_eval = &Symbol::follow_ref( &object_or_type_eval[0].symbol.get_symbol( session, context, &mut diagnostics, Some(parent.clone())), session, &mut None, false, false, None, &mut diagnostics)[0]; if object_or_type_weak_eval.is_weak() { - is_instance = object_or_type_weak_eval.as_weak().instance; + is_instance = Some(object_or_type_weak_eval.as_weak().instance.unwrap_or(default_instance)); + } else { + is_instance = Some(default_instance); } } Some((class_sym_weak_eval.weak.clone(), is_instance)) @@ -804,7 +843,18 @@ impl Evaluation { } None }, - Some(parent_class) => Some((parent_class.clone(), Some(true))) + Some(parent_class) => { + let mut instance = Some(true); + if parent.borrow().typ() == SymType::FUNCTION { + if parent.borrow().as_func().is_class_method { + instance = Some(false); + } + if parent.borrow().as_func().is_static { + instance = None; + } + } + Some((parent_class.clone(), instance)) + } } }; if let Some((super_class, instance)) = super_class{ @@ -934,12 +984,7 @@ impl Evaluation { _ => Weak::new() }; if is_in_validation { - let mut on_instance = !base_sym.borrow().as_func().is_static; - if on_instance { - //check that the call is indeed done on an instance - on_instance = base_sym_weak_eval.context.get(&S!("is_attr_of_instance")) - .unwrap_or(&ContextValue::BOOLEAN(false)).as_bool(); - } + let on_instance = base_sym_weak_eval.context.get(&S!("is_attr_of_instance")).map(|v| v.as_bool()); diagnostics.extend(Evaluation::validate_call_arguments(session, &base_sym.borrow().as_func(), expr, @@ -970,7 +1015,7 @@ impl Evaluation { } }, ExprOrIdent::Expr(Expr::Attribute(expr)) => { - let (base_evals, diags) = Evaluation::eval_from_ast(session, &expr.value, parent.clone(), max_infer, required_dependencies); + let (base_evals, diags) = Evaluation::eval_from_ast(session, &expr.value, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); if base_evals.is_empty() { return AnalyzeAstResult::from_only_diagnostics(diagnostics); @@ -1006,7 +1051,16 @@ impl Evaluation { let is_instance = ibase.as_weak().instance.unwrap_or(false); attributes.iter().for_each(|attribute|{ let instance = match attribute.borrow().typ() { - SymType::CLASS => Some(false), + SymType::CLASS => match for_annotation{ + true => Some(true), + false => Some(false) + }, + SymType::VARIABLE => match for_annotation { + // this is a variable, but a follow_ref would probably lead to a class, + // and here, because of annotation, we know we want an instance + true => Some(true), + false => None + } _ => None }; let mut eval = Evaluation::eval_from_symbol(&Rc::downgrade(attribute), instance); @@ -1053,7 +1107,7 @@ impl Evaluation { }; match ast { ExprOrIdent::Expr(Expr::Named(expr)) => { - let (_, diags) = Evaluation::eval_from_ast(session, &expr.value, parent.clone(), max_infer, required_dependencies); + let (_, diags) = Evaluation::eval_from_ast(session, &expr.value, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags.clone()); } _ => {} @@ -1064,7 +1118,16 @@ impl Evaluation { } for inferred_sym in inferred_syms.symbols.iter() { let instance = match inferred_sym.borrow().typ() { - SymType::CLASS => Some(false), + SymType::CLASS => match for_annotation{ + true => Some(true), + false => Some(false) + }, + SymType::VARIABLE => match for_annotation { + // this is a variable, but a follow_ref would probably lead to a class, + // and here, because of annotation, we know we want an instance + true => Some(true), + false => None + } _ => None }; evals.push(Evaluation::eval_from_symbol(&Rc::downgrade(inferred_sym), instance)); @@ -1074,7 +1137,7 @@ impl Evaluation { } }, ExprOrIdent::Expr(Expr::Subscript(sub)) => 'subscript_block: { - let (eval_left, diags) = Evaluation::eval_from_ast(session, &sub.value, parent.clone(), max_infer, required_dependencies); + let (eval_left, diags) = Evaluation::eval_from_ast(session, &sub.value, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); // TODO handle multiple eval_left if eval_left.is_empty() { @@ -1094,9 +1157,13 @@ impl Evaluation { if let Some(SymType::CLASS) = base.upgrade_weak().map(|s| s.borrow().typ()) { // This is a Generic type (Field[int], or List[int]), for now we just return the main type/Class (Field/List) // TODO: handle generic types + let mut new_base = base.clone(); + if for_annotation { + new_base.as_mut_weak().instance = Some(true); + } evals.push(Evaluation { symbol: EvaluationSymbol { - sym: base.clone(), + sym: new_base, get_symbol_hook: None, }, value: None, @@ -1107,7 +1174,7 @@ impl Evaluation { } _ => {} } - let value = Evaluation::expr_to_str(session, &sub.slice, parent.clone(), max_infer, &mut diagnostics); + let value = Evaluation::expr_to_str(session, &sub.slice, parent.clone(), max_infer, false, &mut diagnostics); diagnostics.extend(value.1); if let Some(value) = value.0 { if !base.is_weak() { @@ -1162,11 +1229,11 @@ impl Evaluation { } }, ExprOrIdent::Expr(Expr::If(if_expr)) => { - let (_, diags) = Evaluation::eval_from_ast(session, &if_expr.test, parent.clone(), max_infer, required_dependencies); + let (_, diags) = Evaluation::eval_from_ast(session, &if_expr.test, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); - let (body_evals, diags) = Evaluation::eval_from_ast(session, &if_expr.body, parent.clone(), max_infer, required_dependencies); + let (body_evals, diags) = Evaluation::eval_from_ast(session, &if_expr.body, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); - let (orelse_evals, diags) = Evaluation::eval_from_ast(session, &if_expr.orelse, parent.clone(), max_infer, required_dependencies); + let (orelse_evals, diags) = Evaluation::eval_from_ast(session, &if_expr.orelse, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); evals.extend(body_evals.into_iter().chain(orelse_evals.into_iter())); }, @@ -1193,7 +1260,7 @@ impl Evaluation { break 'u_op_block }, }; - let (bases, diags) = Evaluation::eval_from_ast(session, &unary_operator.operand, parent.clone(), max_infer, required_dependencies); + let (bases, diags) = Evaluation::eval_from_ast(session, &unary_operator.operand, parent.clone(), max_infer, false, required_dependencies); diagnostics.extend(diags); for base in bases.into_iter(){ let base_sym_weak_eval= base.symbol.get_symbol_weak_transformed(session, context, &mut diagnostics, None); @@ -1242,8 +1309,12 @@ impl Evaluation { AnalyzeAstResult { evaluations: evals, diagnostics } } - fn validate_call_arguments(session: &mut SessionInfo, function: &FunctionSymbol, expr_call: &ExprCall, on_object: Weak>, from_module: Option>>, is_on_instance: bool) -> Vec { - if function.is_overloaded() { + /** + * parameters: + * object_instance: None if called on nothing, true on an instance, false on a class + */ + fn validate_call_arguments(session: &mut SessionInfo, function: &FunctionSymbol, expr_call: &ExprCall, on_object: Weak>, from_module: Option>>, object_instance: Option) -> Vec { + if function.is_overloaded() || function.is_property { return vec![]; } let mut diagnostics = vec![]; @@ -1268,28 +1339,31 @@ impl Evaluation { _ => {} } } - if is_on_instance { - //check that there is at least one positional argument - let mut pos_arg = false; - for arg in function.args.iter() { - match arg.arg_type { - ArgumentType::ARG | ArgumentType::VARARG | ArgumentType::POS_ONLY => { - pos_arg = true; - break; + if !function.is_static { + if object_instance.is_some_and(|x| x) || //on instance + object_instance.is_some_and(|x| !x) && function.is_class_method { //on classmethod + //check that there is at least one positional argument + let mut pos_arg = false; + for arg in function.args.iter() { + match arg.arg_type { + ArgumentType::ARG | ArgumentType::VARARG | ArgumentType::POS_ONLY => { + pos_arg = true; + break; + } + _ => {} } - _ => {} } - } - if !pos_arg { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS01007, &[&function.name, &0.to_string(), &1.to_string()]) { - diagnostics.push(Diagnostic { - range: Range::new(Position::new(expr_call.range().start().to_u32(), 0), Position::new(expr_call.range().end().to_u32(), 0)), - ..diagnostic - }); + if !pos_arg { + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS01007, &[&function.name, &0.to_string(), &1.to_string()]) { + diagnostics.push(Diagnostic { + range: Range::new(Position::new(expr_call.range().start().to_u32(), 0), Position::new(expr_call.range().end().to_u32(), 0)), + ..diagnostic + }); + } + return diagnostics; } - return diagnostics; + arg_index += 1; } - arg_index += 1; } for arg in expr_call.arguments.args.iter() { if arg.is_starred_expr() { @@ -1342,7 +1416,7 @@ impl Evaluation { found_pos_arg_with_kw = number_pos_arg; } } - if found_pos_arg_with_kw + 1 < number_pos_arg { + if found_pos_arg_with_kw < number_pos_arg { if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS01007, &[&function.name, &number_pos_arg.to_string(), &arg_index.to_string()]) { diagnostics.push(Diagnostic { range: Range::new(Position::new(expr_call.range().start().to_u32(), 0), Position::new(expr_call.range().end().to_u32(), 0)), @@ -1673,4 +1747,11 @@ impl EvaluationSymbolPtr { _ => panic!("Not an EvaluationSymbolWeak") } } + + pub(crate) fn as_mut_weak(&mut self) -> &mut EvaluationSymbolWeak { + match self { + EvaluationSymbolPtr::WEAK(w) => w, + _ => panic!("Not an EvaluationSymbolWeak") + } + } } \ No newline at end of file diff --git a/server/src/core/file_mgr.rs b/server/src/core/file_mgr.rs index ea76d6f5..866d976e 100644 --- a/server/src/core/file_mgr.rs +++ b/server/src/core/file_mgr.rs @@ -44,7 +44,7 @@ pub fn combine_noqa_info(noqas: &Vec) -> NoqaInfo { NoqaInfo::Codes(codes.iter().cloned().collect()) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum AstType { Python, Xml, @@ -351,6 +351,13 @@ impl FileInfo { } } + pub fn std_range_to_range(&self, range: &std::ops::Range) -> Range { + Range { + start: self.offset_to_position(range.start), + end: self.offset_to_position(range.end) + } + } + pub fn position_to_offset_with_rope(rope: &Rope, line: u32, char: u32) -> usize { let line_char = rope.try_line_to_char(line as usize).expect("unable to get char from line"); rope.try_char_to_byte(line_char + char as usize).expect("unable to get byte from char") @@ -423,6 +430,29 @@ impl FileMgr { }; Range::default() } + + + pub fn std_range_to_range(&self, session: &mut SessionInfo, path: &String, range: &std::ops::Range) -> Range { + let file = self.files.get(path); + if let Some(file) = file { + if file.borrow().file_info_ast.borrow().text_rope.is_none() { + file.borrow_mut().prepare_ast(session); + } + return file.borrow().std_range_to_range(range); + } + //file not in cache, let's load rope on the fly + match fs::read_to_string(path) { + Ok(content) => { + let rope = ropey::Rope::from(content.as_str()); + return Range { + start: FileInfo::offset_to_position_with_rope(&rope, range.start), + end: FileInfo::offset_to_position_with_rope(&rope, range.end) + }; + }, + Err(_) => session.log_message(MessageType::ERROR, format!("Failed to read file {}", path)) + }; + Range::default() + } pub fn update_file_info(&mut self, session: &mut SessionInfo, uri: &str, content: Option<&Vec>, version: Option, force: bool) -> (bool, Rc>) { let file_info = self.files.entry(uri.to_string()).or_insert_with(|| Rc::new(RefCell::new(FileInfo::new(uri.to_string())))); diff --git a/server/src/core/import_resolver.rs b/server/src/core/import_resolver.rs index e33d4ce0..9a4b51a2 100644 --- a/server/src/core/import_resolver.rs +++ b/server/src/core/import_resolver.rs @@ -1,12 +1,13 @@ use glob::glob; use lsp_types::{Diagnostic, DiagnosticTag, Position, Range}; +use ruff_python_ast::name::Name; use tracing::error; use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::cell::RefCell; use std::path::{Path, PathBuf}; -use ruff_text_size::TextRange; +use ruff_text_size::{TextRange, TextSize}; use ruff_python_ast::{Alias, Identifier}; use crate::{constants::*, oyarn, Sy, S}; use crate::core::diagnostics::{create_diagnostic, DiagnosticCode}; @@ -51,15 +52,32 @@ fn resolve_import_stmt_hook(alias: &Alias, from_symbol: &Option>, from_stmt:Option, name: &str, asname: Option, level: Option, diagnostics: &mut Option<&mut Vec>) -> Vec { + let name_aliases = vec![Alias { + name: Identifier { id: Name::new(name), range: TextRange::new(TextSize::new(0), TextSize::new(0)) }, + asname: match asname { + Some(asname_inner) => Some(Identifier { id: Name::new(asname_inner), range: TextRange::new(TextSize::new(0), TextSize::new(0)) }), + None => None, + }, + range: TextRange::new(TextSize::new(0), TextSize::new(0)), + }]; + let from_stmt = match from_stmt { + Some(from_stmt_inner) => Some(Identifier { id: Name::new(from_stmt_inner), range: TextRange::new(TextSize::new(0), TextSize::new(0)) }), + None => None, + }; + resolve_import_stmt(session, source_file_symbol, from_stmt.as_ref(), &name_aliases, level, diagnostics) +} + pub fn resolve_import_stmt(session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: Option, diagnostics: &mut Option<&mut Vec>) -> Vec { //A: search base of different imports let source_root = source_file_symbol.borrow().get_root().as_ref().unwrap().upgrade().unwrap(); let entry = source_root.borrow().get_entry().unwrap(); let _source_file_symbol_lock = source_file_symbol.borrow_mut(); let file_tree = _resolve_packages( - &_source_file_symbol_lock.paths()[0].clone(), - &_source_file_symbol_lock.get_tree(), - &_source_file_symbol_lock.typ(), + &_source_file_symbol_lock, level, from_stmt); drop(_source_file_symbol_lock); @@ -178,6 +196,11 @@ pub fn resolve_import_stmt(session: &mut SessionInfo, source_file_symbol: &Rc>, None } -fn _resolve_packages(file_path: &String, file_tree: &Tree, file_sym_type: &SymType, level: Option, from_stmt: Option<&Identifier>) -> Vec { +fn _resolve_packages(from_file: &Symbol, level: Option, from_stmt: Option<&Identifier>) -> Vec { let mut first_part_tree: Vec = vec![]; if level.is_some() && level.unwrap() > 0 { let mut lvl = level.unwrap(); - if lvl > Path::new(file_path).components().count() as u32 { + if lvl > Path::new(&from_file.paths()[0]).components().count() as u32 { panic!("Level is too high!") } - if matches!(*file_sym_type, SymType::PACKAGE(_)) { + if matches!(from_file.typ(), SymType::PACKAGE(_)) { lvl -= 1; } if lvl == 0 { - first_part_tree = file_tree.0.clone(); + first_part_tree = from_file.get_tree().0.clone(); } else { - let tree = file_tree; + let tree = from_file.get_tree(); if lvl > tree.0.len() as u32 { error!("Level is too high and going out of scope"); first_part_tree = vec![]; @@ -373,6 +396,14 @@ fn _resolve_new_symbol(session: &mut SessionInfo, parent: Rc>, n SyncOdoo::build_now(session, &_arc_symbol, BuildSteps::ARCH); return Ok(_arc_symbol); } + } else if is_dir_cs(full_path.sanitize()) { + //namespace directory + let _rc_symbol = Symbol::create_from_path(session, &full_path, parent.clone(), false); + if _rc_symbol.is_some() { + let _arc_symbol = _rc_symbol.unwrap(); + SyncOdoo::build_now(session, &_arc_symbol, BuildSteps::ARCH); + return Ok(_arc_symbol); + } } else if !matches!(parent.borrow().typ(), SymType::ROOT) { if cfg!(target_os = "windows") { for entry in glob((full_path.sanitize() + "*.pyd").as_str()).expect("Failed to read glob pattern") { @@ -393,14 +424,6 @@ fn _resolve_new_symbol(session: &mut SessionInfo, parent: Rc>, n } } } - } else if is_dir_cs(full_path.sanitize()) { - //namespace directory - let _rc_symbol = Symbol::create_from_path(session, &full_path, parent.clone(), false); - if _rc_symbol.is_some() { - let _arc_symbol = _rc_symbol.unwrap(); - SyncOdoo::build_now(session, &_arc_symbol, BuildSteps::ARCH); - return Ok(_arc_symbol); - } } } return Err("Symbol not found".to_string()) @@ -412,9 +435,7 @@ pub fn get_all_valid_names(session: &mut SessionInfo, source_file_symbol: &Rc>>) -> Vec<(Rc>, Option)> { - let mut symbol = Vec::new(); + pub fn all_symbols(&self, session: &mut SessionInfo, from_module: Option>>, with_inheritance: bool) -> Vec<(Rc>, Option)> { + self.all_symbols_helper(session, from_module, with_inheritance, &mut HashSet::new()) + } + + fn all_symbols_helper(&self, session: &mut SessionInfo, from_module: Option>>, with_inheritance: bool, seen_inherited_models: &mut HashSet) -> Vec<(Rc>, Option)> { + let mut symbols = Vec::new(); for s in self.symbols.iter() { if let Some(from_module) = from_module.as_ref() { let module = s.borrow().find_module(); if let Some(module) = module { if ModuleSymbol::is_in_deps(session, &from_module, &module.borrow().as_module_package().dir_name) { - symbol.push((s, None)); + symbols.push((s.clone(), None)); } else { - symbol.push((s, Some(module.borrow().as_module_package().dir_name.clone()))); + symbols.push((s.clone(), Some(module.borrow().as_module_package().dir_name.clone()))); } } else { session.log_message(MessageType::WARNING, "A model should be declared in a module.".to_string()); } } else { - symbol.push((s.clone(), None)); + symbols.push((s.clone(), None)); + } + if !with_inheritance { + continue; + } + let inherited_models = s.borrow().as_class_sym()._model.as_ref().unwrap().inherit.clone(); + for inherited_model in inherited_models.iter() { + if !seen_inherited_models.contains(inherited_model) { + seen_inherited_models.insert(inherited_model.clone()); + if let Some(model) = session.sync_odoo.models.get(inherited_model).cloned() { + symbols.extend(model.borrow().all_symbols_helper(session, from_module.clone(), true, seen_inherited_models)); + } + } } } - symbol + symbols + } + + pub fn all_symbols_inherits(&self, session: &mut SessionInfo, from_module: Option>>) -> (Vec<(Rc>, Option)>, Vec<(Rc>, Option)>) { + let mut visited_models = HashSet::new(); + self.all_inherits_helper(session, from_module, &mut visited_models) + } + + fn all_inherits_helper(&self, session: &mut SessionInfo, from_module: Option>>, visited_models: &mut HashSet) -> (Vec<(Rc>, Option)>, Vec<(Rc>, Option)>) { + if visited_models.contains(&self.name.to_string()) { + return (Vec::new(), Vec::new()); + } + visited_models.insert(self.name.to_string()); + let mut symbols = Vec::new(); + let mut inherits_symbols = Vec::new(); + for s in self.symbols.iter() { + if let Some(from_module) = from_module.as_ref() { + let module = s.borrow().find_module(); + if let Some(module) = module { + if ModuleSymbol::is_in_deps(session, &from_module, &module.borrow().as_module_package().dir_name) { + symbols.push((s.clone(), None)); + } else { + symbols.push((s.clone(), Some(module.borrow().as_module_package().dir_name.clone()))); + } + } else { + session.log_message(MessageType::WARNING, "A model should be declared in a module.".to_string()); + } + } else { + symbols.push((s.clone(), None)); + } + // First get results from normal inherit + // To make sure we visit all of inherit before inherits, since it is DFS + // Only inherits in the tree that are not already visited will be processed in the next iteration + let inherited_models = s.borrow().as_class_sym()._model.as_ref().unwrap().inherit.clone(); + for inherited_model in inherited_models.iter() { + if let Some(model) = session.sync_odoo.models.get(inherited_model).cloned() { + let (main_result, inherits_result) = model.borrow().all_inherits_helper(session, from_module.clone(), visited_models); + symbols.extend(main_result); + inherits_symbols.extend(inherits_result); + } + } + for (inherits_model, _) in s.borrow().as_class_sym()._model.as_ref().unwrap().inherits.clone() { + if let Some(model) = session.sync_odoo.models.get(&inherits_model).cloned() { + let (main_result, inherits_result) = model.borrow().all_inherits_helper(session, from_module.clone(), visited_models); + // Everything that is in inherits should be added to inherits_symbols, regardless of whether + // it was in inherit or inherits. Since we need that distinction to later only get fields + inherits_symbols.extend(main_result); + inherits_symbols.extend(inherits_result); + } + } + } + (symbols, inherits_symbols) } pub fn add_dependent(&mut self, symbol: &Rc>) { diff --git a/server/src/core/odoo.rs b/server/src/core/odoo.rs index d71137ae..9171a434 100644 --- a/server/src/core/odoo.rs +++ b/server/src/core/odoo.rs @@ -1,5 +1,11 @@ +use crate::core::diagnostics::{create_diagnostic, DiagnosticCode}; use crate::core::entry_point::EntryPointType; +use crate::core::file_mgr::AstType; +use crate::core::symbols::file_symbol; +use crate::core::xml_data::XmlData; +use crate::core::xml_validation::XmlValidator; use crate::features::document_symbols::DocumentSymbolFeature; +use crate::features::references::ReferenceFeature; use crate::threads::SessionInfo; use crate::features::completion::CompletionFeature; use crate::features::definition::DefinitionFeature; @@ -15,6 +21,7 @@ use lsp_server::ResponseError; use lsp_types::*; use request::{RegisterCapability, Request, WorkspaceConfiguration}; use ruff_python_parser::{Mode, ParseOptions}; +use serde_json::Value; use tracing::{error, warn, info, trace}; use std::collections::HashSet; @@ -57,6 +64,7 @@ pub struct SyncOdoo { pub version_minor: u32, pub version_micro: u32, pub full_version: String, + pub python_version: Vec, pub config: ConfigEntry, pub config_file: Option, pub entry_point_mgr: Rc>, //An Rc to be able to clone it and free session easily @@ -94,6 +102,7 @@ impl SyncOdoo { version_minor: 0, version_micro: 0, full_version: "0.0.0".to_string(), + python_version: vec![0, 0, 0], config: ConfigEntry::new(), config_file: None, entry_point_mgr: Rc::new(RefCell::new(EntryPointMgr::new())), @@ -186,7 +195,6 @@ impl SyncOdoo { session.send_notification("$Odoo/loadingStatusUpdate", "stop"); return; } - session.sync_odoo.has_valid_python = true; let output = output.unwrap(); if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); @@ -205,6 +213,31 @@ impl SyncOdoo { let stderr = String::from_utf8_lossy(&output.stderr); error!("{}", stderr); } + let output = Command::new(session.sync_odoo.config.python_path.clone()).args(&["-c", "import sys; import json; print(json.dumps(sys.version_info))"]).output(); + if let Err(_output) = &output { + error!("Wrong python command: {}", session.sync_odoo.config.python_path.clone()); + session.send_notification("$Odoo/invalid_python_path", ()); + session.send_notification("$Odoo/loadingStatusUpdate", "stop"); + return; + } + session.sync_odoo.has_valid_python = true; + let output = output.unwrap(); + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + session.log_message(MessageType::INFO, format!("Detected sys.version_info: {}", stdout)); + let version_infos: Value = serde_json::from_str(&stdout).expect("Unable to get python version info with json of sys.version_info output"); + session.sync_odoo.python_version = version_infos.as_array() + .expect("Expected JSON array") + .iter() + .filter_map(|v| v.as_u64()) + .map(|v| v as u32) + .take(3) + .collect(); + info!("Detected python version: {}.{}.{}", session.sync_odoo.python_version[0], session.sync_odoo.python_version[1], session.sync_odoo.python_version[2]); + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + error!("{}", stderr); + } } if SyncOdoo::load_builtins(session) { session.sync_odoo.state_init = InitState::PYTHON_READY; @@ -597,8 +630,17 @@ impl SyncOdoo { session.sync_odoo.add_to_validations(sym_rc.clone()); return true; } - let mut validator = PythonValidator::new(entry.unwrap(), sym_rc); - validator.validate(session); + let typ = sym_rc.borrow().typ(); + match typ { + SymType::XML_FILE => { + let mut validator = XmlValidator::new(entry.as_ref().unwrap(), sym_rc); + validator.validate(session); + }, + _ => { + let mut validator = PythonValidator::new(entry.unwrap(), sym_rc); + validator.validate(session); + } + } continue; } } @@ -814,6 +856,13 @@ impl SyncOdoo { pub fn get_symbol_of_opened_file(session: &mut SessionInfo, path: &PathBuf) -> Option>> { let path_in_tree = path.to_tree_path(); for entry in session.sync_odoo.entry_point_mgr.borrow().iter_main() { + let sym_in_data = entry.borrow().data_symbols.get(path.sanitize().as_str()).cloned(); + if let Some(sym) = sym_in_data { + if let Some(sym) = sym.upgrade() { + return Some(sym); + } + continue; + } if (entry.borrow().typ == EntryPointType::MAIN || entry.borrow().addon_to_odoo_path.is_some()) && entry.borrow().is_valid_for(path) { let tree = entry.borrow().get_tree_for_entry(path); let path_symbol = entry.borrow().root.borrow().get_symbol(&tree, u32::MAX); @@ -826,6 +875,13 @@ impl SyncOdoo { //Not found? Then return if it is matching a non-public entry strictly matching the file let mut found_an_entry = false; //there to ensure that a wrongly built entry would create infinite loop for entry in session.sync_odoo.entry_point_mgr.borrow().custom_entry_points.iter() { + let sym_in_data = entry.borrow().data_symbols.get(path.sanitize().as_str()).cloned(); + if let Some(sym) = sym_in_data { + if let Some(sym) = sym.upgrade() { + return Some(sym); + } + continue; + } if !entry.borrow().is_public() && &path_in_tree == &PathBuf::from(&entry.borrow().path) { found_an_entry = true; let tree = entry.borrow().get_tree_for_entry(path); @@ -904,6 +960,44 @@ impl SyncOdoo { self.capabilities = capabilities.clone(); } + /** + * search for an xml_id in the already registered xml files. + * */ + pub fn get_xml_ids(session: &mut SessionInfo, from_file: &Rc>, xml_id: &str, range: &std::ops::Range, diagnostics: &mut Vec) -> Vec { + if !from_file.borrow().get_entry().unwrap().borrow().is_main() { + return vec![]; + } + let id_split = xml_id.split(".").collect::>(); + let mut module = None; + if id_split.len() == 1 { + // If no module name, we are in the current module + module = from_file.borrow().find_module(); + } else if id_split.len() == 2 { + // Try to find the module by name + if let Some(m) = session.sync_odoo.modules.get(&Sy!(id_split.first().unwrap().to_string())) { + module = m.upgrade(); + } + } else if id_split.len() > 2 { + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05051, &[xml_id]) { + diagnostics.push(lsp_types::Diagnostic { + range: lsp_types::Range { + start: lsp_types::Position::new(range.start as u32, 0), + end: lsp_types::Position::new(range.end as u32, 0), + }, + ..diagnostic.clone() + }); + } + return vec![]; + } + if module.is_none() { + warn!("Module not found for id: {}", xml_id); + return vec![]; + } + let module = module.unwrap(); + let module = module.borrow(); + module.as_module_package().get_xml_id(&oyarn!("{}", id_split.last().unwrap())) + } + } #[derive(Debug)] @@ -1079,8 +1173,19 @@ impl Odoo { if file_info.borrow().file_info_ast.borrow().ast.is_none() { file_info.borrow_mut().prepare_ast(session); } - if file_info.borrow_mut().file_info_ast.borrow().ast.is_some() { - return Ok(HoverFeature::get_hover(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone(); + match ast_type { + AstType::Python => { + if file_info.borrow_mut().file_info_ast.borrow().ast.is_some() { + return Ok(HoverFeature::hover_python(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } + }, + AstType::Xml => { + return Ok(HoverFeature::hover_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, + AstType::Csv => { + return Ok(HoverFeature::hover_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, } } } @@ -1105,8 +1210,56 @@ impl Odoo { if file_info.borrow().file_info_ast.borrow().ast.is_none() { file_info.borrow_mut().prepare_ast(session); } - if file_info.borrow().file_info_ast.borrow().ast.is_some() { - return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone(); + match ast_type { + AstType::Python => { + if file_info.borrow().file_info_ast.borrow().ast.is_some() { + return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } + }, + AstType::Xml => { + return Ok(DefinitionFeature::get_location_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, + AstType::Csv => { + return Ok(DefinitionFeature::get_location_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, + } + } + } + } + Ok(None) + } + + pub fn handle_references(session: &mut SessionInfo, params: ReferenceParams) -> Result>, ResponseError> { + if session.sync_odoo.state_init == InitState::NOT_READY { + return Ok(None); + } + session.log_message(MessageType::INFO, format!("References requested on {} at {} - {}", + params.text_document_position.text_document.uri.to_string(), + params.text_document_position.position.line, + params.text_document_position.position.character)); + let uri = params.text_document_position.text_document.uri.to_string(); + let path = FileMgr::uri2pathname(uri.as_str()); + if uri.ends_with(".py") || uri.ends_with(".pyi") || uri.ends_with(".xml") || uri.ends_with(".csv") { + if let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(session, &PathBuf::from(path.clone())) { + let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().ast.is_none() { + file_info.borrow_mut().prepare_ast(session); + } + let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone(); + match ast_type { + AstType::Python => { + if file_info.borrow_mut().file_info_ast.borrow().ast.is_some() { + return Ok(ReferenceFeature::get_references(session, &file_symbol, &file_info, params.text_document_position.position.line, params.text_document_position.position.character)); + } + }, + AstType::Xml => { + return Ok(ReferenceFeature::get_references_xml(session, &file_symbol, &file_info, params.text_document_position.position.line, params.text_document_position.position.character)); + }, + AstType::Csv => { + return Ok(ReferenceFeature::get_references_csv(session, &file_symbol, &file_info, params.text_document_position.position.line, params.text_document_position.position.character)); + }, } } } diff --git a/server/src/core/python_arch_builder.rs b/server/src/core/python_arch_builder.rs index 3d839699..ade4966e 100644 --- a/server/src/core/python_arch_builder.rs +++ b/server/src/core/python_arch_builder.rs @@ -4,12 +4,12 @@ use std::cell::RefCell; use std::vec; use anyhow::Error; use ruff_text_size::{Ranged, TextRange, TextSize}; -use ruff_python_ast::{Alias, Expr, ExprNamed, FStringPart, Identifier, Pattern, Stmt, StmtAnnAssign, StmtAssign, StmtClassDef, StmtFor, StmtFunctionDef, StmtIf, StmtMatch, StmtTry, StmtWhile, StmtWith}; +use ruff_python_ast::{Alias, CmpOp, Expr, ExprNamed, ExprTuple, FStringPart, Identifier, Pattern, Stmt, StmtAnnAssign, StmtAssign, StmtClassDef, StmtFor, StmtFunctionDef, StmtIf, StmtMatch, StmtTry, StmtWhile, StmtWith}; use lsp_types::Diagnostic; use tracing::{trace, warn}; use weak_table::traits::WeakElement; -use crate::constants::{BuildStatus, BuildSteps, OYarn, PackageType, SymType, DEBUG_STEPS}; +use crate::constants::{BuildStatus, BuildSteps, OYarn, PackageType, SymType, DEBUG_STEPS, DEBUG_STEPS_ONLY_INTERNAL}; use crate::core::python_utils; use crate::core::import_resolver::resolve_import_stmt; use crate::core::symbols::symbol::Symbol; @@ -72,7 +72,7 @@ impl PythonArchBuilder { self.current_step = if self.file_mode {BuildSteps::ARCH} else {BuildSteps::VALIDATION}; self.ast_indexes = symbol.borrow().ast_indexes().unwrap_or(&vec![]).clone(); //copy current ast_indexes if we are not evaluating a file } - if DEBUG_STEPS { + if DEBUG_STEPS && (!DEBUG_STEPS_ONLY_INTERNAL || !symbol.borrow().is_external()) { trace!("building {} - {}", self.file.borrow().paths().first().unwrap_or(&S!("No path found")), symbol.borrow().name()); } symbol.borrow_mut().set_build_status(BuildSteps::ARCH, BuildStatus::IN_PROGRESS); @@ -113,7 +113,12 @@ impl PythonArchBuilder { file_info_ast.ast.as_ref().unwrap() }, false => { - &AstUtils::find_stmt_from_ast(file_info_ast.ast.as_ref().unwrap(), self.sym_stack[0].borrow().ast_indexes().unwrap()).as_function_def_stmt().unwrap().body + if !self.sym_stack[0].borrow().ast_indexes().unwrap().is_empty() { + &AstUtils::find_stmt_from_ast(file_info_ast.ast.as_ref().unwrap(), self.sym_stack[0].borrow().ast_indexes().unwrap()).as_function_def_stmt().unwrap().body + } else { + //if ast_index is empty, this is because the function has been added manually and do not belong to the ast. Skip it's building + &vec![] + } } }; let old_stack_noqa = session.noqas_stack.clone(); @@ -543,7 +548,7 @@ impl PythonArchBuilder { if parent.is_some() { let parent = parent.unwrap(); let mut deps = vec![vec![]]; //only arch level - let eval = Evaluation::eval_from_ast(session, &assign.value.as_ref().unwrap(), parent, &assign_stmt.range.start(), &mut deps); + let eval = Evaluation::eval_from_ast(session, &assign.value.as_ref().unwrap(), parent, &assign_stmt.range.start(), false, &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, BuildSteps::ARCH); variable.as_variable_mut().evaluations = eval.0; self.diagnostics.extend(eval.1); @@ -661,6 +666,11 @@ impl PythonArchBuilder { else if decorator.expression.as_name_expr().unwrap().id.to_string() == "classmethod" { func_sym.is_class_method = true; } + else if decorator.expression.as_name_expr().unwrap().id.to_string() == "classproperty" || + decorator.expression.as_name_expr().unwrap().id.to_string() == "lazy_classproperty" { + func_sym.is_property = true; + func_sym.is_class_method = true; + } } } if func_def.body[0].is_expr_stmt() { @@ -674,9 +684,13 @@ impl PythonArchBuilder { for arg in func_def.parameters.posonlyargs.iter() { let param = sym.borrow_mut().add_new_variable(session, oyarn!("{}", arg.parameter.name.id), &arg.range); param.borrow_mut().as_variable_mut().is_parameter = true; + let mut default = None; + if arg.default.is_some() { + default = Some(Evaluation::new_none()); //TODO evaluate default? actually only used to know if there is a default or not + } sym.borrow_mut().as_func_mut().args.push(Argument { symbol: Rc::downgrade(¶m), - default_value: None, + default_value: default, arg_type: ArgumentType::POS_ONLY, annotation: arg.parameter.annotation.clone(), }); @@ -793,6 +807,102 @@ impl PythonArchBuilder { } } + fn check_tuples(&self, version: &Vec, op: &CmpOp, tuple: &ExprTuple) -> bool { + let mut tuple = tuple.elts.iter().map(|elt| { + if let Expr::NumberLiteral(num) = elt { + if num.value.is_int() { + num.value.as_int().unwrap().as_u32().unwrap() + } else { + 0 as u32 + } + } else { + 0 as u32 // If not a number, treat as 0 + } + }).collect::>(); + // ensure that the vec is sized of 3 + tuple.resize(3, 0); + return match op { + CmpOp::Gt => { + version[0] > tuple[0] || + (version[0] == tuple[0] && version[1] > tuple[1]) || + (version[0] == tuple[0] && version[1] == tuple[1] && version[2] > tuple[2]) + }, + CmpOp::GtE => { + version[0] >= tuple[0] || + (version[0] == tuple[0] && version[1] >= tuple[1]) || + (version[0] == tuple[0] && version[1] == tuple[1] && version[2] >= tuple[2]) + }, + CmpOp::Lt => { + version[0] < tuple[0] || + (version[0] == tuple[0] && version[1] < tuple[1]) || + (version[0] == tuple[0] && version[1] == tuple[1] && version[2] < tuple[2]) + }, + CmpOp::LtE => { + version[0] <= tuple[0] || + (version[0] == tuple[0] && version[1] <= tuple[1]) || + (version[0] == tuple[0] && version[1] == tuple[1] && version[2] <= tuple[2]) + }, + CmpOp::Eq => { + version[0] == tuple[0] && + version[1] == tuple[1] && + version[2] == tuple[2] + }, + CmpOp::NotEq => { + version[0] != tuple[0] || + version[1] != tuple[1] || + version[2] != tuple[2] + }, + _ => { + false + } + } + } + + /** returns + * first bool: true if we can go in the condition, because no version check is preventing it + * second bool: true if there was a version check or false if the condition was unrelated + */ + fn _check_sys_version_condition(&self, session: &mut SessionInfo, expr: &Expr) -> (bool, bool) { + if session.sync_odoo.python_version[0] == 0 { + return (true, false); //unknown python version + } + if let Expr::Compare(expr_comp) = expr { + if expr_comp.comparators.len() == 1 { + let p1 = expr_comp.left.as_ref(); + let p2 = expr_comp.comparators.first().unwrap(); + if !p1.is_tuple_expr() && !p2.is_tuple_expr() { + return (true, false); + } + if !p1.is_attribute_expr() && !p2.is_attribute_expr() { + return (true, false); + } + let (tuple, attr) = if p1.is_tuple_expr() { + (p1.as_tuple_expr().unwrap(), p2.as_attribute_expr().unwrap()) + } else { + (p2.as_tuple_expr().unwrap(), p1.as_attribute_expr().unwrap()) + }; + if attr.value.is_name_expr() && attr.value.as_name_expr().unwrap().id == "sys" { + if attr.attr.id == "version_info" { + let mut op = expr_comp.ops.first().unwrap(); + if p1.is_tuple_expr() { //invert if tuple is in front + if op.is_gt() { + op = &CmpOp::Lt; + } else if op.is_gt_e() { + op = &CmpOp::LtE; + } else if op.is_lt() { + op = &CmpOp::Gt; + } else if op.is_lt_e() { + op = &CmpOp::GtE; + } + } + return (self.check_tuples(&session.sync_odoo.python_version, op, tuple), true) + } + } + } + } + (true, false) + } + fn visit_if(&mut self, session: &mut SessionInfo, if_stmt: &StmtIf) -> Result<(), Error> { //TODO check platform condition (sys.version > 3.12, etc...) let scope = self.sym_stack.last().unwrap().clone(); @@ -803,17 +913,26 @@ impl PythonArchBuilder { let mut last_test_section = test_section.index; self.visit_expr(session, &if_stmt.test); + let mut body_version_ok = false; //if true, it means we found a condition that is true and contained a version check. Used to avoid else clause let mut stmt_sections = if if_stmt.body.is_empty() { vec![] } else { - scope.borrow_mut().as_mut_symbol_mgr().add_section( // first body section - if_stmt.body[0].range().start(), - None // Take preceding section (if test) - ); - self.ast_indexes.push(0 as u16); //0 for body - self.visit_node(session, &if_stmt.body)?; - self.ast_indexes.pop(); - vec![ SectionIndex::INDEX(scope.borrow().as_symbol_mgr().get_last_index())] + scope.borrow_mut().as_mut_symbol_mgr().add_section( // first body section + if_stmt.body[0].range().start(), + None // Take preceding section (if test) + ); + let check_version = self._check_sys_version_condition(session, if_stmt.test.as_ref()); + if check_version.0 { + if check_version.1 { + body_version_ok = true; + } + self.ast_indexes.push(0 as u16); //0 for body + self.visit_node(session, &if_stmt.body)?; + self.ast_indexes.pop(); + vec![ SectionIndex::INDEX(scope.borrow().as_symbol_mgr().get_last_index())] + } else { + vec![] + } }; let mut else_clause_exists = false; @@ -836,9 +955,22 @@ impl PythonArchBuilder { elif_else_clause.body[0].range().start(), Some(SectionIndex::INDEX(last_test_section)) ); - self.ast_indexes.push((index + 1) as u16); //0 for body, so index + 1 - self.visit_node(session, &elif_else_clause.body)?; - self.ast_indexes.pop(); + if elif_else_clause.test.is_some() { + let version_check = self._check_sys_version_condition(session, elif_else_clause.test.as_ref().unwrap()); + if version_check.0 { + if version_check.1 { + body_version_ok = true; + } + self.ast_indexes.push((index + 1) as u16); //0 for body, so index + 1 + self.visit_node(session, &elif_else_clause.body)?; + self.ast_indexes.pop(); + } + } + else if !body_version_ok { //else clause + self.ast_indexes.push((index + 1) as u16); //0 for body, so index + 1 + self.visit_node(session, &elif_else_clause.body)?; + self.ast_indexes.pop(); + } let clause_section = SectionIndex::INDEX(scope.borrow().as_symbol_mgr().get_last_index()); Ok::, Error>(Some(clause_section)) }); diff --git a/server/src/core/python_arch_builder_hooks.rs b/server/src/core/python_arch_builder_hooks.rs index cb55f329..9c3e8d4b 100644 --- a/server/src/core/python_arch_builder_hooks.rs +++ b/server/src/core/python_arch_builder_hooks.rs @@ -4,11 +4,12 @@ use std::rc::Rc; use std::cell::RefCell; use once_cell::sync::Lazy; use ruff_text_size::{TextRange, TextSize}; -use tracing::warn; +use tracing::{info, warn}; use crate::core::entry_point::EntryPoint; +use crate::core::import_resolver::manual_import; use crate::core::symbols::symbol::Symbol; use crate::threads::SessionInfo; -use crate::utils::compare_semver; +use crate::utils::{compare_semver, is_file_cs, PathSanitizer}; use crate::{Sy, S}; use crate::constants::OYarn; @@ -72,6 +73,17 @@ static arch_class_hooks: Lazy> = Lazy::new(|| {vec![ let _ = symbol.borrow_mut().add_new_variable(session, Sy!("registry"), &range); } }, + PythonArchClassHook { + odoo_entry: true, + trees: vec![ + (Sy!("15.0"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("addons"), Sy!("base"), Sy!("models"), Sy!("ir_rule")], vec![Sy!("IrRule")])), + ], + func: |session: &mut SessionInfo, entry_point: &Rc>, symbol: Rc>| { + let mut range = symbol.borrow().range().clone(); + // ----------- env.cr ------------ + symbol.borrow_mut().add_new_variable(session, Sy!("global"), &range); + } + }, PythonArchClassHook { odoo_entry: true, trees: vec![ @@ -184,6 +196,45 @@ impl PythonArchBuilderHooks { } } } + } else if name == "werkzeug" { + if symbol.borrow().get_main_entry_tree(session) == (vec![Sy!("odoo"), Sy!("_monkeypatches"), Sy!("werkzeug")], vec![]) { + //doing this patch like this imply that an odoo project will make these functions available for all entrypoints, but heh + let werkzeug_url = session.sync_odoo.get_symbol(symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![]), u32::MAX); + if let Some(werkzeug_url) = werkzeug_url.first() { + //fake variable, as ext_symbols are not seen through get_symbol, etc... + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_decode"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_encode"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_join"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_parse"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_quote"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_unquote"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_quote_plus"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_unquote_plus"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("url_unparse"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + werkzeug_url.borrow_mut().add_new_variable(session, Sy!("URL"), &TextRange::new(TextSize::new(0), TextSize::new(0))); + } else { + warn!("Unable to find werkzeug.urls to monkeypatch it"); + } + } + } else if name == "urls" { + if symbol.borrow().get_local_tree() == (vec![Sy!("werkzeug"), Sy!("urls")], vec![]) { + //manually load patch, as a manual dependency + let full_path_monkeypatches = S!("odoo._monkeypatches"); + let mut main_odoo_symbol = None; + if let Some(main_ep) = session.sync_odoo.entry_point_mgr.borrow().main_entry_point.as_ref() { + //To import from main entry point, we have to import 'from' a symbol coming from main entry point. + //We then use the main symbol of the main entry point to achieve that, instead of the werkzeug symbol + main_odoo_symbol = Some(main_ep.borrow().get_symbol().unwrap()); + } + if let Some(main_odoo_symbol) = main_odoo_symbol { + let werkzeug_patch = manual_import(session, &main_odoo_symbol, Some(full_path_monkeypatches), "werkzeug", None, None, &mut None); + for werkzeug_patch in werkzeug_patch { + if werkzeug_patch.found { + info!("monkeypatch manually found"); + } + } + } + } } } } \ No newline at end of file diff --git a/server/src/core/python_arch_eval.rs b/server/src/core/python_arch_eval.rs index c14d22fa..93336702 100644 --- a/server/src/core/python_arch_eval.rs +++ b/server/src/core/python_arch_eval.rs @@ -72,7 +72,7 @@ impl PythonArchEval { self.file_mode = Rc::ptr_eq(&file, &symbol); self.current_step = if self.file_mode {BuildSteps::ARCH_EVAL} else {BuildSteps::VALIDATION}; } - if DEBUG_STEPS { + if DEBUG_STEPS && (!DEBUG_STEPS_ONLY_INTERNAL || !symbol.borrow().is_external()) { trace!("evaluating {} - {}", self.file.borrow().paths().first().unwrap_or(&S!("No path found")), symbol.borrow().name()); } symbol.borrow_mut().set_build_status(BuildSteps::ARCH_EVAL, BuildStatus::IN_PROGRESS); @@ -460,13 +460,13 @@ impl PythonArchEval { if !self.file_mode { deps.push(vec![]); } - let ann_evaluations = assign.annotation.as_ref().map(|annotation| Evaluation::eval_from_ast(session, annotation, parent.clone(), &range.start(), &mut deps)); + let mut ann_evaluations = assign.annotation.as_ref().map(|annotation| Evaluation::eval_from_ast(session, annotation, parent.clone(), &range.start(), true, &mut deps)); Symbol::insert_dependencies(&self.file, &mut deps, self.current_step); deps = vec![vec![], vec![]]; if !self.file_mode { deps.push(vec![]); } - let value_evaluations = assign.value.as_ref().map(|value| Evaluation::eval_from_ast(session, value, parent.clone(), &range.start(), &mut deps)); + let value_evaluations = assign.value.as_ref().map(|value| Evaluation::eval_from_ast(session, value, parent.clone(), &range.start(), false, &mut deps)); Symbol::insert_dependencies(&self.file, &mut deps, self.current_step); let mut take_value = false; if let Some((ref val_eval, ref _diags)) = value_evaluations{ @@ -483,7 +483,14 @@ impl PythonArchEval { take_value = ann_evaluations.is_none(); } } - let (eval, diags) = if take_value {value_evaluations.unwrap()} else {ann_evaluations.unwrap()}; + let (eval, diags) = if take_value { + value_evaluations.unwrap() + } else { + if value_evaluations.is_some() { + ann_evaluations.as_mut().unwrap().0.extend(value_evaluations.unwrap().0); + } + ann_evaluations.unwrap() + }; variable_rc.borrow_mut().evaluations_mut().unwrap().extend(eval); self.diagnostics.extend(diags); let mut dep_to_add = vec![]; @@ -541,7 +548,7 @@ impl PythonArchEval { let Some(model) = session.sync_odoo.models.get(&model_data.name).cloned() else { continue; }; - let model_classes = model.borrow().all_symbols(session, parent_class.find_module()); + let model_classes = model.borrow().all_symbols(session, parent_class.find_module(), false); let fn_name = self.sym_stack[0].borrow().name().clone(); let allowed_fields: HashSet<_> = model_classes.iter().filter_map(|(sym, _)| sym.borrow().as_class_sym()._model.as_ref().unwrap().computes.get(&fn_name).cloned()).flatten().collect(); if allowed_fields.is_empty() { @@ -554,7 +561,7 @@ impl PythonArchEval { // Check the whole attribute chain, to see if we are in a field of the model that is valid // so for z.a.b.c, checks, z.a, z.a.b, z.a.b.c, if one of them is valid it is okay 'while_block: while matches!(expr, Expr::Attribute(_)){ - let assignee = Evaluation::eval_from_ast(session, &expr, self.sym_stack.last().unwrap().clone(), &attr_expr.range.start(), &mut vec![]); + let assignee = Evaluation::eval_from_ast(session, &expr, self.sym_stack.last().unwrap().clone(), &attr_expr.range.start(), false, &mut vec![]); for evaluation in assignee.0{ let evaluation_symbol_ptr = evaluation.symbol.get_symbol_weak_transformed(session, &mut None, &mut vec![], None); let Some(sym_rc) = evaluation_symbol_ptr.upgrade_weak() else { @@ -620,7 +627,7 @@ impl PythonArchEval { fn load_base_classes(&mut self, session: &mut SessionInfo, loc_sym: &Rc>, class_stmt: &StmtClassDef) { for base in class_stmt.bases() { let mut deps = vec![vec![], vec![]]; - let eval_base = Evaluation::eval_from_ast(session, base, self.sym_stack.last().unwrap().clone(), &class_stmt.range().start(), &mut deps); + let eval_base = Evaluation::eval_from_ast(session, base, self.sym_stack.last().unwrap().clone(), &class_stmt.range().start(), false, &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, BuildSteps::ARCH_EVAL); self.diagnostics.extend(eval_base.1); let eval_base = eval_base.0; @@ -665,6 +672,18 @@ impl PythonArchEval { } } } else { + //Even if this is a valid class, we have to be sure that its own bases should have been loaded already + let sym_file = symbol.borrow().get_file().clone(); + if let Some(file) = sym_file { + if let Some(file) = file.upgrade() { + if file.borrow().build_status(BuildSteps::ARCH_EVAL) != BuildStatus::DONE { + SyncOdoo::build_now(session, &file, BuildSteps::ARCH_EVAL); + } + if !Rc::ptr_eq(&self.file, &file) { + self.file.borrow_mut().add_dependency(&mut file.borrow_mut(), self.current_step, BuildSteps::ARCH_EVAL); + } + } + } loc_sym.borrow_mut().as_class_sym_mut().bases.push(Rc::downgrade(&symbol)); } } @@ -719,7 +738,9 @@ impl PythonArchEval { let (eval, diags) = Evaluation::eval_from_ast(session, &arg.parameter.annotation.as_ref().unwrap(), self.sym_stack.last().unwrap().clone(), - &func_stmt.range.start(), &mut deps); + &func_stmt.range.start(), + true, + &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, self.current_step); let mut var_bw = function_sym.borrow_mut(); let symbol = var_bw.as_func_mut().symbols.get(&OYarn::from(arg.parameter.name.id.to_string())).unwrap().get(&0).unwrap().get(0).unwrap(); //get first declaration @@ -733,7 +754,9 @@ impl PythonArchEval { let (eval, diags) = Evaluation::eval_from_ast(session, arg.default.as_ref().unwrap(), self.sym_stack.last().unwrap().clone(), - &func_stmt.range.start(), &mut deps); + &func_stmt.range.start(), + false, + &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, self.current_step); let mut var_bw = function_sym.borrow_mut(); let symbol = var_bw.as_func_mut().symbols.get(&OYarn::from(arg.parameter.name.id.to_string())).unwrap().get(&0).unwrap().get(0).unwrap(); //get first declaration @@ -782,7 +805,7 @@ impl PythonArchEval { let (eval_iter_node, diags) = Evaluation::eval_from_ast(session, &for_stmt.iter, self.sym_stack.last().unwrap().clone(), - &for_stmt.target.range().start(), &mut deps); + &for_stmt.target.range().start(), false, &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, self.current_step); self.diagnostics.extend(diags); if eval_iter_node.len() == 1 { //Only handle values that we are sure about @@ -855,7 +878,7 @@ impl PythonArchEval { if !self.file_mode { deps.push(vec![]); } - let (eval, diags) = Evaluation::eval_from_ast(session, value, func.clone(), &return_stmt.range.start(), &mut deps); + let (eval, diags) = Evaluation::eval_from_ast(session, value, func.clone(), &return_stmt.range.start(), false, &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, self.current_step); self.diagnostics.extend(diags); FunctionSymbol::add_return_evaluations(func, session, eval); @@ -878,7 +901,7 @@ impl PythonArchEval { if !self.file_mode { deps.push(vec![]); } - let (eval, diags) = Evaluation::eval_from_ast(session, &item.context_expr, parent, &with_stmt.range.start(), &mut deps); + let (eval, diags) = Evaluation::eval_from_ast(session, &item.context_expr, parent, &with_stmt.range.start(), false, &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, self.current_step); let mut evals = vec![]; for eval in eval.iter() { @@ -934,13 +957,22 @@ impl PythonArchEval { if let Some(returns_ann) = func_stmt.returns.as_ref() { let file_sym = func_sym.borrow().get_file().and_then(|file_weak| file_weak.upgrade()); let mut deps = vec![vec![], vec![]]; - let (evaluations, diags) = Evaluation::eval_from_ast( + let (mut evaluations, diags) = Evaluation::eval_from_ast( session, &returns_ann, func_sym.borrow().parent().and_then(|p| p.upgrade()).unwrap(), max_infer, + true, &mut deps, ); + for eval in evaluations.iter_mut() { //as this is an evaluation, we need to set the instance to true + match eval.symbol.get_mut_symbol_ptr() { + EvaluationSymbolPtr::WEAK(ref mut sym_weak) => { + sym_weak.instance = Some(true); + }, + _ => {} + } + } if file_sym.is_some() { Symbol::insert_dependencies(&file_sym.as_ref().unwrap(), &mut deps, BuildSteps::ARCH_EVAL); } diff --git a/server/src/core/python_arch_eval_hooks.rs b/server/src/core/python_arch_eval_hooks.rs index 09eab453..7792d040 100644 --- a/server/src/core/python_arch_eval_hooks.rs +++ b/server/src/core/python_arch_eval_hooks.rs @@ -1,5 +1,7 @@ use std::cmp::Ordering; use std::collections::HashMap; +use std::ops::Index; +use std::path::PathBuf; use std::rc::Rc; use std::rc::Weak; use std::cell::RefCell; @@ -21,6 +23,8 @@ use crate::constants::*; use crate::oyarn; use crate::threads::SessionInfo; use crate::utils::compare_semver; +use crate::utils::is_file_cs; +use crate::utils::PathSanitizer; use crate::Sy; use crate::S; @@ -30,7 +34,7 @@ use super::file_mgr::FileMgr; use super::python_arch_eval::PythonArchEval; use super::symbols::module_symbol::ModuleSymbol; -type PythonArchEvalHookFile = fn (odoo: &mut SyncOdoo, entry: &Rc>, file_symbol: Rc>, symbol: Rc>); +type PythonArchEvalHookFile = fn (odoo: &mut SessionInfo, entry: &Rc>, file_symbol: Rc>, symbol: Rc>); pub struct PythonArchEvalFileHook { pub odoo_entry: bool, @@ -45,9 +49,9 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("BaseModel"), Sy!("env")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("BaseModel"), Sy!("env")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, file_symbol: Rc>, symbol: Rc>| { - let env_file = odoo.get_symbol(odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("api")], vec![]), u32::MAX); - let env_class = odoo.get_symbol(odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("api")], vec![Sy!("Environment")]), u32::MAX); + func: |odoo: &mut SessionInfo, entry: &Rc>, file_symbol: Rc>, symbol: Rc>| { + let env_file = odoo.sync_odoo.get_symbol(odoo.sync_odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("api")], vec![]), u32::MAX); + let env_class = odoo.sync_odoo.get_symbol(odoo.sync_odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("api")], vec![Sy!("Environment")]), u32::MAX); if !env_class.is_empty() { let mut env = symbol.borrow_mut(); let env_class = env_class.last().unwrap(); @@ -70,11 +74,11 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("BaseModel"), Sy!("ids")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("BaseModel"), Sy!("ids")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { let values: Vec = Vec::new(); let mut id = symbol.borrow_mut(); let range = id.range().clone(); - id.set_evaluations(vec![Evaluation::new_list(odoo, values, range)]); + id.set_evaluations(vec![Evaluation::new_list(odoo.sync_odoo, values, range)]); }}, /*PythonArchEvalFileHook {file_tree: vec![Sy!("odoo"), Sy!("models")], content_tree: vec![Sy!("BaseModel"), Sy!("search_count")], @@ -89,8 +93,8 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("api")], vec![Sy!("Environment"), Sy!("registry")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("environments")], vec![Sy!("Environment"), Sy!("registry")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - let registry_sym = odoo.get_symbol(odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("modules"), Sy!("registry")], vec![Sy!("Registry")]), u32::MAX); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + let registry_sym = odoo.sync_odoo.get_symbol(odoo.sync_odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("modules"), Sy!("registry")], vec![Sy!("Registry")]), u32::MAX); if !registry_sym.is_empty() { symbol.borrow_mut().set_evaluations(vec![Evaluation { symbol: EvaluationSymbol::new_with_symbol( @@ -109,135 +113,135 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Boolean")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_misc")], vec![Sy!("Boolean")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("bool")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("bool")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Integer")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_numeric")], vec![Sy!("Integer")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("int")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("int")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Float")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_numeric")], vec![Sy!("Float")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("float")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("float")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Monetary")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_numeric")], vec![Sy!("Monetary")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("float")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("float")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Char")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_textual")], vec![Sy!("Char")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Text")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_textual")], vec![Sy!("Text")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Html")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_textual")], vec![Sy!("Html")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("markupsafe")], vec![Sy!("Markup")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("markupsafe")], vec![Sy!("Markup")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Date")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_temporal")], vec![Sy!("Date")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("datetime")], vec![Sy!("date")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("datetime")], vec![Sy!("date")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Datetime")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_temporal")], vec![Sy!("Datetime")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("datetime")], vec![Sy!("datetime")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("datetime")], vec![Sy!("datetime")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Binary")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_binary")], vec![Sy!("Binary")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("bytes")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("bytes")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Image")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_binary")], vec![Sy!("Image")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("bytes")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("bytes")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Selection")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_selection")], vec![Sy!("Selection")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Reference")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_reference")], vec![Sy!("Reference")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("str")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Json")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_misc")], vec![Sy!("Json")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("object")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("object")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Properties")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_properties")], vec![Sy!("Properties")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("object")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("object")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("PropertiesDefinition")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_properties")], vec![Sy!("PropertiesDefinition")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - PythonArchEvalHooks::_update_get_eval(odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("object")])); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + PythonArchEvalHooks::_update_get_eval(odoo.sync_odoo, entry, symbol.clone(), (vec![Sy!("builtins")], vec![Sy!("object")])); PythonArchEvalHooks::_update_field_init(symbol.clone(), false); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Many2one")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_relational")], vec![Sy!("Many2one")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { PythonArchEvalHooks::_update_get_eval_relational(symbol.clone()); PythonArchEvalHooks::_update_field_init(symbol.clone(), true); }}, @@ -245,22 +249,22 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("One2many")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_relational")], vec![Sy!("One2many")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { PythonArchEvalHooks::_update_field_init(symbol.clone(), true); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Many2many")])), (Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_relational")], vec![Sy!("Many2many")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { PythonArchEvalHooks::_update_get_eval_relational(symbol.clone()); PythonArchEvalHooks::_update_field_init(symbol.clone(), true); }}, PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("init")], vec![Sy!("_")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - let odoo_underscore = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("_")]), u32::MAX); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + let odoo_underscore = odoo.sync_odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("_")]), u32::MAX); if let Some(eval_1) = odoo_underscore.first() { eval_1.borrow_mut().set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), Some(false))]); } @@ -268,8 +272,8 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("init")], vec![Sy!("SUPERUSER_ID")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - let odoo_superuser_id = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("SUPERUSER_ID")]), u32::MAX); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + let odoo_superuser_id = odoo.sync_odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("SUPERUSER_ID")]), u32::MAX); if let Some(eval_1) = odoo_superuser_id.first() { eval_1.borrow_mut().set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), Some(false))]); } @@ -277,8 +281,8 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("init")], vec![Sy!("_lt")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - let odoo_lt = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("_lt")]), u32::MAX); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + let odoo_lt = odoo.sync_odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("_lt")]), u32::MAX); if let Some(eval_1) = odoo_lt.first() { eval_1.borrow_mut().set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), Some(false))]); } @@ -286,12 +290,123 @@ static arch_eval_file_hooks: Lazy> = Lazy::new(|| {v PythonArchEvalFileHook {odoo_entry: true, trees: vec![(Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("init")], vec![Sy!("Command")]))], if_exist_only: true, - func: |odoo: &mut SyncOdoo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { - let odoo_command = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("Command")]), u32::MAX); + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + let odoo_command = odoo.sync_odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("Command")]), u32::MAX); if let Some(eval_1) = odoo_command.first() { eval_1.borrow_mut().set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), Some(false))]); } }}, + PythonArchEvalFileHook {odoo_entry: true, + trees: vec![(Sy!("15.0"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("addons"), Sy!("base"), Sy!("models"), Sy!("ir_rule")], vec![Sy!("IrRule"), Sy!("global")]))], + if_exist_only: true, + func: |odoo: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + let mut boolean_field = odoo.sync_odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo")], vec![Sy!("fields"), Sy!("Boolean")]), u32::MAX); + if compare_semver(odoo.sync_odoo.full_version.as_str(), "18.1") >= Ordering::Equal { + boolean_field = odoo.sync_odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("odoo"), Sy!("orm"), Sy!("fields_misc")], vec![Sy!("Boolean")]), u32::MAX); ; + } + if let Some(boolean) = boolean_field.first() { + let mut eval = Evaluation::eval_from_symbol(&Rc::downgrade(&boolean), Some(true)); + let weak = eval.symbol.get_mut_symbol_ptr().as_mut_weak(); + weak.context.insert(S!("compute"), ContextValue::STRING(S!("_compute_global"))); + symbol.borrow_mut().set_evaluations(vec![eval]); + } + }}, + PythonArchEvalFileHook {odoo_entry: true, + trees: vec![(Sy!("0.0"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("_monkeypatches"), Sy!("werkzeug")], vec![]))], + if_exist_only: true, + func: |session: &mut SessionInfo, entry: &Rc>, _file_symbol: Rc>, symbol: Rc>| { + let odoo = &mut session.sync_odoo; + let url_decode = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_decode")]), u32::MAX); + let werkzeug_url_decode = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_decode")]), u32::MAX); + if let Some(werkzeug_url_decode) = werkzeug_url_decode.first() { + if let Some(eval_1) = url_decode.first() { + werkzeug_url_decode.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_encode = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_encode")]), u32::MAX); + let werkzeug_url_encode = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_encode")]), u32::MAX); + if let Some(werkzeug_url_encode) = werkzeug_url_encode.first() { + if let Some(eval_1) = url_encode.first() { + werkzeug_url_encode.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_join = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_join")]), u32::MAX); + let werkzeug_url_join = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_join")]), u32::MAX); + if let Some(werkzeug_url_join) = werkzeug_url_join.first() { + if let Some(eval_1) = url_join.first() { + werkzeug_url_join.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_parse = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_parse")]), u32::MAX); + let werkzeug_url_parse = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_parse")]), u32::MAX); + if let Some(werkzeug_url_parse) = werkzeug_url_parse.first() { + if let Some(eval_1) = url_parse.first() { + werkzeug_url_parse.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_quote = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_quote")]), u32::MAX); + let werkzeug_url_quote = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_quote")]), u32::MAX); + if let Some(werkzeug_url_quote) = werkzeug_url_quote.first() { + if let Some(eval_1) = url_quote.first() { + werkzeug_url_quote.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_unquote = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_unquote")]), u32::MAX); + let werkzeug_url_unquote = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_unquote")]), u32::MAX); + if let Some(werkzeug_url_unquote) = werkzeug_url_unquote.first() { + if let Some(eval_1) = url_unquote.first() { + werkzeug_url_unquote.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_quote_plus = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_quote_plus")]), u32::MAX); + let werkzeug_url_quote_plus = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_quote_plus")]), u32::MAX); + if let Some(werkzeug_url_quote_plus) = werkzeug_url_quote_plus.first() { + if let Some(eval_1) = url_quote_plus.first() { + werkzeug_url_quote_plus.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_unquote_plus = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_unquote_plus")]), u32::MAX); + let werkzeug_url_unquote_plus = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_unquote_plus")]), u32::MAX); + if let Some(werkzeug_url_unquote_plus) = werkzeug_url_unquote_plus.first() { + if let Some(eval_1) = url_unquote_plus.first() { + werkzeug_url_unquote_plus.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url_unparse = symbol.borrow().get_symbol(&(vec![], vec![Sy!("url_unparse")]), u32::MAX); + let werkzeug_url_unparse = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("url_unparse")]), u32::MAX); + if let Some(werkzeug_url_unparse) = werkzeug_url_unparse.first() { + if let Some(eval_1) = url_unparse.first() { + werkzeug_url_unparse.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + let url = symbol.borrow().get_symbol(&(vec![], vec![Sy!("URL")]), u32::MAX); + let werkzeug_url = odoo.get_symbol(_file_symbol.borrow().paths()[0].as_str(), &(vec![Sy!("werkzeug"), Sy!("urls")], vec![Sy!("URL")]), u32::MAX); + if let Some(werkzeug_URL) = werkzeug_url.first() { + if let Some(eval_1) = url.first() { + werkzeug_URL.borrow_mut().set_evaluations(vec![ + Evaluation::eval_from_symbol(&Rc::downgrade(&eval_1), Some(false)) + ]); + } + } + }}, ]}); type PythonArchEvalHookFunc = fn (odoo: &mut SyncOdoo, entry_point: &Rc>, symbol: Rc>); @@ -500,12 +615,12 @@ impl PythonArchEvalHooks { } if name.eq(hook_tree.0.last().unwrap()) && ((hook.odoo_entry && session.sync_odoo.has_main_entry && odoo_tree.0 == hook_tree.0) || (!hook.odoo_entry && tree.0 == hook_tree.0)) { - if hook_tree.0.is_empty() { - (hook.func)(session.sync_odoo, entry_point, symbol.clone(), symbol.clone()); + if hook_tree.1.is_empty() { + (hook.func)(session, entry_point, symbol.clone(), symbol.clone()); } else { let sub_symbol = symbol.borrow().get_symbol(&(vec![], hook_tree.1.clone()), u32::MAX); if !sub_symbol.is_empty() { - (hook.func)(session.sync_odoo, entry_point, symbol.clone(), sub_symbol.last().unwrap().clone()); + (hook.func)(session, entry_point, symbol.clone(), sub_symbol.last().unwrap().clone()); } } } @@ -557,7 +672,7 @@ impl PythonArchEvalHooks { return diagnostics // failed to find parent }; let mut deps = vec![vec![], vec![], vec![]]; - let (dec_evals, diags) = Evaluation::eval_from_ast(session, &decorator_base, parent, &func_stmt.range.start(),&mut deps); + let (dec_evals, diags) = Evaluation::eval_from_ast(session, &decorator_base, parent, &func_stmt.range.start(), false, &mut deps); Symbol::insert_dependencies(&file, &mut deps, current_step); diagnostics.extend(diags); let mut followed_evals = vec![]; @@ -736,20 +851,20 @@ impl PythonArchEvalHooks { } fn _update_get_eval(odoo: &mut SyncOdoo, entry_point: &Rc>, symbol: Rc>, tree: Tree) { - let get_sym = symbol.borrow().get_symbol(&(vec![], vec![Sy!("__get__")]), u32::MAX); - if get_sym.is_empty() { + let get_syms = symbol.borrow().get_symbol(&(vec![], vec![Sy!("__get__")]), u32::MAX); + let Some(get_sym) = get_syms.last() else { return; - } - let return_sym = odoo.get_symbol(odoo.config.odoo_path.as_ref().unwrap(), &tree, u32::MAX); - if return_sym.is_empty() { + }; + let return_syms = odoo.get_symbol(odoo.config.odoo_path.as_ref().unwrap(), &tree, u32::MAX); + let Some(return_sym) = return_syms.last() else { let file = symbol.borrow().get_file().clone(); file.as_ref().unwrap().upgrade().unwrap().borrow_mut().not_found_paths_mut().push((BuildSteps::ARCH_EVAL, flatten_tree(&tree))); entry_point.borrow_mut().not_found_symbols.insert(symbol); return; - } - get_sym.last().unwrap().borrow_mut().set_evaluations(vec![Evaluation { + }; + get_sym.borrow_mut().set_evaluations(vec![Evaluation { symbol: EvaluationSymbol::new_with_symbol( - Rc::downgrade(return_sym.last().unwrap()), + Rc::downgrade(return_sym), Some(true), HashMap::new(), Some(PythonArchEvalHooks::eval_get) @@ -757,6 +872,18 @@ impl PythonArchEvalHooks { value: None, range: None }]); + + let tree = if compare_semver(odoo.full_version.as_str(), "18.1.0") == Ordering::Less { + (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Field"), Sy!("__get__")]) + } else { + (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields")], vec![Sy!("Field"), Sy!("__get__")]) + }; + let Some(field_get) = odoo.get_symbol(odoo.config.odoo_path.as_ref().unwrap(), &tree, u32::MAX).first().cloned() + else { + return; + }; + let field_get_borrowed = field_get.borrow(); + get_sym.borrow_mut().as_func_mut().args = field_get_borrowed.as_func().args.clone(); } fn eval_relational_with_related(session: &mut SessionInfo, related_field: &ContextValue, context: &Context) -> Option{ let Some(ContextValue::SYMBOL(class_sym_weak)) = context.get(&S!("field_parent")) else {return None}; @@ -866,6 +993,9 @@ impl PythonArchEvalHooks { "comodel_name", "related", "compute", + "delegate", + "required", + "default", ]; contexts_to_add.extend( context_arguments.into_iter() @@ -876,10 +1006,18 @@ impl PythonArchEvalHooks { ); for (arg_name, (field_name_expr, arg_range)) in contexts_to_add { - let maybe_related_string = Evaluation::expr_to_str(session, field_name_expr, parent.clone(), ¶meters.range.start(), &mut vec![]).0; + let maybe_related_string = Evaluation::expr_to_str(session, field_name_expr, parent.clone(), ¶meters.range.start(), false, &mut vec![]).0; if let Some(related_string) = maybe_related_string { context.insert(S!(arg_name), ContextValue::STRING(related_string.to_string())); context.insert(format!("{arg_name}_arg_range"), ContextValue::RANGE(arg_range.clone())); + } else { + let maybe_boolean = Evaluation::expr_to_bool(session, field_name_expr, parent.clone(), ¶meters.range.start(), false, &mut vec![]).0; + if let Some(boolean) = maybe_boolean { + context.insert(S!(arg_name), ContextValue::BOOLEAN(boolean)); + } + if arg_name == "default" { + context.insert(S!("default"), ContextValue::BOOLEAN(true)); //set to True as the value is not really useful for now, but we want the key in context if one default is set + } } } @@ -1061,7 +1199,7 @@ impl PythonArchEvalHooks { return None; }; let module_rc_bw = module_rc.borrow(); - let Some(symbol) = module_rc_bw.as_module_package().xml_ids.get(xml_id.as_str()) else { + let Some(symbol) = module_rc_bw.as_module_package().xml_id_locations.get(xml_id.as_str()) else { if in_validation { /*diagnostics.push(Diagnostic::new( FileMgr::textRange_to_temporary_Range(&xml_id_expr.range()), diff --git a/server/src/core/python_odoo_builder.rs b/server/src/core/python_odoo_builder.rs index abb0bd4d..0b1ef825 100644 --- a/server/src/core/python_odoo_builder.rs +++ b/server/src/core/python_odoo_builder.rs @@ -1,5 +1,5 @@ use std::cmp::Ordering; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::cell::RefCell; use ruff_python_ast::Expr; @@ -13,6 +13,7 @@ use crate::core::file_mgr::{FileMgr}; use crate::constants::{OYarn, SymType}; use crate::core::model::{Model, ModelData}; use crate::core::symbols::symbol::Symbol; +use crate::core::xml_data::{XmlData, XmlDataRecord}; use crate::threads::SessionInfo; use crate::utils::compare_semver; use crate::{oyarn, Sy, S}; @@ -49,6 +50,28 @@ impl PythonOdooBuilder { self._load_class_attributes(session, &mut diagnostics); self._add_magic_fields(session); let model_name = sym.borrow().as_class_sym()._model.as_ref().unwrap().name.clone(); + if let Some(module) = sym.borrow().find_module() { + let file = self.symbol.borrow().get_file().unwrap().upgrade().unwrap(); + let xml_id_model_name = oyarn!("model_{}", model_name.replace(".", "_").as_str()); + let mut module = module.borrow_mut(); + let set = module.as_module_package_mut().xml_id_locations.entry(xml_id_model_name.clone()).or_insert(PtrWeakHashSet::new()); + set.insert(file.clone()); + drop(module); //in case of file being same than module + let mut file = file.borrow_mut(); + file.insert_xml_id(xml_id_model_name.clone(), XmlData::RECORD(XmlDataRecord { + file_symbol: Rc::downgrade(&sym), + model: (Sy!("ir.model"), std::ops::Range:: { + start: 0, + end: 1, + }), + xml_id: Some(xml_id_model_name), + fields: vec![], + range: std::ops::Range:: { + start: self.symbol.borrow().range().start().to_usize(), + end: self.symbol.borrow().range().end().to_usize(), + } + })); + } match session.sync_odoo.models.get(&model_name).cloned(){ Some(model) => { let inherited_model_names = sym.borrow().as_class_sym()._model.as_ref().unwrap().inherit.clone(); @@ -80,13 +103,6 @@ impl PythonOdooBuilder { }, None => { let model = Model::new(model_name.clone(), sym.clone()); - session.sync_odoo.modules.get("base").map(|module| { - let xml_id_model_name = oyarn!("model_{}", model_name.replace(".", "_").as_str()); - let module = module.upgrade().unwrap(); - let mut module = module.borrow_mut(); - let set = module.as_module_package_mut().xml_ids.entry(xml_id_model_name).or_insert(PtrWeakHashSet::new()); - set.insert(sym.clone()); - }); session.sync_odoo.models.insert(model_name.clone(), Rc::new(RefCell::new(model))); } } @@ -178,6 +194,32 @@ impl PythonOdooBuilder { } } } + drop(_inherits); + drop(symbol); + //Add inherits from delegate=True from fields + let all_fields = Symbol::all_members(&self.symbol, session, false, true, false, None, false); + for (field_name, symbols) in all_fields.iter() { + for (symbol, _deps) in symbols.iter() { + if let Some(evals) = symbol.borrow().evaluations() { + for eval in evals.iter() { + let symbol_weak = eval.symbol.get_symbol_as_weak(session, &mut None, diagnostics, self.symbol.borrow().get_file().unwrap().upgrade()); + if let Some(eval_symbol) = symbol_weak.weak.upgrade() { + if eval_symbol.borrow().name() == &Sy!("Many2one") { + let context = &symbol_weak.context; + if let Some(delegate) = context.get("delegate") { + if delegate.as_bool() == true { + if let Some(comodel) = context.get("comodel_name") { + let comodel_name = oyarn!("{}", comodel.as_string()); + self.symbol.borrow_mut().as_class_sym_mut()._model.as_mut().unwrap().inherits.push((comodel_name, field_name.clone())); + } + } + } + } + } + } + } + } + } } fn _get_attribute(session: &mut SessionInfo, loc_sym: &mut Symbol, attr: &String, diagnostics: &mut Vec) -> Option { @@ -188,7 +230,9 @@ impl PythonOdooBuilder { let attr_sym = &attr_sym[0]; for eval in attr_sym.borrow().evaluations().unwrap().iter() { let eval = eval.follow_ref_and_get_value(session, &mut None, diagnostics); - return eval; + if eval.is_some() { + return eval; + } } None } diff --git a/server/src/core/python_validator.rs b/server/src/core/python_validator.rs index fe21322a..3d3463bc 100644 --- a/server/src/core/python_validator.rs +++ b/server/src/core/python_validator.rs @@ -5,8 +5,9 @@ use tracing::{trace, warn}; use std::rc::Rc; use std::cell::RefCell; use std::path::PathBuf; -use lsp_types::{Diagnostic, Position, Range}; +use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Position, Range}; use crate::core::diagnostics::{create_diagnostic, DiagnosticCode}; +use crate::core::evaluation::ContextValue; use crate::{constants::*, oyarn, Sy}; use crate::core::symbols::symbol::Symbol; use crate::core::odoo::SyncOdoo; @@ -77,7 +78,7 @@ impl PythonValidator { if self.sym_stack[0].borrow().build_status(BuildSteps::ARCH_EVAL) != BuildStatus::DONE { return; } - if DEBUG_STEPS { + if DEBUG_STEPS && (!DEBUG_STEPS_ONLY_INTERNAL || !self.sym_stack[0].borrow().is_external()) { trace!("Validating {}", self.sym_stack[0].borrow().paths().first().unwrap_or(&S!("No path found"))); } self.sym_stack[0].borrow_mut().set_build_status(BuildSteps::VALIDATION, BuildStatus::IN_PROGRESS); @@ -102,7 +103,7 @@ impl PythonValidator { file_info.replace_diagnostics(BuildSteps::VALIDATION, self.diagnostics.clone()); }, SymType::FUNCTION => { - if DEBUG_STEPS { + if DEBUG_STEPS && (!DEBUG_STEPS_ONLY_INTERNAL || !self.sym_stack[0].borrow().is_external()) { trace!("Validating function {}", self.sym_stack[0].borrow().name()); } self.file_mode = false; @@ -389,7 +390,7 @@ impl PythonValidator { if !symbol.borrow().is_field_class(session){ continue; } - if let Some(related_field_name) = eval_weak.as_weak().context.get(&S!("related")).map(|ctx_val| ctx_val.as_string()) { + if let Some(related_field_name) = eval_weak.as_weak().context.get(&S!("related")).filter(|val| matches!(val, ContextValue::STRING(s))).map(|ctx_val| ctx_val.as_string()) { let Some(special_arg_range) = eval_weak.as_weak().context.get(&S!("related_arg_range")).map(|ctx_val| ctx_val.as_text_range()) else { continue; }; @@ -593,7 +594,7 @@ impl PythonValidator { fn validate_expr(&mut self, session: &mut SessionInfo, expr: &Expr, max_infer: &TextSize) { let mut deps = vec![vec![], vec![], vec![]]; - let (eval, diags) = Evaluation::eval_from_ast(session, expr, self.sym_stack.last().unwrap().clone(), max_infer, &mut deps); + let (eval, diags) = Evaluation::eval_from_ast(session, expr, self.sym_stack.last().unwrap().clone(), max_infer, false, &mut deps); Symbol::insert_dependencies(&self.file, &mut deps, BuildSteps::VALIDATION); self.diagnostics.extend(diags); } diff --git a/server/src/core/symbols/class_symbol.rs b/server/src/core/symbols/class_symbol.rs index a0ebcaf3..4f909bc5 100644 --- a/server/src/core/symbols/class_symbol.rs +++ b/server/src/core/symbols/class_symbol.rs @@ -28,6 +28,7 @@ pub struct ClassSymbol { pub body_range: TextRange, pub _model: Option, pub noqas: NoqaInfo, + pub(crate) _is_field_class: Rc>>, //cache, do not call directly, use is_field_class() method instead //Trait SymbolMgr //--- Body symbols @@ -57,6 +58,7 @@ impl ClassSymbol { bases: vec![], _model: None, noqas: NoqaInfo::None, + _is_field_class: Rc::new(RefCell::new(None)), }; res._init_symbol_mgr(); res diff --git a/server/src/core/symbols/file_symbol.rs b/server/src/core/symbols/file_symbol.rs index 7b72a8bf..0b5d2b41 100644 --- a/server/src/core/symbols/file_symbol.rs +++ b/server/src/core/symbols/file_symbol.rs @@ -1,6 +1,6 @@ use weak_table::{PtrWeakHashSet, PtrWeakKeyHashMap}; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model}, oyarn}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model, xml_data::XmlData}, oyarn}; use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}}; use super::{symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; @@ -17,6 +17,7 @@ pub struct FileSymbol { pub odoo_status: BuildStatus, pub validation_status: BuildStatus, pub not_found_paths: Vec<(BuildSteps, Vec)>, + pub xml_ids: HashMap>, //used for dynamic XML_ID records, like ir.models in_workspace: bool, pub self_import: bool, pub model_dependencies: PtrWeakHashSet>>, //always on validation level, as odoo step is always required @@ -47,6 +48,7 @@ impl FileSymbol { odoo_status: BuildStatus::PENDING, validation_status: BuildStatus::PENDING, not_found_paths: vec![], + xml_ids: HashMap::new(), in_workspace: false, self_import: false, sections: vec![], diff --git a/server/src/core/symbols/function_symbol.rs b/server/src/core/symbols/function_symbol.rs index 8fcd8be3..056d0161 100644 --- a/server/src/core/symbols/function_symbol.rs +++ b/server/src/core/symbols/function_symbol.rs @@ -9,7 +9,7 @@ use crate::{constants::{BuildStatus, BuildSteps, OYarn, SymType}, core::{evaluat use super::{symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum ArgumentType { POS_ONLY, ARG, @@ -18,7 +18,7 @@ pub enum ArgumentType { KWORD_ONLY, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Argument { pub symbol: Weak>, //always a weak to a symbol of the function //other informations about arg @@ -90,6 +90,9 @@ impl FunctionSymbol { is_class_method: false, noqas: NoqaInfo::None, }; + if name == "__new__" { + res.is_static = true; + } res._init_symbol_mgr(); res } @@ -160,7 +163,7 @@ impl FunctionSymbol { } /* Given a call of this function and an index, return the corresponding parameter definition */ - pub fn get_indexed_arg_in_call(&self, call: &ExprCall, index: u32, is_on_instance: bool) -> Option<&Argument> { + pub fn get_indexed_arg_in_call(&self, call: &ExprCall, index: u32, is_on_instance: Option) -> Option<&Argument> { if self.is_overloaded() { return None; } @@ -169,7 +172,7 @@ impl FunctionSymbol { call_arg_keyword = call.arguments.keywords.get((index - call.arguments.args.len() as u32) as usize); } let mut arg_index = 0; - if is_on_instance { + if is_on_instance.is_some() { arg_index += 1; } if let Some(keyword) = call_arg_keyword { diff --git a/server/src/core/symbols/module_symbol.rs b/server/src/core/symbols/module_symbol.rs index 343afc6e..f9f7a203 100644 --- a/server/src/core/symbols/module_symbol.rs +++ b/server/src/core/symbols/module_symbol.rs @@ -8,6 +8,7 @@ use std::collections::{HashMap, HashSet}; use crate::core::csv_arch_builder::CsvArchBuilder; use crate::core::diagnostics::{create_diagnostic, DiagnosticCode}; use crate::core::xml_arch_builder::XmlArchBuilder; +use crate::core::xml_data::XmlData; use crate::{constants::*, oyarn, Sy}; use crate::core::file_mgr::{FileInfo, FileMgr, NoqaInfo}; use crate::core::import_resolver::find_module; @@ -40,7 +41,8 @@ pub struct ModuleSymbol { all_depends: HashSet, //computed all depends to avoid too many recomputations data: Vec<(String, TextRange)>, // TODO pub module_symbols: HashMap>>, - pub xml_ids: HashMap>>>, + pub xml_id_locations: HashMap>>>, //contains all xml_file_symbols that contains the xml_id. Needed because it can be in another module. + pub xml_ids: HashMap>, //used for dynamic XML_ID records, like ir.models. normal ids are in their XmlFile pub arch_status: BuildStatus, pub arch_eval_status: BuildStatus, pub odoo_status: BuildStatus, @@ -79,6 +81,7 @@ impl ModuleSymbol { root_path: dir_path.sanitize(), loaded: false, module_name: OYarn::from(""), + xml_id_locations: HashMap::new(), xml_ids: HashMap::new(), dir_name: OYarn::from(""), depends: vec!((OYarn::from("base"), TextRange::default())), @@ -374,7 +377,6 @@ impl ModuleSymbol { let root = document.root_element(); let mut xml_builder = XmlArchBuilder::new(xml_sym); xml_builder.load_arch(session, &mut file_info, &root); - file_info.publish_diagnostics(session); //TODO do it only if diagnostics are not empty, else in validation } else if data.len() > 0 { let mut diagnostics = vec![]; XmlFileSymbol::build_syntax_diagnostics(&session, &mut diagnostics, &mut file_info, &document.unwrap_err()); @@ -513,4 +515,31 @@ impl ModuleSymbol { result } + pub fn this_and_dependencies(&self, session: &mut SessionInfo) -> PtrWeakHashSet>> { + let mut result = PtrWeakHashSet::new(); + result.insert(self.weak_self.as_ref().unwrap().upgrade().unwrap()); + for dep in self.depends.iter() { + if let Some(module) = session.sync_odoo.modules.get(&dep.0) { + if let Some(module) = module.upgrade() { + result.insert(module); + } + } + } + result + } + + //given an xml_id without "module." part, return all XmlData that declare it ("this_module.xml_id"), regardless of the module declaring it. + //For example, stock could create an xml_id called "account.my_xml_id", and so be returned by this function called on "account" module with xml_id "my_xml_id" + pub fn get_xml_id(&self, xml_id: &OYarn) -> Vec { + let mut res = vec![]; + if let Some(xml_file_set) = self.xml_id_locations.get(xml_id) { + for xml_file in xml_file_set.iter() { + if let Some(xml_data) = xml_file.borrow().get_xml_id(xml_id) { + res.extend(xml_data.iter().cloned()); + } + } + } + res + } + } diff --git a/server/src/core/symbols/package_symbol.rs b/server/src/core/symbols/package_symbol.rs index 1422840a..6d5fbaba 100644 --- a/server/src/core/symbols/package_symbol.rs +++ b/server/src/core/symbols/package_symbol.rs @@ -1,6 +1,6 @@ use weak_table::{PtrWeakHashSet, PtrWeakKeyHashMap}; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model}, oyarn, threads::SessionInfo, S}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model, xml_data::XmlData}, oyarn, threads::SessionInfo, S}; use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::{Rc, Weak}}; use super::{module_symbol::ModuleSymbol, symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; @@ -111,6 +111,7 @@ pub struct PythonPackageSymbol { pub not_found_paths: Vec<(BuildSteps, Vec)>, pub in_workspace: bool, pub self_import: bool, + pub xml_ids: HashMap>, //used for dynamic XML_ID records, like ir.models pub module_symbols: HashMap>>, pub model_dependencies: PtrWeakHashSet>>, //always on validation level, as odoo step is always required pub dependencies: Vec>>>>>, @@ -142,6 +143,7 @@ impl PythonPackageSymbol { validation_status: BuildStatus::PENDING, not_found_paths: vec![], in_workspace: false, + xml_ids: HashMap::new(), self_import: false, //indicates that if unloaded, the symbol should be added in the rebuild automatically as nothing depends on it (used for root packages) module_symbols: HashMap::new(), sections: vec![], diff --git a/server/src/core/symbols/symbol.rs b/server/src/core/symbols/symbol.rs index 13fe6d6d..041748f6 100644 --- a/server/src/core/symbols/symbol.rs +++ b/server/src/core/symbols/symbol.rs @@ -5,6 +5,7 @@ use weak_table::traits::WeakElement; use crate::core::diagnostics::{create_diagnostic, DiagnosticCode}; use crate::core::file_mgr::NoqaInfo; +use crate::core::xml_data::XmlData; use crate::{constants::*, oyarn, Sy}; use crate::core::entry_point::EntryPoint; use crate::core::evaluation::{Context, ContextValue, Evaluation, EvaluationSymbolPtr, EvaluationSymbolWeak}; @@ -548,6 +549,20 @@ impl Symbol { } } + pub fn as_xml_file_sym(&self) -> &XmlFileSymbol { + match self { + Symbol::XmlFileSymbol(x) => x, + _ => {panic!("Not an XML file symbol")} + } + } + + pub fn as_xml_file_sym_mut(&mut self) -> &mut XmlFileSymbol { + match self { + Symbol::XmlFileSymbol(x) => x, + _ => {panic!("Not an XML file symbol")} + } + } + pub fn as_symbol_mgr(&self) -> &dyn SymbolMgr { match self { Symbol::File(f) => f, @@ -1074,7 +1089,7 @@ impl Symbol { match step { BuildSteps::SYNTAX => panic!(), BuildSteps::ARCH => x.arch_status = status, - BuildSteps::ARCH_EVAL => panic!(), + BuildSteps::ARCH_EVAL => {}, BuildSteps::VALIDATION => x.validation_status = status, } }, @@ -1330,6 +1345,17 @@ impl Symbol { res } + /** + * Return the tree without the entrypoint tree. + */ + pub fn get_local_tree(&self) -> Tree { + let (mut tree, entry) = self.get_tree_and_entry(); + if let Some(entry) = entry { + tree.0.drain(0..entry.borrow().tree.len()); + } + tree + } + pub fn get_symbol(&self, tree: &Tree, position: u32) -> Vec>> { let symbol_tree_files: &Vec = &tree.0; let symbol_tree_content: &Vec = &tree.1; @@ -1571,8 +1597,8 @@ impl Symbol { } } - //Add a symbol as dependency on the step of the other symbol for the build level. - //-> The build of the 'step' of self requires the build of 'dep_level' of the other symbol to be done + /**Add a symbol as dependency on the step of the other symbol for the build level. + * -> The build of the 'step' of self requires the build of 'dep_level' of the other symbol to be done */ pub fn add_dependency(&mut self, symbol: &mut Symbol, step:BuildSteps, dep_level:BuildSteps) { if step == BuildSteps::SYNTAX || dep_level == BuildSteps::SYNTAX { panic!("Can't add dependency for syntax step") @@ -1651,9 +1677,9 @@ impl Symbol { if let Some(hashset) = hashset { for sym in hashset { if !Symbol::is_symbol_in_parents(&sym, &ref_to_inv) { - if index == BuildSteps::ARCH_EVAL as usize { + if index + 1 == BuildSteps::ARCH_EVAL as usize { session.sync_odoo.add_to_rebuild_arch_eval(sym.clone()); - } else if index == BuildSteps::VALIDATION as usize { + } else if index + 1 == BuildSteps::VALIDATION as usize { sym.borrow_mut().invalidate_sub_functions(session); session.sync_odoo.add_to_validations(sym.clone()); } @@ -2029,7 +2055,14 @@ impl Symbol { let mut res = VecDeque::new(); let symbol = &*symbol_rc.borrow(); if !stop_on_type { - if let Some(base_attr) = symbol_context.get(&S!("base_attr")) { + let mut base_attr = symbol_context.get(&S!("base_attr")); + if base_attr.is_none() { + //search in context (used in decorators to indicate on which base the field is searched) + if let Some(context) = context.as_ref() { + base_attr = context.get(&S!("base_attr")); + } + } + if let Some(base_attr) = base_attr { let base_attr = base_attr.as_symbol().upgrade(); if let Some(base_attr) = base_attr { let attribute_type_sym = symbol; @@ -2078,7 +2111,14 @@ impl Symbol { match sym { EvaluationSymbolPtr::WEAK(ref mut w) => { if let Some(base_attr) = symbol_context.get(&S!("base_attr")) { - w.context.insert(S!("base_attr"), base_attr.clone()); + if !w.context.get(&S!("is_attr_of_instance")).map(|x| x.as_bool()).unwrap_or(false) { + w.context.insert(S!("base_attr"), base_attr.clone()); + } + } + if let Some(base_attr) = symbol_context.get(&S!("is_attr_of_instance")) { + if !w.context.get(&S!("is_attr_of_instance")).map(|x| x.as_bool()).unwrap_or(false) { + w.context.insert(S!("is_attr_of_instance"), base_attr.clone()); + } } }, _ => {} @@ -2115,6 +2155,15 @@ impl Symbol { if results.is_empty() { return vec![evaluation.clone()]; } + if w.instance.is_some_and(|v| v) { + //if the previous evaluation was set to True, we want to keep it + results = results.into_iter().map(|mut r| { + if let EvaluationSymbolPtr::WEAK(ref mut weak) = r { + weak.instance = Some(true); + } + r + }).collect(); + } let mut acc: PtrWeakHashSet>> = PtrWeakHashSet::new(); let can_eval_external = !symbol.borrow().is_external(); let mut index = 0; @@ -2124,6 +2173,7 @@ impl Symbol { match next_ref { EvaluationSymbolPtr::WEAK(next_ref_weak) => { let sym = next_ref_weak.weak.upgrade(); + let next_ref_weak_instance = next_ref_weak.instance.clone(); if sym.is_none() { index += 1; continue; @@ -2160,10 +2210,17 @@ impl Symbol { } } } - let next_sym_refs = Symbol::next_refs(session, sym_rc.clone(), context, &next_ref_weak.context, stop_on_type, &mut vec![]); + let mut next_sym_refs = Symbol::next_refs(session, sym_rc.clone(), context, &next_ref_weak.context, stop_on_type, &mut vec![]); if !next_sym_refs.is_empty() { results.pop_front(); index -= 1; + // /!\ we want to keep instance = True if previous evaluation was set to True! + if next_ref_weak_instance.is_some_and(|v| v) { + next_sym_refs = next_sym_refs.into_iter().map(|mut next_results| { + next_results.as_mut_weak().instance = Some(true); + next_results + }).collect(); + } for next_results in next_sym_refs { results.push_back(next_results); } @@ -2200,22 +2257,22 @@ impl Symbol { let mut iter: Vec>> = Vec::new(); match self { Symbol::File(_) => { - for symbol in self.iter_symbols().flat_map(|(name, hashmap)| hashmap.into_iter().flat_map(|(_, vec)| vec.clone())) { + for symbol in self.iter_symbols().flat_map(|(_, hashmap)| hashmap.into_iter().flat_map(|(_, vec)| vec.clone())) { iter.push(symbol.clone()); } }, Symbol::Class(_) => { - for symbol in self.iter_symbols().flat_map(|(name, hashmap)| hashmap.into_iter().flat_map(|(_, vec)| vec.clone())) { + for symbol in self.iter_symbols().flat_map(|(_, hashmap)| hashmap.into_iter().flat_map(|(_, vec)| vec.clone())) { iter.push(symbol.clone()); } }, Symbol::Function(_) => { - for symbol in self.iter_symbols().flat_map(|(name, hashmap)| hashmap.iter().flat_map(|(_, vec)| vec.clone())) { + for symbol in self.iter_symbols().flat_map(|(_, hashmap)| hashmap.iter().flat_map(|(_, vec)| vec.clone())) { iter.push(symbol.clone()); } }, Symbol::Package(PackageSymbol::Module(m)) => { - for symbol in self.iter_symbols().flat_map(|(name, hashmap)| hashmap.iter().flat_map(|(_, vec)| vec.clone())) { + for symbol in self.iter_symbols().flat_map(|(_, hashmap)| hashmap.iter().flat_map(|(_, vec)| vec.clone())) { iter.push(symbol.clone()); } for symbol in m.module_symbols.values().cloned() { @@ -2223,7 +2280,7 @@ impl Symbol { } }, Symbol::Package(PackageSymbol::PythonPackage(p)) => { - for symbol in self.iter_symbols().flat_map(|(name, hashmap)| hashmap.iter().flat_map(|(_, vec)| vec.clone())) { + for symbol in self.iter_symbols().flat_map(|(_, hashmap)| hashmap.iter().flat_map(|(_, vec)| vec.clone())) { iter.push(symbol.clone()); } for symbol in p.module_symbols.values().cloned() { @@ -2245,86 +2302,104 @@ impl Symbol { iter.into_iter() } - //store in result all available members for self: sub symbols, base class elements and models symbols + //store in result all available members for symbol: sub symbols, base class elements and models symbols //TODO is order right of Vec in HashMap? if we take first or last in it, do we have the last effective value? - pub fn all_members(symbol: &Rc>, session: &mut SessionInfo, result: &mut HashMap>, Option)>>, with_co_models: bool, only_fields: bool, only_methods: bool, from_module: Option>>, acc: &mut Option>, is_super: bool) { - if acc.is_none() { - *acc = Some(HashSet::new()); - } + pub fn all_members( + symbol: &Rc>, + session: &mut SessionInfo, + with_co_models: bool, + only_fields: bool, + only_methods: bool, + from_module: Option>>, + is_super: bool) -> HashMap>, Option)>>{ + let mut result: HashMap>, Option)>> = HashMap::new(); + let mut acc: HashSet = HashSet::new(); + Symbol::_all_members(symbol, session, &mut result, with_co_models, only_fields, only_methods, from_module, &mut acc, is_super); + return result; + } + fn _all_members(symbol: &Rc>, session: &mut SessionInfo, result: &mut HashMap>, Option)>>, with_co_models: bool, only_fields: bool, only_methods: bool, from_module: Option>>, acc: &mut HashSet, is_super: bool) { let tree = symbol.borrow().get_tree(); - if acc.as_mut().unwrap().contains(&tree) { + if acc.contains(&tree) { return; } - acc.as_mut().unwrap().insert(tree); + acc.insert(tree); + let mut append_result = |symbol: Rc>, dep: Option| { + let name = symbol.borrow().name().clone(); + if let Some(vec) = result.get_mut(&name) { + vec.push((symbol, dep)); + } else { + result.insert(name.clone(), vec![(symbol, dep)]); + } + }; let typ = symbol.borrow().typ(); match typ { SymType::CLASS => { // Skip current class symbols for super if !is_super{ for symbol in symbol.borrow().all_symbols() { - if only_fields && !symbol.borrow().is_field(session){ + if (only_fields && !symbol.borrow().is_field(session)) || (only_methods && symbol.borrow().typ() != SymType::FUNCTION) { continue; } - if only_methods && symbol.borrow().typ() != SymType::FUNCTION{ - continue; - } - let name = symbol.borrow().name().clone(); - if let Some(vec) = result.get_mut(&name) { - vec.push((symbol, None)); - } else { - result.insert(name.clone(), vec![(symbol, None)]); - } + append_result(symbol, None); } } + let mut bases: PtrWeakHashSet>> = PtrWeakHashSet::new(); + symbol.borrow().as_class_sym().bases.iter().for_each(|base| { + base.upgrade().map(|b| bases.insert(b)); + }); if with_co_models { - let sym = symbol.borrow(); - let model_data = sym.as_class_sym()._model.as_ref(); - if let Some(model_data) = model_data { - if let Some(model) = session.sync_odoo.models.get(&model_data.name).cloned() { - for (model_sym, dependency) in model.borrow().all_symbols(session, from_module.clone()) { - if dependency.is_none() && !Rc::ptr_eq(symbol, &model_sym) { - for s in model_sym.borrow().all_symbols() { - if only_fields && !s.borrow().is_field(session){ - continue; - } - if only_methods && symbol.borrow().typ() != SymType::FUNCTION{ - continue; - } - let name = s.borrow().name().clone(); - if let Some(vec) = result.get_mut(&name) { - vec.push((s, Some(model_sym.borrow().name().clone()))); - } else { - result.insert(name.clone(), vec![(s, Some(model_sym.borrow().name().clone()))]); - } - } - } + let Some(model) = symbol.borrow().as_class_sym()._model.as_ref().and_then(|model_data| + session.sync_odoo.models.get(&model_data.name).cloned() + ) else { + return; + }; + // no recursion because it is handled in all_symbols_inherits + let (model_symbols, model_inherits_symbols) = model.borrow().all_symbols_inherits(session, from_module.clone()); + for (model_sym, dependency) in model_symbols { + if dependency.is_some() || Rc::ptr_eq(symbol, &model_sym) { + continue; + } + model_sym.borrow().as_class_sym().bases.iter().for_each(|base| { + base.upgrade().map(|b| bases.insert(b)); + }); + for s in model_sym.borrow().all_symbols() { + if (only_fields && !s.borrow().is_field(session)) || (only_methods && s.borrow().typ() != SymType::FUNCTION) { + continue; } + append_result(s, Some(model_sym.borrow().name().clone())); + } + } + for (model_sym, dependency) in model_inherits_symbols { + if dependency.is_some() || Rc::ptr_eq(symbol, &model_sym) { + continue; + } + model_sym.borrow().as_class_sym().bases.iter().for_each(|base| { + base.upgrade().map(|b| bases.insert(b)); + }); + // for inherits symbols, we only add fields + for s in model_sym.borrow().all_symbols().filter(|s| s.borrow().is_field(session)) { + append_result(s, Some(model_sym.borrow().name().clone())); } } } let bases = symbol.borrow().as_class_sym().bases.clone(); for base in bases.iter() { - //no comodel as we will process only model in base class (overrided _name?) + //no comodel as we will search for co-model from original class (what about overrided _name?) + //TODO what about base of co-models classes? if let Some(base) = base.upgrade() { - Symbol::all_members(&base, session, result, false, only_fields, only_methods, from_module.clone(), acc, false); + Symbol::_all_members(&base, session, result, false, only_fields, only_methods, from_module.clone(), acc, false); } } }, - _ => { - for symbol in symbol.borrow().all_symbols() { - if only_fields && !symbol.borrow().is_field(session){ - continue; - } - let name = symbol.borrow().name().clone(); - if let Some(vec) = result.get_mut(&name) { - vec.push((symbol, None)); - } else { - result.insert(name.clone(), vec![(symbol, None)]); - } - } - } + // if not class just add it to result + _ => symbol.borrow().all_symbols().for_each(|s| + if !(only_fields && !s.borrow().is_field(session)) {append_result(s, None)} + ) } } + + + /* return the Symbol (class, function or file) the closest to the given offset */ pub fn get_scope_symbol(file_symbol: Rc>, offset: u32, is_param: bool) -> Rc> { let mut result = file_symbol.clone(); @@ -2510,44 +2585,53 @@ impl Symbol { if !matches!(self.typ(), SymType::CLASS) { return false; } - let tree = flatten_tree(&self.get_main_entry_tree(session)); - if compare_semver(session.sync_odoo.full_version.as_str(), "18.1.0") >= Ordering::Equal { - if tree.len() == 4 && tree[0] == "odoo" && tree[1] == "orm" && ( - tree[2] == "fields_misc" && tree[3] == "Boolean" || - tree[2] == "fields_numeric" && tree[3] == "Integer" || - tree[2] == "fields_numeric" && tree[3] == "Float" || - tree[2] == "fields_numeric" && tree[3] == "Monetary" || - tree[2] == "fields_textual" && tree[3] == "Char" || - tree[2] == "fields_textual" && tree[3] == "Text" || - tree[2] == "fields_textual" && tree[3] == "Html" || - tree[2] == "fields_temporal" && tree[3] == "Date" || - tree[2] == "fields_temporal" && tree[3] == "Datetime" || - tree[2] == "fields_binary" && tree[3] == "Binary" || - tree[2] == "fields_binary" && tree[3] == "Image" || - tree[2] == "fields_selection" && tree[3] == "Selection" || - tree[2] == "fields_reference" && tree[3] == "Reference" || - tree[2] == "fields_relational" && tree[3] == "Many2one" || - tree[2] == "fields_reference" && tree[3] == "Many2oneReference" || - tree[2] == "fields_misc" && tree[3] == "Json" || - tree[2] == "fields_properties" && tree[3] == "Properties" || - tree[2] == "fields_properties" && tree[3] == "PropertiesDefinition" || - tree[2] == "fields_relational" && tree[3] == "One2many" || - tree[2] == "fields_relational" && tree[3] == "Many2many" || - tree[2] == "fields_misc" && tree[3] == "Id" - ){ - return true; - } + let mut cache = self.as_class_sym()._is_field_class.borrow_mut(); + if let Some(is_field_class) = cache.as_ref() { + return *is_field_class; } else { - if tree.len() == 3 && tree[0] == "odoo" && tree[1] == "fields" { - if matches!(tree[2].as_str(), "Boolean" | "Integer" | "Float" | "Monetary" | "Char" | "Text" | "Html" | "Date" | "Datetime" | - "Binary" | "Image" | "Selection" | "Reference" | "Json" | "Properties" | "PropertiesDefinition" | "Id" | "Many2one" | "One2many" | "Many2many" | "Many2oneReference") { + let tree = &self.get_main_entry_tree(session); + if compare_semver(session.sync_odoo.full_version.as_str(), "18.1.0") >= Ordering::Equal { + if tree.0.len() == 3 && tree.1.len() == 1 && tree.0[0] == "odoo" && tree.0[1] == "orm" && ( + tree.0[2] == "fields_misc" && tree.1[0] == "Boolean" || + tree.0[2] == "fields_numeric" && tree.1[0] == "Integer" || + tree.0[2] == "fields_numeric" && tree.1[0] == "Float" || + tree.0[2] == "fields_numeric" && tree.1[0] == "Monetary" || + tree.0[2] == "fields_textual" && tree.1[0] == "Char" || + tree.0[2] == "fields_textual" && tree.1[0] == "Text" || + tree.0[2] == "fields_textual" && tree.1[0] == "Html" || + tree.0[2] == "fields_temporal" && tree.1[0] == "Date" || + tree.0[2] == "fields_temporal" && tree.1[0] == "Datetime" || + tree.0[2] == "fields_binary" && tree.1[0] == "Binary" || + tree.0[2] == "fields_binary" && tree.1[0] == "Image" || + tree.0[2] == "fields_selection" && tree.1[0] == "Selection" || + tree.0[2] == "fields_reference" && tree.1[0] == "Reference" || + tree.0[2] == "fields_relational" && tree.1[0] == "Many2one" || + tree.0[2] == "fields_reference" && tree.1[0] == "Many2oneReference" || + tree.0[2] == "fields_misc" && tree.1[0] == "Json" || + tree.0[2] == "fields_properties" && tree.1[0] == "Properties" || + tree.0[2] == "fields_properties" && tree.1[0] == "PropertiesDefinition" || + tree.0[2] == "fields_relational" && tree.1[0] == "One2many" || + tree.0[2] == "fields_relational" && tree.1[0] == "Many2many" || + tree.0[2] == "fields_misc" && tree.1[0] == "Id" + ){ + cache.replace(true); return true; } + } else { + if tree.0.len() == 2 && tree.1.len() == 1 && tree.0[0] == "odoo" && tree.0[1] == "fields" { + if matches!(tree.1[0].as_str(), "Boolean" | "Integer" | "Float" | "Monetary" | "Char" | "Text" | "Html" | "Date" | "Datetime" | + "Binary" | "Image" | "Selection" | "Reference" | "Json" | "Properties" | "PropertiesDefinition" | "Id" | "Many2one" | "One2many" | "Many2many" | "Many2oneReference") { + cache.replace(true); + return true; + } + } + } + if self.is_inheriting_from_field(session) { + cache.replace(true); + return true; } } - if self.is_inheriting_from_field(session) { - return true; - } + cache.replace(false); false } @@ -2596,6 +2680,10 @@ impl Symbol { false } + pub fn all_fields(symbol: &Rc>, session: &mut SessionInfo, from_module: Option>>) -> HashMap>, Option)>> { + Symbol::all_members(symbol, session, true, true, false, from_module.clone(), false) + } + /* similar to get_symbol: will return the symbol that is under this one with the specified name. However, if the symbol is a class or a model, it will search in the base class or in comodel classes if not all, it will return the first found. If all, the all found symbols are returned, but the first one @@ -2819,6 +2907,31 @@ impl Symbol { res } + pub fn get_xml_id(&self, xml_id: &OYarn) -> Option> { + match self { + Symbol::XmlFileSymbol(xml_file) => xml_file.xml_ids.get(xml_id).cloned(), + Symbol::Package(PackageSymbol::Module(module)) => module.xml_ids.get(xml_id).cloned(), + Symbol::Package(PackageSymbol::PythonPackage(package)) => package.xml_ids.get(xml_id).cloned(), + Symbol::File(file) => file.xml_ids.get(xml_id).cloned(), + _ => None, + } + } + + pub fn insert_xml_id(&mut self, xml_id: OYarn, xml_data: XmlData) { + match self { + Symbol::File(file) => { + file.xml_ids.entry(xml_id).or_insert(vec![]).push(xml_data); + }, + Symbol::Package(PackageSymbol::Module(module)) => { + module.xml_ids.entry(xml_id).or_insert(vec![]).push(xml_data); + }, + Symbol::Package(PackageSymbol::PythonPackage(package)) => { + package.xml_ids.entry(xml_id).or_insert(vec![]).push(xml_data); + }, + _ => {} + } + } + pub fn print_dependencies(&self) { /*println!("------- Output dependencies of {} -------", self.name()); println!("--- ARCH"); diff --git a/server/src/core/symbols/variable_symbol.rs b/server/src/core/symbols/variable_symbol.rs index 6a0aca5a..483ef88e 100644 --- a/server/src/core/symbols/variable_symbol.rs +++ b/server/src/core/symbols/variable_symbol.rs @@ -1,7 +1,7 @@ use ruff_text_size::TextRange; -use crate::{constants::{OYarn, SymType}, core::evaluation::Evaluation, oyarn, threads::SessionInfo}; -use std::{cell::RefCell, rc::{Rc, Weak}}; +use crate::{constants::{OYarn, SymType}, core::evaluation::{ContextValue, Evaluation}, oyarn, threads::SessionInfo, Sy, S}; +use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}, u32}; use super::symbol::Symbol; @@ -62,7 +62,14 @@ impl VariableSymbol { pub fn get_relational_model(&self, session: &mut SessionInfo, from_module: Option>>) -> Vec>> { for eval in self.evaluations.iter() { let symbol = eval.symbol.get_symbol(session, &mut None, &mut vec![], None); - let eval_weaks = Symbol::follow_ref(&symbol, session, &mut None, false, false, None, &mut vec![]); + let mut context = None; + if let Some(parent) = self.parent.as_ref() { + // To be able to follow related fields, we need to have the base_attr set in order to find the __get__ hook in next_refs + // we update the context here for the case where we are coming from a decorator for example. + context = Some(HashMap::new()); + context.as_mut().unwrap().insert(S!("base_attr"), ContextValue::SYMBOL(parent.clone())); + } + let eval_weaks = Symbol::follow_ref(&symbol, session, &mut context, false, false, None, &mut vec![]); for eval_weak in eval_weaks.iter() { if let Some(symbol) = eval_weak.upgrade_weak() { if ["Many2one", "One2many", "Many2many"].contains(&symbol.borrow().name().as_str()) { diff --git a/server/src/core/symbols/xml_file_symbol.rs b/server/src/core/symbols/xml_file_symbol.rs index 198ee39f..0453b03b 100644 --- a/server/src/core/symbols/xml_file_symbol.rs +++ b/server/src/core/symbols/xml_file_symbol.rs @@ -2,7 +2,8 @@ use lsp_types::Diagnostic; use roxmltree::Error; use weak_table::PtrWeakHashSet; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{diagnostics::DiagnosticCode, file_mgr::{FileInfo, NoqaInfo}, model::Model}, oyarn, threads::SessionInfo, S}; +use crate::{core::diagnostics::DiagnosticCode, threads::SessionInfo}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn, EXTENSION_NAME}, core::{file_mgr::{FileInfo, NoqaInfo}, model::Model, xml_data::XmlData}, oyarn, S}; use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}}; use super::{symbol::Symbol, symbol_mgr::SectionRange}; @@ -17,6 +18,7 @@ pub struct XmlFileSymbol { pub arch_status: BuildStatus, pub validation_status: BuildStatus, pub not_found_paths: Vec<(BuildSteps, Vec)>, + pub xml_ids: HashMap>, in_workspace: bool, pub self_import: bool, pub model_dependencies: PtrWeakHashSet>>, //always on validation level, as odoo step is always required @@ -44,6 +46,7 @@ impl XmlFileSymbol { arch_status: BuildStatus::PENDING, validation_status: BuildStatus::PENDING, not_found_paths: vec![], + xml_ids: HashMap::new(), in_workspace: false, self_import: false, sections: vec![], diff --git a/server/src/core/xml_arch_builder.rs b/server/src/core/xml_arch_builder.rs index bb9b94b1..09399dbc 100644 --- a/server/src/core/xml_arch_builder.rs +++ b/server/src/core/xml_arch_builder.rs @@ -1,11 +1,13 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, fmt, fs, path::PathBuf, rc::{Rc, Weak}}; -use lsp_types::{Diagnostic}; -use roxmltree::Node; -use tracing::{warn}; +use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; +use regex::Regex; +use roxmltree::{Attribute, Node}; +use tracing::{error, warn}; use weak_table::PtrWeakHashSet; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{diagnostics::{create_diagnostic, DiagnosticCode}, entry_point::EntryPointType}, threads::SessionInfo, Sy}; +use crate::core::{diagnostics::{create_diagnostic, DiagnosticCode}, odoo::SyncOdoo}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn, EXTENSION_NAME}, core::{entry_point::EntryPointType, xml_data::XmlData}, oyarn, threads::SessionInfo, Sy, S}; use super::{file_mgr::FileInfo, symbols::{symbol::Symbol}}; @@ -36,6 +38,7 @@ impl XmlArchBuilder { self.load_odoo_openerp_data(session, node, &mut diagnostics); self.xml_symbol.borrow_mut().set_build_status(BuildSteps::ARCH, BuildStatus::DONE); file_info.replace_diagnostics(BuildSteps::ARCH, diagnostics); + session.sync_odoo.add_to_validations(self.xml_symbol.clone()); } pub fn on_operation_creation( @@ -43,7 +46,8 @@ impl XmlArchBuilder { session: &mut SessionInfo, id: Option, node: &Node, - diagnostics: &mut Vec, + mut xml_data: XmlData, + diagnostics: &mut Vec ) { if !self.is_in_main_ep { return; @@ -76,25 +80,25 @@ impl XmlArchBuilder { xml_module = m.upgrade().unwrap(); } } - let xml_module_bw = xml_module.borrow(); - let already_existing = xml_module_bw.as_module_package().xml_ids.get(&Sy!(id.clone())).cloned(); - drop(xml_module_bw); - let mut found_one = false; - if let Some(existing) = already_existing { - //Check that it exists a main xml_id - for s in existing.iter() { - if Rc::ptr_eq(&s, &xml_module) { - found_one = true; - break; + xml_data.set_file_symbol(&self.xml_symbol); + xml_module.borrow_mut().as_module_package_mut().xml_id_locations.entry(Sy!(id.clone())).or_insert(PtrWeakHashSet::new()).insert(self.xml_symbol.clone()); + self.xml_symbol.borrow_mut().as_xml_file_sym_mut().xml_ids.entry(Sy!(id.clone())).or_insert(vec![]).push(xml_data); + } + } + + pub fn get_group_ids(&self, session: &mut SessionInfo, xml_id: &str, attr: &Attribute, diagnostics: &mut Vec) -> Vec { + let xml_ids = SyncOdoo::get_xml_ids(session, &self.xml_symbol, xml_id, &attr.range(), diagnostics); + let mut res = vec![]; + for data in xml_ids.iter() { + match data { + XmlData::RECORD(r) => { + if r.model.0 == "res.groups" { + res.push(data.clone()); } - } - } else { - xml_module.borrow_mut().as_module_package_mut().xml_ids.insert(Sy!(id.clone()), PtrWeakHashSet::new()); - } - if !found_one && !Rc::ptr_eq(&xml_module, &module) { - // no diagnostic to create. + }, + _ => {} } - xml_module.borrow_mut().as_module_package_mut().xml_ids.get_mut(&Sy!(id)).unwrap().insert(self.xml_symbol.clone()); } + res } } \ No newline at end of file diff --git a/server/src/core/xml_arch_builder_rng_validation.rs b/server/src/core/xml_arch_builder_rng_validation.rs index 0904d06d..042d2bca 100644 --- a/server/src/core/xml_arch_builder_rng_validation.rs +++ b/server/src/core/xml_arch_builder_rng_validation.rs @@ -1,9 +1,11 @@ +use std::rc::Rc; + use lsp_types::{Diagnostic, Position, Range}; use once_cell::sync::Lazy; use regex::Regex; use roxmltree::Node; -use crate::{core::diagnostics::{create_diagnostic, DiagnosticCode}, threads::SessionInfo, S}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn, EXTENSION_NAME}, core::{diagnostics::{create_diagnostic, DiagnosticCode}, odoo::SyncOdoo, xml_data::{XmlData, XmlDataDelete, XmlDataField, XmlDataMenuItem, XmlDataRecord, XmlDataTemplate}}, oyarn, threads::SessionInfo, Sy, S}; use super::xml_arch_builder::XmlArchBuilder; @@ -37,8 +39,6 @@ impl XmlArchBuilder { || self.load_record(session, &child, diagnostics) || self.load_template(session, &child, diagnostics) || self.load_delete(session, &child, diagnostics) - || self.load_act_window(session, &child, diagnostics) - || self.load_report(session, &child, diagnostics) || self.load_function(session, &child, diagnostics) || child.is_text() || child.is_comment()) { if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05005, &[child.tag_name().name(), node.tag_name().name()]) { @@ -76,7 +76,20 @@ impl XmlArchBuilder { } } }, - "name" | "groups" | "active" => {}, + "groups" => { + for group in attr.value().split(",") { + let group = group.trim_start_matches("-"); + if self.get_group_ids(session, group, &attr, diagnostics).is_empty() { + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05054, &[group]) { + diagnostics.push(Diagnostic { + range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) }, + ..diagnostic.clone() + }); + } + } + } + }, + "name" | "active" => {}, "action" => { if (has_parent || is_submenu) && node.has_children() { let other_than_text = node.children().any(|c| !c.is_text() && !c.is_comment()); @@ -89,6 +102,15 @@ impl XmlArchBuilder { } } } + //check that action exists + if SyncOdoo::get_xml_ids(session, &self.xml_symbol, attr.value(), &attr.range(), diagnostics).is_empty() { + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05053, &[attr.value()]) { + diagnostics.push(Diagnostic { + range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) }, + ..diagnostic.clone() + }); + } + } } "parent" => { if is_submenu { @@ -98,8 +120,18 @@ impl XmlArchBuilder { ..diagnostic.clone() }); } + } else { + //check that parent exists + if SyncOdoo::get_xml_ids(session, &self.xml_symbol, attr.value(), &attr.range(), diagnostics).is_empty() { + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05052, &[attr.value()]) { + diagnostics.push(Diagnostic { + range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) }, + ..diagnostic.clone() + }); + } + } } - } + }, "web_icon" => { if has_parent || is_submenu { if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05010, &[]) { @@ -141,7 +173,11 @@ impl XmlArchBuilder { self.load_menuitem(session, &child, true, diagnostics); } } - self.on_operation_creation(session, found_id, node, diagnostics); + let data = XmlData::MENUITEM(XmlDataMenuItem { + file_symbol: Rc::downgrade(&self.xml_symbol), + xml_id: found_id.clone().map(|id| oyarn!("{}", id)), + }); + self.on_operation_creation(session, found_id, node, data, diagnostics); true } @@ -176,9 +212,20 @@ impl XmlArchBuilder { } return false; } - + let mut data = XmlDataRecord { + file_symbol: Rc::downgrade(&self.xml_symbol), + model: (oyarn!("{}", node.attribute("model").unwrap()), node.attribute_node("model").unwrap().range()), + xml_id: found_id.clone().map(|id| oyarn!("{}", id)), + fields: vec![], + range: std::ops::Range:: { + start: node.range().start as usize, + end: node.range().end as usize, + } + }; for child in node.children().filter(|n| n.is_element()) { - if !self.load_field(session, &child, diagnostics) { + if let Some(field) = self.load_field(session, &child, diagnostics) { + data.fields.push(field); + } else { if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05015, &[child.tag_name().name()]) { diagnostics.push(Diagnostic { range: Range { start: Position::new(child.range().start as u32, 0), end: Position::new(child.range().end as u32, 0) }, @@ -187,12 +234,13 @@ impl XmlArchBuilder { } } } - self.on_operation_creation(session, found_id, node, diagnostics); + let data = XmlData::RECORD(data); + self.on_operation_creation(session, found_id, node, data, diagnostics); true } - fn load_field(&mut self, session: &mut SessionInfo, node: &Node, diagnostics: &mut Vec) -> bool { - if node.tag_name().name() != "field" { return false; } + fn load_field(&mut self, session: &mut SessionInfo, node: &Node, diagnostics: &mut Vec) -> Option { + if node.tag_name().name() != "field" { return None; } if node.attribute("name").is_none() { if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05016, &[]) { diagnostics.push(Diagnostic { @@ -213,7 +261,7 @@ impl XmlArchBuilder { ..diagnostic.clone() }); } - return false; + return None; } let mut is_xml_or_html = false; if let Some(field_type) = node.attribute("type") { @@ -323,7 +371,20 @@ impl XmlArchBuilder { } } } - true + let mut text = None; + let mut text_range = None; + for child in node.children() { + if child.is_text() { + text = child.text().map(|s| s.to_string()); + text_range = Some(child.range()); + } + } + Some(XmlDataField { + name: oyarn!("{}", node.attribute("name").unwrap()), + range: node.attribute_node("name").unwrap().range(), + text: text, + text_range: text_range, + }) } fn load_value(&mut self, session: &mut SessionInfo, node: &Node, diagnostics: &mut Vec) -> bool { @@ -401,7 +462,11 @@ impl XmlArchBuilder { if node.tag_name().name() != "template" { return false; } //no interesting rule to check, as 'any' is valid let found_id = node.attribute("id").map(|s| s.to_string()); - self.on_operation_creation(session, found_id, node, diagnostics); + let data = XmlData::TEMPLATE(XmlDataTemplate { + file_symbol: Rc::downgrade(&self.xml_symbol), + xml_id: found_id.clone().map(|id| oyarn!("{}", id)), + }); + self.on_operation_creation(session, found_id, node, data, diagnostics); true } @@ -433,109 +498,12 @@ impl XmlArchBuilder { }); } } - self.on_operation_creation(session, found_id, node, diagnostics); - true - } - - fn load_act_window(&mut self, session: &mut SessionInfo, node: &Node, diagnostics: &mut Vec) -> bool { - if node.tag_name().name() != "act_window" { return false; } - let mut found_id = None; - for attr in ["id", "name", "res_model"] { - if node.attribute(attr).is_none() { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05036, &[attr]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(node.range().start as u32, 0), end: Position::new(node.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - if attr == "id" { - found_id = node.attribute(attr).map(|v| v.to_string()); - } - } - for attr in node.attributes() { - match attr.name() { - "id" | "name" | "res_model" => {}, - "domain" | "view_mode" | "view_id" | "target" | "context" | "groups" | "limit" | "usage" | "binding_model" => {}, - "binding_type" => { - if attr.value() != "action" && attr.value() != "report" { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05039, &[attr.value()]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - }, - "binding_views" => { - if !BINDING_VIEWS_RE.is_match(attr.value()) { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05040, &[attr.value()]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - }, - _ => { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05037, &[attr.name()]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - } - } - if node.text().is_some() { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05038, &[]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(node.range().start as u32, 0), end: Position::new(node.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - self.on_operation_creation(session, found_id, node, diagnostics); - true - } - - fn load_report(&mut self, session: &mut SessionInfo, node: &Node, diagnostics: &mut Vec) -> bool { - if node.tag_name().name() != "report" { return false; } - let mut found_id = None; - for attr in ["string", "model", "name"] { - if node.attribute(attr).is_none() { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05041, &[attr]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(node.range().start as u32, 0), end: Position::new(node.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - } - for attr in node.attributes() { - match attr.name() { - "id" => { found_id = Some(attr.value().to_string()); }, - "print_report_name" | "report_type" | "multi"| "menu" | "keyword" | "file" | - "xml" | "parser" | "auto" | "header" | "attachment" | "attachment_use" | "groups" | "paperformat" | "usage" => {}, - _ => { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05042, &[attr.name()]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - } - } - if node.text().is_some() { - if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05043, &[]) { - diagnostics.push(Diagnostic { - range: Range { start: Position::new(node.range().start as u32, 0), end: Position::new(node.range().end as u32, 0) }, - ..diagnostic.clone() - }); - } - } - self.on_operation_creation(session, found_id, node, diagnostics); + let data = XmlData::DELETE(XmlDataDelete { + file_symbol: Rc::downgrade(&self.xml_symbol), + xml_id: found_id.clone().map(|id| oyarn!("{}", id)), + model: Sy!(node.attribute("model").unwrap().to_string()), + }); + self.on_operation_creation(session, found_id, node, data, diagnostics); true } diff --git a/server/src/core/xml_data.rs b/server/src/core/xml_data.rs new file mode 100644 index 00000000..7bf99f2f --- /dev/null +++ b/server/src/core/xml_data.rs @@ -0,0 +1,98 @@ +use std::{cell::RefCell, ops::Range, rc::{Rc, Weak}}; + +use ruff_text_size::TextRange; + +use crate::{constants::{OYarn, SymType}, core::symbols::symbol::Symbol}; + + +#[derive(Debug, Clone)] +pub enum XmlData { + RECORD(XmlDataRecord), + MENUITEM(XmlDataMenuItem), + TEMPLATE(XmlDataTemplate), + DELETE(XmlDataDelete), +} + +#[derive(Debug, Clone)] +pub struct XmlDataRecord { + pub file_symbol: Weak>, + pub model: (OYarn, Range), + pub xml_id: Option, + pub fields: Vec, + pub range: Range, +} + +#[derive(Debug, Clone)] +pub struct XmlDataField { + pub name: OYarn, + pub range: Range, + pub text: Option, + pub text_range: Option>, +} + +#[derive(Debug, Clone)] +pub struct XmlDataMenuItem { + pub file_symbol: Weak>, + pub xml_id: Option, +} + +#[derive(Debug, Clone)] +pub struct XmlDataTemplate { + pub file_symbol: Weak>, + pub xml_id: Option, +} + +#[derive(Debug, Clone)] +pub struct XmlDataDelete { + pub file_symbol: Weak>, + pub xml_id: Option, + pub model: OYarn, +} + +impl XmlData { + + pub fn set_file_symbol(&mut self, xml_symbol: &Rc>) { + match self { + XmlData::RECORD(ref mut record) => { + record.file_symbol = Rc::downgrade(xml_symbol); + }, + XmlData::MENUITEM(ref mut menu_item) => { + menu_item.file_symbol = Rc::downgrade(xml_symbol); + }, + XmlData::TEMPLATE(ref mut template) => { + template.file_symbol = Rc::downgrade(xml_symbol); + }, + XmlData::DELETE(ref mut delete) => { + delete.file_symbol = Rc::downgrade(xml_symbol); + }, + } + } + + pub fn get_xml_file_symbol(&self) -> Option>> { + let file_symbol = self.get_file_symbol()?; + if let Some(symbol) = file_symbol.upgrade() { + if symbol.borrow().typ() == SymType::XML_FILE { + return Some(symbol); + } + } + None + } + + /* Warning: the returned symbol can of a different type than an XML_SYMBOL */ + pub fn get_file_symbol(&self) -> Option>> { + match self { + XmlData::RECORD(ref record) => { + Some(record.file_symbol.clone()) + }, + XmlData::MENUITEM(ref menu_item) => { + Some(menu_item.file_symbol.clone()) + }, + XmlData::TEMPLATE(ref template) => { + Some(template.file_symbol.clone()) + }, + XmlData::DELETE(ref delete) => { + Some(delete.file_symbol.clone()) + } + } + } +} \ No newline at end of file diff --git a/server/src/core/xml_validation.rs b/server/src/core/xml_validation.rs new file mode 100644 index 00000000..51edd3eb --- /dev/null +++ b/server/src/core/xml_validation.rs @@ -0,0 +1,190 @@ +use std::{cell::RefCell, cmp::Ordering, collections::HashMap, hash::Hash, path::PathBuf, rc::Rc}; + +use lsp_types::{Diagnostic, Position, Range}; +use tracing::{info, trace}; + +use crate::{constants::{BuildSteps, OYarn, SymType, DEBUG_STEPS, EXTENSION_NAME}, core::{diagnostics::{create_diagnostic, DiagnosticCode}, entry_point::{EntryPoint, EntryPointType}, evaluation::ContextValue, file_mgr::FileInfo, model::Model, odoo::SyncOdoo, symbols::symbol::Symbol, xml_data::{XmlData, XmlDataDelete, XmlDataMenuItem, XmlDataRecord, XmlDataTemplate}}, oyarn, threads::SessionInfo, utils::compare_semver, Sy, S}; + + + +pub struct XmlValidator { + pub xml_symbol: Rc>, + pub is_in_main_ep: bool, +} + +impl XmlValidator { + + pub fn new(entry: &Rc>, symbol: Rc>) -> Self { + let is_in_main_ep = entry.borrow().typ == EntryPointType::MAIN || entry.borrow().typ == EntryPointType::ADDON; + Self { + xml_symbol: symbol, + is_in_main_ep, + } + } + + fn get_file_info(&mut self, odoo: &mut SyncOdoo) -> Rc> { + let file_symbol = self.xml_symbol.borrow(); + let mut path = file_symbol.paths()[0].clone(); + let file_info_rc = odoo.get_file_mgr().borrow().get_file_info(&path).expect("File not found in cache").clone(); + file_info_rc + } + + pub fn validate(&mut self, session: &mut SessionInfo) { + if DEBUG_STEPS { + trace!("Validating XML File {}", self.xml_symbol.borrow().name()); + } + let module = self.xml_symbol.borrow().find_module().unwrap(); + let mut dependencies = vec![]; + let mut model_dependencies = vec![]; + let mut diagnostics = vec![]; + for xml_ids in self.xml_symbol.borrow().as_xml_file_sym().xml_ids.values() { + for xml_id in xml_ids.iter() { + self.validate_xml_id(session, &module, xml_id, &mut diagnostics, &mut dependencies, &mut model_dependencies); + } + } + for dep in dependencies.iter_mut() { + self.xml_symbol.borrow_mut().add_dependency(&mut dep.borrow_mut(), BuildSteps::VALIDATION, BuildSteps::ARCH_EVAL); + } + for model in model_dependencies.iter() { + self.xml_symbol.borrow_mut().add_model_dependencies(&model); + } + let file_info = self.get_file_info(&mut session.sync_odoo); + file_info.borrow_mut().replace_diagnostics(BuildSteps::VALIDATION, diagnostics); + file_info.borrow_mut().publish_diagnostics(session); + } + + pub fn validate_xml_id(&self, session: &mut SessionInfo, module: &Rc>, data: &XmlData, diagnostics: &mut Vec, dependencies: &mut Vec>>, model_dependencies: &mut Vec>>) { + let Some(xml_file) = data.get_xml_file_symbol() else { + return; + }; + let path = xml_file.borrow().paths()[0].clone(); + match data { + XmlData::RECORD(xml_data_record) => self.validate_record(session, module, xml_data_record, diagnostics, dependencies, model_dependencies), + XmlData::MENUITEM(xml_data_menu_item) => self.validate_menu_item(session, module, xml_data_menu_item, diagnostics, dependencies, model_dependencies), + XmlData::TEMPLATE(xml_data_template) => self.validate_template(session, module, xml_data_template, diagnostics, dependencies, model_dependencies), + XmlData::DELETE(xml_data_delete) => self.validate_delete(session, module, xml_data_delete, diagnostics, dependencies, model_dependencies), + } + } + + fn validate_record(&self, session: &mut SessionInfo, module: &Rc>, xml_data_record: &XmlDataRecord, diagnostics: &mut Vec, dependencies: &mut Vec>>, model_dependencies: &mut Vec>>) { + let Some(model) = session.sync_odoo.models.get(&xml_data_record.model.0).cloned() else { + //TODO register to not_found_models + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05055, &[&xml_data_record.model.0, module.borrow().name()]) { + diagnostics.push(Diagnostic { + range: Range { start: Position::new(xml_data_record.model.1.start.try_into().unwrap(), 0), end: Position::new(xml_data_record.model.1.end.try_into().unwrap(), 0) }, + ..diagnostic.clone() + }); + } + info!("Model '{}' not found in module '{}'", xml_data_record.model.0, module.borrow().name()); + return; + }; + model_dependencies.push(model.clone()); + let main_symbols = model.borrow().get_main_symbols(session, Some(module.clone())); + for main_sym in main_symbols.iter() { + dependencies.push(main_sym.borrow().get_file().unwrap().upgrade().unwrap()); + } + let all_fields = Symbol::all_fields(&main_symbols[0], session, Some(module.clone())); + self.validate_fields(session, xml_data_record, &all_fields, diagnostics); + } + + fn validate_fields(&self, session: &mut SessionInfo, xml_data_record: &XmlDataRecord, all_fields: &HashMap>, Option)>>, diagnostics: &mut Vec) { + //Compute mandatory fields + let mut mandatory_fields: Vec = vec![]; + for (field_name, field_sym) in all_fields.iter() { + for (fs, deps) in field_sym.iter() { + if deps.is_none() { + let has_required = fs.borrow().evaluations().unwrap_or(&vec![]).iter() + .any(|eval| + eval.symbol.get_symbol_as_weak(session, &mut None, diagnostics, None) + .context.get("required").unwrap_or(&ContextValue::BOOLEAN(false)).as_bool() + ); + let has_default = fs.borrow().evaluations().unwrap_or(&vec![]).iter() + .any(|eval| + eval.symbol.get_symbol_as_weak(session, &mut None, diagnostics, None) + .context.contains_key("default") + ); + if has_required && !has_default { + mandatory_fields.push(field_name.to_string()); + } + } + } + } + //check each field in the record + for field in &xml_data_record.fields { + let mut field_name = field.name.clone(); + let mut has_translation = false; + if compare_semver(&session.sync_odoo.full_version, "18.2.0") >= Ordering::Equal { + let translation = field.name.split("@").collect::>(); + if translation.len() > 1 { + field_name = oyarn!("{}", translation[0]); + has_translation = true; + //TODO check that the language exists + } + } + //Check that the field belong to the model + let declared_field = all_fields.get(&field.name); + if let Some(_declared_field) = declared_field { + mandatory_fields.retain(|f| f != &field.name.as_str()); + //Check specific attributes + match xml_data_record.model.0.as_str() { + "ir.ui.view" => { + if field.name == "model" && field.text.is_some() && field.text_range.is_some() { + //TODO text that field.text is a valid model + let model = session.sync_odoo.models.get(&Sy!(field.text.as_ref().unwrap().clone())).cloned(); + let mut main_sym = vec![]; + if let Some(model) = model { + let from_module = self.xml_symbol.borrow().find_module(); + main_sym = model.borrow().get_main_symbols(session, from_module); + } + if main_sym.is_empty() { + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05056, &[&field.text.as_ref().unwrap()]) { + diagnostics.push(Diagnostic { + range: Range { start: Position::new(field.text_range.as_ref().unwrap().start.try_into().unwrap(), 0), end: Position::new(field.text_range.as_ref().unwrap().end.try_into().unwrap(), 0) }, + ..diagnostic.clone() + }); + } + } + } + }, + _ => {} + } + //TODO check type + } else { + if has_translation { + continue; + } + if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05057, &[&field.name, &xml_data_record.model.0]) { + diagnostics.push(Diagnostic { + range: Range { start: Position::new(field.range.start.try_into().unwrap(), 0), end: Position::new(field.range.end.try_into().unwrap(), 0) }, + ..diagnostic.clone() + }); + } + } + } + //Diagnostic if some mandatory fields are not detected + // if !mandatory_fields.is_empty() { + // We have to check that remaining fields are not declared in an inherited record or is automatically field (delegate=True) + // diagnostics.push(Diagnostic::new( + // Range::new(Position::new(xml_data_record.range.start.try_into().unwrap(), 0), Position::new(xml_data_record.range.end.try_into().unwrap(), 0)), + // Some(lsp_types::DiagnosticSeverity::ERROR), + // Some(lsp_types::NumberOrString::String(S!("OLS30452"))), + // Some(EXTENSION_NAME.to_string()), + // format!("Some mandatory fields are not declared in the record: {:?}", mandatory_fields), + // None, + // None + // )); + // } + } + + fn validate_menu_item(&self, session: &mut SessionInfo, module: &Rc>, xml_data_menu_item: &XmlDataMenuItem, diagnostics: &mut Vec, dependencies: &mut Vec>>, model_dependencies: &mut Vec>>) { + + } + + fn validate_template(&self, session: &mut SessionInfo, module: &Rc>, xml_data_template: &XmlDataTemplate, diagnostics: &mut Vec, dependencies: &mut Vec>>, model_dependencies: &mut Vec>>) { + + } + + fn validate_delete(&self, session: &mut SessionInfo, module: &Rc>, xml_data_delete: &XmlDataDelete, diagnostics: &mut Vec, dependencies: &mut Vec>>, model_dependencies: &mut Vec>>) { + + } +} \ No newline at end of file diff --git a/server/src/features/ast_utils.rs b/server/src/features/ast_utils.rs index 587ddfd2..8e179086 100644 --- a/server/src/features/ast_utils.rs +++ b/server/src/features/ast_utils.rs @@ -41,7 +41,7 @@ impl AstUtils { (S!("module"), from_module), (S!("range"), ContextValue::RANGE(expr.range())) ])); - let analyse_ast_result: AnalyzeAstResult = Evaluation::analyze_ast(session, &expr, parent_symbol.clone(), &expr.range().end(), &mut context, &mut vec![]); + let analyse_ast_result: AnalyzeAstResult = Evaluation::analyze_ast(session, &expr, parent_symbol.clone(), &expr.range().end(), &mut context,false, &mut vec![]); (analyse_ast_result, Some(expr.range()), call_expr) } diff --git a/server/src/features/completion.rs b/server/src/features/completion.rs index f505ab12..48d4efca 100644 --- a/server/src/features/completion.rs +++ b/server/src/features/completion.rs @@ -505,7 +505,7 @@ fn complete_decorator_call( return None; // All the decorators we handle have at least one arg for now } let scope = Symbol::get_scope_symbol(file.clone(), offset as u32, false); - let dec_evals = Evaluation::eval_from_ast(session, &decorator_base, scope.clone(), max_infer, &mut vec![]).0; + let dec_evals = Evaluation::eval_from_ast(session, &decorator_base, scope.clone(), max_infer, false, &mut vec![]).0; let mut followed_evals = vec![]; for eval in dec_evals { followed_evals.extend(Symbol::follow_ref(&eval.symbol.get_symbol(session, &mut None, &mut vec![], None), session, &mut None, true, false, None, &mut vec![])); @@ -548,7 +548,7 @@ fn complete_call(session: &mut SessionInfo, file: &Rc>, expr_cal return complete_expr( &expr_call.func, session, file, offset, is_param, expected_type); } let scope = Symbol::get_scope_symbol(file.clone(), offset as u32, is_param); - let callable_evals = Evaluation::eval_from_ast(session, &expr_call.func, scope, &expr_call.func.range().start(), &mut vec![]).0; + let callable_evals = Evaluation::eval_from_ast(session, &expr_call.func, scope, &expr_call.func.range().start(), false, &mut vec![]).0; for (arg_index, arg) in expr_call.arguments.args.iter().enumerate() { if offset > arg.range().start().to_usize() && offset <= arg.range().end().to_usize() { for callable_eval in callable_evals.iter() { @@ -561,7 +561,7 @@ fn complete_call(session: &mut SessionInfo, file: &Rc>, expr_cal let func_arg = func.get_indexed_arg_in_call( expr_call, arg_index as u32, - callable.context.get(&S!("is_attr_of_instance")).unwrap_or(&ContextValue::BOOLEAN(false)).as_bool()); + callable.context.get(&S!("is_attr_of_instance")).map(|v| v.as_bool())); if let Some(func_arg) = func_arg { if let Some(func_arg_sym) = func_arg.symbol.upgrade() { let mut expected_type = vec![]; @@ -754,7 +754,7 @@ fn complete_attribut(session: &mut SessionInfo, file: &Rc>, attr if offset > attr.value.range().start().to_usize() && offset <= attr.value.range().end().to_usize() { return complete_expr( &attr.value, session, file, offset, is_param, expected_type); } else { - let parent = Evaluation::eval_from_ast(session, &attr.value, scope.clone(), &attr.range().start(), &mut vec![]).0; + let parent = Evaluation::eval_from_ast(session, &attr.value, scope.clone(), &attr.range().start(), false, &mut vec![]).0; let from_module = file.borrow().find_module().clone(); for parent_eval in parent.iter() { @@ -777,7 +777,7 @@ fn complete_attribut(session: &mut SessionInfo, file: &Rc>, attr fn complete_subscript(session: &mut SessionInfo, file: &Rc>, expr_subscript: &ExprSubscript, offset: usize, is_param: bool, expected_type: &Vec) -> Option { let scope = Symbol::get_scope_symbol(file.clone(), offset as u32, is_param); - let subscripted = Evaluation::eval_from_ast(session, &expr_subscript.value, scope.clone(), &expr_subscript.value.range().start(), &mut vec![]).0; + let subscripted = Evaluation::eval_from_ast(session, &expr_subscript.value, scope.clone(), &expr_subscript.value.range().start(), false, &mut vec![]).0; for eval in subscripted.iter() { let eval_symbol = eval.symbol.get_symbol(session, &mut None, &mut vec![], Some(scope.clone())); if !eval_symbol.is_expired_if_weak() { @@ -941,8 +941,7 @@ fn add_nested_field_names( } if let Some(object) = &obj { if index == split_expr.len() - 1 { - let mut all_symbols: HashMap>, Option)>> = HashMap::new(); - Symbol::all_members(&object, session, &mut all_symbols, true, true, false, from_module.clone(), &mut None, false); + let all_symbols = Symbol::all_members(&object, session, true, true, false, from_module.clone(), false); for (_symbol_name, symbols) in all_symbols { //we could use symbol_name to remove duplicated names, but it would hide functions vs variables if _symbol_name.starts_with(name) { @@ -1000,8 +999,7 @@ fn add_model_attributes( only_methods: bool, attribute_name: &str ){ - let mut all_symbols: HashMap>, Option)>> = HashMap::new(); - Symbol::all_members(&parent_sym, session, &mut all_symbols, true, only_fields, only_methods, from_module.clone(), &mut None, is_super); + let all_symbols = Symbol::all_members(&parent_sym, session, true, only_fields, only_methods, from_module.clone(), is_super); for (_symbol_name, symbols) in all_symbols { //we could use symbol_name to remove duplicated names, but it would hide functions vs variables if _symbol_name.starts_with(attribute_name) { diff --git a/server/src/features/definition.rs b/server/src/features/definition.rs index f6ddb502..045774f9 100644 --- a/server/src/features/definition.rs +++ b/server/src/features/definition.rs @@ -1,4 +1,4 @@ -use lsp_types::{GotoDefinitionResponse, Location, Range}; +use lsp_types::{GotoDefinitionResponse, Location, Position, Range}; use ruff_python_ast::{Expr, ExprCall}; use ruff_text_size::TextSize; use std::path::PathBuf; @@ -8,8 +8,10 @@ use crate::constants::SymType; use crate::core::evaluation::{Evaluation, EvaluationValue}; use crate::core::file_mgr::{FileInfo, FileMgr}; use crate::core::symbols::symbol::Symbol; +use crate::core::symbols::xml_file_symbol; use crate::features::ast_utils::AstUtils; use crate::features::features_utils::FeaturesUtils; +use crate::features::xml_ast_utils::{XmlAstResult, XmlAstUtils}; use crate::oyarn; use crate::threads::SessionInfo; use crate::utils::PathSanitizer as _; @@ -148,4 +150,71 @@ impl DefinitionFeature { Some(GotoDefinitionResponse::Array(links)) } + pub fn get_location_xml(session: &mut SessionInfo, + file_symbol: &Rc>, + file_info: &Rc>, + line: u32, + character: u32 + ) -> Option { + let offset = file_info.borrow().position_to_offset(line, character); + let data = file_info.borrow().file_info_ast.borrow().text_rope.as_ref().unwrap().to_string(); + let document = roxmltree::Document::parse(&data); + if let Ok(document) = document { + let root = document.root_element(); + let (symbols, _range) = XmlAstUtils::get_symbols(session, file_symbol, root, offset, true); + if symbols.is_empty() { + return None; + } + let mut links = vec![]; + for xml_result in symbols.iter() { + match xml_result { + crate::features::xml_ast_utils::XmlAstResult::SYMBOL(s) => { + if let Some(file) = s.borrow().get_file() { + for path in file.upgrade().unwrap().borrow().paths().iter() { + let full_path = match file.upgrade().unwrap().borrow().typ() { + SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.upgrade().unwrap().borrow().as_package().i_ext())).sanitize(), + _ => path.clone() + }; + let range = match s.borrow().typ() { + SymType::PACKAGE(_) | SymType::FILE | SymType::NAMESPACE | SymType::DISK_DIR => Range::default(), + _ => session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &full_path, &s.borrow().range()), + }; + links.push(Location{uri: FileMgr::pathname2uri(&full_path), range}); + } + } + }, + XmlAstResult::XML_DATA(xml_file_symbol, range) => { + let file = xml_file_symbol.borrow().get_file(); //in case of XML_DATA coming from a python class + if let Some(file) = file { + if let Some(file) = file.upgrade() { + for path in file.borrow().paths().iter() { + let full_path = match file.borrow().typ() { + SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.borrow().as_package().i_ext())).sanitize(), + _ => path.clone() + }; + let range = match file.borrow().typ() { + SymType::PACKAGE(_) | SymType::FILE | SymType::NAMESPACE | SymType::DISK_DIR => Range::default(), + _ => session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, &full_path, &range), + }; + links.push(Location{uri: FileMgr::pathname2uri(&full_path), range: range}); + } + } + } + } + } + } + return Some(GotoDefinitionResponse::Array(links)); + } + None + } + + pub fn get_location_csv(session: &mut SessionInfo, + file_symbol: &Rc>, + file_info: &Rc>, + line: u32, + character: u32 + ) -> Option { + None + } + } diff --git a/server/src/features/features_utils.rs b/server/src/features/features_utils.rs index 5d61db29..41ebcaf6 100644 --- a/server/src/features/features_utils.rs +++ b/server/src/features/features_utils.rs @@ -71,7 +71,7 @@ impl FeaturesUtils { if parent_class.borrow().as_class_sym()._model.is_none(){ return vec![]; } - let evaluations = Evaluation::eval_from_ast(session, &call_expr.func, scope.clone(), &call_expr.func.range().start(), &mut vec![]).0; + let evaluations = Evaluation::eval_from_ast(session, &call_expr.func, scope.clone(), &call_expr.func.range().start(), false, &mut vec![]).0; let mut followed_evals = vec![]; for eval in evaluations { followed_evals.extend(Symbol::follow_ref(&eval.symbol.get_symbol(session, &mut None, &mut vec![], None), session, &mut None, true, false, None, &mut vec![])); @@ -189,7 +189,7 @@ impl FeaturesUtils { arg_index: usize, ) -> Vec>>{ let mut arg_symbols: Vec>> = vec![]; - let callable_evals = Evaluation::eval_from_ast(session, &call_expr.func, scope.clone(), &call_expr.func.range().start(), &mut vec![]).0; + let callable_evals = Evaluation::eval_from_ast(session, &call_expr.func, scope.clone(), &call_expr.func.range().start(), false, &mut vec![]).0; let mut followed_evals = vec![]; for eval in callable_evals { followed_evals.extend(Symbol::follow_ref(&eval.symbol.get_symbol(session, &mut None, &mut vec![], None), session, &mut None, true, false, None, &mut vec![])); @@ -228,7 +228,7 @@ impl FeaturesUtils { let func_arg = func.as_func().get_indexed_arg_in_call( call_expr, arg_index as u32, - callable.context.get(&S!("is_attr_of_instance")).unwrap_or(&ContextValue::BOOLEAN(false)).as_bool()); + callable.context.get(&S!("is_attr_of_instance")).map(|v| v.as_bool())); let Some(func_arg_sym) = func_arg.and_then(|func_arg| func_arg.symbol.upgrade()) else { continue }; @@ -261,7 +261,7 @@ impl FeaturesUtils { return vec![]; } let mut arg_symbols: Vec>> = vec![]; - let callable_evals = Evaluation::eval_from_ast(session, &call_expr.func, scope.clone(), &call_expr.func.range().start(), &mut vec![]).0; + let callable_evals = Evaluation::eval_from_ast(session, &call_expr.func, scope.clone(), &call_expr.func.range().start(), false, &mut vec![]).0; let mut followed_evals = vec![]; for eval in callable_evals { followed_evals.extend(Symbol::follow_ref(&eval.symbol.get_symbol(session, &mut None, &mut vec![], None), session, &mut None, true, false, None, &mut vec![])); @@ -393,7 +393,7 @@ impl FeaturesUtils { block = block + " \n*** \n" + main_class.doc_string().as_ref().unwrap(); } block += " \n*** \n"; - block += &model.borrow().all_symbols(session, from_module.clone()).into_iter() + block += &model.borrow().all_symbols(session, from_module.clone(), false).into_iter() .filter_map(|(sym, needed_module)| { if Rc::ptr_eq(&sym, main_class_rc) { None // Skip main_class @@ -486,7 +486,7 @@ impl FeaturesUtils { let Some(type_symbol) = arg.symbol.upgrade() .and_then(|arg_symbol| arg_symbol.borrow().parent()) .and_then(|weak_parent| weak_parent.upgrade()) - .and_then(|parent| Evaluation::eval_from_ast(session, &anno_expr, parent.clone(), &anno_expr.range().start(), &mut vec![]).0.first().cloned()) + .and_then(|parent| Evaluation::eval_from_ast(session, &anno_expr, parent.clone(), &anno_expr.range().start(), false, &mut vec![]).0.first().cloned()) .and_then(|type_evaluation| type_evaluation.symbol.get_symbol_as_weak(session, &mut None, &mut vec![], None).weak.upgrade()) else { return arg_name.to_string() diff --git a/server/src/features/hover.rs b/server/src/features/hover.rs index ee041f74..b8dc3b8d 100644 --- a/server/src/features/hover.rs +++ b/server/src/features/hover.rs @@ -1,5 +1,7 @@ -use lsp_types::{Hover, HoverContents, MarkupContent, Range}; +use lsp_types::{Hover, HoverContents, MarkupContent, Position, Range}; +use crate::core::evaluation::Evaluation; use crate::core::file_mgr::FileInfo; +use crate::features::xml_ast_utils::{XmlAstResult, XmlAstUtils}; use crate::threads::SessionInfo; use std::rc::Rc; use crate::core::symbols::symbol::Symbol; @@ -12,7 +14,7 @@ pub struct HoverFeature {} impl HoverFeature { - pub fn get_hover(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option { + pub fn hover_python(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option { let offset = file_info.borrow().position_to_offset(line, character); let (analyse_ast_result, range, call_expr) = AstUtils::get_symbols(session, file_symbol, file_info, offset as u32); let evals = analyse_ast_result.evaluations; @@ -28,4 +30,29 @@ impl HoverFeature { range: range }) } + + pub fn hover_xml(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option { + let offset = file_info.borrow().position_to_offset(line, character); + let data = file_info.borrow().file_info_ast.borrow().text_rope.as_ref().unwrap().to_string(); + let document = roxmltree::Document::parse(&data); + if let Ok(document) = document { + let root = document.root_element(); + let (symbols, range) = XmlAstUtils::get_symbols(session, file_symbol, root, offset, true); + let range = range.map(|r| (file_info.borrow().std_range_to_range(&r))); + let evals = symbols.iter().filter(|s| matches!(s, XmlAstResult::SYMBOL(_))) + .map(|s| Evaluation::eval_from_symbol(&Rc::downgrade(&s.as_symbol()), Some(false))).collect::>(); + return Some(Hover { contents: + HoverContents::Markup(MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: FeaturesUtils::build_markdown_description(session, Some(file_symbol.clone()), &evals, &None, Some(offset)) + }), + range: range + }) + } + None + } + + pub fn hover_csv(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option { + None + } } \ No newline at end of file diff --git a/server/src/features/mod.rs b/server/src/features/mod.rs index b2316881..bd0f0f19 100644 --- a/server/src/features/mod.rs +++ b/server/src/features/mod.rs @@ -1,6 +1,8 @@ +pub mod ast_utils; pub mod completion; pub mod definition; pub mod document_symbols; +pub mod features_utils; pub mod hover; -pub mod ast_utils; -pub mod features_utils; \ No newline at end of file +pub mod references; +pub mod xml_ast_utils; \ No newline at end of file diff --git a/server/src/features/references.rs b/server/src/features/references.rs new file mode 100644 index 00000000..dd97154c --- /dev/null +++ b/server/src/features/references.rs @@ -0,0 +1,71 @@ +use std::{cell::RefCell, path::PathBuf, rc::Rc}; + +use lsp_server::ResponseError; +use lsp_types::{Location, Range}; + +use crate::{constants::SymType, core::{file_mgr::{FileInfo, FileMgr}, symbols::{file_symbol::FileSymbol, symbol::Symbol}}, features::xml_ast_utils::{XmlAstResult, XmlAstUtils}, threads::SessionInfo, utils::PathSanitizer}; + + + +pub struct ReferenceFeature { + +} + +impl ReferenceFeature { + pub fn get_references(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option> { + // Implementation for getting references + None + } + + pub fn get_references_xml(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option> { + let offset = file_info.borrow().position_to_offset(line, character); + let data = file_info.borrow().file_info_ast.borrow().text_rope.as_ref().unwrap().to_string(); + let document = roxmltree::Document::parse(&data); + if let Ok(document) = document { + let root = document.root_element(); + let (symbols, _range) = XmlAstUtils::get_symbols(session, file_symbol, root, offset, false); + if symbols.is_empty() { + return None; + } + let mut links = vec![]; + for xml_result in symbols.iter() { + match xml_result { + crate::features::xml_ast_utils::XmlAstResult::SYMBOL(s) => { + if let Some(file) = s.borrow().get_file() { + for path in file.upgrade().unwrap().borrow().paths().iter() { + let full_path = match file.upgrade().unwrap().borrow().typ() { + SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.upgrade().unwrap().borrow().as_package().i_ext())).sanitize(), + _ => path.clone() + }; + let range = match s.borrow().typ() { + SymType::PACKAGE(_) | SymType::FILE | SymType::NAMESPACE | SymType::DISK_DIR => Range::default(), + _ => session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &full_path, &s.borrow().range()), + }; + links.push(Location{uri: FileMgr::pathname2uri(&full_path), range}); + } + } + }, + XmlAstResult::XML_DATA(xml_file_symbol, range) => { + for path in xml_file_symbol.borrow().paths().iter() { + let full_path = match xml_file_symbol.borrow().typ() { + SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", xml_file_symbol.borrow().as_package().i_ext())).sanitize(), + _ => path.clone() + }; + let range = match xml_file_symbol.borrow().typ() { + SymType::PACKAGE(_) | SymType::FILE | SymType::NAMESPACE | SymType::DISK_DIR => Range::default(), + _ => session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, &full_path, &range), + }; + links.push(Location{uri: FileMgr::pathname2uri(&full_path), range: range}); + } + } + } + } + return Some(links); + } + None + } + + pub fn get_references_csv(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option> { + None + } +} \ No newline at end of file diff --git a/server/src/features/xml_ast_utils.rs b/server/src/features/xml_ast_utils.rs new file mode 100644 index 00000000..7bd81bcd --- /dev/null +++ b/server/src/features/xml_ast_utils.rs @@ -0,0 +1,224 @@ +use std::{cell::RefCell, collections::HashMap, ops::Range, rc::Rc}; + +use roxmltree::Node; + +use crate::{constants::OYarn, core::{evaluation::ContextValue, odoo::SyncOdoo, symbols::{module_symbol::ModuleSymbol, symbol::Symbol}, xml_data::XmlData}, threads::SessionInfo, Sy, S}; + +pub enum XmlAstResult { + SYMBOL(Rc>), + XML_DATA(Rc>, Range), //xml file symbol and range of the xml data +} + +impl XmlAstResult { + pub fn as_symbol(&self) -> Rc> { + match self { + XmlAstResult::SYMBOL(sym) => sym.clone(), + XmlAstResult::XML_DATA(sym, _) =>panic!("Xml Data is not a symbol"), + } + } + + pub fn as_xml_data(&self) -> (Rc>, Range) { + match self { + XmlAstResult::SYMBOL(_) => panic!("Symbol is not an XML Data"), + XmlAstResult::XML_DATA(sym, range) => (sym.clone(), range.clone()), + } + } +} + +pub struct XmlAstUtils {} + +impl XmlAstUtils { + + pub fn get_symbols(session: &mut SessionInfo, file_symbol: &Rc>, root: roxmltree::Node, offset: usize, on_dep_only: bool) -> (Vec, Option>) { + let mut results = (vec![], None); + let from_module = file_symbol.borrow().find_module(); + let mut context_xml = HashMap::new(); + for node in root.children() { + XmlAstUtils::visit_node(session, &node, offset, from_module.clone(), &mut context_xml, &mut results, on_dep_only); + } + results + } + + fn visit_node(session: &mut SessionInfo<'_>, node: &Node, offset: usize, from_module: Option>>, ctxt: &mut HashMap, results: &mut (Vec, Option>), on_dep_only: bool) { + if node.is_element() { + match node.tag_name().name() { + "record" => { + XmlAstUtils::visit_record(session, &node, offset, from_module.clone(), ctxt, results, on_dep_only); + } + "field" => { + XmlAstUtils::visit_field(session, &node, offset, from_module.clone(), ctxt, results, on_dep_only); + }, + "menuitem" => { + XmlAstUtils::visit_menu_item(session, &node, offset, from_module.clone(), ctxt, results, on_dep_only); + }, + "template" => { + XmlAstUtils::visit_template(session, &node, offset, from_module.clone(), ctxt, results, on_dep_only); + } + _ => { + for child in node.children() { + XmlAstUtils::visit_node(session, &child, offset, from_module.clone(), ctxt, results, on_dep_only); + } + } + } + } else if node.is_text() { + XmlAstUtils::visit_text(session, &node, offset, from_module, ctxt, results, on_dep_only); + } + } + + fn visit_record(session: &mut SessionInfo<'_>, node: &Node, offset: usize, from_module: Option>>, ctxt: &mut HashMap, results: &mut (Vec, Option>), on_dep_only: bool) { + for attr in node.attributes() { + if attr.name() == "model" { + let model_name = attr.value().to_string(); + ctxt.insert(S!("record_model"), ContextValue::STRING(model_name.clone())); + if attr.range_value().start <= offset && attr.range_value().end >= offset { + if let Some(model) = session.sync_odoo.models.get(&Sy!(model_name)).cloned() { + let from_module = match on_dep_only { + true => from_module.clone(), + false => None, + }; + results.0.extend(model.borrow().all_symbols(session, from_module, false).iter().filter(|s| s.1.is_none()).map(|s| XmlAstResult::SYMBOL(s.0.clone()))); + results.1 = Some(attr.range_value()); + } + } + } else if attr.name() == "id" { + if attr.range_value().start <= offset && attr.range_value().end >= offset { + XmlAstUtils::add_xml_id_result(session, attr.value(), &from_module.as_ref().unwrap(), attr.range_value(), results, on_dep_only); + results.1 = Some(attr.range_value()); + } + } + } + for child in node.children() { + XmlAstUtils::visit_node(session, &child, offset, from_module.clone(), ctxt, results, on_dep_only); + } + ctxt.remove(&S!("record_model")); + } + + fn visit_field(session: &mut SessionInfo<'_>, node: &Node, offset: usize, from_module: Option>>, ctxt: &mut HashMap, results: &mut (Vec, Option>), on_dep_only: bool) { + for attr in node.attributes() { + if attr.name() == "name" { + ctxt.insert(S!("field_name"), ContextValue::STRING(attr.value().to_string())); + if attr.range_value().start <= offset && attr.range_value().end >= offset { + let model_name = ctxt.get(&S!("record_model")).cloned().unwrap_or(ContextValue::STRING(S!(""))).as_string(); + if model_name.is_empty() { + continue; + } + if let Some(model) = session.sync_odoo.models.get(&Sy!(model_name)).cloned() { + let from_module = match on_dep_only { + true => from_module.clone(), + false => None, + }; + for symbol in model.borrow().all_symbols(session, from_module, true) { + if symbol.1.is_none() { + let content = symbol.0.borrow().get_content_symbol(attr.value(), u32::MAX); + for symbol in content.symbols.iter() { + results.0.push(XmlAstResult::SYMBOL(symbol.clone())); + } + } + } + results.1 = Some(attr.range_value()); + } + } + } else if attr.name() == "ref" { + if attr.range_value().start <= offset && attr.range_value().end >= offset { + XmlAstUtils::add_xml_id_result(session, attr.value(), &from_module.as_ref().unwrap(), attr.range_value(), results, on_dep_only); + results.1 = Some(attr.range_value()); + } + } + } + for child in node.children() { + XmlAstUtils::visit_node(session, &child, offset, from_module.clone(), ctxt, results, on_dep_only); + } + ctxt.remove(&S!("field_name")); + } + + fn visit_text(session: &mut SessionInfo, node: &Node, offset: usize, from_module: Option>>, ctxt: &mut HashMap, results: &mut (Vec, Option>), on_dep_only: bool) { + if node.range().start <= offset && node.range().end >= offset { + let model = ctxt.get(&S!("record_model")).cloned().unwrap_or(ContextValue::STRING(S!(""))).as_string(); + let field = ctxt.get(&S!("field_name")).cloned().unwrap_or(ContextValue::STRING(S!(""))).as_string(); + if model.is_empty() || field.is_empty() { + return; + } + if field == "model" || field == "res_model" { //do not check model, let's assume it will contains a model name + XmlAstUtils::add_model_result(session, node, from_module, results, on_dep_only); + } + } + } + + fn visit_menu_item(session: &mut SessionInfo<'_>, node: &Node, offset: usize, from_module: Option>>, ctxt: &mut HashMap, results: &mut (Vec, Option>), on_dep_only: bool) { + for attr in node.attributes() { + if attr.name() == "action" { + if attr.range_value().start <= offset && attr.range_value().end >= offset { + XmlAstUtils::add_xml_id_result(session, attr.value(), &from_module.as_ref().unwrap(), attr.range_value(), results, on_dep_only); + results.1 = Some(attr.range_value()); + } + } else if attr.name() == "groups" { + if attr.range_value().start <= offset && attr.range_value().end >= offset { + XmlAstUtils::add_xml_id_result(session, attr.value(), &from_module.as_ref().unwrap(), attr.range_value(), results, on_dep_only); + results.1 = Some(attr.range_value()); + } + } + } + for child in node.children() { + XmlAstUtils::visit_node(session, &child, offset, from_module.clone(), ctxt, results, on_dep_only); + } + } + + fn visit_template(session: &mut SessionInfo<'_>, node: &Node, offset: usize, from_module: Option>>, ctxt: &mut HashMap, results: &mut (Vec, Option>), on_dep_only: bool) { + for attr in node.attributes() { + if attr.name() == "inherit_id" { + if attr.range_value().start <= offset && attr.range_value().end >= offset { + XmlAstUtils::add_xml_id_result(session, attr.value(), &from_module.as_ref().unwrap(), attr.range_value(), results, on_dep_only); + results.1 = Some(attr.range_value()); + } + } else if attr.name() == "groups" { + if attr.range_value().start <= offset && attr.range_value().end >= offset { + XmlAstUtils::add_xml_id_result(session, attr.value(), &from_module.as_ref().unwrap(), attr.range_value(), results, on_dep_only); + results.1 = Some(attr.range_value()); + } + } + } + for child in node.children() { + XmlAstUtils::visit_node(session, &child, offset, from_module.clone(), ctxt, results, on_dep_only); + } + } + + fn add_model_result(session: &mut SessionInfo, node: &Node, from_module: Option>>, results: &mut (Vec, Option>), on_dep_only: bool) { + if let Some(model) = session.sync_odoo.models.get(node.text().unwrap()).cloned() { + let from_module = match on_dep_only { + true => from_module.clone(), + false => None, + }; + results.0.extend(model.borrow().all_symbols(session, from_module, false).iter().filter(|s| s.1.is_none()).map(|s| XmlAstResult::SYMBOL(s.0.clone()))); + results.1 = Some(node.range()); + } + } + + fn add_xml_id_result(session: &mut SessionInfo, xml_id: &str, file_symbol: &Rc>, range: Range, results: &mut (Vec, Option>), on_dep_only: bool) { + let mut xml_ids = SyncOdoo::get_xml_ids(session, file_symbol, xml_id, &range, &mut vec![]); + if on_dep_only { + xml_ids = xml_ids.into_iter().filter(|x| + { + let file = x.get_file_symbol(); + if let Some(file) = file { + if let Some(file) = file.upgrade() { + let module = file.borrow().find_module(); + if let Some(module) = module { + return ModuleSymbol::is_in_deps(session, &file_symbol.borrow().find_module().unwrap(), module.borrow().name()); + } + } + } + return false; + } + ).collect::>(); + } + for xml_data in xml_ids.iter() { + match xml_data { + XmlData::RECORD(r) => { + results.0.push(XmlAstResult::XML_DATA(r.file_symbol.upgrade().unwrap(), r.range.clone())); + }, + _ => {} + } + } + } + +} \ No newline at end of file diff --git a/server/src/server.rs b/server/src/server.rs index 08be9e73..de3672f0 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -4,7 +4,7 @@ use clap::error; use crossbeam_channel::{Receiver, RecvTimeoutError, Select, Sender}; use lsp_server::{Connection, IoThreads, Message, ProtocolError, RequestId, ResponseError}; use lsp_types::{notification::{DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, - DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, Notification}, request::{Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, Request, ResolveCompletionItem, Shutdown}, CompletionOptions, DefinitionOptions, DocumentSymbolOptions, FileOperationFilter, FileOperationPattern, FileOperationRegistrationOptions, HoverProviderCapability, InitializeParams, InitializeResult, OneOf, SaveOptions, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities}; + DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, Notification}, request::{Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, References, Request, ResolveCompletionItem, Shutdown}, CompletionOptions, DefinitionOptions, DocumentSymbolOptions, FileOperationFilter, FileOperationPattern, FileOperationRegistrationOptions, HoverProviderCapability, InitializeParams, InitializeResult, OneOf, ReferencesOptions, SaveOptions, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities}; use serde_json::json; #[cfg(target_os = "linux")] use nix; @@ -208,6 +208,11 @@ impl Server { trigger_characters: Some(vec![S!("."), S!(","), S!("'"), S!("\""), S!("(")]), ..CompletionOptions::default() }), + references_provider: Some(OneOf::Right(ReferencesOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: Some(false) + } + })), document_symbol_provider: Some(OneOf::Right(DocumentSymbolOptions{ label: Some(S!("Odoo")), work_done_progress_options: WorkDoneProgressOptions{ @@ -401,7 +406,7 @@ impl Server { match msg { Message::Request(r) => { match r.method.as_str() { - HoverRequest::METHOD | GotoDefinition::METHOD => { + HoverRequest::METHOD | GotoDefinition::METHOD | References::METHOD => { self.interrupt_rebuild_boolean.store(true, std::sync::atomic::Ordering::SeqCst); if DEBUG_THREADS { info!("Sending request to read thread : {} - {}", r.method, r.id); diff --git a/server/src/threads.rs b/server/src/threads.rs index adbc6ed0..f3ee8611 100644 --- a/server/src/threads.rs +++ b/server/src/threads.rs @@ -4,7 +4,7 @@ use crossbeam_channel::{Receiver, Sender, TryRecvError}; use lsp_server::{Message, RequestId, Response, ResponseError}; use lsp_types::{notification::{DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, LogMessage, - Notification, ShowMessage}, request::{Completion, DocumentSymbolRequest, GotoDefinition, GotoTypeDefinitionResponse, HoverRequest, Request, Shutdown}, CompletionResponse, DocumentSymbolResponse, Hover, LogMessageParams, MessageType, ShowMessageParams}; + Notification, ShowMessage}, request::{Completion, DocumentSymbolRequest, GotoDefinition, GotoTypeDefinitionResponse, HoverRequest, References, Request, Shutdown}, CompletionResponse, DocumentSymbolResponse, Hover, Location, LogMessageParams, MessageType, ShowMessageParams}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; use tracing::{error, info, warn}; @@ -399,6 +399,9 @@ pub fn message_processor_thread_read(sync_odoo: Arc>, generic_re GotoDefinition::METHOD => { to_value::(Odoo::handle_goto_definition(&mut session, serde_json::from_value(r.params).unwrap())) }, + References::METHOD => { + to_value::>(Odoo::handle_references(&mut session, serde_json::from_value(r.params).unwrap())) + }, DocumentSymbolRequest::METHOD => { to_value::(Odoo::handle_document_symbols(&mut session, serde_json::from_value(r.params).unwrap())) }, diff --git a/server/src/utils.rs b/server/src/utils.rs index 611e1f05..32a1f050 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -25,6 +25,17 @@ macro_rules! Sy { }; } +pub fn get_python_command() -> Option { + for cmd in &["python3", "python"] { + if let Ok(output) = Command::new(cmd).arg("--version").output() { + if output.status.success() { + return Some(S!(*cmd)); + } + } + } + None +} + #[cfg(target_os = "windows")] pub fn is_file_cs(path: String) -> bool { let mut p = Path::new(&path); diff --git a/server/tests/setup/setup.rs b/server/tests/setup/setup.rs index 296f9a7f..358184e1 100644 --- a/server/tests/setup/setup.rs +++ b/server/tests/setup/setup.rs @@ -6,6 +6,7 @@ use std::path::PathBuf; use lsp_types::TextDocumentContentChangeEvent; +use odoo_ls_server::utils::get_python_command; use odoo_ls_server::{core::{config::{ConfigEntry, DiagMissingImportsMode}, entry_point::EntryPointMgr, odoo::SyncOdoo}, threads::SessionInfo, utils::PathSanitizer as _}; use odoo_ls_server::S; @@ -13,17 +14,6 @@ use tracing::{info, level_filters::LevelFilter}; use tracing_appender::rolling::RollingFileAppender; use tracing_subscriber::{fmt, layer::SubscriberExt, FmtSubscriber}; -fn get_python_command() -> Option { - for cmd in &["python3", "python"] { - if let Ok(output) = Command::new(cmd).arg("--version").output() { - if output.status.success() { - return Some(S!(*cmd)); - } - } - } - None -} - pub fn setup_server(with_odoo: bool) -> SyncOdoo { let file_appender = RollingFileAppender::builder() diff --git a/server/tests/test_utils.rs b/server/tests/test_utils.rs index c3b5e15f..33bcbeab 100644 --- a/server/tests/test_utils.rs +++ b/server/tests/test_utils.rs @@ -29,7 +29,7 @@ pub static COUNTRY_CLASS_NAME: Lazy &'static str> = Lazy::new(|| { /// Helper to get hover markdown string at a given (line, character) pub fn get_hover_markdown(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option { - let hover = odoo_ls_server::features::hover::HoverFeature::get_hover( + let hover = odoo_ls_server::features::hover::HoverFeature::hover_python( session, file_symbol, file_info, diff --git a/vscode/package.json b/vscode/package.json index a119ec3f..13af774e 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -29,6 +29,9 @@ "mimetypes": ["text/csv"] } ], + "configurationDefaults": { + "editor.gotoLocation.alternativeDefinitionCommand": "" + }, "commands": [ { "command": "odoo.clickStatusBar",