From cde7da1124056cda9268578264dd82b328814b9d Mon Sep 17 00:00:00 2001 From: fda-odoo Date: Fri, 4 Apr 2025 16:25:32 +0200 Subject: [PATCH] [IMP] server: implement NOQA Noqas can now be used to prevent diagnostics to be raised for some part of the code. A noqa can be placed: - before the first expression to be effective on the whole file - before a class or a function to be effective on the whole class/function - at the end of a line to be effective on the current line Noqas can be written: --- server/src/core/evaluation.rs | 61 ++++---- server/src/core/file_mgr.rs | 162 ++++++++++++++++++++- server/src/core/import_resolver.rs | 6 +- server/src/core/python_arch_builder.rs | 50 ++++++- server/src/core/python_arch_eval.rs | 55 ++++--- server/src/core/python_arch_eval_hooks.rs | 17 ++- server/src/core/python_validator.rs | 52 ++++--- server/src/core/symbols/class_symbol.rs | 3 + server/src/core/symbols/file_symbol.rs | 4 +- server/src/core/symbols/function_symbol.rs | 4 +- server/src/core/symbols/module_symbol.rs | 38 ++--- server/src/core/symbols/package_symbol.rs | 4 +- server/src/core/symbols/symbol.rs | 37 ++++- server/src/threads.rs | 24 ++- 14 files changed, 393 insertions(+), 124 deletions(-) diff --git a/server/src/core/evaluation.rs b/server/src/core/evaluation.rs index e02c7fb4..32a69bf5 100644 --- a/server/src/core/evaluation.rs +++ b/server/src/core/evaluation.rs @@ -12,7 +12,7 @@ use crate::core::odoo::SyncOdoo; use crate::threads::SessionInfo; use crate::S; -use super::file_mgr::FileMgr; +use super::file_mgr::{add_diagnostic, FileMgr, NoqaInfo}; use super::python_validator::PythonValidator; use super::symbols::function_symbol::{Argument, ArgumentType, FunctionSymbol}; use super::symbols::symbol::Symbol; @@ -757,7 +757,7 @@ impl Evaluation { } let class_sym_weak_eval = class_sym_weak_eval.as_weak(); if class_sym_weak_eval.instance.unwrap_or(false) { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(expr.arguments.args[0].range().start().to_u32(), 0), Position::new(expr.arguments.args[0].range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), @@ -767,7 +767,7 @@ impl Evaluation { None, None ) - ); + , &session.current_noqa); None } else { let mut is_instance = None; @@ -793,7 +793,7 @@ impl Evaluation { } else { match parent.borrow().get_in_parents(&vec![SymType::CLASS], true){ None => { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(expr.range().start().to_u32(), 0), Position::new(expr.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), @@ -803,7 +803,7 @@ impl Evaluation { None, None ) - ); + , &session.current_noqa); None }, Some(parent_class) => Some((parent_class.clone(), Some(true))) @@ -929,7 +929,8 @@ impl Evaluation { expr, call_parent.clone(), module.clone(), - on_instance)); + on_instance, + )); } context.as_mut().unwrap().insert(S!("base_call"), ContextValue::SYMBOL(call_parent)); for eval in base_sym.borrow().evaluations().unwrap().iter() { @@ -1213,7 +1214,7 @@ impl Evaluation { } } if !pos_arg { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(exprCall.range().start().to_u32(), 0), Position::new(exprCall.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30315"))), @@ -1221,7 +1222,7 @@ impl Evaluation { format!("{} takes 0 positional arguments, but at least 1 is given", function.name), None, None, - )); + ), &session.current_noqa); return diagnostics; } arg_index += 1; @@ -1234,7 +1235,7 @@ impl Evaluation { //match arg with argument from function let function_arg = function.args.get(min(arg_index, vararg_index) as usize); if function_arg.is_none() || function_arg.unwrap().arg_type == ArgumentType::KWORD_ONLY || function_arg.unwrap().arg_type == ArgumentType::KWARG { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(exprCall.range().start().to_u32(), 0), Position::new(exprCall.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30315"))), @@ -1242,7 +1243,7 @@ impl Evaluation { format!("{} takes {} positional arguments, but at least {} is given", function.name, number_pos_arg, arg_index + 1), None, None, - )); + ), &session.current_noqa); return diagnostics; } if function_arg.unwrap().arg_type != ArgumentType::VARARG { @@ -1268,7 +1269,7 @@ impl Evaluation { } } if !found_one && kwarg_index == i32::MAX { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(exprCall.range().start().to_u32(), 0), Position::new(exprCall.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30316"))), @@ -1276,7 +1277,7 @@ impl Evaluation { format!("{} got an unexpected keyword argument '{}'", function.name, arg_identifier.id), None, None, - )) + ), &session.current_noqa) } } else { // if arg is None, it means that it is a **arg @@ -1284,7 +1285,7 @@ impl Evaluation { } } if found_pos_arg_with_kw + 1 < number_pos_arg { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(exprCall.range().start().to_u32(), 0), Position::new(exprCall.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30315"))), @@ -1292,7 +1293,7 @@ impl Evaluation { format!("{} takes {} positional arguments, but only {} is given", function.name, number_pos_arg, arg_index), None, None, - )); + ), &session.current_noqa); return diagnostics; } diagnostics @@ -1312,7 +1313,7 @@ impl Evaluation { Expr::Tuple(t) => { need_tuple = max(need_tuple - 1, 0); if t.elts.len() != 3 { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(t.range().start().to_u32(), 0), Position::new(t.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30314"))), @@ -1320,7 +1321,7 @@ impl Evaluation { format!("Domain tuple should have 3 elements"), None, None, - )); + ), &session.current_noqa); } else { Evaluation::validate_tuple_search_domain(session, on_object.clone(), from_module.clone(), &t.elts[0], &t.elts[1], &t.elts[2], &mut diagnostics); } @@ -1328,7 +1329,7 @@ impl Evaluation { Expr::List(l) => { need_tuple = max(need_tuple - 1, 0); if l.elts.len() != 3 { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(l.range().start().to_u32(), 0), Position::new(l.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30314"))), @@ -1336,7 +1337,7 @@ impl Evaluation { format!("Domain tuple should have 3 elements"), None, None, - )); + ), &session.current_noqa); } else { Evaluation::validate_tuple_search_domain(session, on_object.clone(), from_module.clone(), &l.elts[0], &l.elts[1], &l.elts[2], &mut diagnostics); } @@ -1356,7 +1357,7 @@ impl Evaluation { } } _ => { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(s.range().start().to_u32(), 0), Position::new(s.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30317"))), @@ -1364,7 +1365,7 @@ impl Evaluation { format!("A String value in tuple should contains '&', '|' or '!'"), None, None, - )); + ), &session.current_noqa); } } }, @@ -1373,7 +1374,7 @@ impl Evaluation { } } if need_tuple > 0 { - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(value.range().start().to_u32(), 0), Position::new(value.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30319"))), @@ -1381,7 +1382,7 @@ impl Evaluation { format!("Missing tuple after a search domain operator"), None, None, - )); + ), &session.current_noqa); } diagnostics } @@ -1398,7 +1399,7 @@ impl Evaluation { 'split_name: for name in split_expr { if date_mode { if !["year_number", "quarter_number", "month_number", "iso_week_number", "day_of_week", "day_of_month", "day_of_year", "hour_number", "minute_number", "second_number"].contains(&name) { - diagnostics.push(Diagnostic::new( + add_diagnostic(diagnostics, Diagnostic::new( Range::new(Position::new(s.range().start().to_u32(), 0), Position::new(s.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30321"))), @@ -1406,13 +1407,13 @@ impl Evaluation { format!("Invalid search domain field: Unknown granularity for date field. Use either \"year_number\", \"quarter_number\", \"month_number\", \"iso_week_number\", \"day_of_week\", \"day_of_month\", \"day_of_year\", \"hour_number\", \"minute_number\" or \"second_number\""), None, None, - )); + ), &session.current_noqa); } date_mode = false; continue; } if obj.is_none() { - diagnostics.push(Diagnostic::new( + add_diagnostic(diagnostics, Diagnostic::new( Range::new(Position::new(s.range().start().to_u32(), 0), Position::new(s.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30322"))), @@ -1422,7 +1423,7 @@ In a search domain, when using a dot separator, it should be used either on a Da If you used a relational field and get this error, check that the comodel of this field is valid."), None, None, - )); + ), &session.current_noqa); break; } if let Some(object) = &obj { @@ -1434,7 +1435,7 @@ If you used a relational field and get this error, check that the comodel of thi false, false); if symbols.is_empty() { - diagnostics.push(Diagnostic::new( + add_diagnostic(diagnostics, Diagnostic::new( Range::new(Position::new(s.range().start().to_u32(), 0), Position::new(s.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30320"))), @@ -1442,7 +1443,7 @@ If you used a relational field and get this error, check that the comodel of thi format!("Invalid search domain field: {} is not a member of {}", name, object.borrow().name()), None, None, - )); + ), &session.current_noqa); break; } obj = None; @@ -1478,7 +1479,7 @@ If you used a relational field and get this error, check that the comodel of thi "=" | "!=" | ">" | ">=" | "<" | "<=" | "=?" | "=like" | "like" | "not like" | "ilike" | "not ilike" | "=ilike" | "in" | "not in" | "child_of" | "parent_of" | "any" | "not any" => {}, _ => { - diagnostics.push(Diagnostic::new( + add_diagnostic(diagnostics, Diagnostic::new( Range::new(Position::new(s.range().start().to_u32(), 0), Position::new(s.range().end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30318"))), @@ -1486,7 +1487,7 @@ If you used a relational field and get this error, check that the comodel of thi format!("Invalid comparison operator"), None, None, - )); + ), &session.current_noqa); } } }, diff --git a/server/src/core/file_mgr.rs b/server/src/core/file_mgr.rs index ec77fb90..766731fe 100644 --- a/server/src/core/file_mgr.rs +++ b/server/src/core/file_mgr.rs @@ -1,10 +1,11 @@ use lsp_types::notification::{Notification, PublishDiagnostics}; use ropey::Rope; use ruff_python_ast::Mod; -use ruff_python_parser::Mode; +use ruff_python_parser::{Mode, Parsed, Token, TokenKind}; use lsp_types::{Diagnostic, DiagnosticSeverity, MessageType, NumberOrString, Position, PublishDiagnosticsParams, Range, TextDocumentContentChangeEvent}; use tracing::{error, warn}; use std::collections::hash_map::DefaultHasher; +use std::collections::HashSet; use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::str::FromStr; @@ -15,10 +16,33 @@ use std::rc::Rc; use std::cell::RefCell; use crate::S; use crate::constants::*; -use ruff_text_size::TextRange; +use ruff_text_size::{Ranged, TextRange}; use super::odoo::SyncOdoo; +#[derive(Debug, PartialEq, Clone)] +pub enum NoqaInfo { + None, + All, + Codes(Vec), +} + +pub fn combine_noqa_info(noqas: &Vec) -> NoqaInfo { + let mut codes = HashSet::new(); + for noqa in noqas.iter() { + match noqa { + NoqaInfo::None => {}, + NoqaInfo::All => { + return NoqaInfo::All; + } + NoqaInfo::Codes(c) => { + codes.extend(c.iter().cloned()); + } + } + } + NoqaInfo::Codes(codes.iter().cloned().collect()) +} + #[derive(Debug)] pub struct FileInfo { pub ast: Option>, @@ -30,6 +54,8 @@ pub struct FileInfo { need_push: bool, text_rope: Option, diagnostics: HashMap>, + pub noqas_blocs: HashMap, + noqas_lines: HashMap, } impl FileInfo { @@ -44,9 +70,11 @@ impl FileInfo { text_rope: None, text_hash: 0, diagnostics: HashMap::new(), + noqas_blocs: HashMap::new(), + noqas_lines: HashMap::new(), } } - pub fn update(&mut self, session: &mut SessionInfo, uri: &str, content: Option<&Vec>, version: Option, force: bool) -> bool { + pub fn update(&mut self, session: &mut SessionInfo, uri: &str, content: Option<&Vec>, version: Option, in_workspace: bool, force: bool) -> bool { // update the file info with the given information. // uri: indicates the path of the file // content: if content is given, it will be used to update the ast and text_rope, if not, the loading will be from the disk @@ -90,15 +118,20 @@ impl FileInfo { if old_hash == self.text_hash { return false; } - self._build_ast(); + self._build_ast(in_workspace); true } - pub fn _build_ast(&mut self) { + pub fn _build_ast(&mut self, in_workspace: bool) { let mut diagnostics = vec![]; let content = &self.text_rope.as_ref().unwrap().slice(..); let source = content.to_string(); //cast to string to get a version with all changes let ast = ruff_python_parser::parse_unchecked(source.as_str(), Mode::Module); + if in_workspace { + self.noqas_blocs.clear(); + self.noqas_lines.clear(); + self.extract_tokens(&ast, &source); + } self.valid = true; for error in ast.errors().iter() { self.valid = false; @@ -124,6 +157,70 @@ impl FileInfo { self.replace_diagnostics(BuildSteps::SYNTAX, diagnostics); } + pub fn extract_tokens(&mut self, ast: &Parsed, source: &String) { + let mut is_first_expr: bool = true; + let mut noqa_to_add = None; + let mut previous_token: Option<&Token> = None; + for token in ast.tokens().iter() { + match token.kind() { + TokenKind::Comment => { + let text = &source[token.range()]; + if text.starts_with("#noqa") || text.starts_with("# noqa") || text.starts_with("# odools: noqa") { + let after_noqa = text.split("noqa").skip(1).next(); + if let Some(after_noqa) = after_noqa { + let mut codes = vec![]; + for code in after_noqa.split(|c: char| c == ',' || c.is_whitespace() || c == ':') { + let code = code.trim(); + if code.len() > 0 { + codes.push(code.to_string()); + } + } + if codes.len() > 0 { + noqa_to_add = Some(NoqaInfo::Codes(codes)); + } else { + noqa_to_add = Some(NoqaInfo::All); + } + let char = self.text_rope.as_ref().unwrap().try_byte_to_char(token.start().to_usize()).expect("unable to get char from bytes"); + let line = self.text_rope.as_ref().unwrap().try_char_to_line(char).ok().expect("unable to get line from char"); + if let Some(previous_token) = previous_token { + let previous_token_char = self.text_rope.as_ref().unwrap().try_byte_to_char(previous_token.start().to_usize()).expect("unable to get char from bytes"); + let previous_token_line = self.text_rope.as_ref().unwrap().try_char_to_line(previous_token_char).ok().expect("unable to get line from char"); + if previous_token_line == line { + self.noqas_lines.insert(line as u32, noqa_to_add.unwrap()); + noqa_to_add = None; + continue; + } + } + if is_first_expr { + self.add_noqa_bloc(0, noqa_to_add.unwrap()); + noqa_to_add = None; + } + } + } + }, + TokenKind::Class | TokenKind::Def => { + if noqa_to_add.is_some() { + self.add_noqa_bloc(token.range().start().to_u32(), noqa_to_add.unwrap()); + noqa_to_add = None; + } + } + TokenKind::NonLogicalNewline => {} + _ => { + is_first_expr = false + } + } + previous_token = Some(token); + } + } + + fn add_noqa_bloc(&mut self, index: u32, noqa_to_add: NoqaInfo) { + if let Some(noqa_bloc) = self.noqas_blocs.remove(&index) { + self.noqas_blocs.insert(index, combine_noqa_info(&vec![noqa_bloc, noqa_to_add])); + } else { + self.noqas_blocs.insert(index, noqa_to_add.clone()); + } + } + pub fn replace_diagnostics(&mut self, step: BuildSteps, diagnostics: Vec) { self.need_push = true; self.diagnostics.insert(step, diagnostics); @@ -148,7 +245,33 @@ impl FileInfo { for diagnostics in self.diagnostics.values() { for d in diagnostics.iter() { - all_diagnostics.push(self.update_range(d.clone())); + //check noqa lines + let updated = self.update_range(d.clone()); + let updated_line = updated.range.start.line; + if let Some(noqa_line) = self.noqas_lines.get(&updated_line) { + match noqa_line { + NoqaInfo::None => {}, + NoqaInfo::All => { + continue; + } + NoqaInfo::Codes(codes) => { + match &updated.code { + None => {continue;}, + Some(NumberOrString::Number(n)) => { + if codes.contains(&n.to_string()) { + continue; + } + }, + Some(NumberOrString::String(s)) => { + if codes.contains(&s) { + continue; + } + } + } + } + } + } + all_diagnostics.push(updated); } } session.send_notification::(PublishDiagnostics::METHOD, PublishDiagnosticsParams{ @@ -255,7 +378,7 @@ impl FileMgr { let mut updated: bool = false; if (version.is_some() && version.unwrap() != -100) || !file_info.borrow().opened { let mut file_info_mut = (*return_info).borrow_mut(); - updated = file_info_mut.update(session, uri, content, version, force); + updated = file_info_mut.update(session, uri, content, version, self.is_in_workspace(uri), force); drop(file_info_mut); } (updated, return_info) @@ -351,3 +474,28 @@ impl FileMgr { S!(s) } } + +pub fn add_diagnostic(diagnostic_vec: &mut Vec, new_diagnostic: Diagnostic, noqa: &NoqaInfo){ + match noqa { + NoqaInfo::None => {}, + NoqaInfo::All => { + return; + } + NoqaInfo::Codes(codes) => { + match &new_diagnostic.code { + None => {} + Some(NumberOrString::Number(n)) => { + if codes.contains(&n.to_string()) { + return; + } + } + Some(NumberOrString::String(s)) => { + if codes.contains(&s) { + return; + } + } + } + } + } + diagnostic_vec.push(new_diagnostic); +} diff --git a/server/src/core/import_resolver.rs b/server/src/core/import_resolver.rs index e738b578..33227121 100644 --- a/server/src/core/import_resolver.rs +++ b/server/src/core/import_resolver.rs @@ -13,6 +13,7 @@ use crate::threads::SessionInfo; use crate::utils::{is_dir_cs, is_file_cs, PathSanitizer}; use super::entry_point::{self, EntryPoint, EntryPointType}; +use super::file_mgr::{add_diagnostic, NoqaInfo}; use super::odoo::SyncOdoo; use super::symbols::symbol::Symbol; @@ -36,8 +37,7 @@ fn resolve_import_stmt_hook(alias: &Alias, from_symbol: &Option= 17 && alias.name.as_str() == "Form" && (*(from_symbol.as_ref().unwrap())).borrow().get_main_entry_tree(session).0 == vec!["odoo", "tests", "common"]{ let mut results = resolve_import_stmt(session, source_file_symbol, Some(&Identifier::new(S!("odoo.tests"), from_stmt.unwrap().range)), &[alias.clone()], level, &mut None); if let Some(diagnostic) = diagnostics.as_mut() { - diagnostic.push( - Diagnostic::new( + add_diagnostic(diagnostic, Diagnostic::new( Range::new(Position::new(alias.range.start().to_u32(), 0), Position::new(alias.range.end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20006"))), @@ -46,7 +46,7 @@ fn resolve_import_stmt_hook(alias: &Alias, from_symbol: &Option, diagnostics: Vec, ast_indexes: Vec, + file_info: Option>>, } impl PythonArchBuilder { @@ -49,6 +52,7 @@ impl PythonArchBuilder { __all_symbols_to_add: Vec::new(), diagnostics: vec![], ast_indexes: vec![], + file_info: None, } } @@ -85,6 +89,7 @@ impl PythonArchBuilder { }, false => {session.sync_odoo.get_file_mgr().borrow().get_file_info(&path).unwrap()} }; + self.file_info = Some(file_info_rc.clone()); if !file_info_rc.borrow().valid { symbol.borrow_mut().set_build_status(BuildSteps::ARCH, BuildStatus::PENDING); return @@ -97,15 +102,34 @@ impl PythonArchBuilder { let file_info = file_info_rc.borrow(); if file_info.ast.is_some() { let ast = match self.file_mode { - true => {file_info.ast.as_ref().unwrap()}, + true => { + file_info.ast.as_ref().unwrap() + }, false => { &AstUtils::find_stmt_from_ast(file_info.ast.as_ref().unwrap(), self.sym_stack[0].borrow().ast_indexes().unwrap()).as_function_def_stmt().unwrap().body } }; - if self.file_mode { + let old_stack_noqa = session.noqas_stack.clone(); + session.noqas_stack.clear(); + let old_noqa = if self.file_mode { + let file_noqa = file_info.noqas_blocs.get(&0); + if let Some(file_noqa) = file_noqa { + session.noqas_stack.push(file_noqa.clone()); + } + symbol.borrow_mut().set_noqas(combine_noqa_info(&session.noqas_stack)); //only set for file, functions are set in visit_func_def + let old = session.current_noqa.clone(); + session.current_noqa = symbol.borrow().get_noqas().clone(); symbol.borrow_mut().set_processed_text_hash(file_info.text_hash); - } + old + } else { + session.noqas_stack.push(symbol.borrow().get_noqas().clone()); + let old = session.current_noqa.clone(); + session.current_noqa = symbol.borrow().get_noqas().clone(); + old + }; self.visit_node(session, &ast); + session.current_noqa = old_noqa; + session.noqas_stack = old_stack_noqa; self._resolve_all_symbols(session); if self.file_mode { session.sync_odoo.add_to_rebuild_arch_eval(self.sym_stack[0].clone()); @@ -625,6 +649,13 @@ impl PythonArchBuilder { annotation: arg.annotation.clone(), }); } + let mut add_noqa = false; + if let Some(noqa_bloc) = self.file_info.as_ref().unwrap().borrow().noqas_blocs.get(&func_def.range.start().to_u32()) { + session.noqas_stack.push(noqa_bloc.clone()); + add_noqa = true; + } + sym.borrow_mut().set_noqas(combine_noqa_info(&session.noqas_stack)); + session.current_noqa = sym.borrow().get_noqas().clone(); //visit body if !self.file_mode || sym.borrow().get_in_parents(&vec![SymType::CLASS], true).is_none() { sym.borrow_mut().as_func_mut().arch_status = BuildStatus::IN_PROGRESS; @@ -633,6 +664,9 @@ impl PythonArchBuilder { self.sym_stack.pop(); sym.borrow_mut().as_func_mut().arch_status = BuildStatus::DONE; } + if add_noqa { + session.noqas_stack.pop(); + } Ok(()) } @@ -655,9 +689,19 @@ impl PythonArchBuilder { } } drop(sym_bw); + let mut add_noqa = false; + if let Some(noqa_bloc) = self.file_info.as_ref().unwrap().borrow().noqas_blocs.get(&class_def.range.start().to_u32()) { + session.noqas_stack.push(noqa_bloc.clone()); + add_noqa = true; + } + sym.borrow_mut().set_noqas(combine_noqa_info(&session.noqas_stack)); + session.current_noqa = sym.borrow().get_noqas().clone(); self.sym_stack.push(sym.clone()); self.visit_node(session, &class_def.body)?; self.sym_stack.pop(); + if add_noqa { + session.noqas_stack.pop(); + } PythonArchBuilderHooks::on_class_def(session, sym); Ok(()) } diff --git a/server/src/core/python_arch_eval.rs b/server/src/core/python_arch_eval.rs index b2b3c02f..d17a654a 100644 --- a/server/src/core/python_arch_eval.rs +++ b/server/src/core/python_arch_eval.rs @@ -22,7 +22,7 @@ use crate::S; use super::config::DiagMissingImportsMode; use super::entry_point::EntryPoint; use super::evaluation::{ContextValue, EvaluationSymbolPtr, EvaluationSymbolWeak}; -use super::file_mgr::FileMgr; +use super::file_mgr::{add_diagnostic, FileInfo, FileMgr}; use super::import_resolver::ImportResult; use super::python_arch_eval_hooks::PythonArchEvalHooks; use super::python_utils::Assign; @@ -80,6 +80,8 @@ impl PythonArchEval { let file_info_rc = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path).expect("File not found in cache").clone(); let file_info = (*file_info_rc).borrow(); if file_info.ast.is_some() { + let old_noqa = session.current_noqa.clone(); + session.current_noqa = symbol.borrow().get_noqas(); let (ast, maybe_func_stmt) = match self.file_mode { true => { if file_info.text_hash != symbol.borrow().get_processed_text_hash(){ @@ -99,6 +101,7 @@ impl PythonArchEval { PythonArchEval::handle_function_returns(session, maybe_func_stmt, &self.sym_stack[0], &ast.last().unwrap().range().end(), &mut self.diagnostics); PythonArchEval::handle_func_evaluations(ast, &self.sym_stack[0]); } + session.current_noqa = old_noqa; } drop(file_info); if self.file_mode { @@ -395,7 +398,7 @@ impl PythonArchEval { self.file.borrow_mut().not_found_paths_mut().push((self.current_step, file_tree.clone())); self.entry_point.borrow_mut().not_found_symbols.insert(self.file.clone()); if self._match_diag_config(session.sync_odoo, &_import_result.symbol) { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(_import_result.range.start().to_u32(), 0), Position::new(_import_result.range.end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20004"))), @@ -403,7 +406,7 @@ impl PythonArchEval { format!("Failed to evaluate import {}", file_tree.clone().join(".")), None, None, - )); + ), &session.current_noqa); } } @@ -417,7 +420,7 @@ impl PythonArchEval { self.file.borrow_mut().not_found_paths_mut().push((self.current_step, file_tree.clone())); self.entry_point.borrow_mut().not_found_symbols.insert(self.file.clone()); if self._match_diag_config(session.sync_odoo, &_import_result.symbol) { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(_import_result.range.start().to_u32(), 0), Position::new(_import_result.range.end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20001"))), @@ -425,7 +428,7 @@ impl PythonArchEval { format!("{} not found", file_tree.clone().join(".")), None, None, - )); + ), &session.current_noqa); } } } @@ -511,7 +514,7 @@ impl PythonArchEval { let tree = flatten_tree(tree_not_found); file.not_found_paths_mut().push((BuildSteps::ARCH_EVAL, tree.clone())); self.entry_point.borrow_mut().not_found_symbols.insert(file.get_rc().unwrap()); - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(range.start().to_u32(), 0), Position::new(range.end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20002"))), @@ -519,7 +522,7 @@ impl PythonArchEval { format!("{} not found", tree.join(".")), None, None, - )); + ), &session.current_noqa); } fn load_base_classes(&mut self, session: &mut SessionInfo, loc_sym: &Rc>, class_stmt: &StmtClassDef) { @@ -535,7 +538,7 @@ impl PythonArchEval { continue; } if eval_base.len() > 1 { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(base.range().start().to_u32(), 0), Position::new(base.range().end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20005"))), @@ -543,14 +546,14 @@ impl PythonArchEval { format!("Multiple definition found for base class {}", AstUtils::flatten_expr(base)), None, None, - )); + ), &session.current_noqa); continue; } let eval_base = &eval_base[0]; let eval_symbol = eval_base.symbol.get_symbol(session, &mut None, &mut vec![], None); let ref_sym = Symbol::follow_ref(&eval_symbol, session, &mut None, false, false, None, &mut vec![]); if ref_sym.len() > 1 { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(base.range().start().to_u32(), 0), Position::new(base.range().end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20005"))), @@ -558,7 +561,7 @@ impl PythonArchEval { format!("Multiple definition found for base class {}", AstUtils::flatten_expr(base)), None, None, - )); + ), &session.current_noqa); continue; } let symbol = &ref_sym[0].upgrade_weak(); @@ -566,7 +569,7 @@ impl PythonArchEval { if symbol.borrow().typ() != SymType::COMPILED { if symbol.borrow().typ() != SymType::CLASS { if symbol.borrow().typ() != SymType::VARIABLE { //we followed_ref already, so if it's still a variable, it means we can't evaluate it. Skip diagnostic - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(base.start().to_u32(), 0), Position::new(base.end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20003"))), @@ -574,7 +577,7 @@ impl PythonArchEval { format!("Base class {} is not a class", AstUtils::flatten_expr(base)), None, None, - )); + ), &session.current_noqa); } } else { let file_symbol = symbol.borrow().get_file().unwrap().upgrade().unwrap(); @@ -598,9 +601,12 @@ impl PythonArchEval { panic!("Class not found"); } self.load_base_classes(session, variable.as_ref().unwrap(), class_stmt); + let old_noqa = session.current_noqa.clone(); + session.current_noqa = variable.as_ref().unwrap().borrow().get_noqas(); self.sym_stack.push(variable.unwrap().clone()); self.visit_sub_stmts(session, &class_stmt.body); self.sym_stack.pop(); + session.current_noqa = old_noqa; } fn visit_func_def(&mut self, session: &mut SessionInfo, func_stmt: &StmtFunctionDef) { @@ -642,7 +648,7 @@ impl PythonArchEval { } } } else if !function_sym.borrow_mut().as_func_mut().is_static{ - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( FileMgr::textRange_to_temporary_Range(&func_stmt.range), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30002"))), @@ -650,14 +656,17 @@ impl PythonArchEval { S!("Non-static method should have at least one parameter"), None, None - )) + ), &session.current_noqa) } } if !self.file_mode || function_sym.borrow().get_in_parents(&vec![SymType::CLASS], true).is_none() { function_sym.borrow_mut().as_func_mut().arch_eval_status = BuildStatus::IN_PROGRESS; + let old_noqa = session.current_noqa.clone(); + session.current_noqa = function_sym.borrow().get_noqas(); self.sym_stack.push(function_sym.clone()); self.visit_sub_stmts(session, &func_stmt.body); self.sym_stack.pop(); + session.current_noqa = old_noqa; PythonArchEval::handle_function_returns(session, Some(func_stmt), &function_sym, &func_stmt.range.end(), &mut self.diagnostics); PythonArchEval::handle_func_evaluations(&func_stmt.body, &function_sym); function_sym.borrow_mut().as_func_mut().arch_eval_status = BuildStatus::DONE; @@ -872,7 +881,7 @@ impl PythonArchEval { return; } let Some(model) = session.sync_odoo.models.get(&oyarn!("{}", returns_str)).cloned() else { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( FileMgr::textRange_to_temporary_Range(&expr.range()), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30102"))), @@ -880,11 +889,11 @@ impl PythonArchEval { S!("Unknown model. Check your addons path"), None, None, - )); + ), &session.current_noqa); return; }; let Some(ref main_model_sym) = model.borrow().get_main_symbols(session, func_sym.borrow().find_module()).first().cloned() else { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( FileMgr::textRange_to_temporary_Range(&expr.range()), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30101"))), @@ -892,7 +901,7 @@ impl PythonArchEval { S!("This model is not in the dependencies of your module."), None, None, - )); + ), &session.current_noqa); return }; func_sym.borrow_mut().set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(main_model_sym), Some(false))]); @@ -917,7 +926,7 @@ impl PythonArchEval { let field_name = expr.value.to_string(); let (syms, _) = class_sym.borrow().get_member_symbol(session, &field_name, from_module.clone(), false, false, true, false); if syms.is_empty(){ - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( FileMgr::textRange_to_temporary_Range(&expr.range()), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30323"))), @@ -925,7 +934,7 @@ impl PythonArchEval { format!("Field {field_name} does not exist on model {model_name}"), None, None, - )); + ), &session.current_noqa); } } } @@ -990,7 +999,7 @@ impl PythonArchEval { let field_name = expr.value.to_string(); let syms = PythonArchEval::get_nested_sub_field(session, &field_name, class_sym.clone(), from_module.clone()); if syms.is_empty(){ - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( FileMgr::textRange_to_temporary_Range(&expr.range()), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30323"))), @@ -998,7 +1007,7 @@ impl PythonArchEval { format!("Field {field_name} does not exist on model {model_name}"), None, None, - )); + ), &session.current_noqa); } } } diff --git a/server/src/core/python_arch_eval_hooks.rs b/server/src/core/python_arch_eval_hooks.rs index b1974fcb..7cb0eea1 100644 --- a/server/src/core/python_arch_eval_hooks.rs +++ b/server/src/core/python_arch_eval_hooks.rs @@ -21,6 +21,7 @@ use crate::S; use super::entry_point::EntryPoint; use super::evaluation::{ContextValue, Evaluation, EvaluationSymbolPtr, EvaluationSymbol, EvaluationSymbolWeak}; +use super::file_mgr::add_diagnostic; use super::file_mgr::FileMgr; use super::python_arch_eval::PythonArchEval; use super::symbols::module_symbol::ModuleSymbol; @@ -464,7 +465,7 @@ impl PythonArchEvalHooks { let symbols = model.get_main_symbols(session, None); if symbols.is_empty() { let range = FileMgr::textRange_to_temporary_Range(&context.get(&S!("range")).unwrap().as_text_range()); - diagnostics.push(Diagnostic::new(range, + add_diagnostic(diagnostics, Diagnostic::new(range, Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30105"))), Some(EXTENSION_NAME.to_string()), @@ -472,14 +473,14 @@ impl PythonArchEvalHooks { None, None ) - ); + , &session.current_noqa); } else { let range = FileMgr::textRange_to_temporary_Range(&context.get(&S!("range")).unwrap().as_text_range()); let valid_modules: Vec = symbols.iter().map(|s| match s.borrow().find_module() { Some(sym) => sym.borrow().name().clone(), None => Sy!("Unknown").clone() }).collect(); - diagnostics.push(Diagnostic::new(range, + add_diagnostic(diagnostics, Diagnostic::new(range, Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30101"))), Some(EXTENSION_NAME.to_string()), @@ -487,30 +488,30 @@ impl PythonArchEvalHooks { None, None ) - ); + , &session.current_noqa); } } else { let range = FileMgr::textRange_to_temporary_Range(&context.get(&S!("range")).unwrap().as_text_range()); - diagnostics.push(Diagnostic::new(range, + add_diagnostic(diagnostics, Diagnostic::new(range, Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30102"))), Some(EXTENSION_NAME.to_string()), S!("Unknown model. Check your addons path"), None, None - )); + ), &session.current_noqa); } } } else { let range = FileMgr::textRange_to_temporary_Range(&context.get(&S!("range")).unwrap().as_text_range()); - diagnostics.push(Diagnostic::new(range, + add_diagnostic(diagnostics, Diagnostic::new(range, Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30102"))), None, S!("Unknown model. Check your addons path"), None, None - )); + ), &session.current_noqa); } } _ => { diff --git a/server/src/core/python_validator.rs b/server/src/core/python_validator.rs index 6ef7c88d..adfc9f86 100644 --- a/server/src/core/python_validator.rs +++ b/server/src/core/python_validator.rs @@ -17,7 +17,7 @@ use crate::S; use super::entry_point::EntryPoint; use super::evaluation::{Evaluation, EvaluationSymbolPtr, EvaluationSymbolWeak, EvaluationValue}; -use super::file_mgr::{FileInfo, FileMgr}; +use super::file_mgr::{add_diagnostic, FileInfo, FileMgr}; use super::python_arch_builder::PythonArchBuilder; use super::python_arch_eval::PythonArchEval; @@ -28,7 +28,8 @@ pub struct PythonValidator { sym_stack: Vec>>, pub diagnostics: Vec, //collect diagnostic from arch and arch_eval too from inner functions, but put everything at Validation level safe_imports: Vec, - current_module: Option>> + current_module: Option>>, + file_info: Option>>, } /* PythonValidator operate on a single Symbol. Unlike other steps, it can be done on symbol containing code (file and functions only. Not class, variable, namespace). @@ -43,6 +44,7 @@ impl PythonValidator { diagnostics: vec![], safe_imports: vec![false], current_module: None, + file_info: None, } } @@ -67,6 +69,7 @@ impl PythonValidator { let sym_type = symbol.typ().clone(); drop(symbol); let file_info_rc = self.get_file_info(session.sync_odoo).clone(); + self.file_info = Some(file_info_rc.clone()); match sym_type { SymType::FILE | SymType::PACKAGE(_) => { if self.sym_stack[0].borrow().build_status(BuildSteps::ODOO) != BuildStatus::DONE { @@ -83,7 +86,10 @@ impl PythonValidator { return; } if file_info.ast.is_some() && file_info.valid { + let old_noqa = session.current_noqa.clone(); + session.current_noqa = self.sym_stack[0].borrow().get_noqas(); self.validate_body(session, file_info.ast.as_ref().unwrap()); + session.current_noqa = old_noqa; } drop(file_info); let mut file_info = file_info_rc.borrow_mut(); @@ -127,7 +133,10 @@ impl PythonValidator { }, _ => {panic!("Wrong statement in validation ast extraction {} ", sym_type)} }; + let old_noqa = session.current_noqa.clone(); + session.current_noqa = self.sym_stack[0].borrow().get_noqas(); self.validate_body(session, body); + session.current_noqa = old_noqa; match stmt { Stmt::FunctionDef(_) => { self.sym_stack[0].borrow_mut().as_func_mut().diagnostics.insert(BuildSteps::VALIDATION, self.diagnostics.clone()); @@ -242,9 +251,12 @@ impl PythonValidator { let sym = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&OYarn::from(c.name.to_string()), &c.range); if let Some(sym) = sym { self._check_model(session, &sym); + let old_noqa = session.current_noqa.clone(); + session.current_noqa = sym.borrow().get_noqas().clone(); self.sym_stack.push(sym); self.validate_body(session, &c.body); self.sym_stack.pop(); + session.current_noqa = old_noqa; } else { //TODO panic!("symbol not found."); } @@ -288,7 +300,7 @@ impl PythonValidator { let module = symbol.borrow().find_module(); if let Some(module) = module { if !ModuleSymbol::is_in_deps(session, self.current_module.as_ref().unwrap(), &module.borrow().as_module_package().dir_name) && !self.safe_imports.last().unwrap() { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(alias.range.start().to_u32(), 0), Position::new(alias.range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30103"))), @@ -296,7 +308,7 @@ impl PythonValidator { format!("{} is not in the dependencies of the module", module.borrow().as_module_package().dir_name), None, None, - )) + ), &session.current_noqa) } } } @@ -364,7 +376,7 @@ impl PythonValidator { }; let syms = PythonArchEval::get_nested_sub_field(session, &related_field_name, class.clone(), maybe_from_module.clone()); if syms.is_empty(){ - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(special_arg_range.start().to_u32(), 0), Position::new(special_arg_range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30323"))), @@ -372,7 +384,7 @@ impl PythonValidator { format!("Field {related_field_name} does not exist on model {}", model_data.name), None, None, - )); + ), &session.current_noqa); continue; } let field_type = symbol.borrow().name().clone(); @@ -391,7 +403,7 @@ impl PythonValidator { }) }); if !found_same_type_match{ - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(special_arg_range.start().to_u32(), 0), Position::new(special_arg_range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30326"))), @@ -399,7 +411,7 @@ impl PythonValidator { format!("Related field is not of the same type"), None, None, - )); + ), &session.current_noqa); } } else if let Some(comodel_field_name) = eval_weak.as_weak().context.get(&S!("comodel")).map(|ctx_val| ctx_val.as_string()) { @@ -413,7 +425,7 @@ impl PythonValidator { if let Some(model) = session.sync_odoo.models.get(&oyarn!("{}", comodel_field_name)){ let Some(ref from_module) = maybe_from_module else {continue}; if !model.clone().borrow().model_in_deps(session, from_module) { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(special_arg_range.start().to_u32(), 0), Position::new(special_arg_range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30324"))), @@ -421,10 +433,10 @@ impl PythonValidator { format!("Field comodel_name ({comodel_field_name}) is not in module dependencies"), None, None, - )); + ), &session.current_noqa); } } else { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(special_arg_range.start().to_u32(), 0), Position::new(special_arg_range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30325"))), @@ -432,7 +444,7 @@ impl PythonValidator { format!("Field comodel_name ({comodel_field_name}) does not exist"), None, None, - )); + ), &session.current_noqa); } } } @@ -456,7 +468,7 @@ impl PythonValidator { let Some(arg_range) = eval_weak.as_weak().context.get(&format!("{special_fn_field_name}_range")).map(|ctx_val| ctx_val.as_text_range()) else { continue; }; - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(arg_range.start().to_u32(), 0), Position::new(arg_range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30327"))), @@ -464,7 +476,7 @@ impl PythonValidator { format!("Method {method_name} not found on current model"), None, None, - )); + ), &session.current_noqa); } } @@ -527,7 +539,7 @@ impl PythonValidator { } if !found_one { if !main_modules.is_empty() { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(range.start().to_u32(), 0), Position::new(range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30104"))), @@ -535,9 +547,9 @@ impl PythonValidator { S!("Model is inheriting from a model not declared in the dependencies of the module. Check the manifest."), None, None) - ) + , &session.current_noqa) } else { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(range.start().to_u32(), 0), Position::new(range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30102"))), @@ -545,11 +557,11 @@ impl PythonValidator { S!("Unknown model. Check your addons path"), None, None) - ) + , &session.current_noqa) } } } else { - self.diagnostics.push(Diagnostic::new( + add_diagnostic(&mut self.diagnostics, Diagnostic::new( Range::new(Position::new(range.start().to_u32(), 0), Position::new(range.end().to_u32(), 0)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30102"))), @@ -557,7 +569,7 @@ impl PythonValidator { S!("Unknown model. Check your addons path"), None, None) - ) + , &session.current_noqa) } } else { //TODO do we want to raise something? diff --git a/server/src/core/symbols/class_symbol.rs b/server/src/core/symbols/class_symbol.rs index 1a83b30c..408c7f60 100644 --- a/server/src/core/symbols/class_symbol.rs +++ b/server/src/core/symbols/class_symbol.rs @@ -6,6 +6,7 @@ use std::cell::RefCell; use weak_table::PtrWeakHashSet; use crate::constants::{OYarn, SymType}; +use crate::core::file_mgr::NoqaInfo; use crate::core::model::ModelData; use crate::threads::SessionInfo; use crate::{Sy, S}; @@ -26,6 +27,7 @@ pub struct ClassSymbol { pub range: TextRange, pub body_range: TextRange, pub _model: Option, + pub noqas: NoqaInfo, //Trait SymbolMgr //--- Body symbols @@ -52,6 +54,7 @@ impl ClassSymbol { ext_symbols: HashMap::new(), bases: vec![], _model: None, + noqas: NoqaInfo::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 521403e4..c127e6f7 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; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::model::Model, oyarn}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model}, oyarn}; use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}}; use super::{symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; @@ -23,6 +23,7 @@ pub struct FileSymbol { pub dependencies: Vec>>>>>, pub dependents: Vec>>>>>, pub processed_text_hash: u64, + pub noqas: NoqaInfo, //Trait SymbolMgr pub sections: Vec, @@ -54,6 +55,7 @@ impl FileSymbol { dependencies: vec![], dependents: vec![], processed_text_hash: 0, + noqas: NoqaInfo::None, }; res._init_symbol_mgr(); res diff --git a/server/src/core/symbols/function_symbol.rs b/server/src/core/symbols/function_symbol.rs index d7a1e9a4..4f81bc51 100644 --- a/server/src/core/symbols/function_symbol.rs +++ b/server/src/core/symbols/function_symbol.rs @@ -5,7 +5,7 @@ use ruff_python_ast::{Expr, ExprCall}; use ruff_text_size::{TextRange, TextSize}; use weak_table::PtrWeakHashSet; -use crate::{constants::{BuildStatus, BuildSteps, OYarn, SymType}, core::{evaluation::{Context, Evaluation}, model::Model}, oyarn, threads::SessionInfo}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn, SymType}, core::{evaluation::{Context, Evaluation}, file_mgr::NoqaInfo, model::Model}, oyarn, threads::SessionInfo}; use super::{symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; @@ -49,6 +49,7 @@ pub struct FunctionSymbol { pub args: Vec, pub is_overloaded: bool, //used for @overload decorator. Only indicates if the decorator is present. Use is_overloaded() to know if this function is overloaded pub is_class_method: bool, //used for @classmethod decorator + pub noqas: NoqaInfo, //Trait SymbolMgr //--- Body content @@ -86,6 +87,7 @@ impl FunctionSymbol { args: vec![], is_overloaded: false, is_class_method: false, + noqas: NoqaInfo::None, }; res._init_symbol_mgr(); res diff --git a/server/src/core/symbols/module_symbol.rs b/server/src/core/symbols/module_symbol.rs index 3e0f7295..77bdc93a 100644 --- a/server/src/core/symbols/module_symbol.rs +++ b/server/src/core/symbols/module_symbol.rs @@ -6,7 +6,7 @@ use weak_table::PtrWeakHashSet; use std::collections::{HashMap, HashSet}; use crate::{constants::*, oyarn, Sy}; -use crate::core::file_mgr::FileInfo; +use crate::core::file_mgr::{add_diagnostic, FileInfo, NoqaInfo}; use crate::core::import_resolver::find_module; use crate::core::model::Model; use crate::core::odoo::SyncOdoo; @@ -49,6 +49,7 @@ pub struct ModuleSymbol { pub dependencies: Vec>>>>>, pub dependents: Vec>>>>>, pub processed_text_hash: u64, + pub noqas: NoqaInfo, //Trait SymbolMgr pub sections: Vec, @@ -88,6 +89,7 @@ impl ModuleSymbol { dependencies: vec![], dependents: vec![], processed_text_hash: 0, + noqas: NoqaInfo::None, }; module._init_symbol_mgr(); info!("building new module: {:?}", dir_path.sanitize()); @@ -104,7 +106,7 @@ impl ModuleSymbol { if manifest_file_info.ast.is_none() { return None; } - let diags = module._load_manifest(&manifest_file_info); + let diags = module._load_manifest(session, &manifest_file_info); if session.sync_odoo.modules.contains_key(&module.dir_name) { //TODO: handle multiple modules with the same name } @@ -147,7 +149,7 @@ impl ModuleSymbol { /* Load manifest to identify the module characteristics. Returns list of od diagnostics to publish in manifest file. */ - fn _load_manifest(&mut self, file_info: &FileInfo) -> Vec { + fn _load_manifest(&mut self, session: &mut SessionInfo, file_info: &FileInfo) -> Vec { let mut res = vec![]; let ast = file_info.ast.as_ref().unwrap(); let mut is_manifest_valid = true; @@ -163,7 +165,7 @@ impl ModuleSymbol { _ => {is_manifest_valid = false;} } if !is_manifest_valid { - res.push(Diagnostic::new( + add_diagnostic(&mut res, Diagnostic::new( Range::new(Position::new(0, 0), Position::new(0, 1)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30201"))), @@ -171,7 +173,7 @@ impl ModuleSymbol { "A manifest should only contains one dictionary".to_string(), None, None, - )); + ), &session.current_noqa); return res; } let dict = &ast[0].as_expr_stmt().unwrap().value.clone().dict_expr().unwrap(); @@ -184,21 +186,21 @@ impl ModuleSymbol { let key_str = key_literal.value.to_string(); if key_str == "name" { if !value.is_string_literal_expr() { - res.push(self._create_diagnostic_for_manifest_key("The name of the module should be a string", S!("OLS30203"), &key_literal.range)); + add_diagnostic(&mut res, self._create_diagnostic_for_manifest_key("The name of the module should be a string", S!("OLS30203"), &key_literal.range), &session.current_noqa); } else { self.module_name = oyarn!("{}", value.as_string_literal_expr().unwrap().value); } } else if key_str == "depends" { if !value.is_list_expr() { - res.push(self._create_diagnostic_for_manifest_key("The depends value should be a list", S!("OLS30204"), &key_literal.range)); + add_diagnostic(&mut res, self._create_diagnostic_for_manifest_key("The depends value should be a list", S!("OLS30204"), &key_literal.range), &session.current_noqa); } else { for depend in value.as_list_expr().unwrap().elts.iter() { if !depend.is_string_literal_expr() { - res.push(self._create_diagnostic_for_manifest_key("The depends key should be a list of strings", S!("OLS30205"), &depend.range())); + add_diagnostic(&mut res, self._create_diagnostic_for_manifest_key("The depends key should be a list of strings", S!("OLS30205"), &depend.range()), &session.current_noqa); } else { let depend_value = oyarn!("{}", depend.as_string_literal_expr().unwrap().value); if depend_value == self.dir_name { - res.push(self._create_diagnostic_for_manifest_key("A module cannot depends on itself", S!("OLS30206"), &depend.range())); + add_diagnostic(&mut res, self._create_diagnostic_for_manifest_key("A module cannot depends on itself", S!("OLS30206"), &depend.range()), &session.current_noqa); } else { self.depends.push(depend_value); } @@ -207,18 +209,18 @@ impl ModuleSymbol { } } else if key_str == "data" { if !value.is_list_expr() { - res.push(self._create_diagnostic_for_manifest_key("The data value should be a list", S!("OLS30207"), &key_literal.range)); + add_diagnostic(&mut res, self._create_diagnostic_for_manifest_key("The data value should be a list", S!("OLS30207"), &key_literal.range), &session.current_noqa); } else { for data in value.as_list_expr().unwrap().elts.iter() { if !data.is_literal_expr() { - res.push(self._create_diagnostic_for_manifest_key("The data key should be a list of strings", S!("OLS30208"), &data.range())); + add_diagnostic(&mut res, self._create_diagnostic_for_manifest_key("The data key should be a list of strings", S!("OLS30208"), &data.range()), &session.current_noqa); } else { self.data.push(data.as_string_literal_expr().unwrap().value.to_string()); } } } } else if key_str == "active" { - res.push(Diagnostic::new( + add_diagnostic(&mut res, Diagnostic::new( Range::new(Position::new(key_literal.range.start().to_u32(), 0), Position::new(key_literal.range.end().to_u32(), 0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20201"))), @@ -226,18 +228,18 @@ impl ModuleSymbol { "The active key is deprecated".to_string(), None, Some(vec![DiagnosticTag::DEPRECATED]), - )) + ), &session.current_noqa) } else { //res.push(self._create_diagnostic_for_manifest_key("Manifest keys should be strings", &key.range())); } } _ => { - res.push(self._create_diagnostic_for_manifest_key("Manifest keys should be strings", S!("OLS30209"), &key.range())); + add_diagnostic(&mut res, self._create_diagnostic_for_manifest_key("Manifest keys should be strings", S!("OLS30209"), &key.range()), &session.current_noqa); } } }, None => { - res.push(Diagnostic::new( + add_diagnostic(&mut res, Diagnostic::new( Range::new(Position::new(0, 0), Position::new(0, 1)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30302"))), @@ -245,7 +247,7 @@ impl ModuleSymbol { "Do not use dict unpacking to build your manifest".to_string(), None, None, - )); + ), &session.current_noqa); return res; } } @@ -280,7 +282,7 @@ impl ModuleSymbol { if module.is_none() { symbol.get_entry().unwrap().borrow_mut().not_found_symbols.insert(symbol.weak_self().as_ref().unwrap().upgrade().expect("The symbol must be in the tree")); symbol.not_found_paths_mut().push((BuildSteps::ARCH, vec![Sy!("odoo"), Sy!("addons"), depend.clone()])); - diagnostics.push(Diagnostic::new( + add_diagnostic(&mut diagnostics, Diagnostic::new( Range::new(Position::new(0, 0), Position::new(0, 1)), Some(DiagnosticSeverity::ERROR), Some(NumberOrString::String(S!("OLS30210"))), @@ -288,7 +290,7 @@ impl ModuleSymbol { format!("Module {} depends on {} which is not found. Please review your addons paths", symbol.name(), depend), None, None, - )) + ), &session.current_noqa) } else { loaded.push(depend.clone()); let module = module.unwrap(); diff --git a/server/src/core/symbols/package_symbol.rs b/server/src/core/symbols/package_symbol.rs index f87360d9..2a995b02 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; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::model::Model, oyarn, threads::SessionInfo, S}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model}, 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}}; @@ -116,6 +116,7 @@ pub struct PythonPackageSymbol { pub dependencies: Vec>>>>>, pub dependents: Vec>>>>>, pub processed_text_hash: u64, + pub noqas: NoqaInfo, //Trait SymbolMgr pub sections: Vec, @@ -149,6 +150,7 @@ impl PythonPackageSymbol { dependencies: vec![], dependents: vec![], processed_text_hash: 0, + noqas: NoqaInfo::None, }; res._init_symbol_mgr(); res diff --git a/server/src/core/symbols/symbol.rs b/server/src/core/symbols/symbol.rs index 44b11c53..b216d3ff 100644 --- a/server/src/core/symbols/symbol.rs +++ b/server/src/core/symbols/symbol.rs @@ -3,6 +3,7 @@ use ruff_text_size::{TextSize, TextRange}; use tracing::{info, trace}; use weak_table::traits::WeakElement; +use crate::core::file_mgr::{add_diagnostic, NoqaInfo}; use crate::{constants::*, oyarn, Sy}; use crate::core::entry_point::EntryPoint; use crate::core::evaluation::{Context, ContextValue, Evaluation, EvaluationSymbolPtr, EvaluationSymbolWeak}; @@ -1612,6 +1613,36 @@ impl Symbol { } } + pub fn set_noqas(&mut self, noqa: NoqaInfo) { + match self { + Symbol::File(f) => f.noqas = noqa, + Symbol::DiskDir(_) => panic!("set_noqas called on DiskDir"), + Symbol::Package(PackageSymbol::Module(m)) => m.noqas = noqa, + Symbol::Package(PackageSymbol::PythonPackage(p)) => p.noqas = noqa, + Symbol::Function(f) => f.noqas = noqa, + Symbol::Root(_) => panic!("set_noqas called on Root"), + Symbol::Namespace(_) => panic!("set_noqas called on Namespace"), + Symbol::Compiled(_) => panic!("set_noqas called on Compiled"), + Symbol::Class(c) => c.noqas = noqa, + Symbol::Variable(_) => panic!("set_noqas called on Variable"), + } + } + + pub fn get_noqas(&self) -> NoqaInfo { + match self { + Symbol::File(f) => f.noqas.clone(), + Symbol::Package(PackageSymbol::Module(m)) => m.noqas.clone(), + Symbol::Package(PackageSymbol::PythonPackage(p)) => p.noqas.clone(), + Symbol::DiskDir(_) => panic!("get_noqas called on DiskDir"), + Symbol::Function(f) => f.noqas.clone(), + Symbol::Root(_) => panic!("get_noqas called on Root"), + Symbol::Namespace(_) => panic!("get_noqas called on Namespace"), + Symbol::Compiled(_) => panic!("get_noqas called on Compiled"), + Symbol::Class(c) => c.noqas.clone(), + Symbol::Variable(_) => panic!("get_noqas called on Variable"), + } + } + pub fn get_in_parents(&self, sym_types: &Vec, stop_same_file: bool) -> Option>> { if sym_types.contains(&self.typ()) { return self.weak_self().clone(); @@ -2159,15 +2190,15 @@ impl Symbol { if session.sync_odoo.version_major >= 17 && name == "Form"{ let tree = self.get_tree(); if tree == (vec![Sy!("odoo"), Sy!("tests"), Sy!("common")], vec!()){ - diagnostics.push(Diagnostic::new(Range::new(Position::new(0,0),Position::new(0,0)), + add_diagnostic(diagnostics, Diagnostic::new(Range::new(Position::new(0,0),Position::new(0,0)), Some(DiagnosticSeverity::WARNING), Some(NumberOrString::String(S!("OLS20006"))), Some(EXTENSION_NAME.to_string()), S!("Deprecation Warning: Since 17.0: odoo.tests.common.Form is deprecated, use odoo.tests.Form"), None, Some(vec![DiagnosticTag::DEPRECATED]), - ) - ); + ), + &session.current_noqa); } } } diff --git a/server/src/threads.rs b/server/src/threads.rs index 043c5d11..10be5ade 100644 --- a/server/src/threads.rs +++ b/server/src/threads.rs @@ -9,13 +9,15 @@ use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; use tracing::{error, info, warn}; -use crate::{core::{config::RefreshMode, odoo::{Odoo, SyncOdoo}}, server::ServerError, utils::PathSanitizer, S}; +use crate::{core::{config::RefreshMode, file_mgr::NoqaInfo, odoo::{Odoo, SyncOdoo}}, server::ServerError, utils::PathSanitizer, S}; pub struct SessionInfo<'a> { sender: Sender, receiver: Receiver, pub sync_odoo: &'a mut SyncOdoo, - delayed_process_sender: Option> + delayed_process_sender: Option>, + pub noqas_stack: Vec, + pub current_noqa: NoqaInfo, } impl <'a> SessionInfo<'a> { @@ -118,7 +120,9 @@ impl <'a> SessionInfo<'a> { sender, receiver, sync_odoo, - delayed_process_sender: None + delayed_process_sender: None, + noqas_stack: vec![], + current_noqa: NoqaInfo::None, } } } @@ -169,7 +173,9 @@ pub fn delayed_changes_process_thread(sender_session: Sender, receiver_ sender: sender_session.clone(), receiver: receiver_session.clone(), sync_odoo: &mut sync_odoo.lock().unwrap(), - delayed_process_sender: Some(delayed_process_sender.clone()) + delayed_process_sender: Some(delayed_process_sender.clone()), + noqas_stack: vec![], + current_noqa: NoqaInfo::None, }; let config = session.sync_odoo.config.clone(); session.send_notification(ShowMessage::METHOD, ShowMessageParams{ @@ -257,7 +263,9 @@ pub fn delayed_changes_process_thread(sender_session: Sender, receiver_ sender: sender_session.clone(), receiver: receiver_session.clone(), sync_odoo: &mut sync_odoo.lock().unwrap(), - delayed_process_sender: Some(delayed_process_sender.clone()) + delayed_process_sender: Some(delayed_process_sender.clone()), + noqas_stack: vec![], + current_noqa: NoqaInfo::None, }; if rebuild { let config = session.sync_odoo.config.clone(); @@ -290,7 +298,9 @@ pub fn message_processor_thread_main(sync_odoo: Arc>, generic_re sender: sender.clone(), receiver: receiver.clone(), sync_odoo: &mut sync_odoo.lock().unwrap(), - delayed_process_sender: Some(delayed_process_sender.clone()) + delayed_process_sender: Some(delayed_process_sender.clone()), + noqas_stack: vec![], + current_noqa: NoqaInfo::None, }; match msg { Message::Request(r) => { @@ -345,6 +355,8 @@ pub fn message_processor_thread_read(sync_odoo: Arc>, generic_re receiver: receiver.clone(), sync_odoo: &mut sync_odoo.lock().unwrap(), //TODO work on read access delayed_process_sender: Some(delayed_process_sender.clone()), + noqas_stack: vec![], + current_noqa: NoqaInfo::None, }; match msg { Message::Request(r) => {