Skip to content

Commit 4b677ad

Browse files
committed
feat(analyze): exit code mask configuration for code analysis
Configure which message types should **not** influence the exit code of `robotcode analyze code`, allowing granular control over CI/CD pipeline behavior or pre-commit hooks. **Configuration File (`robot.toml`)** ```toml [tool.robotcode-analyze.code] exit-code-mask = ["error", "warn"] ``` **Command Line Options** ``` robotcode analyze code --exit-code-mask error,warn # or -xm robotcode analyze code --extend-exit-code-mask info # or -xe ``` - `-xm` (or `--exit-code-mask`) overwrites the configuration in `robot.toml` - `-xe` (or `--extend-exit-code-mask`) extends the configuration in `robot.toml` - Both options can be specified multiple times or with comma-separated values: ``` robotcode analyze code -xm error -xm warn # multiple options robotcode analyze code -xm error,warn # comma-separated ``` **Behavior** - Message types in the mask are ignored when determining exit code - Available types: `error`, `warn`/`warning`, `info`/`information`, `hint` - Special value `all` ignores all message types (always exit code 0) - Without configuration, all message types affect the exit code **Example** ```toml # In robot.toml - Ignore warnings but let errors affect exit code [tool.robotcode-analyze.code] exit-code-mask = ["warn"] ``` ```bash # Using short options robotcode analyze code -xm error,hint # Overwrites robot.toml config robotcode analyze code -xe info -xe hint # Extends robot.toml config with multiple types robotcode analyze code -xm all # Always exit with code 0 ``` closes #358
1 parent 0123a50 commit 4b677ad

File tree

12 files changed

+334
-124
lines changed

12 files changed

+334
-124
lines changed

.vscode/launch.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@
7878
// "suites",
7979
// // "discover", "tests", "--tags"
8080
// "."
81-
"discover",
82-
"--no-diagnostics",
83-
"all",
84-
".."
81+
// "discover",
82+
// "--no-diagnostics",
83+
// "all",
84+
// ".."
8585
// "config",
8686
// "show",
8787
// "discover",
@@ -90,8 +90,8 @@
9090
// "-i",
9191
// "-v", "CMD_LINE_VAR:cmd_line_var",
9292
// "E:\\source\\uvtestprj\\tests\\first.robotrepl"
93-
// "analyze",
94-
// "code",
93+
"analyze",
94+
"code",
9595
// "--help"
9696
// "tests"
9797
// "repl-server",

docs/03_reference/cli.md

+68-104
Large diffs are not rendered by default.

docs/03_reference/config.md

+57
Original file line numberDiff line numberDiff line change
@@ -2615,6 +2615,38 @@ Examples:
26152615
- `MyVariables`
26162616
- `myvars.subpackage.subpackage`
26172617

2618+
## tool.robotcode-analyze.code
2619+
2620+
Type: `CodeConfig | None`
2621+
2622+
Defines the code analysis configuration.
2623+
2624+
Examples:
2625+
2626+
```toml
2627+
[tool.robotcode-analyze.code]
2628+
exit_code_mask = "error|warn"
2629+
```
2630+
2631+
## tool.robotcode-analyze.code.exit-code-mask
2632+
2633+
Type: `list[Literal['error', 'warn', 'warning', 'info', 'information', 'hint']] | None`
2634+
2635+
Specifies the exit code mask for the code analysis.
2636+
This is useful if you want to ignore certain types of diagnostics in the result code.
2637+
2638+
Examples:
2639+
```toml
2640+
[tool.robotcode-analyze.code]
2641+
exit_code_mask = ["error", "warn"]
2642+
```
2643+
2644+
## tool.robotcode-analyze.code.extend-exit-code-mask
2645+
2646+
Type: `list[Literal['error', 'warn', 'warning', 'info', 'information', 'hint']] | None`
2647+
2648+
Extend the exit code mask setting.
2649+
26182650
## tool.robotcode-analyze.exclude-patterns
26192651

26202652
Type: `list[str] | None`
@@ -2701,6 +2733,31 @@ Examples:
27012733
- `MyVariables`
27022734
- `myvars.subpackage.subpackage`
27032735

2736+
## tool.robotcode-analyze.extend-code
2737+
2738+
Type: `CodeConfig | None`
2739+
2740+
Extend the code analysis configuration.
2741+
2742+
## tool.robotcode-analyze.extend-code.exit-code-mask
2743+
2744+
Type: `list[Literal['error', 'warn', 'warning', 'info', 'information', 'hint']] | None`
2745+
2746+
Specifies the exit code mask for the code analysis.
2747+
This is useful if you want to ignore certain types of diagnostics in the result code.
2748+
2749+
Examples:
2750+
```toml
2751+
[tool.robotcode-analyze.code]
2752+
exit_code_mask = ["error", "warn"]
2753+
```
2754+
2755+
## tool.robotcode-analyze.extend-code.extend-exit-code-mask
2756+
2757+
Type: `list[Literal['error', 'warn', 'warning', 'info', 'information', 'hint']] | None`
2758+
2759+
Extend the exit code mask setting.
2760+
27042761
## tool.robotcode-analyze.extend-exclude-patterns
27052762

27062763
Type: `list[str] | None`

etc/robot.toml.json

+74
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@
2323
"description": "Defines the cache configuration.",
2424
"title": "Cache"
2525
},
26+
"code": {
27+
"anyOf": [
28+
{
29+
"$ref": "#/definitions/CodeConfig"
30+
},
31+
{
32+
"type": "null"
33+
}
34+
],
35+
"default": null,
36+
"description": "Defines the code analysis configuration.\n\nExamples:\n\n```toml\n[tool.robotcode-analyze.code]\nexit_code_mask = \"error|warn\"\n```\n",
37+
"title": "Code"
38+
},
2639
"exclude-patterns": {
2740
"default": null,
2841
"description": "Specifies glob patterns for excluding files and folders from analysing by the language server.",
@@ -48,6 +61,19 @@
4861
"description": "Extend the cache configuration.",
4962
"title": "Extend cache"
5063
},
64+
"extend-code": {
65+
"anyOf": [
66+
{
67+
"$ref": "#/definitions/CodeConfig"
68+
},
69+
{
70+
"type": "null"
71+
}
72+
],
73+
"default": null,
74+
"description": "Extend the code analysis configuration.",
75+
"title": "Extend code"
76+
},
5177
"extend-exclude-patterns": {
5278
"default": null,
5379
"description": "Extend the exclude patterns.",
@@ -203,6 +229,54 @@
203229
"title": "CacheConfig",
204230
"type": "object"
205231
},
232+
"CodeConfig": {
233+
"additionalProperties": false,
234+
"description": "robotcode-analyze code configuration.",
235+
"properties": {
236+
"exit-code-mask": {
237+
"default": null,
238+
"description": "Specifies the exit code mask for the code analysis.\nThis is useful if you want to ignore certain types of diagnostics in the result code.\n\nExamples:\n```toml\n[tool.robotcode-analyze.code]\nexit_code_mask = [\"error\", \"warn\"]\n```\n",
239+
"items": {
240+
"enum": [
241+
"error",
242+
"warn",
243+
"warning",
244+
"info",
245+
"information",
246+
"hint"
247+
],
248+
"type": "string"
249+
},
250+
"title": "Exit code mask",
251+
"type": [
252+
"array",
253+
"null"
254+
]
255+
},
256+
"extend-exit-code-mask": {
257+
"default": null,
258+
"description": "Extend the exit code mask setting.",
259+
"items": {
260+
"enum": [
261+
"error",
262+
"warn",
263+
"warning",
264+
"info",
265+
"information",
266+
"hint"
267+
],
268+
"type": "string"
269+
},
270+
"title": "Extend exit code mask",
271+
"type": [
272+
"array",
273+
"null"
274+
]
275+
}
276+
},
277+
"title": "CodeConfig",
278+
"type": "object"
279+
},
206280
"Condition": {
207281
"additionalProperties": false,
208282
"description": "Condition to evaluate.",

packages/analyze/src/robotcode/analyze/code/cli.py

+51-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from robotcode.robot.config.utils import get_config_files
1818

1919
from ..__version__ import __version__
20-
from ..config import AnalyzeConfig, ModifiersConfig
20+
from ..config import AnalyzeConfig, ExitCodeMask, ModifiersConfig
2121
from .code_analyzer import CodeAnalyzer, DocumentDiagnosticReport, FolderDiagnosticReport
2222

2323
SEVERITY_COLORS = {
@@ -37,7 +37,8 @@ class ReturnCode(Flag):
3737

3838

3939
class Statistic:
40-
def __init__(self) -> None:
40+
def __init__(self, exit_code_mask: ExitCodeMask) -> None:
41+
self.exit_code_mask = exit_code_mask
4142
self._folders: Set[WorkspaceFolder] = set()
4243
self._files: Set[TextDocument] = set()
4344
self._diagnostics: List[Union[DocumentDiagnosticReport, FolderDiagnosticReport]] = []
@@ -86,17 +87,33 @@ def __str__(self) -> str:
8687

8788
def calculate_return_code(self) -> ReturnCode:
8889
return_code = ReturnCode.SUCCESS
89-
if self.errors > 0:
90+
if self.errors > 0 and not self.exit_code_mask & ExitCodeMask.ERROR:
9091
return_code |= ReturnCode.ERRORS
91-
if self.warnings > 0:
92+
if self.warnings > 0 and not self.exit_code_mask & ExitCodeMask.WARN:
9293
return_code |= ReturnCode.WARNINGS
93-
if self.infos > 0:
94+
if self.infos > 0 and not self.exit_code_mask & ExitCodeMask.INFO:
9495
return_code |= ReturnCode.INFOS
95-
if self.hints > 0:
96+
if self.hints > 0 and not self.exit_code_mask & ExitCodeMask.HINT:
9697
return_code |= ReturnCode.HINTS
9798
return return_code
9899

99100

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+
100117
@click.command(
101118
add_help_option=True,
102119
)
@@ -181,6 +198,24 @@ def calculate_return_code(self) -> ReturnCode:
181198
multiple=True,
182199
help="Specifies the diagnostics codes to treat as hint.",
183200
)
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+
)
184219
@click.argument(
185220
"paths", nargs=-1, type=click.Path(exists=True, dir_okay=True, file_okay=True, readable=True, path_type=Path)
186221
)
@@ -196,6 +231,8 @@ def code(
196231
modifiers_warning: Tuple[str, ...],
197232
modifiers_information: Tuple[str, ...],
198233
modifiers_hint: Tuple[str, ...],
234+
exit_code_mask: ExitCodeMask,
235+
extend_exit_code_mask: ExitCodeMask,
199236
paths: Tuple[Path],
200237
) -> None:
201238
"""\
@@ -291,7 +328,14 @@ def code(
291328
analyzer_config.modifiers.hint = []
292329
analyzer_config.modifiers.hint.extend(modifiers_hint)
293330

294-
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)
295339
for e in CodeAnalyzer(
296340
app=app,
297341
analysis_config=analyzer_config.to_workspace_analysis_config(),

packages/analyze/src/robotcode/analyze/code/code_analyzer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def run(
8383
) -> Iterable[Union[DocumentDiagnosticReport, FolderDiagnosticReport]]:
8484
for folder in self.workspace.workspace_folders:
8585
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)
8787
if initialize_result is not None:
8888
diagnostics: List[Diagnostic] = []
8989
for item in initialize_result:

packages/analyze/src/robotcode/analyze/code/diagnostics_context.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ class DiagnosticHandlers:
1414
@event
1515
def document_analyzers(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
1616
@event
17-
def folder_initializers(sender, folder: WorkspaceFolder) -> Optional[List[Diagnostic]]: ...
17+
def folder_analyzers(sender, folder: WorkspaceFolder) -> Optional[List[Diagnostic]]: ...
1818

1919
@event
2020
def collectors(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
2121

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(
2424
self,
2525
folder,
2626
return_exceptions=True,

packages/analyze/src/robotcode/analyze/code/robot_framework_language_provider.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def __init__(self, diagnostics_context: DiagnosticsContext) -> None:
4747
)
4848

4949
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)
5151
self.diagnostics_context.diagnostics.document_analyzers.add(self.analyze_document)
5252

5353
def _update_python_path(self) -> None:

0 commit comments

Comments
 (0)