Skip to content

Commit e99dad5

Browse files
committed
implement Full Support for BDD Style keywords
1 parent 5a5b49c commit e99dad5

File tree

134 files changed

+265
-1659
lines changed

Some content is hidden

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

134 files changed

+265
-1659
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ All notable changes to the "robotcode" extension will be documented in this file
4747
- optimize loading of imports and collecting keywords
4848
- this addresses [#24](https://github.com/d-biehl/robotcode/issues/24)
4949
- one of the big points here is, beware of namespace pollution ;-)
50-
50+
- Full Support for BDD Style keywords
51+
- includes hover, goto, highlight, references, ...
5152

5253
## 0.5.5
5354

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@
139139
"nameCall": [
140140
"variable.function.keyword-call.robotframework"
141141
],
142+
"bddPrefix": [
143+
"keyword.modifier.bdd-prefix.robotframework"
144+
],
142145
"continuation": [
143146
"punctuation.separator.continuation.robotframework"
144147
],

robotcode/language_server/robotframework/diagnostics/analyzer.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Range,
1818
)
1919
from ...common.text_document import TextDocument
20+
from ..parts.model_helper import ModelHelperMixin
2021
from ..utils.ast import (
2122
Token,
2223
is_not_variable_token,
@@ -31,7 +32,7 @@
3132
ROBOTCODE_PATTERN = re.compile(r"(?P<marker>\brobotcode\b)\s*:\s*(?P<rule>\b\w+\b)")
3233

3334

34-
class Analyzer(AsyncVisitor):
35+
class Analyzer(AsyncVisitor, ModelHelperMixin):
3536
def __init__(self, model: ast.AST, namespace: Namespace) -> None:
3637
from robot.parsing.model.statements import Template, TestTemplate
3738

@@ -109,7 +110,7 @@ async def _analyze_keyword_call(
109110

110111
for e in self.finder.diagnostics:
111112
await self.append_diagnostics(
112-
range=range_from_node_or_token(node, keyword_token),
113+
range=range_from_node_or_token(node, self.strip_bdd_prefix(keyword_token)),
113114
message=e.message,
114115
severity=e.severity,
115116
source=DIAGNOSTICS_SOURCE_NAME,
@@ -119,7 +120,7 @@ async def _analyze_keyword_call(
119120
if result is not None:
120121
if result.errors:
121122
await self.append_diagnostics(
122-
range=range_from_node_or_token(node, keyword_token),
123+
range=range_from_node_or_token(node, self.strip_bdd_prefix(keyword_token)),
123124
message="Keyword definition contains errors.",
124125
severity=DiagnosticSeverity.ERROR,
125126
source=DIAGNOSTICS_SOURCE_NAME,
@@ -162,7 +163,7 @@ async def _analyze_keyword_call(
162163

163164
if result.is_deprecated:
164165
await self.append_diagnostics(
165-
range=range_from_node_or_token(node, keyword_token),
166+
range=range_from_node_or_token(node, self.strip_bdd_prefix(keyword_token)),
166167
message=f"Keyword '{result.name}' is deprecated"
167168
f"{f': {result.deprecated_message}' if result.deprecated_message else ''}.",
168169
severity=DiagnosticSeverity.HINT,
@@ -171,7 +172,7 @@ async def _analyze_keyword_call(
171172
)
172173
if result.is_error_handler:
173174
await self.append_diagnostics(
174-
range=range_from_node_or_token(node, keyword_token),
175+
range=range_from_node_or_token(node, self.strip_bdd_prefix(keyword_token)),
175176
message=f"Keyword definition contains errors: {result.error_handler_message}",
176177
severity=DiagnosticSeverity.ERROR,
177178
source=DIAGNOSTICS_SOURCE_NAME,
@@ -191,7 +192,7 @@ async def _analyze_keyword_call(
191192
except BaseException as e:
192193
await self.append_diagnostics(
193194
range=Range(
194-
start=range_from_token(keyword_token).start,
195+
start=range_from_token(self.strip_bdd_prefix(keyword_token)).start,
195196
end=range_from_token(argument_tokens[-1]).end
196197
if argument_tokens
197198
else range_from_token(keyword_token).end,

robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -911,44 +911,6 @@ def error_from_exception(ex: BaseException, default_source: Optional[str], defau
911911
)
912912

913913

914-
BUILTIN_VARIABLES = [
915-
"${CURDIR}",
916-
"${EMPTY}",
917-
"${TEMPDIR}",
918-
"${EXECDIR}",
919-
"${/}",
920-
"${:}",
921-
"${\\n}",
922-
"${SPACE}",
923-
"${True}",
924-
"${False}",
925-
"${None}",
926-
"${null}",
927-
"${TEST NAME}",
928-
"@{TEST TAGS}",
929-
"${TEST DOCUMENTATION}",
930-
"${TEST STATUS}",
931-
"${TEST MESSAGE}",
932-
"${PREV TEST NAME}",
933-
"${PREV TEST STATUS}",
934-
"${PREV TEST MESSAGE}",
935-
"${SUITE NAME}",
936-
"${SUITE SOURCE}",
937-
"${SUITE DOCUMENTATION}",
938-
"&{SUITE METADATA}",
939-
"${SUITE STATUS}",
940-
"${SUITE MESSAGE}",
941-
"${KEYWORD STATUS}",
942-
"${KEYWORD MESSAGE}",
943-
"${LOG LEVEL}",
944-
"${OUTPUT FILE}",
945-
"${LOG FILE}",
946-
"${REPORT FILE}",
947-
"${DEBUG FILE}",
948-
"${OUTPUT DIR}",
949-
]
950-
951-
952914
@dataclass
953915
class _Variable(object):
954916

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
tokenize_variables,
4949
)
5050
from ..utils.async_ast import AsyncVisitor
51+
from ..utils.variables import BUILTIN_VARIABLES
5152
from .entities import (
5253
ArgumentDefinition,
5354
BuiltInVariableDefinition,
@@ -62,7 +63,6 @@
6263
from .imports_manager import ImportsManager
6364
from .library_doc import (
6465
BUILTIN_LIBRARY_NAME,
65-
BUILTIN_VARIABLES,
6666
DEFAULT_LIBRARIES,
6767
KeywordDoc,
6868
KeywordError,

robotcode/language_server/robotframework/parts/completion.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,9 @@ async def _complete_TestCase_or_Keyword( # noqa: N802
809809

810810
if len(statement_node.tokens) > index:
811811
token = statement_node.tokens[index]
812+
813+
token = self.strip_bdd_prefix(token)
814+
812815
r = range_from_token(token)
813816
if position.is_in_range(r) or r.end == position:
814817
return await create_items(in_assign, in_template, r, token, position)
@@ -821,7 +824,9 @@ async def _complete_TestCase_or_Keyword( # noqa: N802
821824

822825
r.end.character += 1
823826
if position.is_in_range(r) or r.end == position:
824-
return await create_items(in_assign, in_template, r, token, position)
827+
return await create_items(
828+
in_assign, in_template, r, None if self.is_bdd_token(token) else token, position
829+
)
825830

826831
return None
827832

@@ -939,9 +944,26 @@ async def _complete_SuiteSetup_or_SuiteTeardown_or_TestTemplate( # noqa: N802
939944

940945
if len(statement_node.tokens) > 2:
941946
token = cast(Token, statement_node.tokens[2])
947+
948+
token = self.strip_bdd_prefix(token)
949+
942950
r = range_from_token(token)
943951
if position.is_in_range(r) or r.end == position:
944-
return await self.create_keyword_completion_items(token, position, add_reserverd=False)
952+
return await self.create_keyword_completion_items(
953+
None if self.is_bdd_token(token) else token, position, add_reserverd=False
954+
)
955+
956+
if len(statement_node.tokens) > 3:
957+
second_token = statement_node.tokens[3]
958+
ws = whitespace_at_begin_of_token(second_token)
959+
if ws < 1:
960+
return None
961+
962+
r.end.character += 1
963+
if position.is_in_range(r) or r.end == position:
964+
return await self.create_keyword_completion_items(
965+
None if self.is_bdd_token(token) else token, position, add_reserverd=False
966+
)
945967

946968
return None
947969

@@ -1025,6 +1047,7 @@ async def complete_Setup_or_Teardown_or_Template( # noqa: N802
10251047
statement_node = cast(Statement, node)
10261048
if len(statement_node.tokens) > 2:
10271049
token = cast(Token, statement_node.tokens[2])
1050+
10281051
r = range_from_token(token)
10291052
ws = whitespace_at_begin_of_token(token)
10301053
if ws < 2:
@@ -1042,10 +1065,25 @@ async def complete_Setup_or_Teardown_or_Template( # noqa: N802
10421065

10431066
if len(statement_node.tokens) > 3:
10441067
token = cast(Token, statement_node.tokens[3])
1068+
1069+
token = self.strip_bdd_prefix(token)
1070+
10451071
r = range_from_token(token)
10461072
if position.is_in_range(r) or r.end == position:
10471073
return await self.create_keyword_completion_items(token, position, add_reserverd=False)
10481074

1075+
if len(statement_node.tokens) > 4:
1076+
second_token = statement_node.tokens[4]
1077+
ws = whitespace_at_begin_of_token(second_token)
1078+
if ws < 1:
1079+
return None
1080+
1081+
r.end.character += 1
1082+
if position.is_in_range(r) or r.end == position:
1083+
return await self.create_keyword_completion_items(
1084+
None if self.is_bdd_token(token) else token, position, add_reserverd=False
1085+
)
1086+
10491087
return None
10501088

10511089
async def complete_Setup( # noqa: N802

robotcode/language_server/robotframework/parts/document_highlight.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ async def highlight_KeywordCall( # noqa: N802
174174
if result is not None:
175175
keyword_doc, keyword_token = result
176176

177+
keyword_token = self.strip_bdd_prefix(keyword_token)
178+
177179
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
178180

179181
kw_range = range_from_token(keyword_token)
@@ -185,7 +187,12 @@ async def highlight_KeywordCall( # noqa: N802
185187
if position in r:
186188
# TODO highlight namespaces
187189
return None
188-
if keyword_doc is not None and not keyword_doc.is_error_handler and keyword_doc.source:
190+
if (
191+
position in kw_range
192+
and keyword_doc is not None
193+
and not keyword_doc.is_error_handler
194+
and keyword_doc.source
195+
):
189196
return [
190197
*(
191198
[DocumentHighlight(keyword_doc.range, DocumentHighlightKind.TEXT)]
@@ -258,6 +265,9 @@ async def highlight_Fixture( # noqa: N802
258265

259266
if result is not None:
260267
keyword_doc, keyword_token = result
268+
269+
keyword_token = self.strip_bdd_prefix(keyword_token)
270+
261271
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
262272

263273
kw_range = range_from_token(keyword_token)
@@ -270,7 +280,7 @@ async def highlight_Fixture( # noqa: N802
270280
# TODO highlight namespaces
271281
return None
272282

273-
if keyword_doc is not None and not keyword_doc.is_error_handler:
283+
if position in kw_range and keyword_doc is not None and not keyword_doc.is_error_handler:
274284
return [
275285
*(
276286
[DocumentHighlight(keyword_doc.range, DocumentHighlightKind.TEXT)]
@@ -300,6 +310,8 @@ async def _highlight_Template_or_TestTemplate( # noqa: N802
300310
if keyword_token is None:
301311
return None
302312

313+
keyword_token = self.strip_bdd_prefix(keyword_token)
314+
303315
if position.is_in_range(range_from_token(keyword_token)):
304316
namespace = await self.parent.documents_cache.get_namespace(document)
305317
if namespace is None:

robotcode/language_server/robotframework/parts/goto.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,8 @@ 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(keyword_token)
295+
294296
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
295297

296298
kw_range = range_from_token(keyword_token)
@@ -321,7 +323,7 @@ async def definition_KeywordCall( # noqa: N802
321323
else:
322324
return None
323325

324-
if keyword_doc is not None and keyword_doc.source:
326+
if position in kw_range and keyword_doc is not None and keyword_doc.source:
325327
return [
326328
LocationLink(
327329
origin_selection_range=kw_range,
@@ -355,6 +357,8 @@ async def definition_Fixture( # noqa: N802
355357
if result is not None:
356358
keyword_doc, keyword_token = result
357359

360+
keyword_token = self.strip_bdd_prefix(keyword_token)
361+
358362
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
359363

360364
kw_range = range_from_token(keyword_token)
@@ -385,7 +389,7 @@ async def definition_Fixture( # noqa: N802
385389
else:
386390
return None
387391

388-
if keyword_doc is not None and keyword_doc.source:
392+
if position in kw_range and keyword_doc is not None and keyword_doc.source:
389393
return [
390394
LocationLink(
391395
origin_selection_range=kw_range,
@@ -415,6 +419,8 @@ async def _definition_Template_or_TestTemplate( # noqa: N802
415419
if keyword_token is None:
416420
return None
417421

422+
keyword_token = self.strip_bdd_prefix(keyword_token)
423+
418424
if position.is_in_range(range_from_token(keyword_token)):
419425
namespace = await self.parent.documents_cache.get_namespace(document)
420426
if namespace is None:

robotcode/language_server/robotframework/parts/hover.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ 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(keyword_token)
270+
269271
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
270272

271273
kw_range = range_from_token(keyword_token)
@@ -280,7 +282,7 @@ async def hover_KeywordCall( # noqa: N802
280282
range=r,
281283
)
282284

283-
if keyword_doc is not None and not keyword_doc.is_error_handler:
285+
if position in kw_range and keyword_doc is not None and not keyword_doc.is_error_handler:
284286
return Hover(
285287
contents=MarkupContent(kind=MarkupKind.MARKDOWN, value=keyword_doc.to_markdown()),
286288
range=kw_range,
@@ -308,6 +310,9 @@ async def hover_Fixture( # noqa: N802
308310

309311
if result is not None:
310312
keyword_doc, keyword_token = result
313+
314+
keyword_token = self.strip_bdd_prefix(keyword_token)
315+
311316
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
312317

313318
kw_range = range_from_token(keyword_token)
@@ -322,7 +327,7 @@ async def hover_Fixture( # noqa: N802
322327
range=r,
323328
)
324329

325-
if keyword_doc is not None and not keyword_doc.is_error_handler:
330+
if position in kw_range and keyword_doc is not None and not keyword_doc.is_error_handler:
326331
return Hover(
327332
contents=MarkupContent(kind=MarkupKind.MARKDOWN, value=keyword_doc.to_markdown()),
328333
range=kw_range,
@@ -342,6 +347,8 @@ async def _hover_Template_or_TestTemplate( # noqa: N802
342347
if keyword_token is None:
343348
return None
344349

350+
keyword_token = self.strip_bdd_prefix(keyword_token)
351+
345352
if position.is_in_range(range_from_token(keyword_token)):
346353
namespace = await self.parent.documents_cache.get_namespace(document)
347354
if namespace is None:

robotcode/language_server/robotframework/parts/model_helper.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,28 @@ def get_expression_statement_types(cls) -> Tuple[Type[Any]]:
420420
)
421421

422422
return cls.__expression_statement_types
423+
424+
BDD_TOKEN_REGEX = re.compile(r"^(Given|When|Then|And|But)\s", flags=re.IGNORECASE)
425+
BDD_TOKEN = re.compile(r"^(Given|When|Then|And|But)$", flags=re.IGNORECASE)
426+
427+
@classmethod
428+
def strip_bdd_prefix(cls, token: Token) -> Token:
429+
from robot.parsing.lexer import Token as RobotToken
430+
431+
bdd_match = cls.BDD_TOKEN_REGEX.match(token.value)
432+
if bdd_match:
433+
bdd_len = len(bdd_match.group(1))
434+
435+
token = RobotToken(
436+
token.type,
437+
token.value[bdd_len + 1 :],
438+
token.lineno,
439+
token.col_offset + bdd_len + 1,
440+
token.error,
441+
)
442+
return token
443+
444+
@classmethod
445+
def is_bdd_token(cls, token: Token) -> bool:
446+
bdd_match = cls.BDD_TOKEN.match(token.value)
447+
return bool(bdd_match)

0 commit comments

Comments
 (0)