robotcode-robot 0.68.2__py3-none-any.whl → 0.68.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- robotcode/robot/__version__.py +1 -1
- robotcode/robot/config/model.py +63 -51
- robotcode/robot/diagnostics/document_cache_helper.py +523 -0
- robotcode/robot/diagnostics/entities.py +2 -1
- robotcode/robot/diagnostics/errors.py +33 -0
- robotcode/robot/diagnostics/imports_manager.py +1499 -0
- robotcode/robot/diagnostics/library_doc.py +3 -2
- robotcode/robot/diagnostics/model_helper.py +799 -0
- robotcode/robot/diagnostics/namespace.py +2165 -0
- robotcode/robot/diagnostics/namespace_analyzer.py +1121 -0
- robotcode/robot/diagnostics/workspace_config.py +50 -0
- robotcode/robot/utils/ast.py +6 -5
- robotcode/robot/utils/stubs.py +17 -1
- {robotcode_robot-0.68.2.dist-info → robotcode_robot-0.68.5.dist-info}/METADATA +2 -2
- robotcode_robot-0.68.5.dist-info/RECORD +29 -0
- robotcode_robot-0.68.2.dist-info/RECORD +0 -22
- {robotcode_robot-0.68.2.dist-info → robotcode_robot-0.68.5.dist-info}/WHEEL +0 -0
- {robotcode_robot-0.68.2.dist-info → robotcode_robot-0.68.5.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,1121 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import ast
|
4
|
+
import itertools
|
5
|
+
import os
|
6
|
+
from collections import defaultdict
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
|
9
|
+
|
10
|
+
from robot.parsing.lexer.tokens import Token
|
11
|
+
from robot.parsing.model.blocks import Keyword, TestCase
|
12
|
+
from robot.parsing.model.statements import (
|
13
|
+
Arguments,
|
14
|
+
DocumentationOrMetadata,
|
15
|
+
Fixture,
|
16
|
+
KeywordCall,
|
17
|
+
LibraryImport,
|
18
|
+
ResourceImport,
|
19
|
+
Statement,
|
20
|
+
Template,
|
21
|
+
TemplateArguments,
|
22
|
+
TestCaseName,
|
23
|
+
TestTemplate,
|
24
|
+
Variable,
|
25
|
+
VariablesImport,
|
26
|
+
)
|
27
|
+
from robot.utils.escaping import split_from_equals, unescape
|
28
|
+
from robot.variables.search import contains_variable, search_variable
|
29
|
+
from robotcode.core.concurrent import check_current_task_canceled
|
30
|
+
from robotcode.core.lsp.types import (
|
31
|
+
CodeDescription,
|
32
|
+
Diagnostic,
|
33
|
+
DiagnosticRelatedInformation,
|
34
|
+
DiagnosticSeverity,
|
35
|
+
DiagnosticTag,
|
36
|
+
Location,
|
37
|
+
Position,
|
38
|
+
Range,
|
39
|
+
)
|
40
|
+
from robotcode.core.uri import Uri
|
41
|
+
|
42
|
+
from ..utils import get_robot_version
|
43
|
+
from ..utils.ast import (
|
44
|
+
is_not_variable_token,
|
45
|
+
range_from_node,
|
46
|
+
range_from_node_or_token,
|
47
|
+
range_from_token,
|
48
|
+
strip_variable_token,
|
49
|
+
tokenize_variables,
|
50
|
+
)
|
51
|
+
from ..utils.visitor import Visitor
|
52
|
+
from .entities import (
|
53
|
+
ArgumentDefinition,
|
54
|
+
CommandLineVariableDefinition,
|
55
|
+
EnvironmentVariableDefinition,
|
56
|
+
LibraryEntry,
|
57
|
+
LocalVariableDefinition,
|
58
|
+
VariableDefinition,
|
59
|
+
VariableDefinitionType,
|
60
|
+
VariableNotFoundDefinition,
|
61
|
+
)
|
62
|
+
from .errors import DIAGNOSTICS_SOURCE_NAME, Error
|
63
|
+
from .library_doc import (
|
64
|
+
KeywordDoc,
|
65
|
+
is_embedded_keyword,
|
66
|
+
)
|
67
|
+
from .model_helper import ModelHelper
|
68
|
+
from .namespace import KeywordFinder, Namespace
|
69
|
+
|
70
|
+
if get_robot_version() < (7, 0):
|
71
|
+
from robot.variables.search import VariableIterator
|
72
|
+
else:
|
73
|
+
from robot.variables.search import VariableMatches
|
74
|
+
|
75
|
+
|
76
|
+
@dataclass
|
77
|
+
class AnalyzerResult:
|
78
|
+
diagnostics: List[Diagnostic]
|
79
|
+
keyword_references: Dict[KeywordDoc, Set[Location]]
|
80
|
+
variable_references: Dict[VariableDefinition, Set[Location]]
|
81
|
+
local_variable_assignments: Dict[VariableDefinition, Set[Range]]
|
82
|
+
namespace_references: Dict[LibraryEntry, Set[Location]]
|
83
|
+
|
84
|
+
|
85
|
+
class NamespaceAnalyzer(Visitor, ModelHelper):
|
86
|
+
def __init__(
|
87
|
+
self,
|
88
|
+
model: ast.AST,
|
89
|
+
namespace: Namespace,
|
90
|
+
finder: KeywordFinder,
|
91
|
+
ignored_lines: List[int],
|
92
|
+
) -> None:
|
93
|
+
super().__init__()
|
94
|
+
|
95
|
+
self.model = model
|
96
|
+
self.namespace = namespace
|
97
|
+
self.finder = finder
|
98
|
+
self._ignored_lines = ignored_lines
|
99
|
+
|
100
|
+
self.current_testcase_or_keyword_name: Optional[str] = None
|
101
|
+
self.test_template: Optional[TestTemplate] = None
|
102
|
+
self.template: Optional[Template] = None
|
103
|
+
self.node_stack: List[ast.AST] = []
|
104
|
+
self._diagnostics: List[Diagnostic] = []
|
105
|
+
self._keyword_references: Dict[KeywordDoc, Set[Location]] = defaultdict(set)
|
106
|
+
self._variable_references: Dict[VariableDefinition, Set[Location]] = defaultdict(set)
|
107
|
+
self._local_variable_assignments: Dict[VariableDefinition, Set[Range]] = defaultdict(set)
|
108
|
+
self._namespace_references: Dict[LibraryEntry, Set[Location]] = defaultdict(set)
|
109
|
+
|
110
|
+
def run(self) -> AnalyzerResult:
|
111
|
+
self._diagnostics = []
|
112
|
+
self._keyword_references = defaultdict(set)
|
113
|
+
|
114
|
+
self.visit(self.model)
|
115
|
+
|
116
|
+
return AnalyzerResult(
|
117
|
+
self._diagnostics,
|
118
|
+
self._keyword_references,
|
119
|
+
self._variable_references,
|
120
|
+
self._local_variable_assignments,
|
121
|
+
self._namespace_references,
|
122
|
+
)
|
123
|
+
|
124
|
+
def yield_argument_name_and_rest(self, node: ast.AST, token: Token) -> Iterator[Token]:
|
125
|
+
if isinstance(node, Arguments) and token.type == Token.ARGUMENT:
|
126
|
+
argument = next(
|
127
|
+
(
|
128
|
+
v
|
129
|
+
for v in itertools.dropwhile(
|
130
|
+
lambda t: t.type in Token.NON_DATA_TOKENS,
|
131
|
+
tokenize_variables(token, ignore_errors=True),
|
132
|
+
)
|
133
|
+
if v.type == Token.VARIABLE
|
134
|
+
),
|
135
|
+
None,
|
136
|
+
)
|
137
|
+
if argument is None or argument.value == token.value:
|
138
|
+
yield token
|
139
|
+
else:
|
140
|
+
yield argument
|
141
|
+
i = len(argument.value)
|
142
|
+
|
143
|
+
for t in self.yield_argument_name_and_rest(
|
144
|
+
node,
|
145
|
+
Token(
|
146
|
+
token.type,
|
147
|
+
token.value[i:],
|
148
|
+
token.lineno,
|
149
|
+
token.col_offset + i,
|
150
|
+
token.error,
|
151
|
+
),
|
152
|
+
):
|
153
|
+
yield t
|
154
|
+
else:
|
155
|
+
yield token
|
156
|
+
|
157
|
+
def visit_Variable(self, node: Variable) -> None: # noqa: N802
|
158
|
+
name_token = node.get_token(Token.VARIABLE)
|
159
|
+
if name_token is None:
|
160
|
+
return
|
161
|
+
|
162
|
+
name = name_token.value
|
163
|
+
|
164
|
+
if name is not None:
|
165
|
+
match = search_variable(name, ignore_errors=True)
|
166
|
+
if not match.is_assign(allow_assign_mark=True):
|
167
|
+
return
|
168
|
+
|
169
|
+
if name.endswith("="):
|
170
|
+
name = name[:-1].rstrip()
|
171
|
+
|
172
|
+
r = range_from_token(
|
173
|
+
strip_variable_token(
|
174
|
+
Token(
|
175
|
+
name_token.type,
|
176
|
+
name,
|
177
|
+
name_token.lineno,
|
178
|
+
name_token.col_offset,
|
179
|
+
name_token.error,
|
180
|
+
)
|
181
|
+
)
|
182
|
+
)
|
183
|
+
|
184
|
+
var_def = next(
|
185
|
+
(
|
186
|
+
v
|
187
|
+
for v in self.namespace.get_own_variables()
|
188
|
+
if v.name_token is not None and range_from_token(v.name_token) == r
|
189
|
+
),
|
190
|
+
None,
|
191
|
+
)
|
192
|
+
|
193
|
+
if var_def is None:
|
194
|
+
return
|
195
|
+
|
196
|
+
cmd_line_var = self.namespace.find_variable(
|
197
|
+
name,
|
198
|
+
skip_commandline_variables=False,
|
199
|
+
position=r.start,
|
200
|
+
ignore_error=True,
|
201
|
+
)
|
202
|
+
if isinstance(cmd_line_var, CommandLineVariableDefinition):
|
203
|
+
if self.namespace.document is not None:
|
204
|
+
self._variable_references[cmd_line_var].add(Location(self.namespace.document.document_uri, r))
|
205
|
+
|
206
|
+
if var_def not in self._variable_references:
|
207
|
+
self._variable_references[var_def] = set()
|
208
|
+
|
209
|
+
def generic_visit(self, node: ast.AST) -> None:
|
210
|
+
check_current_task_canceled()
|
211
|
+
|
212
|
+
super().generic_visit(node)
|
213
|
+
|
214
|
+
def visit(self, node: ast.AST) -> None:
|
215
|
+
check_current_task_canceled()
|
216
|
+
|
217
|
+
self.node_stack.append(node)
|
218
|
+
try:
|
219
|
+
severity = (
|
220
|
+
DiagnosticSeverity.HINT
|
221
|
+
if isinstance(node, (DocumentationOrMetadata, TestCaseName))
|
222
|
+
else DiagnosticSeverity.ERROR
|
223
|
+
)
|
224
|
+
|
225
|
+
if isinstance(node, KeywordCall) and node.keyword:
|
226
|
+
kw_doc = self.finder.find_keyword(node.keyword, raise_keyword_error=False)
|
227
|
+
if kw_doc is not None and kw_doc.longname == "BuiltIn.Comment":
|
228
|
+
severity = DiagnosticSeverity.HINT
|
229
|
+
|
230
|
+
if isinstance(node, Statement) and not isinstance(node, (TestTemplate, Template)):
|
231
|
+
for token1 in (
|
232
|
+
t
|
233
|
+
for t in node.tokens
|
234
|
+
if not (isinstance(node, Variable) and t.type == Token.VARIABLE)
|
235
|
+
and t.error is None
|
236
|
+
and contains_variable(t.value, "$@&%")
|
237
|
+
):
|
238
|
+
for token in self.yield_argument_name_and_rest(node, token1):
|
239
|
+
if isinstance(node, Arguments) and token.value == "@{}":
|
240
|
+
continue
|
241
|
+
|
242
|
+
for var_token, var in self.iter_variables_from_token(
|
243
|
+
token,
|
244
|
+
self.namespace,
|
245
|
+
self.node_stack,
|
246
|
+
range_from_token(token).start,
|
247
|
+
skip_commandline_variables=False,
|
248
|
+
return_not_found=True,
|
249
|
+
):
|
250
|
+
if isinstance(var, VariableNotFoundDefinition):
|
251
|
+
self.append_diagnostics(
|
252
|
+
range=range_from_token(var_token),
|
253
|
+
message=f"Variable '{var.name}' not found.",
|
254
|
+
severity=severity,
|
255
|
+
source=DIAGNOSTICS_SOURCE_NAME,
|
256
|
+
code=Error.VARIABLE_NOT_FOUND,
|
257
|
+
)
|
258
|
+
else:
|
259
|
+
if isinstance(var, EnvironmentVariableDefinition) and var.default_value is None:
|
260
|
+
env_name = var.name[2:-1]
|
261
|
+
if os.environ.get(env_name, None) is None:
|
262
|
+
self.append_diagnostics(
|
263
|
+
range=range_from_token(var_token),
|
264
|
+
message=f"Environment variable '{var.name}' not found.",
|
265
|
+
severity=severity,
|
266
|
+
source=DIAGNOSTICS_SOURCE_NAME,
|
267
|
+
code=Error.ENVIROMMENT_VARIABLE_NOT_FOUND,
|
268
|
+
)
|
269
|
+
|
270
|
+
if self.namespace.document is not None:
|
271
|
+
if isinstance(var, EnvironmentVariableDefinition):
|
272
|
+
(
|
273
|
+
var_token.value,
|
274
|
+
_,
|
275
|
+
_,
|
276
|
+
) = var_token.value.partition("=")
|
277
|
+
|
278
|
+
var_range = range_from_token(var_token)
|
279
|
+
|
280
|
+
suite_var = None
|
281
|
+
if isinstance(var, CommandLineVariableDefinition):
|
282
|
+
suite_var = self.namespace.find_variable(
|
283
|
+
var.name,
|
284
|
+
skip_commandline_variables=True,
|
285
|
+
ignore_error=True,
|
286
|
+
)
|
287
|
+
if suite_var is not None and suite_var.type != VariableDefinitionType.VARIABLE:
|
288
|
+
suite_var = None
|
289
|
+
|
290
|
+
if var.name_range != var_range:
|
291
|
+
self._variable_references[var].add(
|
292
|
+
Location(
|
293
|
+
self.namespace.document.document_uri,
|
294
|
+
var_range,
|
295
|
+
)
|
296
|
+
)
|
297
|
+
if suite_var is not None:
|
298
|
+
self._variable_references[suite_var].add(
|
299
|
+
Location(
|
300
|
+
self.namespace.document.document_uri,
|
301
|
+
var_range,
|
302
|
+
)
|
303
|
+
)
|
304
|
+
if token1.type == Token.ASSIGN and isinstance(
|
305
|
+
var,
|
306
|
+
(
|
307
|
+
LocalVariableDefinition,
|
308
|
+
ArgumentDefinition,
|
309
|
+
),
|
310
|
+
):
|
311
|
+
self._local_variable_assignments[var].add(var_range)
|
312
|
+
|
313
|
+
elif var not in self._variable_references and token1.type in [
|
314
|
+
Token.ASSIGN,
|
315
|
+
Token.ARGUMENT,
|
316
|
+
Token.VARIABLE,
|
317
|
+
]:
|
318
|
+
self._variable_references[var] = set()
|
319
|
+
if suite_var is not None:
|
320
|
+
self._variable_references[suite_var] = set()
|
321
|
+
|
322
|
+
if (
|
323
|
+
isinstance(node, Statement)
|
324
|
+
and isinstance(node, self.get_expression_statement_types())
|
325
|
+
and (token := node.get_token(Token.ARGUMENT)) is not None
|
326
|
+
):
|
327
|
+
for var_token, var in self.iter_expression_variables_from_token(
|
328
|
+
token,
|
329
|
+
self.namespace,
|
330
|
+
self.node_stack,
|
331
|
+
range_from_token(token).start,
|
332
|
+
skip_commandline_variables=False,
|
333
|
+
return_not_found=True,
|
334
|
+
):
|
335
|
+
if isinstance(var, VariableNotFoundDefinition):
|
336
|
+
self.append_diagnostics(
|
337
|
+
range=range_from_token(var_token),
|
338
|
+
message=f"Variable '{var.name}' not found.",
|
339
|
+
severity=DiagnosticSeverity.ERROR,
|
340
|
+
source=DIAGNOSTICS_SOURCE_NAME,
|
341
|
+
code=Error.VARIABLE_NOT_FOUND,
|
342
|
+
)
|
343
|
+
else:
|
344
|
+
if self.namespace.document is not None:
|
345
|
+
var_range = range_from_token(var_token)
|
346
|
+
|
347
|
+
if var.name_range != var_range:
|
348
|
+
self._variable_references[var].add(
|
349
|
+
Location(
|
350
|
+
self.namespace.document.document_uri,
|
351
|
+
range_from_token(var_token),
|
352
|
+
)
|
353
|
+
)
|
354
|
+
|
355
|
+
if isinstance(var, CommandLineVariableDefinition):
|
356
|
+
suite_var = self.namespace.find_variable(
|
357
|
+
var.name,
|
358
|
+
skip_commandline_variables=True,
|
359
|
+
ignore_error=True,
|
360
|
+
)
|
361
|
+
if suite_var is not None and suite_var.type == VariableDefinitionType.VARIABLE:
|
362
|
+
self._variable_references[suite_var].add(
|
363
|
+
Location(
|
364
|
+
self.namespace.document.document_uri,
|
365
|
+
range_from_token(var_token),
|
366
|
+
)
|
367
|
+
)
|
368
|
+
|
369
|
+
super().visit(node)
|
370
|
+
finally:
|
371
|
+
self.node_stack = self.node_stack[:-1]
|
372
|
+
|
373
|
+
def _should_ignore(self, range: Range) -> bool:
|
374
|
+
import builtins
|
375
|
+
|
376
|
+
for line_no in builtins.range(range.start.line, range.end.line + 1):
|
377
|
+
if line_no in self._ignored_lines:
|
378
|
+
return True
|
379
|
+
|
380
|
+
return False
|
381
|
+
|
382
|
+
def append_diagnostics(
|
383
|
+
self,
|
384
|
+
range: Range,
|
385
|
+
message: str,
|
386
|
+
severity: Optional[DiagnosticSeverity] = None,
|
387
|
+
code: Union[int, str, None] = None,
|
388
|
+
code_description: Optional[CodeDescription] = None,
|
389
|
+
source: Optional[str] = None,
|
390
|
+
tags: Optional[List[DiagnosticTag]] = None,
|
391
|
+
related_information: Optional[List[DiagnosticRelatedInformation]] = None,
|
392
|
+
data: Optional[Any] = None,
|
393
|
+
) -> None:
|
394
|
+
if self._should_ignore(range):
|
395
|
+
return
|
396
|
+
|
397
|
+
self._diagnostics.append(
|
398
|
+
Diagnostic(
|
399
|
+
range,
|
400
|
+
message,
|
401
|
+
severity,
|
402
|
+
code,
|
403
|
+
code_description,
|
404
|
+
source or DIAGNOSTICS_SOURCE_NAME,
|
405
|
+
tags,
|
406
|
+
related_information,
|
407
|
+
data,
|
408
|
+
)
|
409
|
+
)
|
410
|
+
|
411
|
+
def _analyze_keyword_call(
|
412
|
+
self,
|
413
|
+
keyword: Optional[str],
|
414
|
+
node: ast.AST,
|
415
|
+
keyword_token: Token,
|
416
|
+
argument_tokens: List[Token],
|
417
|
+
analyse_run_keywords: bool = True,
|
418
|
+
allow_variables: bool = False,
|
419
|
+
ignore_errors_if_contains_variables: bool = False,
|
420
|
+
) -> Optional[KeywordDoc]:
|
421
|
+
result: Optional[KeywordDoc] = None
|
422
|
+
|
423
|
+
try:
|
424
|
+
if not allow_variables and not is_not_variable_token(keyword_token):
|
425
|
+
return None
|
426
|
+
|
427
|
+
if (
|
428
|
+
self.finder.find_keyword(
|
429
|
+
keyword_token.value,
|
430
|
+
raise_keyword_error=False,
|
431
|
+
handle_bdd_style=False,
|
432
|
+
)
|
433
|
+
is None
|
434
|
+
):
|
435
|
+
keyword_token = self.strip_bdd_prefix(self.namespace, keyword_token)
|
436
|
+
|
437
|
+
kw_range = range_from_token(keyword_token)
|
438
|
+
|
439
|
+
lib_entry = None
|
440
|
+
lib_range = None
|
441
|
+
kw_namespace = None
|
442
|
+
|
443
|
+
result = self.finder.find_keyword(keyword, raise_keyword_error=False)
|
444
|
+
|
445
|
+
if keyword is not None:
|
446
|
+
(
|
447
|
+
lib_entry,
|
448
|
+
kw_namespace,
|
449
|
+
) = self.get_namespace_info_from_keyword_token(self.namespace, keyword_token)
|
450
|
+
|
451
|
+
if lib_entry and kw_namespace:
|
452
|
+
r = range_from_token(keyword_token)
|
453
|
+
lib_range = r
|
454
|
+
r.end.character = r.start.character + len(kw_namespace)
|
455
|
+
kw_range.start.character = r.end.character + 1
|
456
|
+
lib_range.end.character = kw_range.start.character - 1
|
457
|
+
|
458
|
+
if (
|
459
|
+
result is not None
|
460
|
+
and lib_entry is not None
|
461
|
+
and kw_namespace
|
462
|
+
and result.parent is not None
|
463
|
+
and result.parent != lib_entry.library_doc
|
464
|
+
):
|
465
|
+
lib_entry = None
|
466
|
+
kw_range = range_from_token(keyword_token)
|
467
|
+
|
468
|
+
if kw_namespace and lib_entry is not None and lib_range is not None:
|
469
|
+
if self.namespace.document is not None:
|
470
|
+
entries = [lib_entry]
|
471
|
+
if self.finder.multiple_keywords_result is not None:
|
472
|
+
entries = next(
|
473
|
+
(v for k, v in (self.namespace.get_namespaces()).items() if k == kw_namespace),
|
474
|
+
entries,
|
475
|
+
)
|
476
|
+
for entry in entries:
|
477
|
+
self._namespace_references[entry].add(Location(self.namespace.document.document_uri, lib_range))
|
478
|
+
|
479
|
+
if not ignore_errors_if_contains_variables or is_not_variable_token(keyword_token):
|
480
|
+
for e in self.finder.diagnostics:
|
481
|
+
self.append_diagnostics(
|
482
|
+
range=kw_range,
|
483
|
+
message=e.message,
|
484
|
+
severity=e.severity,
|
485
|
+
code=e.code,
|
486
|
+
)
|
487
|
+
|
488
|
+
if result is None:
|
489
|
+
if self.namespace.document is not None and self.finder.multiple_keywords_result is not None:
|
490
|
+
for d in self.finder.multiple_keywords_result:
|
491
|
+
self._keyword_references[d].add(Location(self.namespace.document.document_uri, kw_range))
|
492
|
+
else:
|
493
|
+
if self.namespace.document is not None:
|
494
|
+
self._keyword_references[result].add(Location(self.namespace.document.document_uri, kw_range))
|
495
|
+
|
496
|
+
if result.errors:
|
497
|
+
self.append_diagnostics(
|
498
|
+
range=kw_range,
|
499
|
+
message="Keyword definition contains errors.",
|
500
|
+
severity=DiagnosticSeverity.ERROR,
|
501
|
+
related_information=[
|
502
|
+
DiagnosticRelatedInformation(
|
503
|
+
location=Location(
|
504
|
+
uri=str(
|
505
|
+
Uri.from_path(
|
506
|
+
err.source
|
507
|
+
if err.source is not None
|
508
|
+
else result.source
|
509
|
+
if result.source is not None
|
510
|
+
else "/<unknown>"
|
511
|
+
)
|
512
|
+
),
|
513
|
+
range=Range(
|
514
|
+
start=Position(
|
515
|
+
line=err.line_no - 1 if err.line_no is not None else max(result.line_no, 0),
|
516
|
+
character=0,
|
517
|
+
),
|
518
|
+
end=Position(
|
519
|
+
line=err.line_no - 1 if err.line_no is not None else max(result.line_no, 0),
|
520
|
+
character=0,
|
521
|
+
),
|
522
|
+
),
|
523
|
+
),
|
524
|
+
message=err.message,
|
525
|
+
)
|
526
|
+
for err in result.errors
|
527
|
+
],
|
528
|
+
)
|
529
|
+
|
530
|
+
if result.is_deprecated:
|
531
|
+
self.append_diagnostics(
|
532
|
+
range=kw_range,
|
533
|
+
message=f"Keyword '{result.name}' is deprecated"
|
534
|
+
f"{f': {result.deprecated_message}' if result.deprecated_message else ''}.",
|
535
|
+
severity=DiagnosticSeverity.HINT,
|
536
|
+
tags=[DiagnosticTag.DEPRECATED],
|
537
|
+
code=Error.DEPRECATED_KEYWORD,
|
538
|
+
)
|
539
|
+
if result.is_error_handler:
|
540
|
+
self.append_diagnostics(
|
541
|
+
range=kw_range,
|
542
|
+
message=f"Keyword definition contains errors: {result.error_handler_message}",
|
543
|
+
severity=DiagnosticSeverity.ERROR,
|
544
|
+
code=Error.KEYWORD_CONTAINS_ERRORS,
|
545
|
+
)
|
546
|
+
if result.is_reserved():
|
547
|
+
self.append_diagnostics(
|
548
|
+
range=kw_range,
|
549
|
+
message=f"'{result.name}' is a reserved keyword.",
|
550
|
+
severity=DiagnosticSeverity.ERROR,
|
551
|
+
code=Error.RESERVED_KEYWORD,
|
552
|
+
)
|
553
|
+
|
554
|
+
if get_robot_version() >= (6, 0) and result.is_resource_keyword and result.is_private():
|
555
|
+
if self.namespace.source != result.source:
|
556
|
+
self.append_diagnostics(
|
557
|
+
range=kw_range,
|
558
|
+
message=f"Keyword '{result.longname}' is private and should only be called by"
|
559
|
+
f" keywords in the same file.",
|
560
|
+
severity=DiagnosticSeverity.WARNING,
|
561
|
+
code=Error.PRIVATE_KEYWORD,
|
562
|
+
)
|
563
|
+
|
564
|
+
if not isinstance(node, (Template, TestTemplate)):
|
565
|
+
try:
|
566
|
+
if result.arguments_spec is not None:
|
567
|
+
result.arguments_spec.resolve(
|
568
|
+
[v.value for v in argument_tokens],
|
569
|
+
None,
|
570
|
+
resolve_variables_until=result.args_to_process,
|
571
|
+
resolve_named=not result.is_any_run_keyword(),
|
572
|
+
)
|
573
|
+
except (SystemExit, KeyboardInterrupt):
|
574
|
+
raise
|
575
|
+
except BaseException as e:
|
576
|
+
self.append_diagnostics(
|
577
|
+
range=Range(
|
578
|
+
start=kw_range.start,
|
579
|
+
end=range_from_token(argument_tokens[-1]).end if argument_tokens else kw_range.end,
|
580
|
+
),
|
581
|
+
message=str(e),
|
582
|
+
severity=DiagnosticSeverity.ERROR,
|
583
|
+
code=type(e).__qualname__,
|
584
|
+
)
|
585
|
+
|
586
|
+
except (SystemExit, KeyboardInterrupt):
|
587
|
+
raise
|
588
|
+
except BaseException as e:
|
589
|
+
self.append_diagnostics(
|
590
|
+
range=range_from_node_or_token(node, keyword_token),
|
591
|
+
message=str(e),
|
592
|
+
severity=DiagnosticSeverity.ERROR,
|
593
|
+
code=type(e).__qualname__,
|
594
|
+
)
|
595
|
+
|
596
|
+
if self.namespace.document is not None and result is not None:
|
597
|
+
if result.longname in [
|
598
|
+
"BuiltIn.Evaluate",
|
599
|
+
"BuiltIn.Should Be True",
|
600
|
+
"BuiltIn.Should Not Be True",
|
601
|
+
"BuiltIn.Skip If",
|
602
|
+
"BuiltIn.Continue For Loop If",
|
603
|
+
"BuiltIn.Exit For Loop If",
|
604
|
+
"BuiltIn.Return From Keyword If",
|
605
|
+
"BuiltIn.Run Keyword And Return If",
|
606
|
+
"BuiltIn.Pass Execution If",
|
607
|
+
"BuiltIn.Run Keyword If",
|
608
|
+
"BuiltIn.Run Keyword Unless",
|
609
|
+
]:
|
610
|
+
tokens = argument_tokens
|
611
|
+
if tokens and (token := tokens[0]):
|
612
|
+
for (
|
613
|
+
var_token,
|
614
|
+
var,
|
615
|
+
) in self.iter_expression_variables_from_token(
|
616
|
+
token,
|
617
|
+
self.namespace,
|
618
|
+
self.node_stack,
|
619
|
+
range_from_token(token).start,
|
620
|
+
skip_commandline_variables=False,
|
621
|
+
return_not_found=True,
|
622
|
+
):
|
623
|
+
if isinstance(var, VariableNotFoundDefinition):
|
624
|
+
self.append_diagnostics(
|
625
|
+
range=range_from_token(var_token),
|
626
|
+
message=f"Variable '{var.name}' not found.",
|
627
|
+
severity=DiagnosticSeverity.ERROR,
|
628
|
+
code=Error.VARIABLE_NOT_FOUND,
|
629
|
+
)
|
630
|
+
else:
|
631
|
+
if self.namespace.document is not None:
|
632
|
+
self._variable_references[var].add(
|
633
|
+
Location(
|
634
|
+
self.namespace.document.document_uri,
|
635
|
+
range_from_token(var_token),
|
636
|
+
)
|
637
|
+
)
|
638
|
+
|
639
|
+
if isinstance(var, CommandLineVariableDefinition):
|
640
|
+
suite_var = self.namespace.find_variable(
|
641
|
+
var.name,
|
642
|
+
skip_commandline_variables=True,
|
643
|
+
ignore_error=True,
|
644
|
+
)
|
645
|
+
if suite_var is not None and suite_var.type == VariableDefinitionType.VARIABLE:
|
646
|
+
self._variable_references[suite_var].add(
|
647
|
+
Location(
|
648
|
+
self.namespace.document.document_uri,
|
649
|
+
range_from_token(var_token),
|
650
|
+
)
|
651
|
+
)
|
652
|
+
if result.argument_definitions:
|
653
|
+
for arg in argument_tokens:
|
654
|
+
name, value = split_from_equals(arg.value)
|
655
|
+
if value is not None and name:
|
656
|
+
arg_def = next(
|
657
|
+
(e for e in result.argument_definitions if e.name[2:-1] == name),
|
658
|
+
None,
|
659
|
+
)
|
660
|
+
if arg_def is not None:
|
661
|
+
name_token = Token(Token.ARGUMENT, name, arg.lineno, arg.col_offset)
|
662
|
+
self._variable_references[arg_def].add(
|
663
|
+
Location(
|
664
|
+
self.namespace.document.document_uri,
|
665
|
+
range_from_token(name_token),
|
666
|
+
)
|
667
|
+
)
|
668
|
+
|
669
|
+
if result is not None and analyse_run_keywords:
|
670
|
+
self._analyse_run_keyword(result, node, argument_tokens)
|
671
|
+
|
672
|
+
return result
|
673
|
+
|
674
|
+
def _analyse_run_keyword(
|
675
|
+
self,
|
676
|
+
keyword_doc: Optional[KeywordDoc],
|
677
|
+
node: ast.AST,
|
678
|
+
argument_tokens: List[Token],
|
679
|
+
) -> List[Token]:
|
680
|
+
if keyword_doc is None or not keyword_doc.is_any_run_keyword():
|
681
|
+
return argument_tokens
|
682
|
+
|
683
|
+
if keyword_doc.is_run_keyword() and len(argument_tokens) > 0:
|
684
|
+
self._analyze_keyword_call(
|
685
|
+
unescape(argument_tokens[0].value),
|
686
|
+
node,
|
687
|
+
argument_tokens[0],
|
688
|
+
argument_tokens[1:],
|
689
|
+
allow_variables=True,
|
690
|
+
ignore_errors_if_contains_variables=True,
|
691
|
+
)
|
692
|
+
|
693
|
+
return argument_tokens[1:]
|
694
|
+
|
695
|
+
if keyword_doc.is_run_keyword_with_condition() and len(argument_tokens) > (
|
696
|
+
cond_count := keyword_doc.run_keyword_condition_count()
|
697
|
+
):
|
698
|
+
self._analyze_keyword_call(
|
699
|
+
unescape(argument_tokens[cond_count].value),
|
700
|
+
node,
|
701
|
+
argument_tokens[cond_count],
|
702
|
+
argument_tokens[cond_count + 1 :],
|
703
|
+
allow_variables=True,
|
704
|
+
ignore_errors_if_contains_variables=True,
|
705
|
+
)
|
706
|
+
return argument_tokens[cond_count + 1 :]
|
707
|
+
|
708
|
+
if keyword_doc.is_run_keywords():
|
709
|
+
has_and = False
|
710
|
+
while argument_tokens:
|
711
|
+
t = argument_tokens[0]
|
712
|
+
argument_tokens = argument_tokens[1:]
|
713
|
+
if t.value == "AND":
|
714
|
+
self.append_diagnostics(
|
715
|
+
range=range_from_token(t),
|
716
|
+
message=f"Incorrect use of {t.value}.",
|
717
|
+
severity=DiagnosticSeverity.ERROR,
|
718
|
+
code=Error.INCORRECT_USE,
|
719
|
+
)
|
720
|
+
continue
|
721
|
+
|
722
|
+
and_token = next((e for e in argument_tokens if e.value == "AND"), None)
|
723
|
+
args = []
|
724
|
+
if and_token is not None:
|
725
|
+
args = argument_tokens[: argument_tokens.index(and_token)]
|
726
|
+
argument_tokens = argument_tokens[argument_tokens.index(and_token) + 1 :]
|
727
|
+
has_and = True
|
728
|
+
elif has_and:
|
729
|
+
args = argument_tokens
|
730
|
+
argument_tokens = []
|
731
|
+
|
732
|
+
self._analyze_keyword_call(
|
733
|
+
unescape(t.value),
|
734
|
+
node,
|
735
|
+
t,
|
736
|
+
args,
|
737
|
+
allow_variables=True,
|
738
|
+
ignore_errors_if_contains_variables=True,
|
739
|
+
)
|
740
|
+
|
741
|
+
return []
|
742
|
+
|
743
|
+
if keyword_doc.is_run_keyword_if() and len(argument_tokens) > 1:
|
744
|
+
|
745
|
+
def skip_args() -> List[Token]:
|
746
|
+
nonlocal argument_tokens
|
747
|
+
result = []
|
748
|
+
while argument_tokens:
|
749
|
+
if argument_tokens[0].value in ["ELSE", "ELSE IF"]:
|
750
|
+
break
|
751
|
+
if argument_tokens:
|
752
|
+
result.append(argument_tokens[0])
|
753
|
+
argument_tokens = argument_tokens[1:]
|
754
|
+
|
755
|
+
return result
|
756
|
+
|
757
|
+
result = self.finder.find_keyword(argument_tokens[1].value)
|
758
|
+
|
759
|
+
if result is not None and result.is_any_run_keyword():
|
760
|
+
argument_tokens = argument_tokens[2:]
|
761
|
+
|
762
|
+
argument_tokens = self._analyse_run_keyword(result, node, argument_tokens)
|
763
|
+
else:
|
764
|
+
kwt = argument_tokens[1]
|
765
|
+
argument_tokens = argument_tokens[2:]
|
766
|
+
|
767
|
+
args = skip_args()
|
768
|
+
|
769
|
+
self._analyze_keyword_call(
|
770
|
+
unescape(kwt.value),
|
771
|
+
node,
|
772
|
+
kwt,
|
773
|
+
args,
|
774
|
+
analyse_run_keywords=False,
|
775
|
+
allow_variables=True,
|
776
|
+
ignore_errors_if_contains_variables=True,
|
777
|
+
)
|
778
|
+
|
779
|
+
while argument_tokens:
|
780
|
+
if argument_tokens[0].value == "ELSE" and len(argument_tokens) > 1:
|
781
|
+
kwt = argument_tokens[1]
|
782
|
+
argument_tokens = argument_tokens[2:]
|
783
|
+
|
784
|
+
args = skip_args()
|
785
|
+
|
786
|
+
result = self._analyze_keyword_call(
|
787
|
+
unescape(kwt.value),
|
788
|
+
node,
|
789
|
+
kwt,
|
790
|
+
args,
|
791
|
+
analyse_run_keywords=False,
|
792
|
+
)
|
793
|
+
|
794
|
+
if result is not None and result.is_any_run_keyword():
|
795
|
+
argument_tokens = self._analyse_run_keyword(result, node, argument_tokens)
|
796
|
+
|
797
|
+
break
|
798
|
+
|
799
|
+
if argument_tokens[0].value == "ELSE IF" and len(argument_tokens) > 2:
|
800
|
+
kwt = argument_tokens[2]
|
801
|
+
argument_tokens = argument_tokens[3:]
|
802
|
+
|
803
|
+
args = skip_args()
|
804
|
+
|
805
|
+
result = self._analyze_keyword_call(
|
806
|
+
unescape(kwt.value),
|
807
|
+
node,
|
808
|
+
kwt,
|
809
|
+
args,
|
810
|
+
analyse_run_keywords=False,
|
811
|
+
)
|
812
|
+
|
813
|
+
if result is not None and result.is_any_run_keyword():
|
814
|
+
argument_tokens = self._analyse_run_keyword(result, node, argument_tokens)
|
815
|
+
else:
|
816
|
+
break
|
817
|
+
|
818
|
+
return argument_tokens
|
819
|
+
|
820
|
+
def visit_Fixture(self, node: Fixture) -> None: # noqa: N802
|
821
|
+
keyword_token = node.get_token(Token.NAME)
|
822
|
+
|
823
|
+
# TODO: calculate possible variables in NAME
|
824
|
+
|
825
|
+
if (
|
826
|
+
keyword_token is not None
|
827
|
+
and keyword_token.value is not None
|
828
|
+
and keyword_token.value.upper() not in ("", "NONE")
|
829
|
+
):
|
830
|
+
self._analyze_keyword_call(
|
831
|
+
node.name,
|
832
|
+
node,
|
833
|
+
keyword_token,
|
834
|
+
[e for e in node.get_tokens(Token.ARGUMENT)],
|
835
|
+
allow_variables=True,
|
836
|
+
ignore_errors_if_contains_variables=True,
|
837
|
+
)
|
838
|
+
|
839
|
+
self.generic_visit(node)
|
840
|
+
|
841
|
+
def visit_TestTemplate(self, node: TestTemplate) -> None: # noqa: N802
|
842
|
+
keyword_token = node.get_token(Token.NAME)
|
843
|
+
|
844
|
+
if keyword_token is not None and keyword_token.value.upper() not in (
|
845
|
+
"",
|
846
|
+
"NONE",
|
847
|
+
):
|
848
|
+
self._analyze_keyword_call(
|
849
|
+
node.value,
|
850
|
+
node,
|
851
|
+
keyword_token,
|
852
|
+
[],
|
853
|
+
analyse_run_keywords=False,
|
854
|
+
allow_variables=True,
|
855
|
+
)
|
856
|
+
|
857
|
+
self.test_template = node
|
858
|
+
self.generic_visit(node)
|
859
|
+
|
860
|
+
def visit_Template(self, node: Template) -> None: # noqa: N802
|
861
|
+
keyword_token = node.get_token(Token.NAME)
|
862
|
+
|
863
|
+
if keyword_token is not None and keyword_token.value.upper() not in (
|
864
|
+
"",
|
865
|
+
"NONE",
|
866
|
+
):
|
867
|
+
self._analyze_keyword_call(
|
868
|
+
node.value,
|
869
|
+
node,
|
870
|
+
keyword_token,
|
871
|
+
[],
|
872
|
+
analyse_run_keywords=False,
|
873
|
+
allow_variables=True,
|
874
|
+
)
|
875
|
+
self.template = node
|
876
|
+
self.generic_visit(node)
|
877
|
+
|
878
|
+
def visit_KeywordCall(self, node: KeywordCall) -> None: # noqa: N802
|
879
|
+
keyword_token = node.get_token(Token.KEYWORD)
|
880
|
+
|
881
|
+
if node.assign and keyword_token is None:
|
882
|
+
self.append_diagnostics(
|
883
|
+
range=range_from_node_or_token(node, node.get_token(Token.ASSIGN)),
|
884
|
+
message="Keyword name cannot be empty.",
|
885
|
+
severity=DiagnosticSeverity.ERROR,
|
886
|
+
code=Error.KEYWORD_NAME_EMPTY,
|
887
|
+
)
|
888
|
+
else:
|
889
|
+
self._analyze_keyword_call(
|
890
|
+
node.keyword,
|
891
|
+
node,
|
892
|
+
keyword_token,
|
893
|
+
[e for e in node.get_tokens(Token.ARGUMENT)],
|
894
|
+
)
|
895
|
+
|
896
|
+
if not self.current_testcase_or_keyword_name:
|
897
|
+
self.append_diagnostics(
|
898
|
+
range=range_from_node_or_token(node, node.get_token(Token.ASSIGN)),
|
899
|
+
message="Code is unreachable.",
|
900
|
+
severity=DiagnosticSeverity.HINT,
|
901
|
+
tags=[DiagnosticTag.UNNECESSARY],
|
902
|
+
code=Error.CODE_UNREACHABLE,
|
903
|
+
)
|
904
|
+
|
905
|
+
self.generic_visit(node)
|
906
|
+
|
907
|
+
def visit_TestCase(self, node: TestCase) -> None: # noqa: N802
|
908
|
+
if not node.name:
|
909
|
+
name_token = node.header.get_token(Token.TESTCASE_NAME)
|
910
|
+
self.append_diagnostics(
|
911
|
+
range=range_from_node_or_token(node, name_token),
|
912
|
+
message="Test case name cannot be empty.",
|
913
|
+
severity=DiagnosticSeverity.ERROR,
|
914
|
+
code=Error.TESTCASE_NAME_EMPTY,
|
915
|
+
)
|
916
|
+
|
917
|
+
self.current_testcase_or_keyword_name = node.name
|
918
|
+
try:
|
919
|
+
self.generic_visit(node)
|
920
|
+
finally:
|
921
|
+
self.current_testcase_or_keyword_name = None
|
922
|
+
self.template = None
|
923
|
+
|
924
|
+
def visit_Keyword(self, node: Keyword) -> None: # noqa: N802
|
925
|
+
if node.name:
|
926
|
+
name_token = node.header.get_token(Token.KEYWORD_NAME)
|
927
|
+
kw_doc = self.get_keyword_definition_at_token(self.namespace.get_library_doc(), name_token)
|
928
|
+
|
929
|
+
if kw_doc is not None and kw_doc not in self._keyword_references:
|
930
|
+
self._keyword_references[kw_doc] = set()
|
931
|
+
|
932
|
+
if (
|
933
|
+
get_robot_version() < (6, 1)
|
934
|
+
and is_embedded_keyword(node.name)
|
935
|
+
and any(isinstance(v, Arguments) and len(v.values) > 0 for v in node.body)
|
936
|
+
):
|
937
|
+
self.append_diagnostics(
|
938
|
+
range=range_from_node_or_token(node, name_token),
|
939
|
+
message="Keyword cannot have both normal and embedded arguments.",
|
940
|
+
severity=DiagnosticSeverity.ERROR,
|
941
|
+
code=Error.KEYWORD_CONTAINS_NORMAL_AND_EMBBEDED_ARGUMENTS,
|
942
|
+
)
|
943
|
+
else:
|
944
|
+
name_token = node.header.get_token(Token.KEYWORD_NAME)
|
945
|
+
self.append_diagnostics(
|
946
|
+
range=range_from_node_or_token(node, name_token),
|
947
|
+
message="Keyword name cannot be empty.",
|
948
|
+
severity=DiagnosticSeverity.ERROR,
|
949
|
+
code=Error.KEYWORD_NAME_EMPTY,
|
950
|
+
)
|
951
|
+
|
952
|
+
self.current_testcase_or_keyword_name = node.name
|
953
|
+
try:
|
954
|
+
self.generic_visit(node)
|
955
|
+
finally:
|
956
|
+
self.current_testcase_or_keyword_name = None
|
957
|
+
|
958
|
+
def _format_template(self, template: str, arguments: Tuple[str, ...]) -> Tuple[str, Tuple[str, ...]]:
|
959
|
+
if get_robot_version() < (7, 0):
|
960
|
+
variables = VariableIterator(template, identifiers="$")
|
961
|
+
count = len(variables)
|
962
|
+
if count == 0 or count != len(arguments):
|
963
|
+
return template, arguments
|
964
|
+
temp = []
|
965
|
+
for (before, _, after), arg in zip(variables, arguments):
|
966
|
+
temp.extend([before, arg])
|
967
|
+
temp.append(after)
|
968
|
+
return "".join(temp), ()
|
969
|
+
|
970
|
+
variables = VariableMatches(template, identifiers="$")
|
971
|
+
count = len(variables)
|
972
|
+
if count == 0 or count != len(arguments):
|
973
|
+
return template, arguments
|
974
|
+
temp = []
|
975
|
+
for var, arg in zip(variables, arguments):
|
976
|
+
temp.extend([var.before, arg])
|
977
|
+
temp.append(var.after)
|
978
|
+
return "".join(temp), ()
|
979
|
+
|
980
|
+
def visit_TemplateArguments(self, node: TemplateArguments) -> None: # noqa: N802
|
981
|
+
template = self.template or self.test_template
|
982
|
+
if template is not None and template.value is not None and template.value.upper() not in ("", "NONE"):
|
983
|
+
argument_tokens = node.get_tokens(Token.ARGUMENT)
|
984
|
+
args = tuple(t.value for t in argument_tokens)
|
985
|
+
keyword = template.value
|
986
|
+
keyword, args = self._format_template(keyword, args)
|
987
|
+
|
988
|
+
result = self.finder.find_keyword(keyword)
|
989
|
+
if result is not None:
|
990
|
+
try:
|
991
|
+
if result.arguments_spec is not None:
|
992
|
+
result.arguments_spec.resolve(
|
993
|
+
args,
|
994
|
+
None,
|
995
|
+
resolve_variables_until=result.args_to_process,
|
996
|
+
resolve_named=not result.is_any_run_keyword(),
|
997
|
+
)
|
998
|
+
except (SystemExit, KeyboardInterrupt):
|
999
|
+
raise
|
1000
|
+
except BaseException as e:
|
1001
|
+
self.append_diagnostics(
|
1002
|
+
range=range_from_node(node, skip_non_data=True),
|
1003
|
+
message=str(e),
|
1004
|
+
severity=DiagnosticSeverity.ERROR,
|
1005
|
+
code=type(e).__qualname__,
|
1006
|
+
)
|
1007
|
+
|
1008
|
+
for d in self.finder.diagnostics:
|
1009
|
+
self.append_diagnostics(
|
1010
|
+
range=range_from_node(node, skip_non_data=True),
|
1011
|
+
message=d.message,
|
1012
|
+
severity=d.severity,
|
1013
|
+
code=d.code,
|
1014
|
+
)
|
1015
|
+
|
1016
|
+
self.generic_visit(node)
|
1017
|
+
|
1018
|
+
def visit_ForceTags(self, node: Statement) -> None: # noqa: N802
|
1019
|
+
if get_robot_version() >= (6, 0):
|
1020
|
+
tag = node.get_token(Token.FORCE_TAGS)
|
1021
|
+
if tag is not None and tag.value.upper() == "FORCE TAGS":
|
1022
|
+
self.append_diagnostics(
|
1023
|
+
range=range_from_node_or_token(node, tag),
|
1024
|
+
message="`Force Tags` is deprecated in favour of new `Test Tags` setting.",
|
1025
|
+
severity=DiagnosticSeverity.INFORMATION,
|
1026
|
+
tags=[DiagnosticTag.DEPRECATED],
|
1027
|
+
code=Error.DEPRECATED_FORCE_TAG,
|
1028
|
+
)
|
1029
|
+
|
1030
|
+
def visit_TestTags(self, node: Statement) -> None: # noqa: N802
|
1031
|
+
if get_robot_version() >= (6, 0):
|
1032
|
+
tag = node.get_token(Token.FORCE_TAGS)
|
1033
|
+
if tag is not None and tag.value.upper() == "FORCE TAGS":
|
1034
|
+
self.append_diagnostics(
|
1035
|
+
range=range_from_node_or_token(node, tag),
|
1036
|
+
message="`Force Tags` is deprecated in favour of new `Test Tags` setting.",
|
1037
|
+
severity=DiagnosticSeverity.INFORMATION,
|
1038
|
+
tags=[DiagnosticTag.DEPRECATED],
|
1039
|
+
code=Error.DEPRECATED_FORCE_TAG,
|
1040
|
+
)
|
1041
|
+
|
1042
|
+
def visit_Tags(self, node: Statement) -> None: # noqa: N802
|
1043
|
+
if get_robot_version() >= (6, 0):
|
1044
|
+
for tag in node.get_tokens(Token.ARGUMENT):
|
1045
|
+
if tag.value and tag.value.startswith("-"):
|
1046
|
+
self.append_diagnostics(
|
1047
|
+
range=range_from_node_or_token(node, tag),
|
1048
|
+
message=f"Settings tags starting with a hyphen using the '[Tags]' setting "
|
1049
|
+
f"is deprecated. In Robot Framework 7.0 this syntax will be used "
|
1050
|
+
f"for removing tags. Escape '{tag.value}' like '\\{tag.value}' to use the "
|
1051
|
+
f"literal value and to avoid this warning.",
|
1052
|
+
severity=DiagnosticSeverity.WARNING,
|
1053
|
+
tags=[DiagnosticTag.DEPRECATED],
|
1054
|
+
code=Error.DEPRECATED_HYPHEN_TAG,
|
1055
|
+
)
|
1056
|
+
|
1057
|
+
def visit_ReturnSetting(self, node: Statement) -> None: # noqa: N802
|
1058
|
+
if get_robot_version() >= (7, 0):
|
1059
|
+
token = node.get_token(Token.RETURN_SETTING)
|
1060
|
+
if token is not None and token.error:
|
1061
|
+
self.append_diagnostics(
|
1062
|
+
range=range_from_node_or_token(node, token),
|
1063
|
+
message=token.error,
|
1064
|
+
severity=DiagnosticSeverity.WARNING,
|
1065
|
+
tags=[DiagnosticTag.DEPRECATED],
|
1066
|
+
code=Error.DEPRECATED_RETURN_SETTING,
|
1067
|
+
)
|
1068
|
+
|
1069
|
+
def _check_import_name(self, value: Optional[str], node: ast.AST, type: str) -> None:
|
1070
|
+
if not value:
|
1071
|
+
self.append_diagnostics(
|
1072
|
+
range=range_from_node(node),
|
1073
|
+
message=f"{type} setting requires value.",
|
1074
|
+
severity=DiagnosticSeverity.ERROR,
|
1075
|
+
code=Error.IMPORT_REQUIRES_VALUE,
|
1076
|
+
)
|
1077
|
+
|
1078
|
+
def visit_VariablesImport(self, node: VariablesImport) -> None: # noqa: N802
|
1079
|
+
if get_robot_version() >= (6, 1):
|
1080
|
+
self._check_import_name(node.name, node, "Variables")
|
1081
|
+
|
1082
|
+
name_token = node.get_token(Token.NAME)
|
1083
|
+
if name_token is None:
|
1084
|
+
return
|
1085
|
+
|
1086
|
+
entries = self.namespace.get_import_entries()
|
1087
|
+
if entries and self.namespace.document:
|
1088
|
+
for v in entries.values():
|
1089
|
+
if v.import_source == self.namespace.source and v.import_range == range_from_token(name_token):
|
1090
|
+
if v not in self._namespace_references:
|
1091
|
+
self._namespace_references[v] = set()
|
1092
|
+
|
1093
|
+
def visit_ResourceImport(self, node: ResourceImport) -> None: # noqa: N802
|
1094
|
+
if get_robot_version() >= (6, 1):
|
1095
|
+
self._check_import_name(node.name, node, "Resource")
|
1096
|
+
|
1097
|
+
name_token = node.get_token(Token.NAME)
|
1098
|
+
if name_token is None:
|
1099
|
+
return
|
1100
|
+
|
1101
|
+
entries = self.namespace.get_import_entries()
|
1102
|
+
if entries and self.namespace.document:
|
1103
|
+
for v in entries.values():
|
1104
|
+
if v.import_source == self.namespace.source and v.import_range == range_from_token(name_token):
|
1105
|
+
if v not in self._namespace_references:
|
1106
|
+
self._namespace_references[v] = set()
|
1107
|
+
|
1108
|
+
def visit_LibraryImport(self, node: LibraryImport) -> None: # noqa: N802
|
1109
|
+
if get_robot_version() >= (6, 1):
|
1110
|
+
self._check_import_name(node.name, node, "Library")
|
1111
|
+
|
1112
|
+
name_token = node.get_token(Token.NAME)
|
1113
|
+
if name_token is None:
|
1114
|
+
return
|
1115
|
+
|
1116
|
+
entries = self.namespace.get_import_entries()
|
1117
|
+
if entries and self.namespace.document:
|
1118
|
+
for v in entries.values():
|
1119
|
+
if v.import_source == self.namespace.source and v.import_range == range_from_token(name_token):
|
1120
|
+
if v not in self._namespace_references:
|
1121
|
+
self._namespace_references[v] = set()
|