Skip to content

Commit

Permalink
Resolve document variables for library arguments. Fixes #634
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Apr 5, 2022
1 parent 35f4b7b commit bcbf98b
Show file tree
Hide file tree
Showing 28 changed files with 534 additions and 289 deletions.
3 changes: 2 additions & 1 deletion robocorp-code/.vscode/robocode-vscode.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"python.terminal.activateEnvironment": false
}
}
1 change: 1 addition & 0 deletions robotframework-ls/docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
NEXT
-----------------------------

- Variables in document are considered in Libdoc arguments. [#634](https://github.com/robocorp/robotframework-lsp/issues/634)
- Fixed issue finding variables in python files with annotated assignments (i.e.: `value: int = 10`). [#629](https://github.com/robocorp/robotframework-lsp/issues/629)
- The debugger no longer stops in `Run Keyword And Return Status` by default. [#625](https://github.com/robocorp/robotframework-lsp/issues/625)
- Code-lenses (Run/Debug/Interactive console) are shown by default again.
Expand Down
2 changes: 1 addition & 1 deletion robotframework-ls/src/robotframework_ls/impl/ast_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ def tokenize_variables_from_name(name):
return tokenize_variables(create_token(name)) # May throw error if it's not OK.


def tokenize_variables(token: IRobotToken):
def tokenize_variables(token: IRobotToken) -> Iterator[IRobotToken]:
return token.tokenize_variables() # May throw error if it's not OK.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ def get_resource_import_line(self) -> int:
def _obtain_import_location_info(completion_context) -> _ImportLocationInfo:
from robotframework_ls.impl import ast_utils
from robotframework_ls.impl.libspec_manager import LibspecManager
from robot.api import Token

import_location_info = _ImportLocationInfo()

Expand All @@ -294,12 +295,14 @@ def _obtain_import_location_info(completion_context) -> _ImportLocationInfo:
if ast_utils.is_library_node_info(node_info):
import_location_info.library_node_info = node_info

library_name = node_info.node.name
if library_name:
library_name_token = node_info.node.get_token(Token.NAME)
if library_name_token is not None:
library_doc_or_error = libspec_manager.get_library_doc_or_error(
completion_context.token_value_resolving_variables(library_name),
completion_context.token_value_resolving_variables(
library_name_token
),
create=True,
current_doc_uri=completion_context.doc.uri,
completion_context=completion_context,
args=ast_utils.get_library_arguments_serialized(node_info.node),
)
library_doc = library_doc_or_error.library_doc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ILibraryDoc,
INode,
IVariableFound,
AbstractVariablesCollector,
)
from robocorp_ls_core.lsp import DiagnosticSeverity, DiagnosticTag
from robotframework_ls.impl.robot_lsp_constants import (
Expand Down Expand Up @@ -54,7 +55,7 @@ def get_keyword(self, normalized_keyword_name: str) -> Optional[IKeywordFound]:
return None


class _VariablesCollector(object):
class _VariablesCollector(AbstractVariablesCollector):
def __init__(self, on_unresolved_variable_import):
self._variables_collected = set()
self.on_unresolved_variable_import = on_unresolved_variable_import
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ def _collect_current_doc_keywords(

def _collect_libraries_keywords(
completion_context: ICompletionContext,
current_doc_uri: str,
library_infos: Iterator[LibraryDependencyInfo],
collector: IKeywordCollector,
):
Expand All @@ -337,7 +336,7 @@ def _collect_libraries_keywords(
libspec_manager.get_library_doc_or_error(
library_info.name,
create=True,
current_doc_uri=current_doc_uri,
completion_context=completion_context,
builtin=library_info.builtin,
args=library_info.args,
)
Expand Down Expand Up @@ -431,7 +430,6 @@ def _collect_from_context(
completion_context.check_cancelled()
_collect_libraries_keywords(
completion_context,
completion_context.doc.uri,
dependency_graph.iter_libraries(completion_context.doc.uri),
collector,
)
Expand Down Expand Up @@ -494,7 +492,6 @@ def _collect_from_context(
_collect_current_doc_keywords(new_ctx, collector)
_collect_libraries_keywords(
new_ctx,
resource_doc.uri,
dependency_graph.iter_libraries(resource_doc.uri),
collector,
)
Expand Down
105 changes: 95 additions & 10 deletions robotframework-ls/src/robotframework_ls/impl/completion_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
Set,
Callable,
Dict,
Union,
Iterator,
Sequence,
)
Expand Down Expand Up @@ -40,15 +39,14 @@
IVariableImportNode,
VarTokenInfo,
IVariablesFromArgumentsFileLoader,
IVariableFound,
NodeInfo,
)
from robotframework_ls.impl.robot_workspace import RobotDocument
from robocorp_ls_core import uris
import itertools
from functools import partial
import typing
from robotframework_ls.impl.variables_from_arguments_file import (
VariablesFromArgumentsFileLoader,
)


log = get_logger(__name__)
Expand Down Expand Up @@ -336,12 +334,101 @@ def get_current_token(self) -> Optional[TokenInfo]:
return None
return ast_utils.find_token(section, self.sel.line, self.sel.col)

def get_all_variables(self):
def get_all_variables(self) -> Tuple[NodeInfo, ...]:
from robotframework_ls.impl import ast_utils

ast = self.get_ast()
return tuple(ast_utils.iter_variables(ast))

@instance_cache
def get_doc_normalized_var_name_to_var_found(self) -> Dict[str, IVariableFound]:
from robotframework_ls.impl import ast_utils
from robotframework_ls.impl.variable_resolve import robot_search_variable
from robot.api import Token
from robotframework_ls.impl.variable_types import VariableFoundFromToken
from robotframework_ls.impl.text_utilities import normalize_robot_name

ret: Dict[str, IVariableFound] = {}
for variable_node_info in self.get_all_variables():
variable_node = variable_node_info.node
token = variable_node.get_token(Token.VARIABLE)
if token is None:
continue

variable_match = robot_search_variable(token.value)
# Filter out empty base
if variable_match is None or not variable_match.base:
continue

base_token = ast_utils.convert_variable_match_base_to_token(
token, variable_match
)
ret[normalize_robot_name(variable_match.base)] = VariableFoundFromToken(
self,
base_token,
variable_node.value,
variable_name=variable_match.base,
)

return ret

@instance_cache
def get_settings_normalized_var_name_to_var_found(
self,
) -> Dict[str, IVariableFound]:
from robotframework_ls.impl.text_utilities import normalize_robot_name
from robotframework_ls.impl.variable_types import VariableFoundFromSettings

ret: Dict[str, IVariableFound] = {}

from robotframework_ls.impl.robot_lsp_constants import OPTION_ROBOT_VARIABLES

config = self.config
if config is not None:
robot_variables = config.get_setting(OPTION_ROBOT_VARIABLES, dict, {})
for key, val in robot_variables.items():
ret[normalize_robot_name(key)] = VariableFoundFromSettings(key, val)

return ret

@instance_cache
def get_builtins_normalized_var_name_to_var_found(
self, resolved
) -> Dict[str, IVariableFound]:
from robotframework_ls.impl.text_utilities import normalize_robot_name
from robotframework_ls.impl.variable_types import VariableFoundFromBuiltins
from robotframework_ls.impl.robot_constants import BUILTIN_VARIABLES_RESOLVED

ret: Dict[str, IVariableFound] = {}

from robotframework_ls.impl.robot_constants import get_builtin_variables

for key, val in get_builtin_variables():
ret[normalize_robot_name(key)] = VariableFoundFromBuiltins(key, val)

if resolved:
for key, val in BUILTIN_VARIABLES_RESOLVED.items():
# Provide a resolved value for the ones we can resolve.
ret[normalize_robot_name(key)] = VariableFoundFromBuiltins(key, val)

return ret

def get_arguments_files_normalized_var_name_to_var_found(
self,
) -> Dict[str, IVariableFound]:
from robotframework_ls.impl.text_utilities import normalize_robot_name

ret: Dict[str, IVariableFound] = {}

if not self.variables_from_arguments_files_loader:
return ret

for c in self.variables_from_arguments_files_loader:
for variable in c.get_variables():
ret[normalize_robot_name(variable.variable_name)] = variable

return ret

@instance_cache
def get_current_variable(self, section=None) -> Optional[VarTokenInfo]:
"""
Expand Down Expand Up @@ -389,20 +476,18 @@ def get_variable_imports(self) -> Tuple[INode, ...]:
ret.append(resource.node)
return tuple(ret)

def token_value_resolving_variables(self, token: Union[str, IRobotToken]) -> str:
def token_value_resolving_variables(self, token: IRobotToken) -> str:
from robotframework_ls.impl.variable_resolve import ResolveVariablesContext

return ResolveVariablesContext(
self.config, self._doc.path
).token_value_resolving_variables(token)
return ResolveVariablesContext(self).token_value_resolving_variables(token)

def token_value_and_unresolved_resolving_variables(
self, token: IRobotToken
) -> Tuple[str, Tuple[IRobotToken, ...]]:
from robotframework_ls.impl.variable_resolve import ResolveVariablesContext

return ResolveVariablesContext(
self.config, self._doc.path
self
).token_value_and_unresolved_resolving_variables(token)

@instance_cache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def __init__(
self._lock = threading.Lock()
# Small cache because invalidation could become slow in a big cache
# (and it should be enough to hold what we're currently working with).
self._cached: _LRU[ICompletionContextDependencyGraph] = _LRU(4)
self._cached: _LRU[ICompletionContextDependencyGraph] = _LRU(5)
self.cache_hits = 0
self.invalidations = 0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def _get_completions(
from robotframework_ls.impl.string_matcher import RobotStringMatcher
from robocorp_ls_core import uris
from robotframework_ls.impl.robot_constants import BUILTIN_LIB, RESERVED_LIB
from robotframework_ls.impl import ast_utils

ret: List[CompletionItemTypedDict] = []

Expand All @@ -112,7 +113,7 @@ def _get_completions(
value_to_cursor = value_to_cursor[: -(token.end_col_offset - sel.col)]
if "{" in value_to_cursor:
value_to_cursor = completion_context.token_value_resolving_variables(
value_to_cursor
ast_utils.create_token(value_to_cursor)
)

value_to_cursor_split = os.path.split(value_to_cursor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def find_definition_extended(
library_doc = libspec_manager.get_library_doc_or_error(
completion_context.token_value_resolving_variables(token),
create=True,
current_doc_uri=completion_context.doc.uri,
completion_context=completion_context,
args=ast_utils.get_library_arguments_serialized(token_info.node),
).library_doc
if library_doc is not None:
Expand Down
39 changes: 26 additions & 13 deletions robotframework-ls/src/robotframework_ls/impl/libspec_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import threading
from typing import Optional, Dict, Set, Iterator, Union, Any
from robocorp_ls_core.protocols import Sentinel, IEndPoint
from robotframework_ls.impl.protocols import ILibraryDoc, ILibraryDocOrError
from robotframework_ls.impl.protocols import (
ILibraryDoc,
ILibraryDocOrError,
ICompletionContext,
)
import itertools
from robocorp_ls_core.watchdog_wrapper import IFSObserver
from robotframework_ls.impl.robot_lsp_constants import (
Expand Down Expand Up @@ -1126,7 +1130,7 @@ def get_library_doc_or_error(
self,
libname: str,
create: bool,
current_doc_uri: str,
completion_context: ICompletionContext,
builtin: bool = False,
args: Optional[str] = None,
) -> ILibraryDocOrError:
Expand All @@ -1136,12 +1140,12 @@ def get_library_doc_or_error(
absolute path to a .py file.
"""
from robotframework_ls.impl import robot_constants

assert current_doc_uri is not None
from robotframework_ls.impl import ast_utils

libname_lower = libname.lower()
target_file: str = ""
normalized_target_file: str = ""
pre_error_msg: str = ""

config = self.config
libraries_libdoc_needs_args_lower: Set[str]
Expand Down Expand Up @@ -1171,15 +1175,24 @@ def get_library_doc_or_error(
ResolveVariablesContext,
)

args = ResolveVariablesContext(
self.config, current_doc_uri
).token_value_resolving_variables(args)
assert completion_context.config is config

args, unresolved = ResolveVariablesContext(
completion_context
).token_value_and_unresolved_resolving_variables(
ast_utils.create_token(args)
)

pre_error_msg = (
"It was not possible to statically resolve the following variables:\n%s\nFollow-up error:\n"
% (", ".join(str(x) for x in unresolved),)
)

args = args.replace("\\\\", "\\")

if not builtin:
found_target_filename = self._get_library_target_filename(
libname, current_doc_uri
libname, completion_context.doc.uri
)
if found_target_filename:
target_file = found_target_filename
Expand Down Expand Up @@ -1252,7 +1265,7 @@ def get_library_doc_or_error(
return self.get_library_doc_or_error(
libname,
create=False,
current_doc_uri=current_doc_uri,
completion_context=completion_context,
builtin=builtin,
args=args,
)
Expand All @@ -1270,22 +1283,22 @@ def get_library_doc_or_error(
return self.get_library_doc_or_error(
libname,
create=False,
current_doc_uri=current_doc_uri,
completion_context=completion_context,
builtin=builtin,
args=args,
)
return _LibraryDocOrError(None, error_msg)
return _LibraryDocOrError(None, pre_error_msg + error_msg)

error_msg = self._get_cached_error(
libname, is_builtin=builtin, target_file=target_file, args=args
)
if error_msg:
log.debug("Unable to get library named: %s. Reason: %s", libname, error_msg)
return _LibraryDocOrError(None, error_msg)
return _LibraryDocOrError(None, pre_error_msg + error_msg)

msg = f"Unable to find library named: {libname}"
log.debug(msg)
return _LibraryDocOrError(None, msg)
return _LibraryDocOrError(None, pre_error_msg + msg)


class _LibraryDocOrError:
Expand Down
Loading

0 comments on commit bcbf98b

Please sign in to comment.