robotcode-robot 2.6.2__py3-none-any.whl → 2.8.0.dev548__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 +258 -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 +158 -17
  9. robotcode/robot/diagnostics/errors.py +7 -0
  10. robotcode/robot/diagnostics/import_resolver.py +178 -12
  11. robotcode/robot/diagnostics/imports_manager.py +556 -220
  12. robotcode/robot/diagnostics/library_doc.py +874 -162
  13. robotcode/robot/diagnostics/namespace.py +84 -18
  14. robotcode/robot/diagnostics/namespace_analyzer.py +59 -16
  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 +790 -201
  18. robotcode/robot/diagnostics/semantic_analyzer/enums.py +39 -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.6.2.dist-info → robotcode_robot-2.8.0.dev548.dist-info}/METADATA +4 -3
  30. robotcode_robot-2.8.0.dev548.dist-info/RECORD +48 -0
  31. {robotcode_robot-2.6.2.dist-info → robotcode_robot-2.8.0.dev548.dist-info}/WHEEL +1 -1
  32. robotcode_robot-2.6.2.dist-info/RECORD +0 -46
@@ -1 +1 @@
1
- __version__ = "2.6.2"
1
+ __version__ = "2.8.0-dev.548"
@@ -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_
@@ -2912,22 +2977,48 @@ class RobotBaseProfile(CommonOptions, CommonExtendOptions, RobotOptions, RobotEx
2912
2977
  """
2913
2978
  )
2914
2979
 
2980
+ wrapper: Optional[List[Union[str, StringExpression]]] = field(
2981
+ description="""\
2982
+ Command prefix the test run is executed through (the actual
2983
+ `robotcode` command line is appended to it), e.g. to bring up the
2984
+ services, servers, mocks or session the tests need around them. For
2985
+ plain environment variables use `env` instead. The selected
2986
+ profile's `env` is applied first, so the wrapper can rely on it.
2987
+
2988
+ Examples:
2989
+ ```toml
2990
+ [profiles.integration]
2991
+ wrapper = ["./with-test-services.sh"]
2992
+ # or a ready-made tool, e.g. ["xvfb-run", "-a"] for headless UI tests
2993
+ ```
2994
+ """,
2995
+ )
2996
+
2915
2997
  rebot: Optional[RebotProfile] = field(
2916
2998
  description="""\
2917
- 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`.
2918
3006
  """
2919
3007
  )
2920
3008
 
2921
3009
  libdoc: Optional[LibDocProfile] = field(
2922
3010
  description="""\
2923
- Options to be passed to _libdoc_.
2924
- """
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
+ """
2925
3014
  )
2926
3015
 
2927
3016
  testdoc: Optional[TestDocProfile] = field(
2928
3017
  description="""\
2929
- Options to be passed to _testdoc_.
2930
- """
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
+ """
2931
3022
  )
2932
3023
 
2933
3024
  def save(self, path: "os.PathLike[str]") -> None:
@@ -2979,6 +3070,63 @@ class RobotExtendBaseProfile(RobotBaseProfile):
2979
3070
  alias="extend-env",
2980
3071
  )
2981
3072
 
3073
+ extend_wrapper: Optional[List[Union[str, StringExpression]]] = field(
3074
+ description="""\
3075
+ Append extra entries to the `wrapper` command prefix.
3076
+
3077
+ Examples:
3078
+ ```toml
3079
+ extend-wrapper = ["--some-flag"]
3080
+ ```
3081
+ """,
3082
+ alias="extend-wrapper",
3083
+ )
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
+
2982
3130
  extend_paths: Union[str, List[str], None] = field(
2983
3131
  description="""\
2984
3132
  Append extra entries to the paths argument.
@@ -3003,15 +3151,23 @@ class RobotProfile(RobotExtendBaseProfile):
3003
3151
 
3004
3152
  detached: Optional[bool] = field(
3005
3153
  description="""\
3006
- The profile should be detached.
3007
- 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
+ ```
3008
3164
  """
3009
3165
  )
3010
3166
 
3011
3167
  enabled: Union[bool, Condition, None] = field(
3012
3168
  description="""\
3013
- If enabled the profile is used. You can also use and `if` condition
3014
- 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.
3015
3171
 
3016
3172
  Examples:
3017
3173
  ```toml
@@ -3020,7 +3176,7 @@ class RobotProfile(RobotExtendBaseProfile):
3020
3176
  ```
3021
3177
 
3022
3178
  ```toml
3023
- # enabled if TEST_VAR is set
3179
+ # enabled if the environment variable CI is "true"
3024
3180
  enabled = { if = 'environ.get("CI") == "true"' }
3025
3181
  ```
3026
3182
  """
@@ -3031,8 +3187,9 @@ class RobotProfile(RobotExtendBaseProfile):
3031
3187
 
3032
3188
  hidden: Union[bool, Condition, None] = field(
3033
3189
  description="""\
3034
- The profile should be hidden.
3035
- 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.
3036
3193
 
3037
3194
  Examples:
3038
3195
  ```toml
@@ -3047,13 +3204,15 @@ class RobotProfile(RobotExtendBaseProfile):
3047
3204
 
3048
3205
  precedence: Optional[int] = field(
3049
3206
  description="""\
3050
- 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.
3051
3209
  """
3052
3210
  )
3053
3211
 
3054
3212
  inherits: Union[str, StringExpression, List[Union[str, StringExpression]], None] = field(
3055
3213
  description="""\
3056
- 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`.
3057
3216
 
3058
3217
  Examples:
3059
3218
  ```toml
@@ -3069,7 +3228,8 @@ class RobotConfig(RobotExtendBaseProfile):
3069
3228
 
3070
3229
  default_profiles: Union[str, List[str], None] = field(
3071
3230
  description="""\
3072
- 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.
3073
3233
 
3074
3234
  Examples:
3075
3235
  ```toml
@@ -3082,13 +3242,46 @@ class RobotConfig(RobotExtendBaseProfile):
3082
3242
  """,
3083
3243
  alias="default-profiles",
3084
3244
  )
3085
- 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
+ )
3086
3261
 
3087
3262
  extend_profiles: Optional[Dict[str, RobotProfile]] = field(
3088
- 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",
3089
3277
  )
3090
3278
 
3091
- 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
+ )
3092
3285
 
3093
3286
  def _select_profiles(
3094
3287
  self,
@@ -3261,7 +3454,8 @@ class RobotConfig(RobotExtendBaseProfile):
3261
3454
  if profile.detached:
3262
3455
  result = RobotBaseProfile()
3263
3456
 
3264
- 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)):
3265
3459
  if f.name.startswith(EXTEND_PREFIX):
3266
3460
  new = self._verified_value(
3267
3461
  f.name,
@@ -3286,6 +3480,12 @@ class RobotConfig(RobotExtendBaseProfile):
3286
3480
  setattr(result, f.name[EXTEND_PREFIX_LEN:], [*old, *new])
3287
3481
  elif isinstance(old, tuple):
3288
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)
3289
3489
  else:
3290
3490
  setattr(result, f.name[EXTEND_PREFIX_LEN:], new)
3291
3491
  continue
@@ -3293,9 +3493,6 @@ class RobotConfig(RobotExtendBaseProfile):
3293
3493
  if f.name not in base_field_names:
3294
3494
  continue
3295
3495
 
3296
- if getattr(profile, f"{EXTEND_PREFIX}_{f.name}", None) is not None:
3297
- continue
3298
-
3299
3496
  new = self._verified_value(
3300
3497
  f.name,
3301
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