robotcode-analyze 2.7.0__tar.gz → 2.8.0.dev8__tar.gz

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 (20) hide show
  1. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/.gitignore +4 -1
  2. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/PKG-INFO +6 -5
  3. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/pyproject.toml +4 -3
  4. robotcode_analyze-2.8.0.dev8/src/robotcode/analyze/__version__.py +1 -0
  5. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/cache/cli.py +8 -6
  6. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/cli.py +2 -0
  7. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/code/cli.py +2 -2
  8. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/code/code_analyzer.py +9 -2
  9. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/code/diagnostics_context.py +7 -0
  10. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/code/robot_framework_language_provider.py +9 -0
  11. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/config.py +52 -18
  12. robotcode_analyze-2.8.0.dev8/src/robotcode/analyze/dump_model.py +158 -0
  13. robotcode_analyze-2.7.0/src/robotcode/analyze/__version__.py +0 -1
  14. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/README.md +0 -0
  15. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/__init__.py +0 -0
  16. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/code/_models.py +0 -0
  17. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/code/_sarif.py +0 -0
  18. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/code/language_provider.py +0 -0
  19. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/hooks.py +0 -0
  20. {robotcode_analyze-2.7.0 → robotcode_analyze-2.8.0.dev8}/src/robotcode/analyze/py.typed +0 -0
@@ -279,6 +279,9 @@ dist
279
279
  # vuepress build output
280
280
  .vuepress/dist
281
281
 
282
+ # Astro (documentation site)
283
+ docs/.astro/
284
+
282
285
  # Serverless directories
283
286
  .serverless/
284
287
 
@@ -339,7 +342,7 @@ bundled/libs
339
342
  # .agents
340
343
  .agents/
341
344
  skills-lock.json
342
- .claude
345
+ #.claude
343
346
 
344
347
  # sarif files
345
348
  /**/*.sarif.json
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: robotcode-analyze
3
- Version: 2.7.0
3
+ Version: 2.8.0-dev.8
4
4
  Summary: RobotCode analyze plugin for Robot Framework
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -20,14 +20,15 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
23
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
25
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
26
  Classifier: Topic :: Utilities
26
27
  Classifier: Typing :: Typed
27
28
  Requires-Python: >=3.10
28
- Requires-Dist: robotcode-plugin==2.7.0
29
- Requires-Dist: robotcode-robot==2.7.0
30
- Requires-Dist: robotcode==2.7.0
29
+ Requires-Dist: robotcode-plugin==2.8.0-dev.8
30
+ Requires-Dist: robotcode-robot==2.8.0-dev.8
31
+ Requires-Dist: robotcode==2.8.0-dev.8
31
32
  Requires-Dist: robotframework>=5.0.0
32
33
  Description-Content-Type: text/markdown
33
34
 
@@ -18,6 +18,7 @@ classifiers = [
18
18
  "Programming Language :: Python :: 3.12",
19
19
  "Programming Language :: Python :: 3.13",
20
20
  "Programming Language :: Python :: 3.14",
21
+ "Programming Language :: Python :: 3.15",
21
22
  "Programming Language :: Python :: Implementation :: CPython",
22
23
  "Programming Language :: Python :: Implementation :: PyPy",
23
24
  "Operating System :: OS Independent",
@@ -28,9 +29,9 @@ classifiers = [
28
29
  ]
29
30
  dependencies = [
30
31
  "robotframework>=5.0.0",
31
- "robotcode-plugin==2.7.0",
32
- "robotcode-robot==2.7.0",
33
- "robotcode==2.7.0",
32
+ "robotcode-plugin==2.8.0-dev.8",
33
+ "robotcode-robot==2.8.0-dev.8",
34
+ "robotcode==2.8.0-dev.8",
34
35
  ]
35
36
  dynamic = ["version"]
36
37
 
@@ -0,0 +1 @@
1
+ __version__ = "2.8.0-dev.8"
@@ -43,7 +43,7 @@ def _resolve_cache(
43
43
  cache_base_path = root_folder or Path.cwd()
44
44
  if analyzer_config is not None and isinstance(analyzer_config, AnalyzeConfig):
45
45
  if analyzer_config.cache is not None and analyzer_config.cache.cache_dir is not None:
46
- cache_base_path = Path(analyzer_config.cache.cache_dir)
46
+ cache_base_path = cache_base_path / analyzer_config.cache.cache_dir
47
47
 
48
48
  cache_base_path = resolve_cache_base_path(cache_base_path)
49
49
 
@@ -271,7 +271,9 @@ def cache_list(app: Application, sections: Tuple[str, ...], patterns: Tuple[str,
271
271
  entries = [e for e in db.list_entries(section) if _entry_matches(e.entry_name, patterns)]
272
272
  if not entries:
273
273
  continue
274
- lines.append(f"### {section.name.lower()} ({len(entries)} entries)")
274
+ lines.append(
275
+ f"### {section.name.lower()} ({len(entries)} {'entry' if len(entries) == 1 else 'entries'})"
276
+ )
275
277
  lines.append("")
276
278
  lines.append("| Name | Size | Created | Modified |")
277
279
  lines.append("|---|---:|---|---|")
@@ -292,7 +294,7 @@ def cache_list(app: Application, sections: Tuple[str, ...], patterns: Tuple[str,
292
294
  if not entries:
293
295
  continue
294
296
  found = True
295
- app.echo(f"[{section.name.lower()}] ({len(entries)} entries)")
297
+ app.echo(f"[{section.name.lower()}] ({len(entries)} {'entry' if len(entries) == 1 else 'entries'})")
296
298
  for entry in entries:
297
299
  size = _format_bytes(entry.meta_bytes + entry.data_bytes)
298
300
  created = entry.created_at or "—"
@@ -360,11 +362,11 @@ def cache_clear(app: Application, sections: Tuple[str, ...], paths: Tuple[Path,
360
362
  for section in selected:
361
363
  count = db.clear_section(section)
362
364
  total += count
363
- app.echo(f"Cleared {count} entries from {section.name.lower()}.")
365
+ app.echo(f"Cleared {count} {'entry' if count == 1 else 'entries'} from {section.name.lower()}.")
364
366
  else:
365
367
  total = db.clear_all()
366
368
 
367
- app.echo(f"Removed {total} entries total.")
369
+ app.echo(f"Removed {total} {'entry' if total == 1 else 'entries'} total.")
368
370
  finally:
369
371
  db.close()
370
372
 
@@ -390,7 +392,7 @@ def _resolve_cache_root(
390
392
  cache_base_path = root_folder or Path.cwd()
391
393
  if analyzer_config is not None and isinstance(analyzer_config, AnalyzeConfig):
392
394
  if analyzer_config.cache is not None and analyzer_config.cache.cache_dir is not None:
393
- cache_base_path = Path(analyzer_config.cache.cache_dir)
395
+ cache_base_path = cache_base_path / analyzer_config.cache.cache_dir
394
396
 
395
397
  cache_base_path = resolve_cache_base_path(cache_base_path)
396
398
 
@@ -5,6 +5,7 @@ from robotcode.plugin import Application, pass_application
5
5
  from .__version__ import __version__
6
6
  from .cache.cli import cache_group
7
7
  from .code.cli import code
8
+ from .dump_model import dump_model
8
9
 
9
10
 
10
11
  @click.group(
@@ -26,3 +27,4 @@ def analyze(app: Application) -> None:
26
27
 
27
28
  analyze.add_command(cache_group)
28
29
  analyze.add_command(code)
30
+ analyze.add_command(dump_model)
@@ -473,8 +473,8 @@ def code(
473
473
  Performs static code analysis to identify potential issues in the specified *PATHS*. The analysis detects syntax
474
474
  errors, missing keywords or variables, missing arguments, and other problems.
475
475
 
476
- - **PATHS**: Can be individual files or directories. If no *PATHS* are provided, the current directory is
477
- analyzed by default.
476
+ - **PATHS**: Can be individual files or directories. If no *PATHS* are provided, the whole project is
477
+ analyzed.
478
478
 
479
479
  The return code is a bitwise combination of the following values:
480
480
 
@@ -136,6 +136,8 @@ class CodeAnalyzer(DiagnosticsContext):
136
136
  # Always yield, even when empty: the result collector uses this to count analyzed files.
137
137
  yield DocumentDiagnosticReport(document, diagnostics)
138
138
 
139
+ self.diagnostics.finish_folder(folder)
140
+
139
141
  def collect_documents(
140
142
  self, folder: WorkspaceFolder, paths: Iterable[Path] = (), filter_patterns: Iterable[str] = ()
141
143
  ) -> List[TextDocument]:
@@ -168,7 +170,12 @@ class CodeAnalyzer(DiagnosticsContext):
168
170
  self.app.error(f"Error reading {file}: {e}")
169
171
  continue
170
172
 
171
- self.app.verbose(f"{file_counter} files in workspace folder '{folder.uri.to_path()}'")
172
- self.app.verbose(f"Collected {len(documents)} files for analyzing in workspace folder '{folder.uri.to_path()}'")
173
+ self.app.verbose(
174
+ f"{file_counter} {'file' if file_counter == 1 else 'files'} in workspace folder '{folder.uri.to_path()}'"
175
+ )
176
+ self.app.verbose(
177
+ f"Collected {len(documents)} {'file' if len(documents) == 1 else 'files'} for analyzing "
178
+ f"in workspace folder '{folder.uri.to_path()}'"
179
+ )
173
180
 
174
181
  return documents
@@ -19,6 +19,9 @@ class DiagnosticHandlers:
19
19
  @event
20
20
  def document_collectors(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
21
21
 
22
+ @event
23
+ def folder_finishers(sender, folder: WorkspaceFolder) -> None: ...
24
+
22
25
  def analyze_folder(self, folder: WorkspaceFolder) -> List[Union[List[Diagnostic], BaseException, None]]:
23
26
  return self.folder_analyzers(
24
27
  self,
@@ -42,6 +45,10 @@ class DiagnosticHandlers:
42
45
  return_exceptions=False,
43
46
  )
44
47
 
48
+ def finish_folder(self, folder: WorkspaceFolder) -> None:
49
+ """After the documents of a folder were analyzed and their diagnostics collected."""
50
+ self.folder_finishers(self, folder, return_exceptions=False)
51
+
45
52
 
46
53
  class DiagnosticsContext(ABC):
47
54
  @property
@@ -58,10 +58,15 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
58
58
  self.diagnostics_context.diagnostics.folder_analyzers.add(self.analyze_folder)
59
59
  self.diagnostics_context.diagnostics.document_analyzers.add(self.analyze_document)
60
60
  self.diagnostics_context.diagnostics.document_collectors.add(self.collect_diagnostics)
61
+ self.diagnostics_context.diagnostics.folder_finishers.add(self.finish_folder)
61
62
  if self.diagnostics_context.collect_unused:
62
63
  self.diagnostics_context.diagnostics.document_collectors.add(self.collect_unused_keywords)
63
64
  self.diagnostics_context.diagnostics.document_collectors.add(self.collect_unused_variables)
64
65
 
66
+ @property
67
+ def document_cache(self) -> DocumentsCacheHelper:
68
+ return self._document_cache
69
+
65
70
  def _update_python_path(self) -> None:
66
71
  root_path = (
67
72
  self.diagnostics_context.workspace.root_uri.to_path()
@@ -120,6 +125,10 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
120
125
 
121
126
  return None
122
127
 
128
+ def finish_folder(self, sender: Any, folder: WorkspaceFolder) -> None:
129
+ # the cache is shared with the language server and other runs, so only files that do not exist lose entries
130
+ self._document_cache.remove_cached_analyses_of_missing_files(folder)
131
+
123
132
  def collect_diagnostics(self, sender: Any, document: TextDocument) -> Optional[List[Diagnostic]]:
124
133
  namespace = self._document_cache.get_namespace(document)
125
134
 
@@ -30,12 +30,14 @@ class ModifiersConfig(BaseOptions):
30
30
  extend_ignore: Optional[List[str]] = field(
31
31
  description="""
32
32
  Extend the diagnostics codes to ignore.
33
+ It takes effect in `[tool.robotcode-analyze.extend-modifiers]`, where it adds to the codes set so far,
34
+ for example in `robot.toml` when used in `.robot.toml`.
33
35
 
34
36
  Examples:
35
37
 
36
38
  ```toml
37
- [tool.robotcode-analyze.modifiers]
38
- extend_ignore = ["VariableNotFound", "multiple-keywords"]
39
+ [tool.robotcode-analyze.extend-modifiers]
40
+ extend-ignore = ["VariableNotFound", "multiple-keywords"]
39
41
  ```
40
42
  """,
41
43
  alias="extend-ignore",
@@ -55,12 +57,14 @@ class ModifiersConfig(BaseOptions):
55
57
  extend_error: Optional[List[str]] = field(
56
58
  description="""
57
59
  Extend the diagnostics codes to treat as errors.
60
+ It takes effect in `[tool.robotcode-analyze.extend-modifiers]`, where it adds to the codes set so far,
61
+ for example in `robot.toml` when used in `.robot.toml`.
58
62
 
59
63
  Examples:
60
64
 
61
65
  ```toml
62
- [tool.robotcode-analyze.modifiers]
63
- extend_error = ["VariableNotFound", "multiple-keywords"]
66
+ [tool.robotcode-analyze.extend-modifiers]
67
+ extend-error = ["VariableNotFound", "multiple-keywords"]
64
68
  ```
65
69
  """,
66
70
  alias="extend-error",
@@ -80,12 +84,14 @@ class ModifiersConfig(BaseOptions):
80
84
  extend_warning: Optional[List[str]] = field(
81
85
  description="""
82
86
  Extend the diagnostics codes to treat as warning.
87
+ It takes effect in `[tool.robotcode-analyze.extend-modifiers]`, where it adds to the codes set so far,
88
+ for example in `robot.toml` when used in `.robot.toml`.
83
89
 
84
90
  Examples:
85
91
 
86
92
  ```toml
87
- [tool.robotcode-analyze.modifiers]
88
- extend_warning = ["VariableNotFound", "multiple-keywords"]
93
+ [tool.robotcode-analyze.extend-modifiers]
94
+ extend-warning = ["VariableNotFound", "multiple-keywords"]
89
95
  ```
90
96
  """,
91
97
  alias="extend-warning",
@@ -105,12 +111,14 @@ class ModifiersConfig(BaseOptions):
105
111
  extend_information: Optional[List[str]] = field(
106
112
  description="""
107
113
  Extend the diagnostics codes to treat as information.
114
+ It takes effect in `[tool.robotcode-analyze.extend-modifiers]`, where it adds to the codes set so far,
115
+ for example in `robot.toml` when used in `.robot.toml`.
108
116
 
109
117
  Examples:
110
118
 
111
119
  ```toml
112
- [tool.robotcode-analyze.modifiers]
113
- extend_information = ["VariableNotFound", "multiple-keywords"]
120
+ [tool.robotcode-analyze.extend-modifiers]
121
+ extend-information = ["VariableNotFound", "multiple-keywords"]
114
122
  ```
115
123
  """,
116
124
  alias="extend-information",
@@ -130,12 +138,14 @@ class ModifiersConfig(BaseOptions):
130
138
  extend_hint: Optional[List[str]] = field(
131
139
  description="""
132
140
  Extend the diagnostics codes to treat as hint.
141
+ It takes effect in `[tool.robotcode-analyze.extend-modifiers]`, where it adds to the codes set so far,
142
+ for example in `robot.toml` when used in `.robot.toml`.
133
143
 
134
144
  Examples:
135
145
 
136
146
  ```toml
137
- [tool.robotcode-analyze.modifiers]
138
- extend_hint = ["VariableNotFound", "multiple-keywords"]
147
+ [tool.robotcode-analyze.extend-modifiers]
148
+ extend-hint = ["VariableNotFound", "multiple-keywords"]
139
149
  ```
140
150
  """,
141
151
  alias="extend-hint",
@@ -146,7 +156,15 @@ class ModifiersConfig(BaseOptions):
146
156
  class CacheConfig(BaseOptions):
147
157
  """Cache configuration."""
148
158
 
149
- cache_dir: Optional[str] = field(description="Path to the cache directory.", alias="cache-dir")
159
+ cache_dir: Optional[str] = field(
160
+ description="""\
161
+ Path to the cache directory. A relative path counts from the project root.
162
+
163
+ `robotcode analyze` and the language server keep their cache there, in a `.robotcode_cache`
164
+ folder. For `robotcode analyze`, the `ROBOTCODE_CACHE_DIR` environment variable takes precedence.
165
+ """,
166
+ alias="cache-dir",
167
+ )
150
168
 
151
169
  ignored_libraries: Optional[List[str]] = field(
152
170
  description="""\
@@ -164,7 +182,11 @@ class CacheConfig(BaseOptions):
164
182
  alias="ignored-libraries",
165
183
  )
166
184
  extend_ignored_libraries: Optional[List[str]] = field(
167
- description="Extend the ignored libraries setting.",
185
+ description="""\
186
+ Extend the ignored libraries setting.
187
+ It takes effect in `[tool.robotcode-analyze.extend-cache]`, where it adds to the list set so far,
188
+ for example in `robot.toml` when used in `.robot.toml`.
189
+ """,
168
190
  alias="extend-ignored-libraries",
169
191
  )
170
192
 
@@ -183,14 +205,18 @@ class CacheConfig(BaseOptions):
183
205
  alias="ignored-variables",
184
206
  )
185
207
  extend_ignored_variables: Optional[List[str]] = field(
186
- description="Extend the ignored variables setting.",
208
+ description="""\
209
+ Extend the ignored variables setting.
210
+ It takes effect in `[tool.robotcode-analyze.extend-cache]`, where it adds to the list set so far,
211
+ for example in `robot.toml` when used in `.robot.toml`.
212
+ """,
187
213
  alias="extend-ignored-variables",
188
214
  )
189
215
 
190
216
  ignore_arguments_for_library: Optional[List[str]] = field(
191
217
  description="""\
192
218
  Specifies a list of libraries for which arguments will be ignored during analysis.
193
- This is usefull if you have library that gets variables from a python file as arguments that contains
219
+ This is useful if you have a library that gets variables from a python file as arguments that contains
194
220
  complex data like big dictionaries or complex objects that **RobotCode** can't handle.
195
221
  You can specify a glob pattern that matches the library name or the source file.
196
222
 
@@ -207,7 +233,11 @@ class CacheConfig(BaseOptions):
207
233
  alias="ignore-arguments-for-library",
208
234
  )
209
235
  extend_ignore_arguments_for_library: Optional[List[str]] = field(
210
- description="Extend the ignore arguments for library settings.",
236
+ description="""\
237
+ Extend the ignore arguments for library settings.
238
+ It takes effect in `[tool.robotcode-analyze.extend-cache]`, where it adds to the list set so far,
239
+ for example in `robot.toml` when used in `.robot.toml`.
240
+ """,
211
241
  alias="extend-ignore-arguments-for-library",
212
242
  )
213
243
 
@@ -277,7 +307,11 @@ class CodeConfig(BaseOptions):
277
307
  alias="exit-code-mask",
278
308
  )
279
309
  extend_exit_code_mask: Optional[ExitCodeMaskList] = field(
280
- description="Extend the exit code mask setting.",
310
+ description="""\
311
+ Extend the exit code mask setting.
312
+ It takes effect in `[tool.robotcode-analyze.extend-code]`, where it adds to the mask set so far,
313
+ for example in `robot.toml` when used in `.robot.toml`.
314
+ """,
281
315
  alias="extend-exit-code-mask",
282
316
  )
283
317
 
@@ -367,7 +401,7 @@ class AnalyzeConfig(BaseOptions):
367
401
  description="""\
368
402
  Specifies a global [search order](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#specifying-explicit-priority-between-libraries-and-resources)
369
403
  for libraries and resources.
370
- This is usefull when you have libraries containing keywords with the same name. **RobotCode** is unable to
404
+ This is useful when you have libraries containing keywords with the same name. **RobotCode** is unable to
371
405
  analyze the library search order in a file specified with
372
406
  [`Set Library Search Order`](https://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Set%20Library%20Search%20Order),
373
407
  so you can define a global order here. Just make sure to call the `Set Library Search Order`
@@ -440,7 +474,6 @@ class AnalyzeConfig(BaseOptions):
440
474
  semantic_model=self.semantic_model if self.semantic_model is not None else False,
441
475
  cache=(
442
476
  WorkspaceCacheConfig(
443
- # TODO savelocation
444
477
  ignored_libraries=self.cache.ignored_libraries or [],
445
478
  ignored_variables=self.cache.ignored_variables or [],
446
479
  ignore_arguments_for_library=self.cache.ignore_arguments_for_library or [],
@@ -449,6 +482,7 @@ class AnalyzeConfig(BaseOptions):
449
482
  if self.cache is not None
450
483
  else WorkspaceCacheConfig()
451
484
  ),
485
+ cache_dir=self.cache.cache_dir if self.cache is not None else None,
452
486
  robot=AnalysisRobotConfig(
453
487
  global_library_search_order=self.global_library_search_order or [],
454
488
  load_library_timeout=self.load_library_timeout,
@@ -0,0 +1,158 @@
1
+ """Hidden `robotcode analyze dump-model` command.
2
+
3
+ Developer/diagnostic tool: builds the real namespace for a single file — same
4
+ configuration path as `robotcode analyze code` (robot.toml, profiles,
5
+ `-v`/`-V`/`-P` overrides) — with the semantic-model build forced on, and
6
+ serializes the resulting `SemanticModel` to deterministic JSON. The JSON
7
+ format carries no stability guarantee.
8
+ """
9
+
10
+ import json
11
+ from pathlib import Path
12
+ from typing import Optional, Tuple
13
+
14
+ import click
15
+
16
+ from robotcode.core.utils.cli import show_hidden_arguments
17
+ from robotcode.plugin import Application, pass_application
18
+ from robotcode.robot.config.loader import load_robot_config_from_path
19
+ from robotcode.robot.config.utils import get_config_files
20
+ from robotcode.robot.diagnostics.semantic_analyzer.json_dump import model_to_dict
21
+
22
+ from .code.code_analyzer import CodeAnalyzer
23
+ from .code.robot_framework_language_provider import RobotFrameworkLanguageProvider
24
+ from .config import AnalyzeConfig
25
+
26
+
27
+ @click.command(name="dump-model", add_help_option=True, hidden=show_hidden_arguments())
28
+ @click.option(
29
+ "-v",
30
+ "--variable",
31
+ metavar="name:value",
32
+ type=str,
33
+ multiple=True,
34
+ help="Set variables in the test data. see `robot --variable` option.",
35
+ )
36
+ @click.option(
37
+ "-V",
38
+ "--variablefile",
39
+ metavar="PATH",
40
+ type=str,
41
+ multiple=True,
42
+ help="Python or YAML file file to read variables from. see `robot --variablefile` option.",
43
+ )
44
+ @click.option(
45
+ "-P",
46
+ "--pythonpath",
47
+ metavar="PATH",
48
+ type=str,
49
+ multiple=True,
50
+ help="Additional locations where to search test libraries"
51
+ " and other extensions when they are imported. see `robot --pythonpath` option.",
52
+ )
53
+ @click.option(
54
+ "-o",
55
+ "--output",
56
+ metavar="FILE",
57
+ type=click.Path(dir_okay=False, writable=True, path_type=Path),
58
+ default=None,
59
+ help="Write the JSON dump to FILE instead of stdout.",
60
+ )
61
+ @click.argument("file", nargs=1, type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path))
62
+ @pass_application
63
+ def dump_model(
64
+ app: Application,
65
+ variable: Tuple[str, ...],
66
+ variablefile: Tuple[str, ...],
67
+ pythonpath: Tuple[str, ...],
68
+ output: Optional[Path],
69
+ file: Path,
70
+ ) -> None:
71
+ """\
72
+ Dumps the semantic model of a Robot Framework FILE as JSON.
73
+
74
+ Developer/diagnostic tool: the semantic-model build is forced regardless
75
+ of the `robotcode.experimental.semanticModel` setting. The JSON format is
76
+ not a stable interface and may change without notice.
77
+ """
78
+
79
+ config_files, root_folder, _ = get_config_files(
80
+ [file],
81
+ app.config.config_files,
82
+ root_folder=app.config.root,
83
+ no_vcs=app.config.no_vcs,
84
+ verbose_callback=app.verbose,
85
+ )
86
+
87
+ try:
88
+ robot_config = load_robot_config_from_path(
89
+ *config_files, extra_tools={"robotcode-analyze": AnalyzeConfig}, verbose_callback=app.verbose
90
+ )
91
+
92
+ analyzer_config = robot_config.tool.get("robotcode-analyze", None) if robot_config.tool is not None else None
93
+ if analyzer_config is None:
94
+ analyzer_config = AnalyzeConfig()
95
+
96
+ robot_profile = robot_config.combine_profiles(
97
+ *(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error
98
+ ).evaluated_with_env()
99
+ except (TypeError, ValueError) as e:
100
+ raise click.ClickException(str(e)) from e
101
+
102
+ if variable:
103
+ if robot_profile.variables is None:
104
+ robot_profile.variables = {}
105
+ for v in variable:
106
+ name, value = v.split(":", 1) if ":" in v else (v, "")
107
+ robot_profile.variables.update({name: value})
108
+
109
+ if pythonpath:
110
+ if robot_profile.python_path is None:
111
+ robot_profile.python_path = []
112
+ robot_profile.python_path.extend(pythonpath)
113
+
114
+ if variablefile:
115
+ if robot_profile.variable_files is None:
116
+ robot_profile.variable_files = []
117
+ for vf in variablefile:
118
+ robot_profile.variable_files.append(vf)
119
+
120
+ analysis_config = analyzer_config.to_workspace_analysis_config()
121
+ analysis_config.semantic_model = True
122
+
123
+ analyzer = CodeAnalyzer(
124
+ app=app,
125
+ analysis_config=analysis_config,
126
+ robot_profile=robot_profile,
127
+ root_folder=root_folder,
128
+ )
129
+
130
+ provider = next((h for h in analyzer.language_handlers if isinstance(h, RobotFrameworkLanguageProvider)), None)
131
+ if provider is None:
132
+ raise click.ClickException("no Robot Framework language provider available")
133
+
134
+ try:
135
+ document = analyzer.workspace.documents.get_or_open_document(file.absolute())
136
+ namespace = provider.document_cache.get_namespace(document)
137
+ namespace.analyze()
138
+ except (SystemExit, KeyboardInterrupt):
139
+ raise
140
+ except BaseException as e:
141
+ raise click.ClickException(f"cannot analyze '{file}': {e}") from e
142
+
143
+ semantic_model = namespace.semantic_model
144
+ if semantic_model is None:
145
+ raise click.ClickException(f"no semantic model was built for '{file}'")
146
+
147
+ dump = model_to_dict(
148
+ semantic_model,
149
+ workspace_root=analyzer.root_folder,
150
+ source=str(document.uri.to_path()),
151
+ )
152
+ text = json.dumps(dump, indent=2, ensure_ascii=False)
153
+
154
+ if output is not None:
155
+ output.write_text(text + "\n", encoding="utf-8")
156
+ app.verbose(f"Semantic model written to {output}")
157
+ else:
158
+ click.echo(text)
@@ -1 +0,0 @@
1
- __version__ = "2.7.0"