robotcode-analyze 1.0.2__tar.gz → 1.1.0__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 (17) hide show
  1. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/PKG-INFO +4 -4
  2. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/pyproject.toml +3 -3
  3. robotcode_analyze-1.1.0/src/robotcode/analyze/__version__.py +1 -0
  4. robotcode_analyze-1.1.0/src/robotcode/analyze/cli.py +26 -0
  5. {robotcode_analyze-1.0.2/src/robotcode/analyze → robotcode_analyze-1.1.0/src/robotcode/analyze/code}/cli.py +53 -27
  6. {robotcode_analyze-1.0.2/src/robotcode/analyze → robotcode_analyze-1.1.0/src/robotcode/analyze/code}/code_analyzer.py +1 -1
  7. {robotcode_analyze-1.0.2/src/robotcode/analyze → robotcode_analyze-1.1.0/src/robotcode/analyze/code}/diagnostics_context.py +3 -3
  8. {robotcode_analyze-1.0.2/src/robotcode/analyze → robotcode_analyze-1.1.0/src/robotcode/analyze/code}/language_provider.py +1 -1
  9. {robotcode_analyze-1.0.2/src/robotcode/analyze → robotcode_analyze-1.1.0/src/robotcode/analyze/code}/robot_framework_language_provider.py +17 -12
  10. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/src/robotcode/analyze/config.py +66 -1
  11. robotcode_analyze-1.0.2/src/robotcode/analyze/__version__.py +0 -1
  12. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/.gitignore +0 -0
  13. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/LICENSE.txt +0 -0
  14. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/README.md +0 -0
  15. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/src/robotcode/analyze/__init__.py +0 -0
  16. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/src/robotcode/analyze/hooks.py +0 -0
  17. {robotcode_analyze-1.0.2 → robotcode_analyze-1.1.0}/src/robotcode/analyze/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotcode-analyze
3
- Version: 1.0.2
3
+ Version: 1.1.0
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
@@ -25,9 +25,9 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Classifier: Topic :: Utilities
26
26
  Classifier: Typing :: Typed
27
27
  Requires-Python: >=3.8
28
- Requires-Dist: robotcode-plugin==1.0.2
29
- Requires-Dist: robotcode-robot==1.0.2
30
- Requires-Dist: robotcode==1.0.2
28
+ Requires-Dist: robotcode-plugin==1.1.0
29
+ Requires-Dist: robotcode-robot==1.1.0
30
+ Requires-Dist: robotcode==1.1.0
31
31
  Requires-Dist: robotframework>=4.1.0
32
32
  Description-Content-Type: text/markdown
33
33
 
@@ -27,9 +27,9 @@ classifiers = [
27
27
  ]
28
28
  dependencies = [
29
29
  "robotframework>=4.1.0",
30
- "robotcode-plugin==1.0.2",
31
- "robotcode-robot==1.0.2",
32
- "robotcode==1.0.2",
30
+ "robotcode-plugin==1.1.0",
31
+ "robotcode-robot==1.1.0",
32
+ "robotcode==1.1.0",
33
33
  ]
34
34
  dynamic = ["version"]
35
35
 
@@ -0,0 +1 @@
1
+ __version__ = "1.1.0"
@@ -0,0 +1,26 @@
1
+ import click
2
+
3
+ from robotcode.plugin import Application, pass_application
4
+
5
+ from .__version__ import __version__
6
+ from .code.cli import code
7
+
8
+
9
+ @click.group(
10
+ add_help_option=True,
11
+ invoke_without_command=False,
12
+ )
13
+ @click.version_option(
14
+ version=__version__,
15
+ package_name="robotcode.analyze",
16
+ prog_name="RobotCode Analyze",
17
+ )
18
+ @pass_application
19
+ def analyze(app: Application) -> None:
20
+ """\
21
+ The analyze command provides various subcommands for analyzing Robot Framework code.
22
+ These subcommands support specialized tasks, such as code analysis, style checking or dependency graphs.
23
+ """
24
+
25
+
26
+ analyze.add_command(code)
@@ -16,27 +16,9 @@ from robotcode.robot.config.loader import (
16
16
  )
17
17
  from robotcode.robot.config.utils import get_config_files
18
18
 
19
- from .__version__ import __version__
19
+ from ..__version__ import __version__
20
+ from ..config import AnalyzeConfig, ExitCodeMask, ModifiersConfig
20
21
  from .code_analyzer import CodeAnalyzer, DocumentDiagnosticReport, FolderDiagnosticReport
21
- from .config import AnalyzeConfig, ModifiersConfig
22
-
23
-
24
- @click.group(
25
- add_help_option=True,
26
- invoke_without_command=False,
27
- )
28
- @click.version_option(
29
- version=__version__,
30
- package_name="robotcode.analyze",
31
- prog_name="RobotCode Analyze",
32
- )
33
- @pass_application
34
- def analyze(app: Application) -> None:
35
- """\
36
- The analyze command provides various subcommands for analyzing Robot Framework code.
37
- These subcommands support specialized tasks, such as code analysis, style checking or dependency graphs.
38
- """
39
-
40
22
 
41
23
  SEVERITY_COLORS = {
42
24
  DiagnosticSeverity.ERROR: "red",
@@ -55,7 +37,8 @@ class ReturnCode(Flag):
55
37
 
56
38
 
57
39
  class Statistic:
58
- def __init__(self) -> None:
40
+ def __init__(self, exit_code_mask: ExitCodeMask) -> None:
41
+ self.exit_code_mask = exit_code_mask
59
42
  self._folders: Set[WorkspaceFolder] = set()
60
43
  self._files: Set[TextDocument] = set()
61
44
  self._diagnostics: List[Union[DocumentDiagnosticReport, FolderDiagnosticReport]] = []
@@ -104,18 +87,34 @@ class Statistic:
104
87
 
105
88
  def calculate_return_code(self) -> ReturnCode:
106
89
  return_code = ReturnCode.SUCCESS
107
- if self.errors > 0:
90
+ if self.errors > 0 and not self.exit_code_mask & ExitCodeMask.ERROR:
108
91
  return_code |= ReturnCode.ERRORS
109
- if self.warnings > 0:
92
+ if self.warnings > 0 and not self.exit_code_mask & ExitCodeMask.WARN:
110
93
  return_code |= ReturnCode.WARNINGS
111
- if self.infos > 0:
94
+ if self.infos > 0 and not self.exit_code_mask & ExitCodeMask.INFO:
112
95
  return_code |= ReturnCode.INFOS
113
- if self.hints > 0:
96
+ if self.hints > 0 and not self.exit_code_mask & ExitCodeMask.HINT:
114
97
  return_code |= ReturnCode.HINTS
115
98
  return return_code
116
99
 
117
100
 
118
- @analyze.command(
101
+ def _parse_exit_code_mask(ctx: click.Context, param: click.Option, value: Tuple[str, ...]) -> ExitCodeMask:
102
+ try:
103
+ return ExitCodeMask.parse(value)
104
+ except KeyError as e:
105
+ raise click.BadParameter(str(e)) from e
106
+
107
+
108
+ def _split_comma(ctx: click.Context, param: click.Option, value: Optional[List[str]]) -> List[str]:
109
+ if value is None:
110
+ return []
111
+ result: List[str] = []
112
+ for item in value:
113
+ result.extend([x.strip() for x in item.split(",") if x.strip()])
114
+ return result
115
+
116
+
117
+ @click.command(
119
118
  add_help_option=True,
120
119
  )
121
120
  @click.version_option(
@@ -199,6 +198,24 @@ class Statistic:
199
198
  multiple=True,
200
199
  help="Specifies the diagnostics codes to treat as hint.",
201
200
  )
201
+ @click.option(
202
+ "--exit-code-mask",
203
+ "-xm",
204
+ multiple=True,
205
+ callback=_parse_exit_code_mask,
206
+ metavar="[" + "|".join(member.name.lower() for member in ExitCodeMask if member.name is not None) + "|all]",
207
+ help="Specifies which diagnostic severities should not affect the exit code. "
208
+ "For example, with 'warn' in the mask, warnings won't cause a non-zero exit code.",
209
+ )
210
+ @click.option(
211
+ "--extend-exit-code-mask",
212
+ "-xe",
213
+ multiple=True,
214
+ callback=_parse_exit_code_mask,
215
+ metavar="[" + "|".join(member.name.lower() for member in ExitCodeMask if member.name is not None) + "|all]",
216
+ help="Extend the exit code mask with the specified values. This appends to the default mask, defined in the config"
217
+ " file.",
218
+ )
202
219
  @click.argument(
203
220
  "paths", nargs=-1, type=click.Path(exists=True, dir_okay=True, file_okay=True, readable=True, path_type=Path)
204
221
  )
@@ -214,6 +231,8 @@ def code(
214
231
  modifiers_warning: Tuple[str, ...],
215
232
  modifiers_information: Tuple[str, ...],
216
233
  modifiers_hint: Tuple[str, ...],
234
+ exit_code_mask: ExitCodeMask,
235
+ extend_exit_code_mask: ExitCodeMask,
217
236
  paths: Tuple[Path],
218
237
  ) -> None:
219
238
  """\
@@ -309,7 +328,14 @@ def code(
309
328
  analyzer_config.modifiers.hint = []
310
329
  analyzer_config.modifiers.hint.extend(modifiers_hint)
311
330
 
312
- statistics = Statistic()
331
+ default_mask = (
332
+ exit_code_mask
333
+ if exit_code_mask != ExitCodeMask.NONE
334
+ else ExitCodeMask.parse(analyzer_config.code.exit_code_mask if analyzer_config.code is not None else None)
335
+ )
336
+ mask = default_mask | extend_exit_code_mask
337
+
338
+ statistics = Statistic(mask)
313
339
  for e in CodeAnalyzer(
314
340
  app=app,
315
341
  analysis_config=analyzer_config.to_workspace_analysis_config(),
@@ -83,7 +83,7 @@ class CodeAnalyzer(DiagnosticsContext):
83
83
  ) -> Iterable[Union[DocumentDiagnosticReport, FolderDiagnosticReport]]:
84
84
  for folder in self.workspace.workspace_folders:
85
85
  self.app.verbose(f"Initialize folder {folder.uri.to_path()}")
86
- initialize_result = self.diagnostics.initialize_folder(folder)
86
+ initialize_result = self.diagnostics.analyze_folder(folder)
87
87
  if initialize_result is not None:
88
88
  diagnostics: List[Diagnostic] = []
89
89
  for item in initialize_result:
@@ -14,13 +14,13 @@ class DiagnosticHandlers:
14
14
  @event
15
15
  def document_analyzers(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
16
16
  @event
17
- def folder_initializers(sender, folder: WorkspaceFolder) -> Optional[List[Diagnostic]]: ...
17
+ def folder_analyzers(sender, folder: WorkspaceFolder) -> Optional[List[Diagnostic]]: ...
18
18
 
19
19
  @event
20
20
  def collectors(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
21
21
 
22
- def initialize_folder(self, folder: WorkspaceFolder) -> List[Union[List[Diagnostic], BaseException, None]]:
23
- return self.folder_initializers(
22
+ def analyze_folder(self, folder: WorkspaceFolder) -> List[Union[List[Diagnostic], BaseException, None]]:
23
+ return self.folder_analyzers(
24
24
  self,
25
25
  folder,
26
26
  return_exceptions=True,
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
2
2
  from pathlib import Path
3
3
  from typing import Callable, Iterable, List, Optional
4
4
 
5
- from robotcode.analyze.diagnostics_context import DiagnosticsContext
5
+ from robotcode.analyze.code.diagnostics_context import DiagnosticsContext
6
6
  from robotcode.core.language import LanguageDefinition
7
7
  from robotcode.core.workspace import WorkspaceFolder
8
8
 
@@ -5,7 +5,7 @@ from typing import Any, Iterable, List, Optional
5
5
 
6
6
  from robot.utils import FileReader
7
7
 
8
- from robotcode.analyze.diagnostics_context import DiagnosticsContext
8
+ from robotcode.analyze.code.diagnostics_context import DiagnosticsContext
9
9
  from robotcode.core.filewatcher import FileWatcherManagerDummy
10
10
  from robotcode.core.ignore_spec import DEFAULT_SPEC_RULES, GIT_IGNORE_FILE, ROBOT_IGNORE_FILE, IgnoreSpec, iter_files
11
11
  from robotcode.core.language import LanguageDefinition, language_id
@@ -47,20 +47,25 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
47
47
  )
48
48
 
49
49
  self.diagnostics_context.workspace.documents.on_read_document_text.add(self.on_read_document_text)
50
- self.diagnostics_context.diagnostics.folder_initializers.add(self.analyze_folder)
50
+ self.diagnostics_context.diagnostics.folder_analyzers.add(self.analyze_folder)
51
51
  self.diagnostics_context.diagnostics.document_analyzers.add(self.analyze_document)
52
52
 
53
53
  def _update_python_path(self) -> None:
54
- if self.diagnostics_context.workspace.root_uri is not None:
55
- for p in self.diagnostics_context.profile.python_path or []:
56
- pa = Path(str(p))
57
- if not pa.is_absolute():
58
- pa = Path(self.diagnostics_context.workspace.root_uri.to_path(), pa)
59
-
60
- absolute_path = str(pa.absolute())
61
- for f in glob.glob(absolute_path):
62
- if Path(f).is_dir() and f not in sys.path:
63
- sys.path.insert(0, f)
54
+ root_path = (
55
+ self.diagnostics_context.workspace.root_uri.to_path()
56
+ if self.diagnostics_context.workspace.root_uri is not None
57
+ else None
58
+ )
59
+
60
+ for p in self.diagnostics_context.profile.python_path or []:
61
+ pa = Path(str(p))
62
+ if root_path is not None and not pa.is_absolute():
63
+ pa = Path(root_path, pa)
64
+
65
+ absolute_path = str(pa.absolute())
66
+ for f in glob.glob(absolute_path):
67
+ if Path(f).is_dir() and f not in sys.path:
68
+ sys.path.insert(0, f)
64
69
 
65
70
  @language_id("robotframework")
66
71
  def on_read_document_text(self, sender: Any, uri: Uri) -> str:
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: RUF009
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
3
+ from enum import IntFlag
4
+ from typing import Iterable, List, Literal, Optional, Union
4
5
 
5
6
  from robotcode.robot.config.model import BaseOptions, field
6
7
  from robotcode.robot.diagnostics.workspace_config import (
@@ -197,10 +198,74 @@ class CacheConfig(BaseOptions):
197
198
  )
198
199
 
199
200
 
201
+ class ExitCodeMask(IntFlag):
202
+ NONE = 0
203
+ ERROR = 1
204
+ WARN = 2
205
+ WARNING = WARN
206
+ INFO = 4
207
+ INFORMATION = INFO
208
+ HINT = 8
209
+ ALL = ERROR | WARN | INFO | HINT
210
+
211
+ @staticmethod
212
+ def parse(value: Union[Iterable[str], str, None]) -> "ExitCodeMask":
213
+ if value is None:
214
+ return ExitCodeMask.NONE
215
+
216
+ flags = ExitCodeMask(0)
217
+ for entry in value:
218
+ for part_orig in entry.split(","):
219
+ part = part_orig.strip().upper()
220
+ if part:
221
+ try:
222
+ flags |= ExitCodeMask[part]
223
+ except KeyError as e:
224
+ raise KeyError(f"Invalid exit code mask value: {part_orig}") from e
225
+ return flags
226
+
227
+
228
+ ExitCodeMaskLiteral = Literal["error", "warn", "warning", "info", "information", "hint", "all"]
229
+ ExitCodeMaskList = List[ExitCodeMaskLiteral]
230
+
231
+
232
+ @dataclass
233
+ class CodeConfig(BaseOptions):
234
+ """robotcode-analyze code configuration."""
235
+
236
+ exit_code_mask: Optional[ExitCodeMaskList] = field(
237
+ description="""\
238
+ Specifies the exit code mask for the code analysis.
239
+ This is useful if you want to ignore certain types of diagnostics in the result code.
240
+
241
+ Examples:
242
+ ```toml
243
+ [tool.robotcode-analyze.code]
244
+ exit_code_mask = ["error", "warn"]
245
+ ```
246
+ """,
247
+ )
248
+ extend_exit_code_mask: Optional[ExitCodeMaskList] = field(description="Extend the exit code mask setting.")
249
+
250
+
200
251
  @dataclass
201
252
  class AnalyzeConfig(BaseOptions):
202
253
  """robotcode-analyze configuration."""
203
254
 
255
+ code: Optional[CodeConfig] = field(
256
+ description="""\
257
+ Defines the code analysis configuration.
258
+
259
+ Examples:
260
+
261
+ ```toml
262
+ [tool.robotcode-analyze.code]
263
+ exit_code_mask = "error|warn"
264
+ ```
265
+ """
266
+ )
267
+ extend_code: Optional[CodeConfig] = field(description="Extend the code analysis configuration.")
268
+
204
269
  modifiers: Optional[ModifiersConfig] = field(
205
270
  description="""\
206
271
  Defines the modifiers for the analysis.
@@ -1 +0,0 @@
1
- __version__ = "1.0.2"