diff --git a/packages/robot/src/robotcode/robot/diagnostics/keyword_finder.py b/packages/robot/src/robotcode/robot/diagnostics/keyword_finder.py index 86304fc4..051802a0 100644 --- a/packages/robot/src/robotcode/robot/diagnostics/keyword_finder.py +++ b/packages/robot/src/robotcode/robot/diagnostics/keyword_finder.py @@ -1,3 +1,4 @@ +import functools import re from itertools import chain from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple @@ -59,7 +60,6 @@ def __init__(self, namespace: "Namespace", library_doc: LibraryDoc) -> None: self._all_keywords: Optional[List[LibraryEntry]] = None self._resource_keywords: Optional[List[ResourceEntry]] = None self._library_keywords: Optional[List[LibraryEntry]] = None - self._bdd_prefix_regexp: Optional["re.Pattern[str]"] = None def reset_diagnostics(self) -> None: self.diagnostics = [] @@ -459,20 +459,18 @@ def _create_custom_and_standard_keyword_conflict_warning_message( f"or '{'' if standard[0] is None else standard[0].alias or standard[0].name}.{standard[1].name}'." ) - @property + @functools.cached_property def bdd_prefix_regexp(self) -> "re.Pattern[str]": - if not self._bdd_prefix_regexp: - prefixes = ( - "|".join( - self.namespace.languages.bdd_prefixes - if self.namespace.languages is not None - else ["given", "when", "then", "and", "but"] - ) - .replace(" ", r"\s") - .lower() + prefixes = ( + "|".join( + self.namespace.languages.bdd_prefixes + if self.namespace.languages is not None + else ["given", "when", "then", "and", "but"] ) - self._bdd_prefix_regexp = re.compile(rf"({prefixes})\s", re.IGNORECASE) - return self._bdd_prefix_regexp + .replace(" ", r"\s") + .lower() + ) + return re.compile(rf"({prefixes})\s", re.IGNORECASE) def _get_bdd_style_keyword(self, name: str) -> Optional[KeywordDoc]: match = self.bdd_prefix_regexp.match(name) diff --git a/packages/robot/src/robotcode/robot/diagnostics/library_doc.py b/packages/robot/src/robotcode/robot/diagnostics/library_doc.py index b655df70..f964f49f 100644 --- a/packages/robot/src/robotcode/robot/diagnostics/library_doc.py +++ b/packages/robot/src/robotcode/robot/diagnostics/library_doc.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +import functools import hashlib import importlib import importlib.util @@ -197,14 +198,29 @@ def convert_from_rest(text: str) -> str: return text +if get_robot_version() >= (6, 0): + + @functools.lru_cache(maxsize=None) + def _get_embedded_arguments(name: str) -> Any: + try: + return EmbeddedArguments.from_name(name) + except (VariableError, DataError): + return () + +else: + + @functools.lru_cache(maxsize=None) + def _get_embedded_arguments(name: str) -> Any: + try: + return EmbeddedArguments(name) + except (VariableError, DataError): + return () + + def is_embedded_keyword(name: str) -> bool: try: - if get_robot_version() >= (6, 0): - if EmbeddedArguments.from_name(name): - return True - else: - if EmbeddedArguments(name): - return True + if _get_embedded_arguments(name): + return True except (VariableError, DataError): return True @@ -235,18 +251,22 @@ def normalized_name(self) -> str: def embedded_arguments(self) -> Any: if self._embedded_arguments is None: if self._can_have_embedded: - try: - if get_robot_version() >= (6, 0): - self._embedded_arguments = EmbeddedArguments.from_name(self.name) - else: - self._embedded_arguments = EmbeddedArguments(self.name) - except (VariableError, DataError): - self._embedded_arguments = () + self._embedded_arguments = _get_embedded_arguments(self.name) else: self._embedded_arguments = () return self._embedded_arguments + if get_robot_version() >= (6, 0): + + def __match_embedded(self, name: str) -> bool: + return self.embedded_arguments.match(name) is not None + + else: + + def __match_embedded(self, name: str) -> bool: + return self.embedded_arguments.name.match(name) is not None + def __eq__(self, o: object) -> bool: if cached_isinstance(o, KeywordMatcher): if self._is_namespace != o._is_namespace: @@ -261,10 +281,7 @@ def __eq__(self, o: object) -> bool: return False if self.embedded_arguments: - if get_robot_version() >= (6, 0): - return self.embedded_arguments.match(o) is not None - - return self.embedded_arguments.name.match(o) is not None + return self.__match_embedded(o) return self.normalized_name == str(normalize_namespace(o) if self._is_namespace else normalize(o)) @@ -935,8 +952,6 @@ def __getitem__(self, key: str) -> KeywordDoc: ) def __contains__(self, _x: object) -> bool: - if not isinstance(_x, KeywordMatcher): - _x = KeywordMatcher(str(_x)) return any(k == _x for k in self._matchers.keys()) def __len__(self) -> int: diff --git a/packages/robot/src/robotcode/robot/diagnostics/namespace.py b/packages/robot/src/robotcode/robot/diagnostics/namespace.py index cf3f563d..729cf8e5 100644 --- a/packages/robot/src/robotcode/robot/diagnostics/namespace.py +++ b/packages/robot/src/robotcode/robot/diagnostics/namespace.py @@ -893,6 +893,7 @@ def get_namespaces(self) -> Dict[KeywordMatcher, List[LibraryEntry]]: self._namespaces[KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True)].append(v) for v in (self.get_resources()).values(): self._namespaces[KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True)].append(v) + return self._namespaces def get_resources(self) -> Dict[str, ResourceEntry]: @@ -1793,11 +1794,10 @@ def iter_all_keywords(self) -> Iterator[KeywordDoc]: libdoc = self.get_library_doc() - for doc in itertools.chain( + yield from itertools.chain( self.get_imported_keywords(), libdoc.keywords if libdoc is not None else [], - ): - yield doc + ) @_logger.call def get_keywords(self) -> List[KeywordDoc]: diff --git a/packages/robot/src/robotcode/robot/diagnostics/namespace_analyzer.py b/packages/robot/src/robotcode/robot/diagnostics/namespace_analyzer.py index eda0f91c..a8e392c4 100644 --- a/packages/robot/src/robotcode/robot/diagnostics/namespace_analyzer.py +++ b/packages/robot/src/robotcode/robot/diagnostics/namespace_analyzer.py @@ -531,6 +531,20 @@ def _append_diagnostics( ) ) + KEYWORDS_WITH_EXPRESSIONS = [ + "BuiltIn.Evaluate", + "BuiltIn.Should Be True", + "BuiltIn.Should Not Be True", + "BuiltIn.Skip If", + "BuiltIn.Continue For Loop If", + "BuiltIn.Exit For Loop If", + "BuiltIn.Return From Keyword If", + "BuiltIn.Run Keyword And Return If", + "BuiltIn.Pass Execution If", + "BuiltIn.Run Keyword If", + "BuiltIn.Run Keyword Unless", + ] + def _analyze_keyword_call( self, node: ast.AST, @@ -708,19 +722,7 @@ def _analyze_keyword_call( ) if result is not None: - if result.longname in [ - "BuiltIn.Evaluate", - "BuiltIn.Should Be True", - "BuiltIn.Should Not Be True", - "BuiltIn.Skip If", - "BuiltIn.Continue For Loop If", - "BuiltIn.Exit For Loop If", - "BuiltIn.Return From Keyword If", - "BuiltIn.Run Keyword And Return If", - "BuiltIn.Pass Execution If", - "BuiltIn.Run Keyword If", - "BuiltIn.Run Keyword Unless", - ]: + if result.longname in self.KEYWORDS_WITH_EXPRESSIONS: tokens = argument_tokens if tokens and (token := tokens[0]): self._analyze_token_expression_variables(token) diff --git a/packages/robot/src/robotcode/robot/utils/match.py b/packages/robot/src/robotcode/robot/utils/match.py index 06169ec6..9ede42fa 100644 --- a/packages/robot/src/robotcode/robot/utils/match.py +++ b/packages/robot/src/robotcode/robot/utils/match.py @@ -3,13 +3,13 @@ _transform_table = str.maketrans("", "", "_ ") -@lru_cache(maxsize=5000) +@lru_cache(maxsize=None) def normalize(text: str) -> str: # return text.lower().replace("_", "").replace(" ", "") return text.casefold().translate(_transform_table) -@lru_cache(maxsize=5000) +@lru_cache(maxsize=None) def normalize_namespace(text: str) -> str: return text.lower().replace(" ", "")