robotcode-robot 0.91.0__py3-none-any.whl → 0.92.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|