robotcode-robot 2.7.0__py3-none-any.whl → 2.8.0.dev631__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.
Files changed (32) hide show
  1. robotcode/robot/__version__.py +1 -1
  2. robotcode/robot/config/loader.py +18 -5
  3. robotcode/robot/config/model.py +229 -61
  4. robotcode/robot/diagnostics/analyzer_result.py +1 -0
  5. robotcode/robot/diagnostics/data_cache.py +12 -1
  6. robotcode/robot/diagnostics/diagnostic_rules.py +75 -0
  7. robotcode/robot/diagnostics/diagnostics_modifier.py +83 -43
  8. robotcode/robot/diagnostics/document_cache_helper.py +76 -8
  9. robotcode/robot/diagnostics/errors.py +7 -0
  10. robotcode/robot/diagnostics/import_resolver.py +162 -12
  11. robotcode/robot/diagnostics/imports_manager.py +318 -97
  12. robotcode/robot/diagnostics/library_doc.py +877 -162
  13. robotcode/robot/diagnostics/namespace.py +60 -15
  14. robotcode/robot/diagnostics/namespace_analyzer.py +90 -28
  15. robotcode/robot/diagnostics/project_index.py +88 -13
  16. robotcode/robot/diagnostics/scope_tree.py +2 -2
  17. robotcode/robot/diagnostics/semantic_analyzer/analyzer.py +855 -213
  18. robotcode/robot/diagnostics/semantic_analyzer/enums.py +40 -2
  19. robotcode/robot/diagnostics/semantic_analyzer/json_dump.py +364 -0
  20. robotcode/robot/diagnostics/semantic_analyzer/model.py +72 -11
  21. robotcode/robot/diagnostics/semantic_analyzer/nodes.py +7 -1
  22. robotcode/robot/diagnostics/semantic_analyzer/variable_tokenizer.py +218 -112
  23. robotcode/robot/diagnostics/workspace_config.py +3 -0
  24. robotcode/robot/utils/markdown_docs.py +733 -0
  25. robotcode/robot/utils/markdownformatter.py +57 -12
  26. robotcode/robot/utils/match.py +13 -0
  27. robotcode/robot/utils/robot_patching.py +4 -0
  28. robotcode/robot/utils/variables.py +3 -0
  29. {robotcode_robot-2.7.0.dist-info → robotcode_robot-2.8.0.dev631.dist-info}/METADATA +4 -3
  30. robotcode_robot-2.8.0.dev631.dist-info/RECORD +48 -0
  31. {robotcode_robot-2.7.0.dist-info → robotcode_robot-2.8.0.dev631.dist-info}/WHEEL +1 -1
  32. robotcode_robot-2.7.0.dist-info/RECORD +0 -46
@@ -1 +1 @@
1
- __version__ = "2.7.0"
1
+ __version__ = "2.8.0-dev.631"
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
  from dataclasses import fields, is_dataclass
3
3
  from enum import Enum
4
- from pathlib import Path
4
+ from pathlib import Path, PurePath
5
5
  from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar, Union
6
6
 
7
7
  from robotcode.core.utils.dataclasses import from_dict
@@ -213,6 +213,20 @@ def load_robot_config_from_path(
213
213
  )
214
214
 
215
215
 
216
+ _TPath = TypeVar("_TPath", bound=PurePath)
217
+
218
+
219
+ def _find_common_base(src_parents: Sequence[Sequence[_TPath]]) -> Optional[_TPath]:
220
+ """The deepest folder all sources have in common.
221
+
222
+ Sources on different drives (Windows) have nothing in common.
223
+ """
224
+ common = set.intersection(*(set(parents) for parents in src_parents))
225
+ if not common:
226
+ return None
227
+ return max(common, key=lambda path: path.parts)
228
+
229
+
216
230
  def find_project_root(
217
231
  *sources: Union[str, Path],
218
232
  root_folder: Optional[Path] = None,
@@ -228,10 +242,9 @@ def find_project_root(
228
242
 
229
243
  src_parents = [list(path.parents) + ([path] if path.is_dir() else []) for path in path_srcs]
230
244
 
231
- common_base = max(
232
- set.intersection(*(set(parents) for parents in src_parents)),
233
- key=lambda path: path.parts,
234
- )
245
+ common_base = _find_common_base(src_parents)
246
+ if common_base is None:
247
+ return None, DiscoverdBy.NOT_FOUND
235
248
 
236
249
  for directory in (common_base, *common_base.parents):
237
250
  if (directory / LOCAL_ROBOT_TOML).is_file():
@@ -297,7 +297,8 @@ class BaseOptions(ValidateMixin):
297
297
  type_hints = get_type_hints(type(self))
298
298
  base_field_names = [f.name for f in dataclasses.fields(self)]
299
299
 
300
- for f in dataclasses.fields(config):
300
+ # A setting replaces the value before it and its `extend-` setting adds to it, so the settings come first.
301
+ for f in sorted(dataclasses.fields(config), key=lambda f: f.name.startswith(EXTEND_PREFIX)):
301
302
  if f.name.startswith(EXTEND_PREFIX):
302
303
  if f.name not in base_field_names:
303
304
  continue
@@ -311,7 +312,13 @@ class BaseOptions(ValidateMixin):
311
312
  if new is None:
312
313
  continue
313
314
 
314
- old_field_name = f.name if combine_extends else f.name[EXTEND_PREFIX_LEN:]
315
+ # In a profile from `extend-profiles`, an `extend-` setting adds to the setting of the profile it
316
+ # extends; only if that profile has none does it stay an `extend-` setting of the profile.
317
+ old_field_name = (
318
+ f.name
319
+ if combine_extends and getattr(self, f.name[EXTEND_PREFIX_LEN:], None) is None
320
+ else f.name[EXTEND_PREFIX_LEN:]
321
+ )
315
322
 
316
323
  old = getattr(self, old_field_name)
317
324
  if old is None:
@@ -331,7 +338,10 @@ class BaseOptions(ValidateMixin):
331
338
  elif isinstance(old, tuple):
332
339
  setattr(self, old_field_name, (*old, *new))
333
340
  elif isinstance(old, BaseOptions):
334
- old.add_options(new)
341
+ # The tables of `rebot`, `libdoc` and `testdoc` keep an `extend-` setting that they don't set
342
+ # themselves, because the command adds the table to the settings outside of it. The tables of
343
+ # the analysis are read as they are, so there it becomes the setting.
344
+ old.add_options(new, isinstance(old, (RebotProfile, LibDocProfile, TestDocProfile)))
335
345
  else:
336
346
  setattr(self, old_field_name, new)
337
347
  continue
@@ -343,9 +353,6 @@ class BaseOptions(ValidateMixin):
343
353
  if EXTEND_PREFIX + f.name in base_field_names and getattr(config, f.name, None) is not None:
344
354
  setattr(self, EXTEND_PREFIX + f.name, None)
345
355
 
346
- if getattr(config, f"{EXTEND_PREFIX}_{f.name}", None) is not None and not combine_extends:
347
- continue
348
-
349
356
  new = self._verified_value(f.name, getattr(config, f.name), type_hints[f.name], config)
350
357
  if new is not None:
351
358
  setattr(self, f.name, new)
@@ -577,7 +584,7 @@ class CommonOptions(RobotBaseOptions):
577
584
 
578
585
  ```toml
579
586
  # tests with both "foo" and "bar*" tags
580
- includes = ["fooANDbar*"]
587
+ includes = ["foo AND bar*"]
581
588
  ```
582
589
 
583
590
  corresponds to the `-i --include tag *` option of _robot_
@@ -828,7 +835,7 @@ class CommonOptions(RobotBaseOptions):
828
835
 
829
836
  ```toml
830
837
  # match by tag pattern (same rules as --include)
831
- remove-keywords = ["foo", "fooANDbar*"]
838
+ remove-keywords = ["foo", "foo AND bar*"]
832
839
  ```
833
840
 
834
841
  corresponds to the `--removekeywords all|passed|for|wuks|name:<pattern>|tag:<pattern> *` option of _robot_
@@ -1024,7 +1031,7 @@ class CommonOptions(RobotBaseOptions):
1024
1031
  Examples:
1025
1032
 
1026
1033
  ```toml
1027
- tag-stat-combine = ["requirement-*", { "tag1ANDtag2" = "My_name" }]
1034
+ tag-stat-combine = ["requirement-*", { "tag1 AND tag2" = "My_name" }]
1028
1035
  ```
1029
1036
 
1030
1037
  corresponds to the `--tagstatcombine tags:name *` option of _robot_
@@ -1264,7 +1271,7 @@ class CommonExtendOptions(RobotBaseOptions):
1264
1271
 
1265
1272
  ```toml
1266
1273
  # tests with both "foo" and "bar*" tags
1267
- extend-includes = ["fooANDbar*"]
1274
+ extend-includes = ["foo AND bar*"]
1268
1275
  ```
1269
1276
 
1270
1277
  corresponds to the `-i --include tag *` option of _robot_
@@ -1400,7 +1407,7 @@ class CommonExtendOptions(RobotBaseOptions):
1400
1407
 
1401
1408
  ```toml
1402
1409
  # match by tag pattern (same rules as --include)
1403
- extend-remove-keywords = ["foo", "fooANDbar*"]
1410
+ extend-remove-keywords = ["foo", "foo AND bar*"]
1404
1411
  ```
1405
1412
 
1406
1413
  corresponds to the `--removekeywords all|passed|for|wuks|name:<pattern>|tag:<pattern> *` option of _robot_
@@ -1480,7 +1487,7 @@ class CommonExtendOptions(RobotBaseOptions):
1480
1487
  Examples:
1481
1488
 
1482
1489
  ```toml
1483
- extend-tag-stat-combine = ["requirement-*", { "tag1ANDtag2" = "My_name" }]
1490
+ extend-tag-stat-combine = ["requirement-*", { "tag1 AND tag2" = "My_name" }]
1484
1491
  ```
1485
1492
 
1486
1493
  corresponds to the `--tagstatcombine tags:name *` option of _robot_
@@ -1588,18 +1595,22 @@ class CommonExtendOptions(RobotBaseOptions):
1588
1595
  class RobotOptions(RobotBaseOptions):
1589
1596
  """Options for _robot_ command."""
1590
1597
 
1591
- console: Optional[Literal["verbose", "dotted", "skipped", "quiet", "none"]] = field(
1598
+ console: Optional[Union[str, Literal["verbose", "dotted", "quiet", "none"]]] = field(
1592
1599
  description="""\
1593
1600
  How to report execution on the console.
1601
+ Built-in consoles:
1594
1602
 
1595
1603
  **verbose:** report every suite and test (default)
1596
1604
 
1597
- **dotted:** only show `.` for passed test, `s` for
1598
- skipped tests, and `F` for failed tests
1605
+ **dotted:** only show `.` for passed test, `s`
1606
+ for skipped and `F` for failed tests
1599
1607
 
1600
- **quiet:** no output except for errors and warnings
1608
+ **quiet:** no output except for errors/warnings
1601
1609
 
1602
1610
  **none:** no output whatsoever
1611
+ Other values are interpreted as a custom
1612
+ console class or module. Argument format is
1613
+ the same as with --listener.
1603
1614
 
1604
1615
  Examples:
1605
1616
 
@@ -1607,7 +1618,12 @@ class RobotOptions(RobotBaseOptions):
1607
1618
  console = "dotted"
1608
1619
  ```
1609
1620
 
1610
- corresponds to the `--console type` option of _robot_
1621
+ ```toml
1622
+ # custom console logger class or module (Robot Framework 7.5 or newer)
1623
+ console = "path/to/MyConsole.py:arg"
1624
+ ```
1625
+
1626
+ corresponds to the `--console console` option of _robot_
1611
1627
  """,
1612
1628
  robot_name="console",
1613
1629
  robot_priority=500,
@@ -2142,7 +2158,7 @@ class RobotExtendOptions(RobotBaseOptions):
2142
2158
  extend-languages = ["German", "Finnish"]
2143
2159
  ```
2144
2160
 
2145
- corresponds to the `--language lang *` option of _rebot_
2161
+ corresponds to the `--language lang *` option of _robot_
2146
2162
  """,
2147
2163
  alias="extend-languages",
2148
2164
  )
@@ -2163,7 +2179,7 @@ class RobotExtendOptions(RobotBaseOptions):
2163
2179
  "path/to/Listener.py" = ["arg1", "arg2"]
2164
2180
  ```
2165
2181
 
2166
- corresponds to the `--listener listener *` option of _rebot_
2182
+ corresponds to the `--listener listener *` option of _robot_
2167
2183
  """,
2168
2184
  alias="extend-listeners",
2169
2185
  )
@@ -2182,7 +2198,7 @@ class RobotExtendOptions(RobotBaseOptions):
2182
2198
  "path/to/MyParser.py" = ["arg1", "arg2"]
2183
2199
  ```
2184
2200
 
2185
- corresponds to the `--parser parser *` option of _rebot_
2201
+ corresponds to the `--parser parser *` option of _robot_
2186
2202
  """,
2187
2203
  alias="extend-parsers",
2188
2204
  )
@@ -2202,7 +2218,7 @@ class RobotExtendOptions(RobotBaseOptions):
2202
2218
  "path/to/Modifier.py" = ["arg1"]
2203
2219
  ```
2204
2220
 
2205
- corresponds to the `--prerunmodifier modifier *` option of _rebot_
2221
+ corresponds to the `--prerunmodifier modifier *` option of _robot_
2206
2222
  """,
2207
2223
  alias="extend-pre-run-modifiers",
2208
2224
  )
@@ -2219,7 +2235,7 @@ class RobotExtendOptions(RobotBaseOptions):
2219
2235
  extend-skip = ["bug-*", "wip"]
2220
2236
  ```
2221
2237
 
2222
- corresponds to the `--skip tag *` option of _rebot_
2238
+ corresponds to the `--skip tag *` option of _robot_
2223
2239
  """,
2224
2240
  alias="extend-skip",
2225
2241
  )
@@ -2236,7 +2252,7 @@ class RobotExtendOptions(RobotBaseOptions):
2236
2252
  extend-skip-on-failure = ["unstable"]
2237
2253
  ```
2238
2254
 
2239
- corresponds to the `--skiponfailure tag *` option of _rebot_
2255
+ corresponds to the `--skiponfailure tag *` option of _robot_
2240
2256
  """,
2241
2257
  alias="extend-skip-on-failure",
2242
2258
  )
@@ -2257,7 +2273,7 @@ class RobotExtendOptions(RobotBaseOptions):
2257
2273
  name = "Robot"
2258
2274
  ```
2259
2275
 
2260
- corresponds to the `-v --variable name:value *` option of _rebot_
2276
+ corresponds to the `-v --variable name:value *` option of _robot_
2261
2277
  """,
2262
2278
  alias="extend-variables",
2263
2279
  )
@@ -2275,7 +2291,7 @@ class RobotExtendOptions(RobotBaseOptions):
2275
2291
  extend-variable-files = ["path/vars.yaml", "environment.py:testing"]
2276
2292
  ```
2277
2293
 
2278
- corresponds to the `-V --variablefile path *` option of _rebot_
2294
+ corresponds to the `-V --variablefile path *` option of _robot_
2279
2295
  """,
2280
2296
  alias="extend-variable-files",
2281
2297
  )
@@ -2285,6 +2301,36 @@ class RobotExtendOptions(RobotBaseOptions):
2285
2301
  class RebotOptions(RobotBaseOptions):
2286
2302
  """Options for _rebot_ command."""
2287
2303
 
2304
+ console: Optional[Union[str, Literal["verbose", "quiet", "none"]]] = field(
2305
+ description="""\
2306
+ How to report on the console.
2307
+ Built-in consoles:
2308
+
2309
+ **verbose:** show output file paths (default)
2310
+
2311
+ **quiet:** no output except for errors/warnings
2312
+
2313
+ **none:** no output whatsoever
2314
+ Other values are interpreted as a custom
2315
+ console class or module. Argument format is
2316
+ the same as with --listener.
2317
+
2318
+ Examples:
2319
+
2320
+ ```toml
2321
+ console = "quiet"
2322
+ ```
2323
+
2324
+ ```toml
2325
+ # custom console logger class or module
2326
+ console = "path/to/Console.py:arg"
2327
+ ```
2328
+
2329
+ corresponds to the `--console console` option of _rebot_
2330
+ """,
2331
+ robot_name="console",
2332
+ robot_priority=500,
2333
+ )
2288
2334
  end_time: Optional[Union[str, StringExpression]] = field(
2289
2335
  description="""\
2290
2336
  Same as --starttime but for end time. If both options
@@ -2388,6 +2434,22 @@ class RebotOptions(RobotBaseOptions):
2388
2434
  robot_is_flag=True,
2389
2435
  alias="process-empty-suite",
2390
2436
  )
2437
+ quiet: Union[bool, Flag, None] = field(
2438
+ description="""\
2439
+ Shortcut for `--console quiet`.
2440
+
2441
+ Examples:
2442
+
2443
+ ```toml
2444
+ quiet = true
2445
+ ```
2446
+
2447
+ corresponds to the `--quiet` option of _rebot_
2448
+ """,
2449
+ robot_name="quiet",
2450
+ robot_priority=500,
2451
+ robot_is_flag=True,
2452
+ )
2391
2453
  start_time: Optional[Union[str, StringExpression]] = field(
2392
2454
  description="""\
2393
2455
  Set execution start time. Timestamp must be given in
@@ -2417,13 +2479,14 @@ class RebotOptions(RobotBaseOptions):
2417
2479
  class LibDocOptions(RobotBaseOptions):
2418
2480
  """Options for _libdoc_ command."""
2419
2481
 
2420
- doc_format: Optional[Literal["ROBOT", "HTML", "TEXT", "REST"]] = field(
2482
+ doc_format: Optional[Literal["ROBOT", "MARKDOWN", "HTML", "TEXT", "REST"]] = field(
2421
2483
  description="""\
2422
2484
  Specifies the source documentation format. Possible
2423
2485
  values are Robot Framework's documentation format,
2424
- HTML, plain text, and reStructuredText. The default
2425
- value can be specified in library source code and
2426
- the initial default value is ROBOT.
2486
+ Markdown, HTML, plain text and reStructuredText.
2487
+ The default value can be specified in library source
2488
+ code and the initial default value is ROBOT. Markdown
2489
+ support is new in RF 7.5.
2427
2490
 
2428
2491
  Examples:
2429
2492
 
@@ -2431,20 +2494,21 @@ class LibDocOptions(RobotBaseOptions):
2431
2494
  doc-format = "REST"
2432
2495
  ```
2433
2496
 
2434
- corresponds to the `-F --docformat ROBOT|HTML|TEXT|REST` option of _libdoc_
2497
+ corresponds to the `-F --docformat ROBOT|MARKDOWN|HTML|TEXT|REST` option of _libdoc_
2435
2498
  """,
2436
2499
  robot_name="docformat",
2437
2500
  robot_priority=500,
2438
2501
  robot_short_name="F",
2439
2502
  alias="doc-format",
2440
2503
  )
2441
- format: Optional[Literal["HTML", "XML", "JSON", "LIBSPEC"]] = field(
2504
+ format: Optional[Literal["HTML", "XML", "JSON", "LIBSPEC", "MARKDOWN"]] = field(
2442
2505
  description="""\
2443
2506
  Specifies whether to generate an HTML output for
2444
- humans or a machine readable spec file in XML or JSON
2445
- format. The LIBSPEC format means XML spec with
2446
- documentations converted to HTML. The default format
2447
- is got from the output file extension.
2507
+ humans, a machine readable spec file in XML or JSON
2508
+ format, or a Markdown file. The LIBSPEC format means
2509
+ XML spec with documentations converted to HTML. The
2510
+ default format is got from the output file
2511
+ extension. MARKDOWN is new in RF 7.5.
2448
2512
 
2449
2513
  Examples:
2450
2514
 
@@ -2452,7 +2516,7 @@ class LibDocOptions(RobotBaseOptions):
2452
2516
  format = "HTML"
2453
2517
  ```
2454
2518
 
2455
- corresponds to the `-f --format HTML|XML|JSON|LIBSPEC` option of _libdoc_
2519
+ corresponds to the `-f --format HTML|XML|JSON|LIBSPEC|MARKDOWN` option of _libdoc_
2456
2520
  """,
2457
2521
  robot_name="format",
2458
2522
  robot_priority=500,
@@ -2516,7 +2580,8 @@ class LibDocOptions(RobotBaseOptions):
2516
2580
  documentation format and HTML means converting
2517
2581
  documentation to HTML. The default is RAW with XML
2518
2582
  spec files and HTML with JSON specs and when using
2519
- the special LIBSPEC format.
2583
+ the special LIBSPEC format. Not applicable with HTML
2584
+ or MARKDOWN outputs.
2520
2585
 
2521
2586
  Examples:
2522
2587
 
@@ -2627,7 +2692,7 @@ class TestDocOptions(RobotBaseOptions):
2627
2692
 
2628
2693
  ```toml
2629
2694
  # tests with both "foo" and "bar*" tags
2630
- includes = ["fooANDbar*"]
2695
+ includes = ["foo AND bar*"]
2631
2696
  ```
2632
2697
 
2633
2698
  corresponds to the `-i --include tag *` option of _testdoc_
@@ -2776,7 +2841,7 @@ class TestDocExtendOptions(RobotBaseOptions):
2776
2841
 
2777
2842
  ```toml
2778
2843
  # tests with both "foo" and "bar*" tags
2779
- extend-includes = ["fooANDbar*"]
2844
+ extend-includes = ["foo AND bar*"]
2780
2845
  ```
2781
2846
 
2782
2847
  corresponds to the `-i --include tag *` option of _testdoc_
@@ -2931,20 +2996,29 @@ class RobotBaseProfile(CommonOptions, CommonExtendOptions, RobotOptions, RobotEx
2931
2996
 
2932
2997
  rebot: Optional[RebotProfile] = field(
2933
2998
  description="""\
2934
- Options to be passed to _rebot_.
2999
+ Options to be passed to _rebot_. They win over the settings outside
3000
+ of this table. Of those, _rebot_ gets the ones that Robot Framework
3001
+ itself passes from _robot_ to _rebot_ after a run, so not
3002
+ `includes`, `excludes`, `tests`, `suites`, `parse-include`, `name`,
3003
+ `doc`, `metadata`, `set-tag`, `output`, `log-level`,
3004
+ `timestamp-outputs`, `console` and `quiet`; `run-empty-suite`
3005
+ becomes `process-empty-suite`.
2935
3006
  """
2936
3007
  )
2937
3008
 
2938
3009
  libdoc: Optional[LibDocProfile] = field(
2939
3010
  description="""\
2940
- Options to be passed to _libdoc_.
2941
- """
3011
+ Options to be passed to _libdoc_. Of the settings outside of this
3012
+ table, _libdoc_ gets only `python-path`, and this table wins over it.
3013
+ """
2942
3014
  )
2943
3015
 
2944
3016
  testdoc: Optional[TestDocProfile] = field(
2945
3017
  description="""\
2946
- Options to be passed to _testdoc_.
2947
- """
3018
+ Options to be passed to _testdoc_. They win over the settings outside
3019
+ of this table that _testdoc_ shares with _robot_, such as `name`,
3020
+ `metadata` or `includes`.
3021
+ """
2948
3022
  )
2949
3023
 
2950
3024
  def save(self, path: "os.PathLike[str]") -> None:
@@ -3008,6 +3082,51 @@ class RobotExtendBaseProfile(RobotBaseProfile):
3008
3082
  alias="extend-wrapper",
3009
3083
  )
3010
3084
 
3085
+ extend_rebot: Optional[RebotProfile] = field(
3086
+ description="""\
3087
+ Changes the `[rebot]` options that come before it, from an earlier
3088
+ configuration file or before a profile: a setting given here
3089
+ replaces the one of `[rebot]`, and an `extend-` setting adds to it.
3090
+
3091
+ Examples:
3092
+ ```toml
3093
+ [extend-rebot]
3094
+ report-title = "My run"
3095
+ ```
3096
+ """,
3097
+ alias="extend-rebot",
3098
+ )
3099
+
3100
+ extend_libdoc: Optional[LibDocProfile] = field(
3101
+ description="""\
3102
+ Changes the `[libdoc]` options that come before it, from an earlier
3103
+ configuration file or before a profile: a setting given here
3104
+ replaces the one of `[libdoc]`, and an `extend-` setting adds to it.
3105
+
3106
+ Examples:
3107
+ ```toml
3108
+ [extend-libdoc]
3109
+ theme = "DARK"
3110
+ ```
3111
+ """,
3112
+ alias="extend-libdoc",
3113
+ )
3114
+
3115
+ extend_testdoc: Optional[TestDocProfile] = field(
3116
+ description="""\
3117
+ Changes the `[testdoc]` options that come before it, from an earlier
3118
+ configuration file or before a profile: a setting given here
3119
+ replaces the one of `[testdoc]`, and an `extend-` setting adds to it.
3120
+
3121
+ Examples:
3122
+ ```toml
3123
+ [extend-testdoc]
3124
+ title = "My tests"
3125
+ ```
3126
+ """,
3127
+ alias="extend-testdoc",
3128
+ )
3129
+
3011
3130
  extend_paths: Union[str, List[str], None] = field(
3012
3131
  description="""\
3013
3132
  Append extra entries to the paths argument.
@@ -3032,15 +3151,23 @@ class RobotProfile(RobotExtendBaseProfile):
3032
3151
 
3033
3152
  detached: Optional[bool] = field(
3034
3153
  description="""\
3035
- The profile should be detached.
3036
- Detached means it is not inherited from the main profile.
3154
+ Starts the profile from scratch: the settings outside of profiles,
3155
+ from all configuration files, and the profiles applied before it
3156
+ are not used.
3157
+
3158
+ Examples:
3159
+ ```toml
3160
+ [profiles.clean]
3161
+ detached = true
3162
+ paths = ["tests"]
3163
+ ```
3037
3164
  """
3038
3165
  )
3039
3166
 
3040
3167
  enabled: Union[bool, Condition, None] = field(
3041
3168
  description="""\
3042
- If enabled the profile is used. You can also use and `if` condition
3043
- to calculate the enabled state.
3169
+ Whether the profile is used when it is selected; a disabled profile
3170
+ is skipped. An `if` condition computes the value.
3044
3171
 
3045
3172
  Examples:
3046
3173
  ```toml
@@ -3049,7 +3176,7 @@ class RobotProfile(RobotExtendBaseProfile):
3049
3176
  ```
3050
3177
 
3051
3178
  ```toml
3052
- # enabled if TEST_VAR is set
3179
+ # enabled if the environment variable CI is "true"
3053
3180
  enabled = { if = 'environ.get("CI") == "true"' }
3054
3181
  ```
3055
3182
  """
@@ -3060,8 +3187,9 @@ class RobotProfile(RobotExtendBaseProfile):
3060
3187
 
3061
3188
  hidden: Union[bool, Condition, None] = field(
3062
3189
  description="""\
3063
- The profile should be hidden.
3064
- Hidden means it is not shown in the list of available profiles.
3190
+ Hides the profile from `robotcode profiles list` and from the
3191
+ profile lists of the editors; it can still be selected by name.
3192
+ Profiles whose names start with `_` are hidden too.
3065
3193
 
3066
3194
  Examples:
3067
3195
  ```toml
@@ -3076,13 +3204,15 @@ class RobotProfile(RobotExtendBaseProfile):
3076
3204
 
3077
3205
  precedence: Optional[int] = field(
3078
3206
  description="""\
3079
- Precedence of the profile. Lower values are executed first. If not set the order is undefined.
3207
+ Precedence of the profile. Lower values are executed first. A profile without a precedence counts as 0;
3208
+ profiles with the same precedence are applied in the order they are selected, for example on the command line.
3080
3209
  """
3081
3210
  )
3082
3211
 
3083
3212
  inherits: Union[str, StringExpression, List[Union[str, StringExpression]], None] = field(
3084
3213
  description="""\
3085
- Profiles to inherit from.
3214
+ Profiles to inherit from. Selecting this profile selects them too;
3215
+ they are applied before it unless they have a higher `precedence`.
3086
3216
 
3087
3217
  Examples:
3088
3218
  ```toml
@@ -3098,7 +3228,8 @@ class RobotConfig(RobotExtendBaseProfile):
3098
3228
 
3099
3229
  default_profiles: Union[str, List[str], None] = field(
3100
3230
  description="""\
3101
- Selects the Default profile if no profile is given at command line.
3231
+ The profiles to use when no profile is selected, for example with
3232
+ `-p` on the command line or in the editor.
3102
3233
 
3103
3234
  Examples:
3104
3235
  ```toml
@@ -3111,13 +3242,46 @@ class RobotConfig(RobotExtendBaseProfile):
3111
3242
  """,
3112
3243
  alias="default-profiles",
3113
3244
  )
3114
- profiles: Optional[Dict[str, RobotProfile]] = field(description="Execution profiles.")
3245
+ profiles: Optional[Dict[str, RobotProfile]] = field(
3246
+ description="""\
3247
+ Execution profiles: named sets of settings that are applied on top
3248
+ of the other settings when the profile is selected, for example
3249
+ with `robotcode -p ci`. In a later configuration file, such as
3250
+ `.robot.toml`, `profiles` replaces all profiles of the earlier
3251
+ files; `extend-profiles` adds to them.
3252
+
3253
+ Examples:
3254
+ ```toml
3255
+ [profiles.ci]
3256
+ description = "CI server"
3257
+ output-dir = "results/ci"
3258
+ ```
3259
+ """
3260
+ )
3115
3261
 
3116
3262
  extend_profiles: Optional[Dict[str, RobotProfile]] = field(
3117
- description="Extra execution profiles.", alias="extend-profiles"
3263
+ description="""\
3264
+ Adds profiles to those of the earlier configuration files, for
3265
+ example in `.robot.toml` to those of `robot.toml`. For a profile
3266
+ that exists already, a setting given here replaces the one of the
3267
+ profile, and an `extend-` setting adds to it.
3268
+
3269
+ Examples:
3270
+ ```toml
3271
+ [extend-profiles.local]
3272
+ description = "My local settings"
3273
+ extend-variables = { BROWSER = "firefox" }
3274
+ ```
3275
+ """,
3276
+ alias="extend-profiles",
3118
3277
  )
3119
3278
 
3120
- tool: Optional[Dict[str, Any]] = field(description="Tool configurations.")
3279
+ tool: Optional[Dict[str, Any]] = field(
3280
+ description="""\
3281
+ Settings of other tools, for example `[tool.robotcode-analyze]`
3282
+ for RobotCode's analysis.
3283
+ """
3284
+ )
3121
3285
 
3122
3286
  def _select_profiles(
3123
3287
  self,
@@ -3290,7 +3454,8 @@ class RobotConfig(RobotExtendBaseProfile):
3290
3454
  if profile.detached:
3291
3455
  result = RobotBaseProfile()
3292
3456
 
3293
- for f in dataclasses.fields(profile):
3457
+ # A setting replaces the value before it and its `extend-` setting adds to it, so the settings come first.
3458
+ for f in sorted(dataclasses.fields(profile), key=lambda f: f.name.startswith(EXTEND_PREFIX)):
3294
3459
  if f.name.startswith(EXTEND_PREFIX):
3295
3460
  new = self._verified_value(
3296
3461
  f.name,
@@ -3315,6 +3480,12 @@ class RobotConfig(RobotExtendBaseProfile):
3315
3480
  setattr(result, f.name[EXTEND_PREFIX_LEN:], [*old, *new])
3316
3481
  elif isinstance(old, tuple):
3317
3482
  setattr(result, f.name[EXTEND_PREFIX_LEN:], (*old, *new))
3483
+ elif isinstance(old, BaseOptions):
3484
+ # a table such as `[rebot]`, changed on a copy because `old` may be the configuration's own;
3485
+ # it keeps the `extend-` settings it doesn't set itself, see `add_options`
3486
+ changed = dataclasses.replace(old)
3487
+ changed.add_options(new, True)
3488
+ setattr(result, f.name[EXTEND_PREFIX_LEN:], changed)
3318
3489
  else:
3319
3490
  setattr(result, f.name[EXTEND_PREFIX_LEN:], new)
3320
3491
  continue
@@ -3322,9 +3493,6 @@ class RobotConfig(RobotExtendBaseProfile):
3322
3493
  if f.name not in base_field_names:
3323
3494
  continue
3324
3495
 
3325
- if getattr(profile, f"{EXTEND_PREFIX}_{f.name}", None) is not None:
3326
- continue
3327
-
3328
3496
  new = self._verified_value(
3329
3497
  f.name,
3330
3498
  getattr(profile, f.name),
@@ -22,5 +22,6 @@ class AnalyzerResult:
22
22
  keyword_tag_references: Dict[str, Set[Location]]
23
23
  testcase_tag_references: Dict[str, Set[Location]]
24
24
  metadata_references: Dict[str, Set[Location]]
25
+ testcase_metadata_references: Dict[str, Set[Location]]
25
26
  scope_tree: ScopeTree
26
27
  semantic_model: Optional["SemanticModel"] = None
@@ -7,7 +7,7 @@ from contextlib import contextmanager
7
7
  from dataclasses import dataclass
8
8
  from enum import Enum
9
9
  from pathlib import Path
10
- from typing import Any, Callable, Generic, Iterator, List, Optional, Tuple, Type, TypeVar, Union
10
+ from typing import Any, Callable, Generic, Iterable, Iterator, List, Optional, Tuple, Type, TypeVar, Union
11
11
 
12
12
  from robotcode.core.utils.logging import LoggingDescriptor
13
13
 
@@ -495,6 +495,17 @@ class SqliteDataCache:
495
495
  for r in rows
496
496
  ]
497
497
 
498
+ def remove_entries(self, section: CacheSection, entry_names: Iterable[str]) -> int:
499
+ """Remove the entries with these names from a section, and return how many of them there were."""
500
+ rows = [(name,) for name in entry_names]
501
+
502
+ def op() -> int:
503
+ cursor = self._conn.executemany(f"DELETE FROM {section.value} WHERE entry_name = ?", rows)
504
+ self._conn.commit()
505
+ return cursor.rowcount
506
+
507
+ return self._run(op)
508
+
498
509
  def clear_section(self, section: CacheSection) -> int:
499
510
  def op() -> int:
500
511
  cursor = self._conn.execute(f"DELETE FROM {section.value}")