Skip to content

Commit 47c1feb

Browse files
committed
feat(langserver): rework "Analysing", "Hover", "Document Highlight", "Goto" and other things to make them faster, simpler, and easier to extend
1 parent c12e1ef commit 47c1feb

File tree

5,942 files changed

+68948
-62002
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

5,942 files changed

+68948
-62002
lines changed

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/analyzer.py

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
LibraryEntry,
4242
ResourceEntry,
4343
VariableDefinition,
44+
VariableDefinitionType,
4445
VariableNotFoundDefinition,
4546
)
4647
from .library_doc import KeywordDoc, KeywordMatcher, is_embedded_keyword
@@ -245,16 +246,36 @@ async def visit(self, node: ast.AST) -> None:
245246
var_range = range_from_token(var_token)
246247
else:
247248
var_range = range_from_token(var_token)
249+
250+
suite_var = None
251+
if isinstance(var, CommandLineVariableDefinition):
252+
suite_var = await self.namespace.find_variable(
253+
var.name,
254+
skip_commandline_variables=True,
255+
ignore_error=True,
256+
)
257+
if suite_var is not None and suite_var.type not in [
258+
VariableDefinitionType.VARIABLE
259+
]:
260+
suite_var = None
261+
248262
if var.name_range != var_range:
249263
self._variable_references[var].add(
250264
Location(self.namespace.document.document_uri, var_range)
251265
)
266+
if suite_var is not None:
267+
self._variable_references[suite_var].add(
268+
Location(self.namespace.document.document_uri, var_range)
269+
)
270+
252271
elif var not in self._variable_references and token1.type in [
253272
RobotToken.ASSIGN,
254273
RobotToken.ARGUMENT,
255274
RobotToken.VARIABLE,
256275
]:
257276
self._variable_references[var] = set()
277+
if suite_var is not None:
278+
self._variable_references[suite_var] = set()
258279

259280
if (
260281
isinstance(node, Statement)
@@ -280,11 +301,23 @@ async def visit(self, node: ast.AST) -> None:
280301
else:
281302
if self.namespace.document is not None:
282303
var_range = range_from_token(var_token)
304+
283305
if var.name_range != var_range:
284306
self._variable_references[var].add(
285307
Location(self.namespace.document.document_uri, range_from_token(var_token))
286308
)
287309

310+
if isinstance(var, CommandLineVariableDefinition):
311+
suite_var = await self.namespace.find_variable(
312+
var.name,
313+
skip_commandline_variables=True,
314+
ignore_error=True,
315+
)
316+
if suite_var is not None and suite_var.type in [VariableDefinitionType.VARIABLE]:
317+
self._variable_references[suite_var].add(
318+
Location(self.namespace.document.document_uri, range_from_token(var_token))
319+
)
320+
288321
await super().visit(node)
289322
finally:
290323
self.node_stack = self.node_stack[:-1]
@@ -377,6 +410,37 @@ async def _analyze_keyword_call(
377410

378411
result = self.finder.find_keyword(keyword)
379412

413+
if (
414+
result is not None
415+
and lib_entry is not None
416+
and kw_namespace
417+
and result.parent is not None
418+
and result.parent != lib_entry.library_doc.digest
419+
):
420+
entry = next(
421+
(
422+
v
423+
for v in (await self.namespace.get_libraries()).values()
424+
if v.library_doc.digest == result.parent
425+
),
426+
None,
427+
)
428+
if entry is None:
429+
entry = next(
430+
(
431+
v
432+
for v in (await self.namespace.get_resources()).values()
433+
if v.library_doc.digest == result.parent
434+
),
435+
None,
436+
)
437+
if entry is not None:
438+
lib_entry = entry
439+
440+
if kw_namespace and lib_entry is not None and lib_range is not None:
441+
if self.namespace.document is not None:
442+
self._namespace_references[lib_entry].add(Location(self.namespace.document.document_uri, lib_range))
443+
380444
if not ignore_errors_if_contains_variables or is_not_variable_token(keyword_token):
381445
for e in self.finder.diagnostics:
382446
self.append_diagnostics(
@@ -386,11 +450,11 @@ async def _analyze_keyword_call(
386450
code=e.code,
387451
)
388452

389-
if kw_namespace and lib_entry is not None and lib_range is not None:
390-
if self.namespace.document is not None:
391-
self._namespace_references[lib_entry].add(Location(self.namespace.document.document_uri, lib_range))
392-
393-
if result is not None:
453+
if result is None:
454+
if self.namespace.document is not None and self.finder.multiple_keywords_result is not None:
455+
for lib_entry, kw_doc in self.finder.multiple_keywords_result:
456+
self._keyword_references[kw_doc].add(Location(self.namespace.document.document_uri, kw_range))
457+
else:
394458
if self.namespace.document is not None:
395459
self._keyword_references[result].add(Location(self.namespace.document.document_uri, kw_range))
396460

@@ -538,6 +602,17 @@ async def _analyze_keyword_call(
538602
self._variable_references[var].add(
539603
Location(self.namespace.document.document_uri, range_from_token(var_token))
540604
)
605+
606+
if isinstance(var, CommandLineVariableDefinition):
607+
suite_var = await self.namespace.find_variable(
608+
var.name,
609+
skip_commandline_variables=True,
610+
ignore_error=True,
611+
)
612+
if suite_var is not None and suite_var.type in [VariableDefinitionType.VARIABLE]:
613+
self._variable_references[suite_var].add(
614+
Location(self.namespace.document.document_uri, range_from_token(var_token))
615+
)
541616
if result.argument_definitions:
542617
for arg in argument_tokens:
543618
name, value = split_from_equals(arg.value)
@@ -964,22 +1039,61 @@ def _check_import_name(self, value: Optional[str], node: ast.AST, type: str) ->
9641039
)
9651040

9661041
async def visit_VariablesImport(self, node: ast.AST) -> None: # noqa: N802
967-
if get_robot_version() >= (6, 1):
968-
from robot.parsing.model.statements import VariablesImport
1042+
from robot.parsing.lexer.tokens import Token as RobotToken
1043+
from robot.parsing.model.statements import VariablesImport
9691044

1045+
if get_robot_version() >= (6, 1):
9701046
import_node = cast(VariablesImport, node)
9711047
self._check_import_name(import_node.name, node, "Variables")
9721048

1049+
n = cast(VariablesImport, node)
1050+
name_token = cast(RobotToken, n.get_token(RobotToken.NAME))
1051+
if name_token is None:
1052+
return
1053+
1054+
entries = await self.namespace.get_imported_variables()
1055+
if entries and self.namespace.document:
1056+
for v in entries.values():
1057+
if v.import_source == self.namespace.source and v.import_range == range_from_token(name_token):
1058+
if v not in self._namespace_references:
1059+
self._namespace_references[v] = set()
1060+
9731061
async def visit_ResourceImport(self, node: ast.AST) -> None: # noqa: N802
974-
if get_robot_version() >= (6, 1):
975-
from robot.parsing.model.statements import ResourceImport
1062+
from robot.parsing.lexer.tokens import Token as RobotToken
1063+
from robot.parsing.model.statements import ResourceImport
9761064

1065+
if get_robot_version() >= (6, 1):
9771066
import_node = cast(ResourceImport, node)
9781067
self._check_import_name(import_node.name, node, "Resource")
9791068

1069+
n = cast(ResourceImport, node)
1070+
name_token = cast(RobotToken, n.get_token(RobotToken.NAME))
1071+
if name_token is None:
1072+
return
1073+
1074+
entries = await self.namespace.get_resources()
1075+
if entries and self.namespace.document:
1076+
for v in entries.values():
1077+
if v.import_source == self.namespace.source and v.import_range == range_from_token(name_token):
1078+
if v not in self._namespace_references:
1079+
self._namespace_references[v] = set()
1080+
9801081
async def visit_LibraryImport(self, node: ast.AST) -> None: # noqa: N802
981-
if get_robot_version() >= (6, 1):
982-
from robot.parsing.model.statements import LibraryImport
1082+
from robot.parsing.lexer.tokens import Token as RobotToken
1083+
from robot.parsing.model.statements import LibraryImport
9831084

1085+
if get_robot_version() >= (6, 1):
9841086
import_node = cast(LibraryImport, node)
9851087
self._check_import_name(import_node.name, node, "Library")
1088+
1089+
n = cast(LibraryImport, node)
1090+
name_token = cast(RobotToken, n.get_token(RobotToken.NAME))
1091+
if name_token is None:
1092+
return
1093+
1094+
entries = await self.namespace.get_libraries()
1095+
if entries and self.namespace.document:
1096+
for v in entries.values():
1097+
if v.import_source == self.namespace.source and v.import_range == range_from_token(name_token):
1098+
if v not in self._namespace_references:
1099+
self._namespace_references[v] = set()

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/entities.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,12 @@ def __repr__(self) -> str:
171171

172172

173173
class VariableDefinitionType(Enum):
174-
VARIABLE = "variable"
174+
VARIABLE = "suite variable"
175175
LOCAL_VARIABLE = "local variable"
176176
ARGUMENT = "argument"
177-
COMMAND_LINE_VARIABLE = "command line variable"
177+
COMMAND_LINE_VARIABLE = "global variable [command line]"
178178
BUILTIN_VARIABLE = "builtin variable"
179-
IMPORTED_VARIABLE = "imported variable"
179+
IMPORTED_VARIABLE = "suite variable [imported]"
180180
ENVIRONMENT_VARIABLE = "environment variable"
181181
VARIABLE_NOT_FOUND = "variable not found"
182182

@@ -310,7 +310,7 @@ def __hash__(self) -> int:
310310
class LibraryEntry:
311311
name: str
312312
import_name: str
313-
library_doc: "LibraryDoc"
313+
library_doc: "LibraryDoc" = field(compare=False)
314314
args: Tuple[Any, ...] = ()
315315
alias: Optional[str] = None
316316
import_range: Range = field(default_factory=lambda: Range.zero())
@@ -328,22 +328,33 @@ def __str__(self) -> str:
328328
@single_call
329329
def __hash__(self) -> int:
330330
return hash(
331-
(type(self), self.name, self.import_name, self.args, self.alias, self.import_range, self.import_source)
331+
(
332+
type(self),
333+
self.name,
334+
self.import_name,
335+
self.args,
336+
self.alias,
337+
self.import_range,
338+
self.import_source,
339+
self.alias_range,
340+
)
332341
)
333342

334343

335344
@dataclass
336345
class ResourceEntry(LibraryEntry):
337-
imports: List[Import] = field(default_factory=list)
338-
variables: List[VariableDefinition] = field(default_factory=list)
346+
imports: List[Import] = field(default_factory=list, compare=False)
347+
variables: List[VariableDefinition] = field(default_factory=list, compare=False)
339348

340349
@single_call
341350
def __hash__(self) -> int:
342-
return hash(
343-
(type(self), self.name, self.import_name, self.args, self.alias, self.import_range, self.import_source)
344-
)
351+
return hash((type(self), self.name, self.import_name, self.import_range, self.import_source))
345352

346353

347354
@dataclass
348355
class VariablesEntry(LibraryEntry):
349-
variables: List[ImportedVariableDefinition] = field(default_factory=list)
356+
variables: List[ImportedVariableDefinition] = field(default_factory=list, compare=False)
357+
358+
@single_call
359+
def __hash__(self) -> int:
360+
return hash((type(self), self.name, self.import_name, self.args, self.import_range, self.import_source))

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/imports_manager.py

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -571,18 +571,24 @@ def clear_cache(self) -> None:
571571

572572
@_logger.call
573573
async def get_command_line_variables(self) -> List[VariableDefinition]:
574-
from robot.errors import DataError
575574
from robot.utils.text import split_args_from_name_or_path
576575

577576
async with self._command_line_variables_lock:
578577
if self._command_line_variables is None:
579578
command_line_vars: List[VariableDefinition] = []
579+
580580
command_line_vars += [
581581
CommandLineVariableDefinition(0, 0, 0, 0, "", f"${{{k}}}", None, has_value=True, value=v)
582-
for k, v in (self.parent_protocol.profile.variables or {}).items()
582+
for k, v in {
583+
**{k1: v1 for k1, v1 in (self.parent_protocol.profile.variables or {}).items()},
584+
**self.config.robot.variables,
585+
}.items()
583586
]
584587

585-
for variable_file in self.parent_protocol.profile.variable_files or []:
588+
for variable_file in [
589+
*(self.parent_protocol.profile.variable_files or []),
590+
*self.config.robot.variable_files,
591+
]:
586592
name, args = split_args_from_name_or_path(str(variable_file))
587593
try:
588594
lib_doc = await self.get_libdoc_for_variables_import(
@@ -594,49 +600,31 @@ async def get_command_line_variables(self) -> List[VariableDefinition]:
594600
resolve_command_line_vars=False,
595601
)
596602
if lib_doc is not None:
597-
command_line_vars += lib_doc.variables
598-
599-
if lib_doc.errors:
600-
# TODO add diagnostics
601-
for error in lib_doc.errors:
602-
self._logger.error(
603-
lambda: f"{error.type_name}: {error.message} in {error.source}:{error.line_no}"
604-
)
605-
except (SystemExit, KeyboardInterrupt, asyncio.CancelledError):
606-
raise
607-
except BaseException as e:
608-
# TODO add diagnostics
609-
self._logger.exception(e)
610-
611-
command_line_vars += [
612-
CommandLineVariableDefinition(0, 0, 0, 0, "", f"${{{k}}}", None, has_value=True, value=v)
613-
for k, v in self.config.robot.variables.items()
614-
]
615-
for variable_file in self.config.robot.variable_files:
616-
name, args = split_args_from_name_or_path(variable_file)
617-
try:
618-
lib_doc = await self.get_libdoc_for_variables_import(
619-
name,
620-
tuple(args),
621-
str(self.folder.to_path()),
622-
self,
623-
resolve_variables=False,
624-
resolve_command_line_vars=False,
625-
)
626-
if lib_doc is not None:
627-
command_line_vars += lib_doc.variables
603+
command_line_vars += [
604+
CommandLineVariableDefinition(
605+
line_no=e.line_no,
606+
col_offset=e.col_offset,
607+
end_line_no=e.end_line_no,
608+
end_col_offset=e.end_col_offset,
609+
source=e.source,
610+
name=e.name,
611+
name_token=e.name_token,
612+
has_value=e.has_value,
613+
resolvable=e.resolvable,
614+
value=e.value,
615+
value_is_native=e.value_is_native,
616+
)
617+
for e in lib_doc.variables
618+
]
628619

629620
if lib_doc.errors:
630621
# TODO add diagnostics
631622
for error in lib_doc.errors:
632623
self._logger.error(
633624
lambda: f"{error.type_name}: {error.message} in {error.source}:{error.line_no}"
634625
)
635-
636626
except (SystemExit, KeyboardInterrupt, asyncio.CancelledError):
637627
raise
638-
except DataError:
639-
pass
640628
except BaseException as e:
641629
# TODO add diagnostics
642630
self._logger.exception(e)

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,9 +1385,11 @@ def get_test_library(
13851385
create_handlers=False,
13861386
variables=robot_variables,
13871387
)
1388+
lib.get_instance()
13881389
except (SystemExit, KeyboardInterrupt):
13891390
raise
13901391
except BaseException as e:
1392+
lib = None
13911393
errors.append(
13921394
error_from_exception(
13931395
e,
@@ -1399,9 +1401,11 @@ def get_test_library(
13991401
if args:
14001402
try:
14011403
lib = get_test_library(libcode, source, library_name, (), create_handlers=False)
1404+
lib.get_instance()
14021405
except (SystemExit, KeyboardInterrupt):
14031406
raise
14041407
except BaseException:
1408+
lib = None
14051409
pass
14061410

14071411
real_source = lib.source if lib is not None else source

0 commit comments

Comments
 (0)