Skip to content

Commit 1f9b5f9

Browse files
committed
Address 6 project-wide SonarCloud findings on main
These predate PR #97 but surface in the project-wide issue list: - 2 × python:S1192 in mcp_server/server.py: extracted _ERR_ACTIONS_LIST and _ERR_TEXT_STRING module constants (literal "'actions' must be a list" appeared 4x; "'text' must be a string" appeared 3x) - python:S5869 in md_authoring/markdown_to_actions.py _TEMPLATE_RE: with re.IGNORECASE, [A-Za-z_] has a duplicate range — replaced with [A-Z_] (lowercase folds via the flag); dropped the misplaced NOSONAR - python:S8513 in visual_review/review_server.py: collapsed `startswith("/img/baseline/") or startswith("/img/current/")` into the single-call tuple form - python:S8520 in test_sharding.py: replaced `sum(parts, [])` flatten with `list(itertools.chain.from_iterable(parts))` - text:S8565 in pyproject.toml (missing lock file): generated uv.lock via `uv lock` to pin all 26 transitive dependencies (project still builds with setuptools; uv.lock just documents resolved versions) All 2920 unit tests pass.
1 parent fd61adf commit 1f9b5f9

5 files changed

Lines changed: 522 additions & 13 deletions

File tree

je_web_runner/mcp_server/server.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class McpServerError(WebRunnerException):
2828

2929

3030
_MCP_PROTOCOL_VERSION = "2024-11-05"
31+
32+
# Reused error messages — extracted so SonarCloud S1192 stays quiet and
33+
# downstream tooling can grep for them.
34+
_ERR_ACTIONS_LIST = "'actions' must be a list"
35+
_ERR_TEXT_STRING = "'text' must be a string"
3136
_SERVER_NAME = "webrunner-mcp"
3237
_SERVER_VERSION = "0.1.0"
3338

@@ -144,7 +149,7 @@ def _tool_lint_action(arguments: Dict[str, Any]) -> Any:
144149
from je_web_runner.utils.linter.action_linter import lint_action
145150
actions = arguments.get("actions")
146151
if not isinstance(actions, list):
147-
raise McpServerError("'actions' must be a list")
152+
raise McpServerError(_ERR_ACTIONS_LIST)
148153
# ``lint_action`` returns ``List[Dict[str, Any]]`` with ``rule`` /
149154
# ``severity`` / ``message`` / ``location`` keys; pass through verbatim
150155
# so MCP clients see the same shape the Python API exposes.
@@ -238,23 +243,23 @@ def _tool_format_actions(arguments: Dict[str, Any]) -> Any:
238243
from je_web_runner.utils.action_formatter.formatter import format_actions
239244
actions = arguments.get("actions")
240245
if not isinstance(actions, list):
241-
raise McpServerError("'actions' must be a list")
246+
raise McpServerError(_ERR_ACTIONS_LIST)
242247
return format_actions(actions, indent=int(arguments.get("indent", 2)))
243248

244249

245250
def _tool_parse_markdown(arguments: Dict[str, Any]) -> Any:
246251
from je_web_runner.utils.md_authoring.markdown_to_actions import parse_markdown
247252
text = arguments.get("text")
248253
if not isinstance(text, str):
249-
raise McpServerError("'text' must be a string")
254+
raise McpServerError(_ERR_TEXT_STRING)
250255
return parse_markdown(text)
251256

252257

253258
def _tool_translate_actions_to_playwright(arguments: Dict[str, Any]) -> Any:
254259
from je_web_runner.utils.sel_to_pw.translator import translate_action_list
255260
actions = arguments.get("actions")
256261
if not isinstance(actions, list):
257-
raise McpServerError("'actions' must be a list")
262+
raise McpServerError(_ERR_ACTIONS_LIST)
258263
return translate_action_list(actions)
259264

260265

@@ -295,7 +300,7 @@ def _tool_scan_pii(arguments: Dict[str, Any]) -> Any:
295300
from je_web_runner.utils.pii_scanner.scanner import scan_text
296301
text = arguments.get("text")
297302
if not isinstance(text, str):
298-
raise McpServerError("'text' must be a string")
303+
raise McpServerError(_ERR_TEXT_STRING)
299304
categories = arguments.get("categories")
300305
findings = scan_text(text, categories=categories)
301306
return [
@@ -309,7 +314,7 @@ def _tool_redact_pii(arguments: Dict[str, Any]) -> Any:
309314
from je_web_runner.utils.pii_scanner.scanner import redact_text
310315
text = arguments.get("text")
311316
if not isinstance(text, str):
312-
raise McpServerError("'text' must be a string")
317+
raise McpServerError(_ERR_TEXT_STRING)
313318
return redact_text(
314319
text,
315320
replacement=str(arguments.get("replacement", "[REDACTED]")),
@@ -351,7 +356,7 @@ def _tool_score_action_locators(arguments: Dict[str, Any]) -> Any:
351356
from je_web_runner.utils.linter.locator_strength import score_action_locators
352357
actions = arguments.get("actions")
353358
if not isinstance(actions, list):
354-
raise McpServerError("'actions' must be a list")
359+
raise McpServerError(_ERR_ACTIONS_LIST)
355360
return list(score_action_locators(actions))
356361

357362

je_web_runner/utils/md_authoring/markdown_to_actions.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ def _type_actions(text: str, selector: str) -> List[List[Any]]:
8181
_PRESS_RE = re.compile(r"^press\s+(\S+)$", re.IGNORECASE)
8282
_SCREENSHOT_RE = re.compile(r"^screenshot$", re.IGNORECASE)
8383
# Template name allows ASCII identifier chars plus dashes; the bounded
84-
# {0,80} caps the worst case at linear in input length.
85-
_TEMPLATE_RE = re.compile( # NOSONAR S5852 / S5869 — bounded class, ``\w`` overlap with first class is intentional
86-
r"^run\s+template\s+([A-Za-z_][\w-]{0,80})$", re.IGNORECASE,
84+
# {0,80} caps the worst case at linear in input length. With re.IGNORECASE
85+
# the first class only needs [A-Z_] — lowercase letters fold in automatically.
86+
_TEMPLATE_RE = re.compile(
87+
r"^run\s+template\s+([A-Z_][\w-]{0,80})$", re.IGNORECASE,
8788
)
8889
_QUIT_RE = re.compile(r"^quit$", re.IGNORECASE)
8990

je_web_runner/utils/visual_review/review_server.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,7 @@ def do_GET(self): # noqa: N802
202202
"text/html; charset=utf-8",
203203
)
204204
return
205-
if (parsed.path.startswith("/img/baseline/")
206-
or parsed.path.startswith("/img/current/")):
205+
if parsed.path.startswith(("/img/baseline/", "/img/current/")):
207206
self._serve_image(parsed.path)
208207
return
209208
self._send(404, b"not found", _TEXT_PLAIN)

test/unit_test/test_sharding.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from itertools import chain
23

34
from je_web_runner.utils.sharding.shard import (
45
ShardingError,
@@ -56,7 +57,7 @@ def test_partition_is_deterministic(self):
5657
def test_partition_with_spec_round_trip(self):
5758
files = [f"x{i}.json" for i in range(20)]
5859
parts = [partition_with_spec(files, f"{i}/4") for i in range(1, 5)]
59-
merged = sum(parts, [])
60+
merged = list(chain.from_iterable(parts))
6061
self.assertEqual(set(merged), set(files))
6162

6263

0 commit comments

Comments
 (0)