def list_tests( completion_context: ICompletionContext) -> List[ITestInfoTypedDict]: from robotframework_ls.impl import ast_utils from robotframework_ls.impl import robot_constants ast = completion_context.get_ast() completion_context.check_cancelled() ret: List[ITestInfoTypedDict] = [] node: NodeInfo for node in ast_utils.iter_tests(ast): completion_context.check_cancelled() try: test_case_name_token = node.node.header.get_token( robot_constants.TESTCASE_NAME) if test_case_name_token is None: # Old versions have slashes and not spaces as a separator. test_case_name_token = node.node.header.get_token( robot_constants.TESTCASE_NAME.replace(" ", "_")) ret.append({ "uri": completion_context.doc.uri, "path": completion_context.doc.path, "name": test_case_name_token.value, }) except Exception: log.exception("Error listing tests in document.") return ret
def _collect_resource_imports_keywords(completion_context: ICompletionContext, collector): """ :param CompletionContext completion_context: """ for resource_doc in completion_context.get_resource_imports_as_docs(): new_ctx = completion_context.create_copy(resource_doc) _collect_following_imports(new_ctx, collector)
def _collect_libraries_keywords( completion_context: ICompletionContext, collector: IKeywordCollector ): """ :param CompletionContext completion_context: """ # Get keywords from libraries from robotframework_ls.impl.robot_constants import BUILTIN_LIB from robocorp_ls_core.lsp import CompletionItemKind libraries = completion_context.get_imported_libraries() library_infos = set( _LibInfo( completion_context.token_value_resolving_variables(library.name), library.alias, False, ) for library in libraries ) library_infos.add(_LibInfo(BUILTIN_LIB, None, True)) libspec_manager = completion_context.workspace.libspec_manager for library_info in library_infos: completion_context.check_cancelled() if not completion_context.memo.complete_for_library(library_info.name): continue library_doc = libspec_manager.get_library_info( library_info.name, create=True, current_doc_uri=completion_context.doc.uri, builtin=library_info.builtin, ) if library_doc is not None: #: :type keyword: KeywordDoc for keyword in library_doc.keywords: keyword_name = keyword.name if collector.accepts(keyword_name): keyword_args = [] if keyword.args: keyword_args = keyword.args collector.on_keyword( _KeywordFoundFromLibrary( library_doc, keyword, keyword_name, keyword_args, completion_context, CompletionItemKind.Method, library_alias=library_info.alias, ) )
def _collect_following_imports(completion_context: ICompletionContext, collector): completion_context.check_cancelled() if completion_context.memo.follow_import(completion_context.doc.uri): # i.e.: prevent collecting keywords for the same doc more than once. _collect_current_doc_keywords(completion_context, collector) _collect_resource_imports_keywords(completion_context, collector) _collect_libraries_keywords(completion_context, collector)
def _collect_following_imports(completion_context: ICompletionContext, collector: _Collector): completion_context.check_cancelled() if completion_context.memo.follow_import_variables( completion_context.doc.uri): # i.e.: prevent collecting variables for the same doc more than once. _collect_current_doc_variables(completion_context, collector) _collect_resource_imports_variables(completion_context, collector) _collect_variable_imports_variables(completion_context, collector)
def code_lens_resolve(completion_context: ICompletionContext, code_lens: CodeLensTypedDict): # Fill in the command arguments command = code_lens.get("command") data = code_lens.get("data") if command is None and isinstance( data, dict) and data.get("type") == "scratchpad": from robotframework_ls import import_rf_interactive import_rf_interactive() code_lens_range: RangeTypedDict = code_lens["range"] code_lens_line = code_lens_range["start"]["line"] ast = completion_context.get_ast() for name_token, header, node in _iter_scratchpad_items(ast): if name_token.lineno - 1 == code_lens_line: code_lens["command"] = _code_lens_scratchpad_command( header, node, data["uri"]) break else: # Unable to resolve log.info("Unable to resolve code lens.") return code_lens return code_lens
def folding_range( completion_context: ICompletionContext) -> Optional[List[Dict]]: return list([ x.to_dict() for x in FoldingVisitor.find_from( completion_context.get_ast(), completion_context) ])
def signature_help(completion_context: ICompletionContext) -> Optional[dict]: keyword_definition = completion_context.get_current_keyword_definition() if keyword_definition is not None: from robocorp_ls_core.lsp import SignatureHelp from robocorp_ls_core.lsp import SignatureInformation from robocorp_ls_core.lsp import ParameterInformation keyword_found: IKeywordFound = keyword_definition.keyword_found keyword_args = keyword_found.keyword_args lst = [arg.original_arg for arg in keyword_args] label = "%s(%s)" % (keyword_found.keyword_name, ", ".join(lst)) documentation = keyword_found.docs parameters: List[ParameterInformation] = [ # Note: the label here is to highlight a part of the main signature label! # (let's leave this out for now) # ParameterInformation("param1", None), ] signatures: List[SignatureInformation] = [ SignatureInformation(label, documentation, parameters) ] return SignatureHelp(signatures, active_signature=0, active_parameter=0).to_dict() return None
def collect_analysis_errors(completion_context: ICompletionContext): errors = [] collector = _KeywordsCollector() collect_keywords(completion_context, collector) ast = completion_context.get_ast() for keyword_usage_info in ast_utils.iter_keyword_usage_tokens(ast): completion_context.check_cancelled() normalized_name = normalize_robot_name(keyword_usage_info.name) if not collector.contains_keyword(normalized_name): # There's not a direct match, but the library name may be builtin # into the keyword name, so, check if we have a match that way. node = keyword_usage_info.node error = create_error_from_node( node, "Undefined keyword: %s." % (keyword_usage_info.name, ), tokens=[keyword_usage_info.token], ) errors.append(error) else: multi = collector.check_multiple_keyword_definitions( normalized_name) if multi is not None: node = keyword_usage_info.node error = create_error_from_node( node, "Multiple keywords with name '%s' found. Give the full name of the keyword you want to use:\n%s" % (keyword_usage_info.name, "\n".join([ f" {m.library_alias}.{m.keyword_name}" for m in multi ])), tokens=[keyword_usage_info.token], ) errors.append(error) if len(errors) >= MAX_ERRORS: # i.e.: Collect at most 100 errors break errors.extend(CodeAnalysisVisitor.find_from(completion_context)) return errors
def _collect_current_doc_variables(completion_context: ICompletionContext, collector: _Collector): """ :param CompletionContext completion_context: """ # Get keywords defined in the file itself ast = completion_context.get_ast() _collect_completions_from_ast(ast, completion_context, collector)
def hover(completion_context: ICompletionContext) -> Optional[dict]: t = completion_context.get_current_token() if t and t.token and t.token.type == Token.KEYWORD: keyword_definition = completion_context.get_current_keyword_definition( ) if keyword_definition is not None: from robocorp_ls_core.lsp import Hover keyword_found: IKeywordFound = keyword_definition.keyword_found signature_args = "" for a in keyword_found.keyword_args: if signature_args: signature_args += ", " if a.is_keyword_arg: signature_args += "**" if a.is_star_arg: signature_args += "*" signature_args += a.arg_name if a.arg_type: signature_args += f": {a.arg_type}" if a.default_value: signature_args += f"={a.default_value}" signature = f"{keyword_found.keyword_name}({signature_args})" hover_text = "```python\n" hover_text += signature hover_text += "\n```" hover_text += "\n" hover_text += keyword_found.docs return Hover(hover_text).to_dict() return None
def find_definition( completion_context: ICompletionContext) -> Sequence[IDefinition]: """ :note: Definitions may be found even if a given source file no longer exists at this place (callers are responsible for validating entries). """ from robotframework_ls.impl import ast_utils from robotframework_ls.impl.string_matcher import RobotStringMatcher from robotframework_ls.impl.variable_completions import collect_variables token_info = completion_context.get_current_token() if token_info is not None: matches = find_keyword_definition(completion_context, token_info) if matches is not None: return matches token = ast_utils.get_library_import_name_token( token_info.node, token_info.token) if token is not None: libspec_manager = completion_context.workspace.libspec_manager completion_context.check_cancelled() library_doc = libspec_manager.get_library_info( completion_context.token_value_resolving_variables(token), create=True, current_doc_uri=completion_context.doc.uri, ) if library_doc is not None: definition = _DefinitionFromLibrary(library_doc) return [definition] token = ast_utils.get_resource_import_name_token( token_info.node, token_info.token) if token is not None: completion_context.check_cancelled() resource_import_as_doc = completion_context.get_resource_import_as_doc( token_info.node) if resource_import_as_doc is not None: return [_DefinitionFromResource(resource_import_as_doc)] token_info = completion_context.get_current_variable() if token_info is not None: token = token_info.token value = token.value collector = _FindDefinitionVariablesCollector( completion_context.sel, token, RobotStringMatcher(value)) collect_variables(completion_context, collector) return collector.matches return []
def code_lens_scratchpad( completion_context: ICompletionContext) -> List[CodeLensTypedDict]: from robot.api import Token # noqa from robotframework_ls import import_rf_interactive import_rf_interactive() ast = completion_context.get_ast() completion_context.check_cancelled() ret: List[CodeLensTypedDict] = [] for name_token, header, node in _iter_scratchpad_items(ast): completion_context.check_cancelled() ret.append( _create_scratchpad_code_lens(completion_context, name_token, header, node)) return ret
def folding_range( completion_context: ICompletionContext ) -> List[FoldingRangeTypedDict]: from robotframework_ls.impl import ast_utils from robotframework_ls.impl.protocols import NodeInfo ast = completion_context.get_ast() completion_context.check_cancelled() ret: List[FoldingRangeTypedDict] = [] node: NodeInfo for node in ast_utils.iter_all_nodes(ast): completion_context.check_cancelled() try: start_line = node.node.lineno - 1 end_line = node.node.end_lineno - 1 if end_line > start_line: ret.append({"startLine": start_line, "endLine": end_line}) except Exception: log.exception("Error computing range") return ret
def collect_variables(completion_context: ICompletionContext, collector: _Collector): from robotframework_ls.impl import ast_utils token_info = completion_context.get_current_token() if token_info is not None: if token_info.stack: stack_node = token_info.stack[-1] else: stack_node = completion_context.get_ast_current_section() for assign_node_info in ast_utils.iter_variable_assigns(stack_node): if collector.accepts(assign_node_info.token.value): rep = " ".join(tok.value for tok in assign_node_info.node.tokens) variable_found = _VariableFoundFromToken( completion_context, assign_node_info.token, rep) collector.on_variable(variable_found) _collect_arguments(completion_context, collector) _collect_following_imports(completion_context, collector) _collect_from_settings(completion_context, collector) _collect_from_builtins(completion_context, collector)
def _collect_completions_from_ast(ast, completion_context: ICompletionContext, collector): from robotframework_ls.impl import ast_utils from robocorp_ls_core.lsp import CompletionItemKind for keyword in ast_utils.iter_keywords(ast): completion_context.check_cancelled() keyword_name = keyword.node.name if collector.accepts(keyword_name): keyword_args = [] for arg in ast_utils.iter_keyword_arguments_as_str(keyword.node): keyword_args.append(KeywordArg(arg)) collector.on_keyword( _KeywordFoundFromAst( ast, keyword.node, keyword_name, keyword_args, completion_context, CompletionItemKind.Function, ))
def _collect_completions_from_ast( ast, completion_context: ICompletionContext, collector ): completion_context.check_cancelled() from robotframework_ls.impl import ast_utils from robot.api import Token ast = completion_context.get_ast() for variable_node_info in ast_utils.iter_variables(ast): variable_node = variable_node_info.node token = variable_node.get_token(Token.VARIABLE) if token is None: continue name = token.value if name.endswith("="): name = name[:-1].rstrip() if collector.accepts(name): variable_found = _VariableFoundFromToken( completion_context, token, variable_node.value, variable_name=name ) collector.on_variable(variable_found)
def complete(completion_context: ICompletionContext) -> List[dict]: from robotframework_ls.impl.collect_keywords import collect_keywords from robotframework_ls.impl import ast_utils token_info = completion_context.get_current_token() if token_info is not None: token = ast_utils.get_keyword_name_token(token_info.node, token_info.token) if token is not None: collector = _Collector(completion_context.sel, token) collect_keywords(completion_context, collector) return collector.completion_items return []
def code_lens(completion_context: ICompletionContext, doc_uri: str) -> Optional[List[Dict]]: result = [] result.append( CodeLens(Range(Position(0, 0), Position(0, 0)), Command("Run Suite", "robot.runTestsuite", [doc_uri]))) result.append( CodeLens(Range(Position(0, 0), Position(0, 0)), Command("Debug Suite", "robot.debugTestsuite", [doc_uri]))) result += CodeLensVisitor.find_from(completion_context.get_ast(), doc_uri, completion_context) return list([x.to_dict() for x in result])
def _collect_completions_from_ast(ast, completion_context: ICompletionContext, collector): completion_context.check_cancelled() from robot.api import Token for variable_node_info in completion_context.get_all_variables(): variable_node = variable_node_info.node token = variable_node.get_token(Token.VARIABLE) if token is None: continue name = token.value if not name: continue name = name.strip() if not name: continue if name.endswith("="): name = name[:-1].rstrip() if name.startswith(("&", "@")): # Allow referencing dict(&)/list(@) variables as regular ($) variables dict_or_list_var = "$" + name[1:] if collector.accepts(dict_or_list_var): variable_found = _VariableFoundFromToken( completion_context, token, variable_node.value, variable_name=dict_or_list_var, ) collector.on_variable(variable_found) if collector.accepts(name): variable_found = _VariableFoundFromToken(completion_context, token, variable_node.value, variable_name=name) collector.on_variable(variable_found)
def _collect_arguments(completion_context: ICompletionContext, collector: _Collector): from robotframework_ls.impl import ast_utils current_token_info = completion_context.get_current_token() if current_token_info is not None: stack = current_token_info.stack if stack: last_in_stack = stack[-1] for arg_token in ast_utils.iter_keyword_arguments_as_tokens( last_in_stack): name = str(arg_token) if collector.accepts(name): variable_found = _VariableFoundFromToken( completion_context, arg_token, "", variable_name=name) collector.on_variable(variable_found)
def complete( completion_context: ICompletionContext, imported_keyword_name_to_keyword: Dict[str, List[IKeywordFound]], ): from robotframework_ls.impl import ast_utils token_info = completion_context.get_current_token() if token_info is not None: token = ast_utils.get_keyword_name_token(token_info.node, token_info.token) if token is not None: import_location_info = _obtain_import_location_info( completion_context) collector = _Collector( completion_context.sel, token, import_location_info, imported_keyword_name_to_keyword, ) _collect_auto_import_completions(completion_context, collector) return collector.completion_items return []
def complete(completion_context: ICompletionContext) -> List[dict]: from robotframework_ls.impl.protocols import IKeywordFound from robotframework_ls.impl.protocols import IKeywordArg ret: List[dict] = [] sel = completion_context.sel if sel.word_from_column: # i.e.: if there's any word after the column, skip it (could work, but # let's simplify for now). return ret token_info = completion_context.get_current_token() if token_info and token_info.token: token = token_info.token if token.type not in (token.ARGUMENT, token.EOL): return [] current_keyword_definition = completion_context.get_current_keyword_definition( ) if current_keyword_definition is not None: keyword_found: IKeywordFound = current_keyword_definition.keyword_found keyword_args = keyword_found.keyword_args if keyword_args: curr_token_value = token.value if "=" in curr_token_value: return ret # Note: If it's an empty word, it's okay to be in the middle. if token.end_col_offset > sel.col and curr_token_value.strip(): return [] word_to_column = curr_token_value.strip() arg: IKeywordArg for arg in keyword_args: if arg.is_keyword_arg or arg.is_star_arg: continue arg_name = arg.arg_name if arg_name.startswith("${") and arg_name.endswith("}"): arg_name = arg_name[2:-1] arg_name = arg_name.strip() if arg_name: arg_name += "=" col_start = sel.col col_end = sel.col new_text = arg_name if word_to_column: if not arg_name.startswith(word_to_column): continue new_text = arg_name[len(word_to_column):] documentation = arg.original_arg ret.append( _create_completion_item(arg_name, new_text, sel, col_start, col_end, documentation)) return ret
def _collect_variable_imports_variables(completion_context: ICompletionContext, collector: _Collector): variable_import_doc: IRobotDocument for variable_import_doc in completion_context.get_variable_imports_as_docs( ): try: if variable_import_doc.path.lower().endswith(".py"): python_ast = variable_import_doc.get_python_ast() if python_ast is not None: import ast as ast_module for node in python_ast.body: if isinstance(node, ast_module.Assign): for target in node.targets: if isinstance(target, ast_module.Name): varname = "${%s}" % (target.id, ) if collector.accepts(varname): value = "" try: # Only available for Python 3.8 onwards... end_lineno = getattr( node.value, "end_lineno", None) if end_lineno is None: end_lineno = node.value.lineno # Only available for Python 3.8 onwards... end_col_offset = getattr( node.value, "end_col_offset", None) if end_col_offset is None: end_col_offset = 99999999 value = variable_import_doc.get_range( node.value.lineno - 1, node.value.col_offset, end_lineno - 1, end_col_offset, ) except: log.exception() variable_found = _VariableFoundFromPythonAst( variable_import_doc.path, target.lineno - 1, target.col_offset, target.lineno - 1, target.col_offset + len(target.id), value, variable_name=varname, ) collector.on_variable(variable_found) elif variable_import_doc.path.lower().endswith(".yaml"): contents = variable_import_doc.get_yaml_contents() if isinstance(contents, dict): for key, val in contents.items(): key = "${%s}" % (key, ) if collector.accepts(key): collector.on_variable( _VariableFoundFromYaml(key, str(val))) except: log.exception()
def _collect_resource_imports_variables(completion_context: ICompletionContext, collector: _Collector): resource_doc: IRobotDocument for resource_doc in completion_context.get_resource_imports_as_docs(): new_ctx = completion_context.create_copy(resource_doc) _collect_following_imports(new_ctx, collector)
def code_lens_runs( completion_context: ICompletionContext) -> List[CodeLensTypedDict]: from robot.api import Token # noqa from robotframework_ls.impl.ast_utils import create_range_from_token ast = completion_context.get_ast() completion_context.check_cancelled() ret: List[CodeLensTypedDict] = [] start: PositionTypedDict = {"line": 0, "character": 0} end: PositionTypedDict = {"line": 0, "character": 0} code_lens_range: RangeTypedDict = {"start": start, "end": end} sections = ast.sections test_case_sections = [ x for x in sections if x.__class__.__name__ == "TestCaseSection" ] if len(test_case_sections) > 0: # Run Test command command: CommandTypedDict = { "title": "Run Suite", "command": "robot.runTest", "arguments": [{ "uri": completion_context.doc.uri, "path": completion_context.doc.path, "name": "*", }], } ret.append({"range": code_lens_range, "command": command}) # Debug Test command command = { "title": "Debug Suite", "command": "robot.debugTest", "arguments": [{ "uri": completion_context.doc.uri, "path": completion_context.doc.path, "name": "*", }], } ret.append({"range": code_lens_range, "command": command}) for test_case in test_case_sections: try: for test_node in test_case.body: test_case_name_token = test_node.header.get_token( Token.TESTCASE_NAME) completion_context.check_cancelled() code_lens_range = create_range_from_token(test_case_name_token) # Run Test command command = { "title": "Run", "command": "robot.runTest", "arguments": [{ "uri": completion_context.doc.uri, "path": completion_context.doc.path, "name": test_case_name_token.value, }], } code_lens_dct: CodeLensTypedDict = { "range": code_lens_range, "command": command, } ret.append(code_lens_dct) # Debug Test command command = { "title": "Debug", "command": "robot.debugTest", "arguments": [{ "uri": completion_context.doc.uri, "path": completion_context.doc.path, "name": test_node.name, }], } code_lens_dct = {"range": code_lens_range, "command": command} ret.append(code_lens_dct) except Exception: log.exception("Error computing code lens") return ret
def find_from(cls, completion_context: ICompletionContext): finder = cls(completion_context) finder.visit(completion_context.get_ast()) return finder.errors