Skip to content

Commit 6589b71

Browse files
committed
fix(langserver): show a hint instead an error if there are variables used in a test case name
1 parent 72bbdbd commit 6589b71

File tree

2 files changed

+54
-71
lines changed

2 files changed

+54
-71
lines changed

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

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
from collections import defaultdict
88
from dataclasses import dataclass
9-
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast
9+
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
1010

1111
from robot.parsing.lexer.tokens import Token
1212
from robot.parsing.model.blocks import Keyword, TestCase
@@ -15,7 +15,6 @@
1515
DocumentationOrMetadata,
1616
Fixture,
1717
KeywordCall,
18-
KeywordName,
1918
LibraryImport,
2019
ResourceImport,
2120
Statement,
@@ -148,10 +147,8 @@ def yield_argument_name_and_rest(self, node: ast.AST, token: Token) -> Iterator[
148147
else:
149148
yield token
150149

151-
async def visit_Variable(self, node: ast.AST) -> None: # noqa: N802
152-
variable = cast(Variable, node)
153-
154-
name_token = variable.get_token(Token.VARIABLE)
150+
async def visit_Variable(self, node: Variable) -> None: # noqa: N802
151+
name_token = node.get_token(Token.VARIABLE)
155152
if name_token is None:
156153
return
157154

@@ -197,7 +194,9 @@ async def visit(self, node: ast.AST) -> None:
197194
self.node_stack.append(node)
198195
try:
199196
severity = (
200-
DiagnosticSeverity.HINT if isinstance(node, DocumentationOrMetadata) else DiagnosticSeverity.ERROR
197+
DiagnosticSeverity.HINT
198+
if isinstance(node, (DocumentationOrMetadata, TestCaseName))
199+
else DiagnosticSeverity.ERROR
201200
)
202201

203202
if isinstance(node, KeywordCall) and node.keyword:
@@ -762,9 +761,8 @@ def skip_args() -> List[Token]:
762761

763762
return argument_tokens
764763

765-
async def visit_Fixture(self, node: ast.AST) -> None: # noqa: N802
766-
value = cast(Fixture, node)
767-
keyword_token = cast(Token, value.get_token(Token.NAME))
764+
async def visit_Fixture(self, node: Fixture) -> None: # noqa: N802
765+
keyword_token = node.get_token(Token.NAME)
768766

769767
# TODO: calculate possible variables in NAME
770768

@@ -774,58 +772,55 @@ async def visit_Fixture(self, node: ast.AST) -> None: # noqa: N802
774772
and keyword_token.value.upper() not in ("", "NONE")
775773
):
776774
await self._analyze_keyword_call(
777-
value.name,
778-
value,
775+
node.name,
776+
node,
779777
keyword_token,
780-
[cast(Token, e) for e in value.get_tokens(Token.ARGUMENT)],
778+
[e for e in node.get_tokens(Token.ARGUMENT)],
781779
allow_variables=True,
782780
ignore_errors_if_contains_variables=True,
783781
)
784782

785783
await self.generic_visit(node)
786784

787-
async def visit_TestTemplate(self, node: ast.AST) -> None: # noqa: N802
788-
value = cast(TestTemplate, node)
789-
keyword_token = cast(Token, value.get_token(Token.NAME))
785+
async def visit_TestTemplate(self, node: TestTemplate) -> None: # noqa: N802
786+
keyword_token = node.get_token(Token.NAME)
790787

791788
if keyword_token is not None and keyword_token.value.upper() not in ("", "NONE"):
792789
await self._analyze_keyword_call(
793-
value.value, value, keyword_token, [], analyse_run_keywords=False, allow_variables=True
790+
node.value, node, keyword_token, [], analyse_run_keywords=False, allow_variables=True
794791
)
795792

796-
self.test_template = value
793+
self.test_template = node
797794
await self.generic_visit(node)
798795

799-
async def visit_Template(self, node: ast.AST) -> None: # noqa: N802
800-
value = cast(Template, node)
801-
keyword_token = cast(Token, value.get_token(Token.NAME))
796+
async def visit_Template(self, node: Template) -> None: # noqa: N802
797+
keyword_token = node.get_token(Token.NAME)
802798

803799
if keyword_token is not None and keyword_token.value.upper() not in ("", "NONE"):
804800
await self._analyze_keyword_call(
805-
value.value, value, keyword_token, [], analyse_run_keywords=False, allow_variables=True
801+
node.value, node, keyword_token, [], analyse_run_keywords=False, allow_variables=True
806802
)
807-
self.template = value
803+
self.template = node
808804
await self.generic_visit(node)
809805

810-
async def visit_KeywordCall(self, node: ast.AST) -> None: # noqa: N802
811-
value = cast(KeywordCall, node)
812-
keyword_token = cast(Token, value.get_token(Token.KEYWORD))
806+
async def visit_KeywordCall(self, node: KeywordCall) -> None: # noqa: N802
807+
keyword_token = node.get_token(Token.KEYWORD)
813808

814-
if value.assign and not value.keyword:
809+
if node.assign and keyword_token is None:
815810
self.append_diagnostics(
816-
range=range_from_node_or_token(value, value.get_token(Token.ASSIGN)),
811+
range=range_from_node_or_token(node, node.get_token(Token.ASSIGN)),
817812
message="Keyword name cannot be empty.",
818813
severity=DiagnosticSeverity.ERROR,
819814
code=Error.KEYWORD_NAME_EMPTY,
820815
)
821816
else:
822817
await self._analyze_keyword_call(
823-
value.keyword, value, keyword_token, [cast(Token, e) for e in value.get_tokens(Token.ARGUMENT)]
818+
node.keyword, node, keyword_token, [e for e in node.get_tokens(Token.ARGUMENT)]
824819
)
825820

826821
if not self.current_testcase_or_keyword_name:
827822
self.append_diagnostics(
828-
range=range_from_node_or_token(value, value.get_token(Token.ASSIGN)),
823+
range=range_from_node_or_token(node, node.get_token(Token.ASSIGN)),
829824
message="Code is unreachable.",
830825
severity=DiagnosticSeverity.HINT,
831826
tags=[DiagnosticTag.UNNECESSARY],
@@ -834,56 +829,52 @@ async def visit_KeywordCall(self, node: ast.AST) -> None: # noqa: N802
834829

835830
await self.generic_visit(node)
836831

837-
async def visit_TestCase(self, node: ast.AST) -> None: # noqa: N802
838-
testcase = cast(TestCase, node)
839-
840-
if not testcase.name:
841-
name_token = cast(TestCaseName, testcase.header).get_token(Token.TESTCASE_NAME)
832+
async def visit_TestCase(self, node: TestCase) -> None: # noqa: N802
833+
if not node.name:
834+
name_token = node.header.get_token(Token.TESTCASE_NAME)
842835
self.append_diagnostics(
843-
range=range_from_node_or_token(testcase, name_token),
836+
range=range_from_node_or_token(node, name_token),
844837
message="Test case name cannot be empty.",
845838
severity=DiagnosticSeverity.ERROR,
846839
code=Error.TESTCASE_NAME_EMPTY,
847840
)
848841

849-
self.current_testcase_or_keyword_name = testcase.name
842+
self.current_testcase_or_keyword_name = node.name
850843
try:
851844
await self.generic_visit(node)
852845
finally:
853846
self.current_testcase_or_keyword_name = None
854847
self.template = None
855848

856-
async def visit_Keyword(self, node: ast.AST) -> None: # noqa: N802
857-
keyword = cast(Keyword, node)
858-
859-
if keyword.name:
860-
name_token = cast(KeywordName, keyword.header).get_token(Token.KEYWORD_NAME)
849+
async def visit_Keyword(self, node: Keyword) -> None: # noqa: N802
850+
if node.name:
851+
name_token = node.header.get_token(Token.KEYWORD_NAME)
861852
kw_doc = self.get_keyword_definition_at_token(await self.namespace.get_library_doc(), name_token)
862853

863854
if kw_doc is not None and kw_doc not in self._keyword_references:
864855
self._keyword_references[kw_doc] = set()
865856

866857
if (
867858
get_robot_version() < (6, 1)
868-
and is_embedded_keyword(keyword.name)
869-
and any(isinstance(v, Arguments) and len(v.values) > 0 for v in keyword.body)
859+
and is_embedded_keyword(node.name)
860+
and any(isinstance(v, Arguments) and len(v.values) > 0 for v in node.body)
870861
):
871862
self.append_diagnostics(
872-
range=range_from_node_or_token(keyword, name_token),
863+
range=range_from_node_or_token(node, name_token),
873864
message="Keyword cannot have both normal and embedded arguments.",
874865
severity=DiagnosticSeverity.ERROR,
875866
code=Error.KEYWORD_CONTAINS_NORMAL_AND_EMBBEDED_ARGUMENTS,
876867
)
877868
else:
878-
name_token = cast(KeywordName, keyword.header).get_token(Token.KEYWORD_NAME)
869+
name_token = node.header.get_token(Token.KEYWORD_NAME)
879870
self.append_diagnostics(
880-
range=range_from_node_or_token(keyword, name_token),
871+
range=range_from_node_or_token(node, name_token),
881872
message="Keyword name cannot be empty.",
882873
severity=DiagnosticSeverity.ERROR,
883874
code=Error.KEYWORD_NAME_EMPTY,
884875
)
885876

886-
self.current_testcase_or_keyword_name = keyword.name
877+
self.current_testcase_or_keyword_name = node.name
887878
try:
888879
await self.generic_visit(node)
889880
finally:
@@ -911,12 +902,10 @@ def _format_template(self, template: str, arguments: Tuple[str, ...]) -> Tuple[s
911902
temp.append(var.after)
912903
return "".join(temp), ()
913904

914-
async def visit_TemplateArguments(self, node: ast.AST) -> None: # noqa: N802
915-
arguments = cast(TemplateArguments, node)
916-
905+
async def visit_TemplateArguments(self, node: TemplateArguments) -> None: # noqa: N802
917906
template = self.template or self.test_template
918907
if template is not None and template.value is not None and template.value.upper() not in ("", "NONE"):
919-
argument_tokens = arguments.get_tokens(Token.ARGUMENT)
908+
argument_tokens = node.get_tokens(Token.ARGUMENT)
920909
args = tuple(t.value for t in argument_tokens)
921910
keyword = template.value
922911
keyword, args = self._format_template(keyword, args)
@@ -935,15 +924,15 @@ async def visit_TemplateArguments(self, node: ast.AST) -> None: # noqa: N802
935924
raise
936925
except BaseException as e:
937926
self.append_diagnostics(
938-
range=range_from_node(arguments, skip_non_data=True),
927+
range=range_from_node(node, skip_non_data=True),
939928
message=str(e),
940929
severity=DiagnosticSeverity.ERROR,
941930
code=type(e).__qualname__,
942931
)
943932

944933
for d in self.finder.diagnostics:
945934
self.append_diagnostics(
946-
range=range_from_node(arguments, skip_non_data=True),
935+
range=range_from_node(node, skip_non_data=True),
947936
message=d.message,
948937
severity=d.severity,
949938
code=d.code,
@@ -1011,13 +1000,11 @@ def _check_import_name(self, value: Optional[str], node: ast.AST, type: str) ->
10111000
code=Error.IMPORT_REQUIRES_VALUE,
10121001
)
10131002

1014-
async def visit_VariablesImport(self, node: ast.AST) -> None: # noqa: N802
1003+
async def visit_VariablesImport(self, node: VariablesImport) -> None: # noqa: N802
10151004
if get_robot_version() >= (6, 1):
1016-
import_node = cast(VariablesImport, node)
1017-
self._check_import_name(import_node.name, node, "Variables")
1005+
self._check_import_name(node.name, node, "Variables")
10181006

1019-
n = cast(VariablesImport, node)
1020-
name_token = n.get_token(Token.NAME)
1007+
name_token = node.get_token(Token.NAME)
10211008
if name_token is None:
10221009
return
10231010

@@ -1028,13 +1015,11 @@ async def visit_VariablesImport(self, node: ast.AST) -> None: # noqa: N802
10281015
if v not in self._namespace_references:
10291016
self._namespace_references[v] = set()
10301017

1031-
async def visit_ResourceImport(self, node: ast.AST) -> None: # noqa: N802
1018+
async def visit_ResourceImport(self, node: ResourceImport) -> None: # noqa: N802
10321019
if get_robot_version() >= (6, 1):
1033-
import_node = cast(ResourceImport, node)
1034-
self._check_import_name(import_node.name, node, "Resource")
1020+
self._check_import_name(node.name, node, "Resource")
10351021

1036-
n = cast(ResourceImport, node)
1037-
name_token = n.get_token(Token.NAME)
1022+
name_token = node.get_token(Token.NAME)
10381023
if name_token is None:
10391024
return
10401025

@@ -1045,13 +1030,11 @@ async def visit_ResourceImport(self, node: ast.AST) -> None: # noqa: N802
10451030
if v not in self._namespace_references:
10461031
self._namespace_references[v] = set()
10471032

1048-
async def visit_LibraryImport(self, node: ast.AST) -> None: # noqa: N802
1033+
async def visit_LibraryImport(self, node: LibraryImport) -> None: # noqa: N802
10491034
if get_robot_version() >= (6, 1):
1050-
import_node = cast(LibraryImport, node)
1051-
self._check_import_name(import_node.name, node, "Library")
1035+
self._check_import_name(node.name, node, "Library")
10521036

1053-
n = cast(LibraryImport, node)
1054-
name_token = n.get_token(Token.NAME)
1037+
name_token = node.get_token(Token.NAME)
10551038
if name_token is None:
10561039
return
10571040

packages/language_server/src/robotcode/language_server/robotframework/parts/code_action_quick_fixes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ async def code_action_create_local_variable(
356356
self, document: TextDocument, range: Range, context: CodeActionContext
357357
) -> Optional[List[Union[Command, CodeAction]]]:
358358
from robot.parsing.model.blocks import Keyword, TestCase
359-
from robot.parsing.model.statements import Documentation, Fixture, Statement, Template
359+
from robot.parsing.model.statements import Documentation, Fixture, Statement, Template, TestCaseName
360360

361361
result: List[Union[Command, CodeAction]] = []
362362

@@ -380,7 +380,7 @@ async def code_action_create_local_variable(
380380
continue
381381

382382
node = nodes[-1] if nodes else None
383-
if node is None or isinstance(node, (Documentation, Fixture, Template)):
383+
if node is None or isinstance(node, (Documentation, Fixture, Template, TestCaseName)):
384384
continue
385385

386386
if not isinstance(node, Statement):

0 commit comments

Comments
 (0)