Skip to content

Commit 4c9782e

Browse files
committed
Correct handling BDD prefixes for multiple languages
1 parent b408999 commit 4c9782e

File tree

8 files changed

+785
-712
lines changed

8 files changed

+785
-712
lines changed

CHANGELOG.md

Lines changed: 648 additions & 645 deletions
Large diffs are not rendered by default.

robotcode/language_server/robotframework/diagnostics/analyzer.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
tokenize_variables,
3535
)
3636
from ..utils.async_ast import AsyncVisitor
37+
from ..utils.version import get_robot_version
3738
from .entities import VariableDefinition, VariableNotFoundDefinition
3839
from .library_doc import KeywordDoc, is_embedded_keyword
3940
from .namespace import DIAGNOSTICS_SOURCE_NAME, KeywordFinder, Namespace
@@ -319,7 +320,14 @@ async def _analyze_keyword_call(
319320
if not allow_variables and not is_not_variable_token(keyword_token):
320321
return None
321322

322-
keyword_token = self.strip_bdd_prefix(self.namespace, keyword_token)
323+
if (
324+
await self.namespace.find_keyword(
325+
keyword_token.value, raise_keyword_error=False, handle_bdd_style=False
326+
)
327+
is None
328+
):
329+
keyword_token = self.strip_bdd_prefix(self.namespace, keyword_token)
330+
323331
kw_range = range_from_token(keyword_token)
324332

325333
if keyword is not None:
@@ -702,7 +710,7 @@ async def visit_KeywordCall(self, node: ast.AST) -> None: # noqa: N802
702710
range=range_from_node_or_token(value, value.get_token(RobotToken.ASSIGN)),
703711
message="Keyword name cannot be empty.",
704712
severity=DiagnosticSeverity.ERROR,
705-
code="KeywordError",
713+
code="KeywordNameEmpty",
706714
)
707715
else:
708716
await self._analyze_keyword_call(
@@ -733,7 +741,7 @@ async def visit_TestCase(self, node: ast.AST) -> None: # noqa: N802
733741
range=range_from_node_or_token(testcase, name_token),
734742
message="Test case name cannot be empty.",
735743
severity=DiagnosticSeverity.ERROR,
736-
code="KeywordError",
744+
code="TestCaseNameEmpty",
737745
)
738746

739747
self.current_testcase_or_keyword_name = testcase.name
@@ -764,15 +772,15 @@ async def visit_Keyword(self, node: ast.AST) -> None: # noqa: N802
764772
range=range_from_node_or_token(keyword, name_token),
765773
message="Keyword cannot have both normal and embedded arguments.",
766774
severity=DiagnosticSeverity.ERROR,
767-
code="KeywordError",
775+
code="KeywordNormalAndEmbbededError",
768776
)
769777
else:
770778
name_token = cast(KeywordName, keyword.header).get_token(RobotToken.KEYWORD_NAME)
771779
await self.append_diagnostics(
772780
range=range_from_node_or_token(keyword, name_token),
773781
message="Keyword name cannot be empty.",
774782
severity=DiagnosticSeverity.ERROR,
775-
code="KeywordError",
783+
code="KeywordNameEmpty",
776784
)
777785

778786
self.current_testcase_or_keyword_name = keyword.name
@@ -836,3 +844,23 @@ async def visit_TemplateArguments(self, node: ast.AST) -> None: # noqa: N802
836844
)
837845

838846
await self.generic_visit(node)
847+
848+
async def visit_Tags(self, node: ast.AST) -> None: # noqa: N802
849+
from robot.parsing.lexer.tokens import Token as RobotToken
850+
from robot.parsing.model.statements import Tags
851+
852+
if get_robot_version() >= (5, 1):
853+
tags = cast(Tags, node)
854+
855+
for tag in tags.get_tokens(RobotToken.ARGUMENT):
856+
if tag.value and tag.value.startswith("-"):
857+
await self.append_diagnostics(
858+
range=range_from_node_or_token(node, tag),
859+
message=f"Settings tags starting with a hyphen using the '[Tags]' setting "
860+
f"is deprecated. In Robot Framework 5.2 this syntax will be used "
861+
f"for removing tags. Escape '{tag.value}' like '\\{tag.value}' to use the "
862+
f"literal value and to avoid this warning.",
863+
severity=DiagnosticSeverity.WARNING,
864+
tags=[DiagnosticTag.Deprecated],
865+
code="DeprecatedHyphenTag",
866+
)

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,11 +1531,14 @@ async def get_finder(self) -> KeywordFinder:
15311531
return self._finder
15321532

15331533
@_logger.call(condition=lambda self, name: self._finder is not None and name not in self._finder._cache)
1534-
async def find_keyword(self, name: Optional[str], raise_keyword_error: bool = True) -> Optional[KeywordDoc]:
1535-
if self._finder is not None:
1536-
return await self._finder.find_keyword(name, raise_keyword_error)
1534+
async def find_keyword(
1535+
self, name: Optional[str], *, raise_keyword_error: bool = True, handle_bdd_style: bool = True
1536+
) -> Optional[KeywordDoc]:
1537+
finder = self._finder if self._finder is not None else await self.get_finder()
15371538

1538-
return await (await self.get_finder()).find_keyword(name, raise_keyword_error)
1539+
return await finder.find_keyword(
1540+
name, raise_keyword_error=raise_keyword_error, handle_bdd_style=handle_bdd_style
1541+
)
15391542

15401543

15411544
class DiagnosticsEntry(NamedTuple):
@@ -1554,16 +1557,21 @@ def __init__(self, namespace: Namespace) -> None:
15541557
self.namespace = namespace
15551558
self.diagnostics: List[DiagnosticsEntry] = []
15561559
self.self_library_doc: Optional[LibraryDoc] = None
1557-
self._cache: Dict[Optional[str], Tuple[Optional[KeywordDoc], List[DiagnosticsEntry]]] = {}
1560+
self._cache: Dict[Tuple[Optional[str], bool], Tuple[Optional[KeywordDoc], List[DiagnosticsEntry]]] = {}
1561+
self.handle_bdd_style = True
15581562

15591563
def reset_diagnostics(self) -> None:
15601564
self.diagnostics = []
15611565

1562-
async def find_keyword(self, name: Optional[str], raise_keyword_error: bool = False) -> Optional[KeywordDoc]:
1566+
async def find_keyword(
1567+
self, name: Optional[str], *, raise_keyword_error: bool = False, handle_bdd_style: bool = True
1568+
) -> Optional[KeywordDoc]:
15631569
try:
15641570
self.reset_diagnostics()
15651571

1566-
cached = self._cache.get(name, None)
1572+
self.handle_bdd_style = handle_bdd_style
1573+
1574+
cached = self._cache.get((name, self.handle_bdd_style), None)
15671575

15681576
if cached is not None:
15691577
self.diagnostics = cached[1]
@@ -1584,7 +1592,7 @@ async def find_keyword(self, name: Optional[str], raise_keyword_error: bool = Fa
15841592
result = None
15851593
self.diagnostics.append(DiagnosticsEntry(str(e), DiagnosticSeverity.ERROR, "KeywordError"))
15861594

1587-
self._cache[name] = (result, self.diagnostics)
1595+
self._cache[(name, self.handle_bdd_style)] = (result, self.diagnostics)
15881596

15891597
return result
15901598
except CancelSearchError:
@@ -1609,7 +1617,7 @@ async def _find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
16091617
if not result:
16101618
result = await self._get_implicit_keyword(name)
16111619

1612-
if not result:
1620+
if not result and self.handle_bdd_style:
16131621
result = await self._get_bdd_style_keyword(name)
16141622

16151623
return result

robotcode/language_server/robotframework/parts/goto.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,11 @@ async def definition_KeywordCall( # noqa: N802
291291
if result is not None:
292292
keyword_doc, keyword_token = result
293293

294-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
294+
if (
295+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
296+
is None
297+
):
298+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
295299

296300
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
297301

@@ -362,7 +366,11 @@ async def definition_Fixture( # noqa: N802
362366
if result is not None:
363367
keyword_doc, keyword_token = result
364368

365-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
369+
if (
370+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
371+
is None
372+
):
373+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
366374

367375
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
368376

@@ -428,7 +436,11 @@ async def _definition_Template_or_TestTemplate( # noqa: N802
428436
if namespace is None:
429437
return None
430438

431-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
439+
if (
440+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
441+
is None
442+
):
443+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
432444

433445
if position.is_in_range(range_from_token(keyword_token)):
434446

robotcode/language_server/robotframework/parts/hover.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,11 @@ async def hover_KeywordCall( # noqa: N802
266266
if result is not None:
267267
keyword_doc, keyword_token = result
268268

269-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
269+
if (
270+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
271+
is None
272+
):
273+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
270274

271275
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
272276

@@ -316,7 +320,11 @@ async def hover_Fixture( # noqa: N802
316320
if result is not None:
317321
keyword_doc, keyword_token = result
318322

319-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
323+
if (
324+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
325+
is None
326+
):
327+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
320328

321329
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
322330

@@ -356,7 +364,11 @@ async def _hover_Template_or_TestTemplate( # noqa: N802
356364
if namespace is None:
357365
return None
358366

359-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
367+
if (
368+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
369+
is None
370+
):
371+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
360372

361373
if position.is_in_range(range_from_token(keyword_token)):
362374
keyword_doc = await namespace.find_keyword(template_node.value)

robotcode/language_server/robotframework/parts/model_helper.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from ...common.lsp_types import Position
2121
from ..diagnostics.entities import VariableDefinition, VariableNotFoundDefinition
22-
from ..diagnostics.library_doc import KeywordDoc, KeywordError
22+
from ..diagnostics.library_doc import KeywordDoc
2323
from ..diagnostics.namespace import LibraryEntry, Namespace
2424
from ..utils.ast_utils import (
2525
Token,
@@ -184,21 +184,18 @@ async def get_keyworddoc_and_token_from_position( # noqa: N802
184184
analyse_run_keywords: bool = True,
185185
) -> Optional[Tuple[Optional[KeywordDoc], Token]]:
186186

187-
try:
188-
keyword_doc = await namespace.find_keyword(keyword_name)
189-
if keyword_doc is None:
190-
return None
191-
192-
if position.is_in_range(range_from_token(keyword_token)):
193-
return keyword_doc, keyword_token
194-
elif analyse_run_keywords:
195-
return (
196-
await cls.get_run_keyword_keyworddoc_and_token_from_position(
197-
keyword_doc, argument_tokens, namespace, position
198-
)
199-
)[0]
200-
except KeywordError:
201-
pass
187+
keyword_doc = await namespace.find_keyword(keyword_name, raise_keyword_error=False)
188+
if keyword_doc is None:
189+
return None
190+
191+
if position.is_in_range(range_from_token(keyword_token)):
192+
return keyword_doc, keyword_token
193+
elif analyse_run_keywords:
194+
return (
195+
await cls.get_run_keyword_keyworddoc_and_token_from_position(
196+
keyword_doc, argument_tokens, namespace, position
197+
)
198+
)[0]
202199

203200
return None
204201

robotcode/language_server/robotframework/parts/rename.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,11 @@ async def _find_KeywordCall( # noqa: N802
336336
if result is not None:
337337
keyword_doc, keyword_token = result
338338

339-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
339+
if (
340+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
341+
is None
342+
):
343+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
340344

341345
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
342346

@@ -446,7 +450,11 @@ async def _find_Fixture( # noqa: N802
446450
if result is not None:
447451
keyword_doc, keyword_token = result
448452

449-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
453+
if (
454+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
455+
is None
456+
):
457+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
450458

451459
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
452460

@@ -493,7 +501,11 @@ async def _find_Template_or_TestTemplate( # noqa: N802
493501
if namespace is None:
494502
return None
495503

496-
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
504+
if (
505+
await namespace.find_keyword(keyword_token.value, raise_keyword_error=False, handle_bdd_style=False)
506+
is None
507+
):
508+
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
497509

498510
if position.is_in_range(range_from_token(keyword_token), False):
499511

robotcode/language_server/robotframework/parts/semantic_tokens.py

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -322,36 +322,37 @@ async def generate_sem_sub_tokens(
322322
elif token.type in [RobotToken.KEYWORD, ROBOT_KEYWORD_INNER] or (
323323
token.type == RobotToken.NAME and isinstance(node, (Fixture, Template, TestTemplate))
324324
):
325-
bdd_len = 0
325+
if await namespace.find_keyword(token.value, raise_keyword_error=False, handle_bdd_style=False) is None:
326+
bdd_len = 0
326327

327-
if get_robot_version() < (5, 1):
328-
bdd_match = cls.BDD_TOKEN_REGEX.match(token.value)
329-
if bdd_match:
330-
bdd_len = len(bdd_match.group(1))
331-
else:
332-
parts = token.value.split(maxsplit=1)
333-
if len(parts) == 2:
334-
prefix, _ = parts
335-
if prefix.title() in (
336-
namespace.languages.bdd_prefixes
337-
if namespace.languages is not None
338-
else {"Given ", "When ", "Then ", "And ", "But "}
339-
):
340-
bdd_len = len(prefix)
341-
342-
if bdd_len > 0:
343-
yield SemTokenInfo.from_token(
344-
token, RobotSemTokenTypes.BDD_PREFIX, sem_mod, token.col_offset, bdd_len
345-
)
346-
yield SemTokenInfo.from_token(token, sem_type, sem_mod, token.col_offset + bdd_len, 1)
328+
if get_robot_version() < (5, 1):
329+
bdd_match = cls.BDD_TOKEN_REGEX.match(token.value)
330+
if bdd_match:
331+
bdd_len = len(bdd_match.group(1))
332+
else:
333+
parts = token.value.split(maxsplit=1)
334+
if len(parts) == 2:
335+
prefix, _ = parts
336+
if prefix.title() in (
337+
namespace.languages.bdd_prefixes
338+
if namespace.languages is not None
339+
else {"Given ", "When ", "Then ", "And ", "But "}
340+
):
341+
bdd_len = len(prefix)
347342

348-
token = RobotToken(
349-
token.type,
350-
token.value[bdd_len + 1 :],
351-
token.lineno,
352-
token.col_offset + bdd_len + 1,
353-
token.error,
354-
)
343+
if bdd_len > 0:
344+
yield SemTokenInfo.from_token(
345+
token, RobotSemTokenTypes.BDD_PREFIX, sem_mod, token.col_offset, bdd_len
346+
)
347+
yield SemTokenInfo.from_token(token, sem_type, sem_mod, token.col_offset + bdd_len, 1)
348+
349+
token = RobotToken(
350+
token.type,
351+
token.value[bdd_len + 1 :],
352+
token.lineno,
353+
token.col_offset + bdd_len + 1,
354+
token.error,
355+
)
355356

356357
if col_offset is None:
357358
col_offset = token.col_offset

0 commit comments

Comments
 (0)