robotcode-robot 0.91.0__py3-none-any.whl → 0.92.0__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/loader.py +63 -17
- robotcode/robot/config/model.py +105 -100
- robotcode/robot/diagnostics/diagnostics_modifier.py +80 -17
- robotcode/robot/diagnostics/document_cache_helper.py +25 -7
- robotcode/robot/diagnostics/workspace_config.py +17 -0
- {robotcode_robot-0.91.0.dist-info → robotcode_robot-0.92.0.dist-info}/METADATA +2 -2
- {robotcode_robot-0.91.0.dist-info → robotcode_robot-0.92.0.dist-info}/RECORD +10 -10
- {robotcode_robot-0.91.0.dist-info → robotcode_robot-0.92.0.dist-info}/WHEEL +0 -0
- {robotcode_robot-0.91.0.dist-info → robotcode_robot-0.92.0.dist-info}/licenses/LICENSE.txt +0 -0
robotcode/robot/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.
|
1
|
+
__version__ = "0.92.0"
|
robotcode/robot/config/loader.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import sys
|
2
|
+
from dataclasses import fields, is_dataclass
|
2
3
|
from enum import Enum
|
3
4
|
from pathlib import Path
|
4
|
-
from typing import Any, Optional, Sequence, Tuple, Type, TypeVar, Union
|
5
|
+
from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar, Union
|
5
6
|
|
6
7
|
from robotcode.core.utils.dataclasses import from_dict
|
7
8
|
|
@@ -58,9 +59,9 @@ def load_robot_config_from_robot_toml_str(__s: str) -> RobotConfig:
|
|
58
59
|
|
59
60
|
|
60
61
|
def load_config_from_robot_toml_str(
|
61
|
-
config_type: Type[_ConfigType],
|
62
|
+
config_type: Type[_ConfigType], data: Union[str, Dict[str, Any]], tool_name: Optional[str] = None
|
62
63
|
) -> _ConfigType:
|
63
|
-
dict_data = tomllib.loads(
|
64
|
+
dict_data = tomllib.loads(data) if isinstance(data, str) else data
|
64
65
|
|
65
66
|
if tool_name:
|
66
67
|
try:
|
@@ -73,34 +74,39 @@ def load_config_from_robot_toml_str(
|
|
73
74
|
return from_dict(dict_data, config_type)
|
74
75
|
|
75
76
|
|
76
|
-
def load_config_from_pyproject_toml_str(
|
77
|
-
|
77
|
+
def load_config_from_pyproject_toml_str(
|
78
|
+
config_type: Type[_ConfigType], tool_name: str, data: Union[str, Dict[str, Any]]
|
79
|
+
) -> _ConfigType:
|
80
|
+
dict_data = tomllib.loads(data) if isinstance(data, str) else data
|
78
81
|
|
79
82
|
return from_dict(dict_data.get("tool", {}).get(tool_name, {}), config_type)
|
80
83
|
|
81
84
|
|
82
85
|
def _load_config_data_from_path(
|
83
86
|
config_type: Type[_ConfigType],
|
84
|
-
|
87
|
+
pyproject_toml_tool_name: str,
|
85
88
|
robot_toml_tool_name: Optional[str],
|
86
|
-
|
89
|
+
path: Path,
|
90
|
+
data: Optional[Dict[str, Any]] = None,
|
87
91
|
) -> _ConfigType:
|
88
92
|
try:
|
89
|
-
if
|
90
|
-
return load_config_from_pyproject_toml_str(
|
93
|
+
if path.name == PYPROJECT_TOML:
|
94
|
+
return load_config_from_pyproject_toml_str(
|
95
|
+
config_type, pyproject_toml_tool_name, path.read_text("utf-8") if data is None else data
|
96
|
+
)
|
91
97
|
|
92
|
-
if
|
98
|
+
if path.name == ROBOT_TOML or path.name == LOCAL_ROBOT_TOML or path.suffix == ".toml":
|
93
99
|
return load_config_from_robot_toml_str(
|
94
100
|
config_type,
|
95
|
-
|
101
|
+
path.read_text("utf-8") if data is None else data,
|
96
102
|
tool_name=robot_toml_tool_name,
|
97
103
|
)
|
98
104
|
raise TypeError("Unknown config file type.")
|
99
105
|
|
100
106
|
except ValueError as e:
|
101
|
-
raise ConfigValueError(
|
107
|
+
raise ConfigValueError(path, f'Parsing "{path}" failed: {e}') from e
|
102
108
|
except TypeError as e:
|
103
|
-
raise ConfigTypeError(
|
109
|
+
raise ConfigTypeError(path, f'Parsing "{path}" failed: {e}') from e
|
104
110
|
|
105
111
|
|
106
112
|
def get_default_config() -> RobotConfig:
|
@@ -113,34 +119,74 @@ def get_default_config() -> RobotConfig:
|
|
113
119
|
def load_config_from_path(
|
114
120
|
config_type: Type[_ConfigType],
|
115
121
|
*__paths: Union[Path, Tuple[Path, ConfigType]],
|
116
|
-
|
122
|
+
pyproject_toml_tool_name: str,
|
117
123
|
robot_toml_tool_name: Optional[str] = None,
|
124
|
+
extra_tools: Optional[Dict[str, Type[Any]]] = None,
|
125
|
+
verbose_callback: Optional[Callable[[str], None]] = None,
|
118
126
|
) -> _ConfigType:
|
119
127
|
result = config_type()
|
128
|
+
tools: Optional[Dict[str, Any]] = (
|
129
|
+
{} if extra_tools and is_dataclass(result) and any(f for f in fields(result) if f.name == "tool") else None
|
130
|
+
)
|
120
131
|
|
121
132
|
for __path in __paths:
|
122
133
|
if isinstance(__path, tuple):
|
123
134
|
path, c_type = __path
|
124
135
|
if path.name == "__no_user_config__.toml" and c_type == ConfigType.DEFAULT_CONFIG_TOML:
|
136
|
+
if verbose_callback:
|
137
|
+
verbose_callback("Load default configuration.")
|
125
138
|
result.add_options(get_default_config())
|
126
139
|
continue
|
127
140
|
|
141
|
+
if verbose_callback:
|
142
|
+
verbose_callback(f"Load configuration from {__path if isinstance(__path, Path) else __path[0]}")
|
143
|
+
|
144
|
+
p = __path if isinstance(__path, Path) else __path[0]
|
145
|
+
data = tomllib.loads(p.read_text("utf-8"))
|
146
|
+
|
128
147
|
result.add_options(
|
129
148
|
_load_config_data_from_path(
|
130
149
|
config_type,
|
131
|
-
|
150
|
+
pyproject_toml_tool_name,
|
132
151
|
robot_toml_tool_name,
|
133
|
-
|
152
|
+
p,
|
153
|
+
data,
|
134
154
|
)
|
135
155
|
)
|
136
156
|
|
157
|
+
if tools is not None and extra_tools:
|
158
|
+
for tool_name, tool_config in extra_tools.items():
|
159
|
+
if tool_name not in tools:
|
160
|
+
tools[tool_name] = tool_config()
|
161
|
+
|
162
|
+
tool = tools[tool_name]
|
163
|
+
tool.add_options(
|
164
|
+
_load_config_data_from_path(
|
165
|
+
tool_config,
|
166
|
+
tool_name,
|
167
|
+
tool_name,
|
168
|
+
p,
|
169
|
+
data,
|
170
|
+
)
|
171
|
+
)
|
172
|
+
if tools is not None:
|
173
|
+
setattr(result, "tool", tools)
|
174
|
+
|
137
175
|
return result
|
138
176
|
|
139
177
|
|
140
178
|
def load_robot_config_from_path(
|
141
179
|
*__paths: Union[Path, Tuple[Path, ConfigType]],
|
180
|
+
extra_tools: Optional[Dict[str, Type[Any]]] = None,
|
181
|
+
verbose_callback: Optional[Callable[[str], None]] = None,
|
142
182
|
) -> RobotConfig:
|
143
|
-
return load_config_from_path(
|
183
|
+
return load_config_from_path(
|
184
|
+
RobotConfig,
|
185
|
+
*__paths,
|
186
|
+
pyproject_toml_tool_name="robot",
|
187
|
+
extra_tools=extra_tools,
|
188
|
+
verbose_callback=verbose_callback,
|
189
|
+
)
|
144
190
|
|
145
191
|
|
146
192
|
def find_project_root(
|
robotcode/robot/config/model.py
CHANGED
@@ -245,65 +245,6 @@ class BaseOptions(ValidateMixin):
|
|
245
245
|
def _decode_case(cls, s: str) -> str:
|
246
246
|
return s.replace("-", "_")
|
247
247
|
|
248
|
-
"""Base class for all options."""
|
249
|
-
|
250
|
-
def build_command_line(self) -> List[str]:
|
251
|
-
"""Build the arguments to pass to Robot Framework."""
|
252
|
-
result = []
|
253
|
-
|
254
|
-
sorted_fields = sorted(
|
255
|
-
(f for f in dataclasses.fields(self) if f.metadata.get("robot_priority", -1) > -1),
|
256
|
-
key=lambda f: f.metadata.get("robot_priority", 0),
|
257
|
-
)
|
258
|
-
|
259
|
-
def append_name(field: "dataclasses.Field[Any]", add_flag: Optional[str] = None) -> None:
|
260
|
-
if "robot_short_name" in field.metadata:
|
261
|
-
result.append(f"-{field.metadata['robot_short_name']}")
|
262
|
-
elif "robot_name" in field.metadata:
|
263
|
-
result.append(f"--{'no' if add_flag else ''}{field.metadata['robot_name']}")
|
264
|
-
|
265
|
-
for field in sorted_fields:
|
266
|
-
try:
|
267
|
-
value = getattr(self, field.name)
|
268
|
-
if value is None:
|
269
|
-
continue
|
270
|
-
|
271
|
-
if field.metadata.get("robot_is_flag", False):
|
272
|
-
if value is None or value == Flag.DEFAULT:
|
273
|
-
continue
|
274
|
-
|
275
|
-
append_name(
|
276
|
-
field,
|
277
|
-
bool(value) != field.metadata.get("robot_flag_default", True),
|
278
|
-
)
|
279
|
-
|
280
|
-
continue
|
281
|
-
|
282
|
-
if isinstance(value, list):
|
283
|
-
for item in value:
|
284
|
-
append_name(field)
|
285
|
-
if isinstance(item, dict):
|
286
|
-
for k, v in item.items():
|
287
|
-
result.append(f"{k}:{v}")
|
288
|
-
else:
|
289
|
-
result.append(str(item))
|
290
|
-
elif isinstance(value, dict):
|
291
|
-
for key, item in value.items():
|
292
|
-
append_name(field)
|
293
|
-
if isinstance(item, list):
|
294
|
-
str_item = [str(i) for i in item]
|
295
|
-
separator = ";" if any(True for s in str_item if ":" in s) else ":"
|
296
|
-
result.append(f"{key}{separator if item else ''}{separator.join(str_item)}")
|
297
|
-
else:
|
298
|
-
result.append(f"{key}:{item}")
|
299
|
-
else:
|
300
|
-
append_name(field)
|
301
|
-
result.append(str(value))
|
302
|
-
except EvaluationError as e:
|
303
|
-
raise ValueError(f"Evaluation of '{field.name}' failed: {e!s}") from e
|
304
|
-
|
305
|
-
return result
|
306
|
-
|
307
248
|
@staticmethod
|
308
249
|
def _verified_value(name: str, value: Any, types: Union[type, Tuple[type, ...]], target: Any) -> Any:
|
309
250
|
errors = validate_types(types, value)
|
@@ -315,6 +256,44 @@ class BaseOptions(ValidateMixin):
|
|
315
256
|
)
|
316
257
|
return value
|
317
258
|
|
259
|
+
def evaluated(
|
260
|
+
self,
|
261
|
+
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
262
|
+
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
263
|
+
) -> Self:
|
264
|
+
if verbose_callback is not None:
|
265
|
+
verbose_callback("Evaluating options")
|
266
|
+
|
267
|
+
result = dataclasses.replace(self)
|
268
|
+
for f in dataclasses.fields(result):
|
269
|
+
try:
|
270
|
+
if isinstance(getattr(result, f.name), Condition):
|
271
|
+
setattr(result, f.name, getattr(result, f.name).evaluate())
|
272
|
+
elif isinstance(getattr(result, f.name), Expression):
|
273
|
+
setattr(result, f.name, getattr(result, f.name).evaluate())
|
274
|
+
elif isinstance(getattr(result, f.name), list):
|
275
|
+
setattr(
|
276
|
+
result,
|
277
|
+
f.name,
|
278
|
+
[e.evaluate() if isinstance(e, Expression) else e for e in getattr(result, f.name)],
|
279
|
+
)
|
280
|
+
elif isinstance(getattr(result, f.name), dict):
|
281
|
+
setattr(
|
282
|
+
result,
|
283
|
+
f.name,
|
284
|
+
{
|
285
|
+
k: e.evaluate() if isinstance(e, Expression) else e
|
286
|
+
for k, e in getattr(result, f.name).items()
|
287
|
+
},
|
288
|
+
)
|
289
|
+
except EvaluationError as e:
|
290
|
+
message = f"Evaluation of '{f.name}' failed: {type(e).__name__}: {e}"
|
291
|
+
if error_callback is None:
|
292
|
+
raise ValueError(message) from e
|
293
|
+
error_callback(message)
|
294
|
+
|
295
|
+
return result
|
296
|
+
|
318
297
|
def add_options(self, config: "BaseOptions", combine_extends: bool = False) -> None:
|
319
298
|
type_hints = get_type_hints(type(self))
|
320
299
|
base_field_names = [f.name for f in dataclasses.fields(self)]
|
@@ -352,6 +331,8 @@ class BaseOptions(ValidateMixin):
|
|
352
331
|
setattr(self, old_field_name, [*old, *new])
|
353
332
|
elif isinstance(old, tuple):
|
354
333
|
setattr(self, old_field_name, (*old, *new))
|
334
|
+
elif isinstance(old, BaseOptions):
|
335
|
+
old.add_options(new)
|
355
336
|
else:
|
356
337
|
setattr(self, old_field_name, new)
|
357
338
|
continue
|
@@ -370,41 +351,65 @@ class BaseOptions(ValidateMixin):
|
|
370
351
|
if new is not None:
|
371
352
|
setattr(self, f.name, new)
|
372
353
|
|
373
|
-
def evaluated(
|
374
|
-
self,
|
375
|
-
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
376
|
-
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
377
|
-
) -> Self:
|
378
|
-
if verbose_callback is not None:
|
379
|
-
verbose_callback("Evaluating options")
|
380
354
|
|
381
|
-
|
382
|
-
|
355
|
+
@dataclass
|
356
|
+
class RobotBaseOptions(BaseOptions):
|
357
|
+
"""Base class for all options."""
|
358
|
+
|
359
|
+
def build_command_line(self) -> List[str]:
|
360
|
+
"""Build the arguments to pass to Robot Framework."""
|
361
|
+
result = []
|
362
|
+
|
363
|
+
sorted_fields = sorted(
|
364
|
+
(f for f in dataclasses.fields(self) if f.metadata.get("robot_priority", -1) > -1),
|
365
|
+
key=lambda f: f.metadata.get("robot_priority", 0),
|
366
|
+
)
|
367
|
+
|
368
|
+
def append_name(field: "dataclasses.Field[Any]", add_flag: Optional[str] = None) -> None:
|
369
|
+
if "robot_short_name" in field.metadata:
|
370
|
+
result.append(f"-{field.metadata['robot_short_name']}")
|
371
|
+
elif "robot_name" in field.metadata:
|
372
|
+
result.append(f"--{'no' if add_flag else ''}{field.metadata['robot_name']}")
|
373
|
+
|
374
|
+
for field in sorted_fields:
|
383
375
|
try:
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
setattr(
|
396
|
-
result,
|
397
|
-
f.name,
|
398
|
-
{
|
399
|
-
k: e.evaluate() if isinstance(e, Expression) else e
|
400
|
-
for k, e in getattr(result, f.name).items()
|
401
|
-
},
|
376
|
+
value = getattr(self, field.name)
|
377
|
+
if value is None:
|
378
|
+
continue
|
379
|
+
|
380
|
+
if field.metadata.get("robot_is_flag", False):
|
381
|
+
if value is None or value == Flag.DEFAULT:
|
382
|
+
continue
|
383
|
+
|
384
|
+
append_name(
|
385
|
+
field,
|
386
|
+
bool(value) != field.metadata.get("robot_flag_default", True),
|
402
387
|
)
|
388
|
+
|
389
|
+
continue
|
390
|
+
|
391
|
+
if isinstance(value, list):
|
392
|
+
for item in value:
|
393
|
+
append_name(field)
|
394
|
+
if isinstance(item, dict):
|
395
|
+
for k, v in item.items():
|
396
|
+
result.append(f"{k}:{v}")
|
397
|
+
else:
|
398
|
+
result.append(str(item))
|
399
|
+
elif isinstance(value, dict):
|
400
|
+
for key, item in value.items():
|
401
|
+
append_name(field)
|
402
|
+
if isinstance(item, list):
|
403
|
+
str_item = [str(i) for i in item]
|
404
|
+
separator = ";" if any(True for s in str_item if ":" in s) else ":"
|
405
|
+
result.append(f"{key}{separator if item else ''}{separator.join(str_item)}")
|
406
|
+
else:
|
407
|
+
result.append(f"{key}:{item}")
|
408
|
+
else:
|
409
|
+
append_name(field)
|
410
|
+
result.append(str(value))
|
403
411
|
except EvaluationError as e:
|
404
|
-
|
405
|
-
if error_callback is None:
|
406
|
-
raise ValueError(message) from e
|
407
|
-
error_callback(message)
|
412
|
+
raise ValueError(f"Evaluation of '{field.name}' failed: {e!s}") from e
|
408
413
|
|
409
414
|
return result
|
410
415
|
|
@@ -413,7 +418,7 @@ class BaseOptions(ValidateMixin):
|
|
413
418
|
|
414
419
|
|
415
420
|
@dataclass
|
416
|
-
class CommonOptions(
|
421
|
+
class CommonOptions(RobotBaseOptions):
|
417
422
|
"""Common options for all _robot_ commands."""
|
418
423
|
|
419
424
|
console_colors: Optional[Literal["auto", "on", "ansi", "off"]] = field(
|
@@ -972,7 +977,7 @@ class CommonOptions(BaseOptions):
|
|
972
977
|
|
973
978
|
|
974
979
|
@dataclass
|
975
|
-
class CommonExtendOptions(
|
980
|
+
class CommonExtendOptions(RobotBaseOptions):
|
976
981
|
"""Extra common options for all _robot_ commands."""
|
977
982
|
|
978
983
|
extend_excludes: Optional[List[Union[str, StringExpression]]] = field(
|
@@ -1297,7 +1302,7 @@ class CommonExtendOptions(BaseOptions):
|
|
1297
1302
|
|
1298
1303
|
|
1299
1304
|
@dataclass
|
1300
|
-
class RobotOptions(
|
1305
|
+
class RobotOptions(RobotBaseOptions):
|
1301
1306
|
"""Options for _robot_ command."""
|
1302
1307
|
|
1303
1308
|
console: Optional[Literal["verbose", "dotted", "skipped", "quiet", "none"]] = field(
|
@@ -1674,7 +1679,7 @@ class RobotOptions(BaseOptions):
|
|
1674
1679
|
|
1675
1680
|
|
1676
1681
|
@dataclass
|
1677
|
-
class RobotExtendOptions(
|
1682
|
+
class RobotExtendOptions(RobotBaseOptions):
|
1678
1683
|
"""Extra options for _robot_ command."""
|
1679
1684
|
|
1680
1685
|
extend_languages: Optional[List[Union[str, StringExpression]]] = field(
|
@@ -1789,7 +1794,7 @@ class RobotExtendOptions(BaseOptions):
|
|
1789
1794
|
|
1790
1795
|
|
1791
1796
|
@dataclass
|
1792
|
-
class RebotOptions(
|
1797
|
+
class RebotOptions(RobotBaseOptions):
|
1793
1798
|
"""Options for _rebot_ command."""
|
1794
1799
|
|
1795
1800
|
end_time: Optional[Union[str, StringExpression]] = field(
|
@@ -1884,7 +1889,7 @@ class RebotOptions(BaseOptions):
|
|
1884
1889
|
|
1885
1890
|
|
1886
1891
|
@dataclass
|
1887
|
-
class LibDocOptions(
|
1892
|
+
class LibDocOptions(RobotBaseOptions):
|
1888
1893
|
"""Options for _libdoc_ command."""
|
1889
1894
|
|
1890
1895
|
doc_format: Optional[Literal["ROBOT", "HTML", "TEXT", "REST"]] = field(
|
@@ -1976,7 +1981,7 @@ class LibDocOptions(BaseOptions):
|
|
1976
1981
|
|
1977
1982
|
|
1978
1983
|
@dataclass
|
1979
|
-
class LibDocExtendOptions(
|
1984
|
+
class LibDocExtendOptions(RobotBaseOptions):
|
1980
1985
|
"""Extra options for _libdoc_ command."""
|
1981
1986
|
|
1982
1987
|
extend_python_path: Optional[List[Union[str, StringExpression]]] = field(
|
@@ -1992,7 +1997,7 @@ class LibDocExtendOptions(BaseOptions):
|
|
1992
1997
|
|
1993
1998
|
|
1994
1999
|
@dataclass
|
1995
|
-
class TestDocOptions(
|
2000
|
+
class TestDocOptions(RobotBaseOptions):
|
1996
2001
|
"""Options for _testdoc_ command."""
|
1997
2002
|
|
1998
2003
|
doc: Optional[Union[str, StringExpression]] = field(
|
@@ -2090,7 +2095,7 @@ class TestDocOptions(BaseOptions):
|
|
2090
2095
|
|
2091
2096
|
|
2092
2097
|
@dataclass
|
2093
|
-
class TestDocExtendOptions(
|
2098
|
+
class TestDocExtendOptions(RobotBaseOptions):
|
2094
2099
|
"""Extra options for _testdoc_ command."""
|
2095
2100
|
|
2096
2101
|
extend_excludes: Optional[List[Union[str, StringExpression]]] = field(
|
@@ -2378,7 +2383,7 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
2378
2383
|
|
2379
2384
|
extend_profiles: Optional[Dict[str, RobotProfile]] = field(description="Extra execution profiles.")
|
2380
2385
|
|
2381
|
-
tool: Any = field(description="Tool configurations.")
|
2386
|
+
tool: Optional[Dict[str, Any]] = field(description="Tool configurations.")
|
2382
2387
|
|
2383
2388
|
def _select_profiles(
|
2384
2389
|
self,
|
@@ -2,7 +2,8 @@ import functools
|
|
2
2
|
import re as re
|
3
3
|
from ast import AST
|
4
4
|
from collections import defaultdict
|
5
|
-
from dataclasses import dataclass
|
5
|
+
from dataclasses import dataclass, field
|
6
|
+
from enum import Enum
|
6
7
|
from typing import Dict, List, Optional, Set, Union
|
7
8
|
|
8
9
|
from robot.parsing.lexer.tokens import Token
|
@@ -12,15 +13,32 @@ from robotcode.core.lsp.types import Diagnostic, DiagnosticSeverity
|
|
12
13
|
|
13
14
|
from ..utils.visitor import Visitor
|
14
15
|
|
15
|
-
ACTIONS = ["ignore", "error", "warn", "information", "hint", "reset"]
|
16
|
+
ACTIONS = ["ignore", "error", "warn", "warning", "info", "information", "hint", "reset"]
|
16
17
|
|
17
18
|
ROBOTCODE_ACTION_AND_CODES_PATTERN = re.compile(rf"(?P<action>{'|'.join(ACTIONS)})(\[(?P<codes>[^\]]*?)\])?")
|
18
19
|
|
19
20
|
|
21
|
+
class ModifierAction(Enum):
|
22
|
+
IGNORE = "ignore"
|
23
|
+
ERROR = "error"
|
24
|
+
WARNING = "warning"
|
25
|
+
INFORMATION = "information"
|
26
|
+
HINT = "hint"
|
27
|
+
RESET = "reset"
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def from_str(cls, value: str) -> "ModifierAction":
|
31
|
+
if value == "warn":
|
32
|
+
value = "warning"
|
33
|
+
elif value == "info":
|
34
|
+
value = "information"
|
35
|
+
return cls(value)
|
36
|
+
|
37
|
+
|
20
38
|
@dataclass
|
21
39
|
class RulesAndCodes:
|
22
40
|
codes: Dict[Union[str, int], Set[int]]
|
23
|
-
actions: Dict[int, Dict[Union[str, int],
|
41
|
+
actions: Dict[int, Dict[Union[str, int], ModifierAction]]
|
24
42
|
|
25
43
|
|
26
44
|
_translation_table = str.maketrans("", "", "_- ")
|
@@ -28,7 +46,7 @@ _translation_table = str.maketrans("", "", "_- ")
|
|
28
46
|
ROBOTCODE_MARKER = "robotcode:"
|
29
47
|
|
30
48
|
|
31
|
-
class
|
49
|
+
class ModifiersVisitor(Visitor):
|
32
50
|
|
33
51
|
def __init__(self) -> None:
|
34
52
|
super().__init__()
|
@@ -65,8 +83,8 @@ class DisablersVisitor(Visitor):
|
|
65
83
|
self._handle_statement_comments(node)
|
66
84
|
self.generic_visit(node)
|
67
85
|
|
68
|
-
def _parse_robotcode_disabler(self, comment: str) -> Dict[
|
69
|
-
result: Dict[
|
86
|
+
def _parse_robotcode_disabler(self, comment: str) -> Dict[ModifierAction, List[str]]:
|
87
|
+
result: Dict[ModifierAction, List[str]] = {}
|
70
88
|
|
71
89
|
comment = comment.strip()
|
72
90
|
m = ROBOTCODE_ACTION_AND_CODES_PATTERN.match(comment)
|
@@ -74,7 +92,8 @@ class DisablersVisitor(Visitor):
|
|
74
92
|
return result
|
75
93
|
|
76
94
|
for m in ROBOTCODE_ACTION_AND_CODES_PATTERN.finditer(comment):
|
77
|
-
|
95
|
+
action_str = m.group("action")
|
96
|
+
action = ModifierAction.from_str(action_str)
|
78
97
|
messages = m.group("codes")
|
79
98
|
result[action] = (
|
80
99
|
[m.strip().translate(_translation_table).lower() for m in messages.split(",")]
|
@@ -153,19 +172,36 @@ class DisablersVisitor(Visitor):
|
|
153
172
|
self.rules_and_codes.actions[i][code] = action
|
154
173
|
|
155
174
|
|
175
|
+
@dataclass
|
176
|
+
class DiagnosticModifiersConfig:
|
177
|
+
ignore: List[str] = field(default_factory=list)
|
178
|
+
error: List[str] = field(default_factory=list)
|
179
|
+
warning: List[str] = field(default_factory=list)
|
180
|
+
information: List[str] = field(default_factory=list)
|
181
|
+
hint: List[str] = field(default_factory=list)
|
182
|
+
|
183
|
+
|
156
184
|
class DiagnosticsModifier:
|
157
|
-
def __init__(self, model: AST) -> None:
|
185
|
+
def __init__(self, model: AST, config: Optional[DiagnosticModifiersConfig] = None) -> None:
|
186
|
+
self.config = config or DiagnosticModifiersConfig()
|
187
|
+
|
188
|
+
self.config.ignore = [i.translate(_translation_table).lower() for i in self.config.ignore]
|
189
|
+
self.config.error = [i.translate(_translation_table).lower() for i in self.config.error]
|
190
|
+
self.config.warning = [i.translate(_translation_table).lower() for i in self.config.warning]
|
191
|
+
self.config.information = [i.translate(_translation_table).lower() for i in self.config.information]
|
192
|
+
self.config.hint = [i.translate(_translation_table).lower() for i in self.config.hint]
|
193
|
+
|
158
194
|
self.model = model
|
159
195
|
|
160
196
|
@functools.cached_property
|
161
197
|
def rules_and_codes(self) -> RulesAndCodes:
|
162
|
-
visitor =
|
198
|
+
visitor = ModifiersVisitor()
|
163
199
|
visitor.visit(self.model)
|
164
200
|
return visitor.rules_and_codes
|
165
201
|
|
166
202
|
def modify_diagnostic(self, diagnostic: Diagnostic) -> Optional[Diagnostic]:
|
167
203
|
if diagnostic.code is not None:
|
168
|
-
code = (
|
204
|
+
code = orig_code = (
|
169
205
|
str(diagnostic.code).translate(_translation_table).lower()
|
170
206
|
if diagnostic.code is not None
|
171
207
|
else "unknowncode"
|
@@ -177,24 +213,51 @@ class DiagnosticsModifier:
|
|
177
213
|
code = "*"
|
178
214
|
lines = self.rules_and_codes.codes.get(code)
|
179
215
|
|
216
|
+
modified = False
|
180
217
|
if lines is not None and diagnostic.range.start.line in lines:
|
181
218
|
actions = self.rules_and_codes.actions.get(diagnostic.range.start.line)
|
182
219
|
if actions is not None:
|
183
220
|
action = actions.get(code)
|
184
221
|
if action is not None:
|
185
|
-
if action ==
|
222
|
+
if action == ModifierAction.IGNORE:
|
186
223
|
return None
|
187
|
-
if action ==
|
188
|
-
pass
|
189
|
-
elif action ==
|
224
|
+
if action == ModifierAction.RESET:
|
225
|
+
pass
|
226
|
+
elif action == ModifierAction.ERROR:
|
227
|
+
modified = True
|
190
228
|
diagnostic.severity = DiagnosticSeverity.ERROR
|
191
|
-
elif action ==
|
229
|
+
elif action == ModifierAction.WARNING:
|
230
|
+
modified = True
|
192
231
|
diagnostic.severity = DiagnosticSeverity.WARNING
|
193
|
-
elif action ==
|
232
|
+
elif action == ModifierAction.INFORMATION:
|
233
|
+
modified = True
|
194
234
|
diagnostic.severity = DiagnosticSeverity.INFORMATION
|
195
|
-
elif action ==
|
235
|
+
elif action == ModifierAction.HINT:
|
236
|
+
modified = True
|
196
237
|
diagnostic.severity = DiagnosticSeverity.HINT
|
197
238
|
|
239
|
+
if not modified:
|
240
|
+
if orig_code in self.config.ignore:
|
241
|
+
return None
|
242
|
+
if orig_code in self.config.error:
|
243
|
+
diagnostic.severity = DiagnosticSeverity.ERROR
|
244
|
+
elif orig_code in self.config.warning:
|
245
|
+
diagnostic.severity = DiagnosticSeverity.WARNING
|
246
|
+
elif orig_code in self.config.information:
|
247
|
+
diagnostic.severity = DiagnosticSeverity.INFORMATION
|
248
|
+
elif orig_code in self.config.hint:
|
249
|
+
diagnostic.severity = DiagnosticSeverity.HINT
|
250
|
+
elif "*" in self.config.hint:
|
251
|
+
diagnostic.severity = DiagnosticSeverity.HINT
|
252
|
+
elif "*" in self.config.information:
|
253
|
+
diagnostic.severity = DiagnosticSeverity.INFORMATION
|
254
|
+
elif "*" in self.config.warning:
|
255
|
+
diagnostic.severity = DiagnosticSeverity.WARNING
|
256
|
+
elif "*" in self.config.error:
|
257
|
+
diagnostic.severity = DiagnosticSeverity.ERROR
|
258
|
+
elif "*" in self.config.ignore:
|
259
|
+
return None
|
260
|
+
|
198
261
|
return diagnostic
|
199
262
|
|
200
263
|
def modify_diagnostics(self, diagnostics: List[Diagnostic]) -> List[Diagnostic]:
|
@@ -26,7 +26,7 @@ from robotcode.core.text_document import TextDocument
|
|
26
26
|
from robotcode.core.uri import Uri
|
27
27
|
from robotcode.core.utils.logging import LoggingDescriptor
|
28
28
|
from robotcode.core.workspace import Workspace, WorkspaceFolder
|
29
|
-
from robotcode.robot.diagnostics.diagnostics_modifier import DiagnosticsModifier
|
29
|
+
from robotcode.robot.diagnostics.diagnostics_modifier import DiagnosticModifiersConfig, DiagnosticsModifier
|
30
30
|
|
31
31
|
from ..config.model import RobotBaseProfile
|
32
32
|
from ..utils import get_robot_version
|
@@ -34,7 +34,13 @@ from ..utils.stubs import Languages
|
|
34
34
|
from .imports_manager import ImportsManager
|
35
35
|
from .library_doc import LibraryDoc
|
36
36
|
from .namespace import DocumentType, Namespace
|
37
|
-
from .workspace_config import
|
37
|
+
from .workspace_config import (
|
38
|
+
AnalysisDiagnosticModifiersConfig,
|
39
|
+
AnalysisRobotConfig,
|
40
|
+
CacheConfig,
|
41
|
+
RobotConfig,
|
42
|
+
WorkspaceAnalysisConfig,
|
43
|
+
)
|
38
44
|
|
39
45
|
|
40
46
|
class UnknownFileTypeError(Exception):
|
@@ -54,6 +60,7 @@ class DocumentsCacheHelper:
|
|
54
60
|
documents_manager: DocumentsManager,
|
55
61
|
file_watcher_manager: FileWatcherManagerBase,
|
56
62
|
robot_profile: Optional[RobotBaseProfile],
|
63
|
+
analysis_config: Optional[WorkspaceAnalysisConfig],
|
57
64
|
) -> None:
|
58
65
|
self.INITIALIZED_NAMESPACE = _CacheEntry()
|
59
66
|
|
@@ -62,6 +69,7 @@ class DocumentsCacheHelper:
|
|
62
69
|
self.file_watcher_manager = file_watcher_manager
|
63
70
|
|
64
71
|
self.robot_profile = robot_profile or RobotBaseProfile()
|
72
|
+
self.analysis_config = analysis_config or WorkspaceAnalysisConfig()
|
65
73
|
|
66
74
|
self._imports_managers_lock = threading.RLock()
|
67
75
|
self._imports_managers: weakref.WeakKeyDictionary[WorkspaceFolder, ImportsManager] = weakref.WeakKeyDictionary()
|
@@ -514,10 +522,10 @@ class DocumentsCacheHelper:
|
|
514
522
|
variables,
|
515
523
|
variable_files,
|
516
524
|
environment,
|
517
|
-
cache_config.ignored_libraries,
|
518
|
-
cache_config.ignored_variables,
|
519
|
-
cache_config.ignore_arguments_for_library,
|
520
|
-
analysis_config.global_library_search_order,
|
525
|
+
self.analysis_config.cache.ignored_libraries + cache_config.ignored_libraries,
|
526
|
+
self.analysis_config.cache.ignored_variables + cache_config.ignored_variables,
|
527
|
+
self.analysis_config.cache.ignore_arguments_for_library + cache_config.ignore_arguments_for_library,
|
528
|
+
self.analysis_config.robot.global_library_search_order + analysis_config.global_library_search_order,
|
521
529
|
cache_base_path,
|
522
530
|
)
|
523
531
|
|
@@ -583,4 +591,14 @@ class DocumentsCacheHelper:
|
|
583
591
|
return document.get_cache(self.__get_diagnostic_modifier)
|
584
592
|
|
585
593
|
def __get_diagnostic_modifier(self, document: TextDocument) -> DiagnosticsModifier:
|
586
|
-
|
594
|
+
modifiers_config = self.workspace.get_configuration(AnalysisDiagnosticModifiersConfig, document.uri)
|
595
|
+
return DiagnosticsModifier(
|
596
|
+
self.get_model(document, False),
|
597
|
+
DiagnosticModifiersConfig(
|
598
|
+
ignore=self.analysis_config.modifiers.ignore + modifiers_config.ignore,
|
599
|
+
error=self.analysis_config.modifiers.error + modifiers_config.error,
|
600
|
+
warning=self.analysis_config.modifiers.warning + modifiers_config.warning,
|
601
|
+
information=self.analysis_config.modifiers.information + modifiers_config.information,
|
602
|
+
hint=self.analysis_config.modifiers.hint + modifiers_config.hint,
|
603
|
+
),
|
604
|
+
)
|
@@ -55,3 +55,20 @@ class CacheConfig(ConfigBase):
|
|
55
55
|
@dataclass
|
56
56
|
class AnalysisRobotConfig(ConfigBase):
|
57
57
|
global_library_search_order: List[str] = field(default_factory=list)
|
58
|
+
|
59
|
+
|
60
|
+
@config_section("robotcode.analysis.diagnosticModifiers")
|
61
|
+
@dataclass
|
62
|
+
class AnalysisDiagnosticModifiersConfig(ConfigBase):
|
63
|
+
ignore: List[str] = field(default_factory=list)
|
64
|
+
error: List[str] = field(default_factory=list)
|
65
|
+
warning: List[str] = field(default_factory=list)
|
66
|
+
information: List[str] = field(default_factory=list)
|
67
|
+
hint: List[str] = field(default_factory=list)
|
68
|
+
|
69
|
+
|
70
|
+
@dataclass
|
71
|
+
class WorkspaceAnalysisConfig:
|
72
|
+
cache: CacheConfig = field(default_factory=CacheConfig)
|
73
|
+
robot: AnalysisRobotConfig = field(default_factory=AnalysisRobotConfig)
|
74
|
+
modifiers: AnalysisDiagnosticModifiersConfig = field(default_factory=AnalysisDiagnosticModifiersConfig)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: robotcode-robot
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.92.0
|
4
4
|
Summary: Support classes for RobotCode for handling Robot Framework projects.
|
5
5
|
Project-URL: Homepage, https://robotcode.io
|
6
6
|
Project-URL: Donate, https://opencollective.com/robotcode
|
@@ -26,7 +26,7 @@ Classifier: Topic :: Utilities
|
|
26
26
|
Classifier: Typing :: Typed
|
27
27
|
Requires-Python: >=3.8
|
28
28
|
Requires-Dist: platformdirs<4.2.0,>=3.2.0
|
29
|
-
Requires-Dist: robotcode-core==0.
|
29
|
+
Requires-Dist: robotcode-core==0.92.0
|
30
30
|
Requires-Dist: robotframework>=4.1.0
|
31
31
|
Requires-Dist: tomli>=1.1.0; python_version < '3.11'
|
32
32
|
Description-Content-Type: text/markdown
|
@@ -1,13 +1,13 @@
|
|
1
1
|
robotcode/robot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
robotcode/robot/__version__.py,sha256=
|
2
|
+
robotcode/robot/__version__.py,sha256=4oQqdZ-jW338RGQfLxI7hoQNb8tTR6y4hzWoJRPdI3o,23
|
3
3
|
robotcode/robot/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
|
4
4
|
robotcode/robot/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
robotcode/robot/config/loader.py,sha256=
|
6
|
-
robotcode/robot/config/model.py,sha256=
|
5
|
+
robotcode/robot/config/loader.py,sha256=5PQTWDn1vYFN0VmLVhEYdu12Foq1AwRtDFvjU44QQvw,7878
|
6
|
+
robotcode/robot/config/model.py,sha256=sgr6-4_E06g-yIXW41Z-NtIXZ_7JMmR5WvUD7kTUqu4,89106
|
7
7
|
robotcode/robot/config/utils.py,sha256=c_WZg39DJgM6kXcAH_h-v68qhf1eStJ0TslTawaJoZw,2827
|
8
8
|
robotcode/robot/diagnostics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
robotcode/robot/diagnostics/diagnostics_modifier.py,sha256=
|
10
|
-
robotcode/robot/diagnostics/document_cache_helper.py,sha256=
|
9
|
+
robotcode/robot/diagnostics/diagnostics_modifier.py,sha256=3dDsu8-ET6weIvv7Sk3IQaPYFNxnXUs8Y7gpGTjfOBs,9796
|
10
|
+
robotcode/robot/diagnostics/document_cache_helper.py,sha256=MlOPXC3mUVjNB2rHn5UJK7NnUPa8S4mE3qojILOi_Mk,23756
|
11
11
|
robotcode/robot/diagnostics/entities.py,sha256=AtrqPAS3XUC-8VpFmqMXfMKjQHmfxXZlyGWWFaEBpQA,10979
|
12
12
|
robotcode/robot/diagnostics/errors.py,sha256=HCE0h0_ui1tx-6pJHs0r4s09ZHmArYwKUo-_lBl9K-4,1600
|
13
13
|
robotcode/robot/diagnostics/imports_manager.py,sha256=qE__lm0Hsj3R0gUauXRmbWJPYihNjk3O-c5hcICGQjc,56251
|
@@ -15,7 +15,7 @@ robotcode/robot/diagnostics/library_doc.py,sha256=CHgyC9GMRQFP3fGTxWa139rIcGIk8Y
|
|
15
15
|
robotcode/robot/diagnostics/model_helper.py,sha256=_5ixKKMrb-nY-uvV8_WjJ1rlNlz7gT7kHM5NYi_hjVg,30232
|
16
16
|
robotcode/robot/diagnostics/namespace.py,sha256=jCUb5GmMDA6KTr2BnwHD_0Mp_gTg1XGskUvRYPL7XQ0,89543
|
17
17
|
robotcode/robot/diagnostics/namespace_analyzer.py,sha256=3eC1xEBm_foCnqoHk8Cj6O11UXOHcHxWnfuKP8M5KIQ,51369
|
18
|
-
robotcode/robot/diagnostics/workspace_config.py,sha256=
|
18
|
+
robotcode/robot/diagnostics/workspace_config.py,sha256=3SoewUj_LZB1Ki5hXM8oxQpJr6vyiog66SUw-ibODSA,2478
|
19
19
|
robotcode/robot/utils/__init__.py,sha256=OjNPMn_XSnfaMCyKd8Kmq6vlRt6mIGlzW4qiiD3ykUg,447
|
20
20
|
robotcode/robot/utils/ast.py,sha256=ynxvXv1SHkAcF07m76ey9_zaGCYXhlPOnny7km-XWSQ,10479
|
21
21
|
robotcode/robot/utils/markdownformatter.py,sha256=0XZZ5wDU2yfzIuShjG7h79PgzMaQJ501WH4YRFcG1VM,11615
|
@@ -24,7 +24,7 @@ robotcode/robot/utils/robot_path.py,sha256=qKBh1cEnReBBLKkWu4gB9EzM-scAwE4xJc1m6
|
|
24
24
|
robotcode/robot/utils/stubs.py,sha256=6-DMI_CQVJHDgG13t-zINKGCRb_Q7MQPm0_AkfhAEvE,748
|
25
25
|
robotcode/robot/utils/variables.py,sha256=fEl8S37lb_mD4hn2MZRAlkiuLGBjAOeZVK0r2o2CfPw,742
|
26
26
|
robotcode/robot/utils/visitor.py,sha256=uYLqEhGPmzWKWI3SSrmCaYMwtKvNShvbiPZ4b3FavX8,3241
|
27
|
-
robotcode_robot-0.
|
28
|
-
robotcode_robot-0.
|
29
|
-
robotcode_robot-0.
|
30
|
-
robotcode_robot-0.
|
27
|
+
robotcode_robot-0.92.0.dist-info/METADATA,sha256=H_aQghUcVbx_6vUwIw0DTqVSUhN1ewvzGjinWv-MEX0,2240
|
28
|
+
robotcode_robot-0.92.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
29
|
+
robotcode_robot-0.92.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
|
30
|
+
robotcode_robot-0.92.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|