From adc459ef86b67bf5fcf43e1b0f627c9337c13120 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Fri, 4 Sep 2026 22:02:31 -0400 Subject: [PATCH 1/2] refactor(checks): split the platforms.ts reader and the gate's file read `typescript_table.py` held the whole normalize-then-decode pipeline as four module-scope functions, and `gate.py` held its default reader alongside `run`. Each file keeps its named export and sheds the rest: - `requoted.py` -- one TypeScript string literal as a JSON one. - `without_comments.py` -- comments stripped and strings normalized in one pass, with the `_TOKENS` alternation that does both. The two cannot be separated: a `//` inside a string is not a comment. - `as_json.py` -- bare keys quoted, trailing commas dropped. - `read.py` -- the gate's default `source`, and the only place in the package that opens a file. `typescript_table.py` keeps `typescript_table`; `gate.py` keeps `run`. Test cases follow the code they cover: the string-form cases now drive `requoted` directly, the comment cases `without_comments`, the key and comma cases `as_json`, and the scratch-file case moves out of `gate_test.py` into `read_test.py`. The table-level tests keep the end-to-end reads and every `ParseError` contract, which is where those belong. Behavior-preserving: no message text, exit code or parse result changes. --- .../src/checks/platforms_mirror/as_json.py | 10 ++++++ .../checks/platforms_mirror/as_json_test.py | 22 ++++++++++++ .../src/checks/platforms_mirror/gate.py | 6 +--- .../src/checks/platforms_mirror/gate_test.py | 26 ++++++++------ .../src/checks/platforms_mirror/read.py | 8 +++++ .../src/checks/platforms_mirror/read_test.py | 17 ++++++++++ .../src/checks/platforms_mirror/requoted.py | 16 +++++++++ .../checks/platforms_mirror/requoted_test.py | 25 ++++++++++++++ .../platforms_mirror/typescript_table.py | 34 +++---------------- .../platforms_mirror/typescript_table_test.py | 14 ++------ .../platforms_mirror/without_comments.py | 27 +++++++++++++++ .../platforms_mirror/without_comments_test.py | 25 ++++++++++++++ 12 files changed, 173 insertions(+), 57 deletions(-) create mode 100644 internals/checks/src/checks/platforms_mirror/as_json.py create mode 100644 internals/checks/src/checks/platforms_mirror/as_json_test.py create mode 100644 internals/checks/src/checks/platforms_mirror/read.py create mode 100644 internals/checks/src/checks/platforms_mirror/read_test.py create mode 100644 internals/checks/src/checks/platforms_mirror/requoted.py create mode 100644 internals/checks/src/checks/platforms_mirror/requoted_test.py create mode 100644 internals/checks/src/checks/platforms_mirror/without_comments.py create mode 100644 internals/checks/src/checks/platforms_mirror/without_comments_test.py diff --git a/internals/checks/src/checks/platforms_mirror/as_json.py b/internals/checks/src/checks/platforms_mirror/as_json.py new file mode 100644 index 000000000..4a5429ffa --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/as_json.py @@ -0,0 +1,10 @@ +"""An object-literal source as JSON: keys quoted, trailing commas dropped.""" + +from __future__ import annotations + +import re + + +def as_json(text: str) -> str: + keyed = re.sub(r"([{,]\s*)([A-Za-z_$][\w$]*)\s*:", r'\1"\2":', text) + return re.sub(r",(\s*[}\]])", r"\1", keyed) diff --git a/internals/checks/src/checks/platforms_mirror/as_json_test.py b/internals/checks/src/checks/platforms_mirror/as_json_test.py new file mode 100644 index 000000000..d519eab87 --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/as_json_test.py @@ -0,0 +1,22 @@ +"""Colocated unit tests for normalizing an object literal to JSON (isolation -- +source text in, source text out). +""" + +from checks.platforms_mirror.as_json import as_json + + +def describe_as_json(): + def it_quotes_a_bare_key(): + assert as_json('{os: "linux"}') == '{"os": "linux"}' + + def it_quotes_every_key_of_an_object(): + assert as_json('{os: "linux", cpu: "x64"}') == '{"os": "linux", "cpu": "x64"}' + + def it_leaves_an_already_quoted_key_alone(): + assert as_json('{"os": "linux"}') == '{"os": "linux"}' + + def it_drops_a_trailing_comma_before_a_brace(): + assert as_json('{"os": "linux",}') == '{"os": "linux"}' + + def it_drops_a_trailing_comma_before_a_bracket(): + assert as_json('["linux",]') == '["linux"]' diff --git a/internals/checks/src/checks/platforms_mirror/gate.py b/internals/checks/src/checks/platforms_mirror/gate.py index 9da9f18e0..f7c8223e4 100644 --- a/internals/checks/src/checks/platforms_mirror/gate.py +++ b/internals/checks/src/checks/platforms_mirror/gate.py @@ -12,14 +12,10 @@ from .parse import ParseError from .problems import problems from .python_table import python_table +from .read import read from .typescript_table import typescript_table -def read(path: str) -> str: - with open(path, encoding="utf-8") as handle: - return handle.read() - - def run( python_path: str, typescript_path: str, diff --git a/internals/checks/src/checks/platforms_mirror/gate_test.py b/internals/checks/src/checks/platforms_mirror/gate_test.py index ed737a7fc..876af330f 100644 --- a/internals/checks/src/checks/platforms_mirror/gate_test.py +++ b/internals/checks/src/checks/platforms_mirror/gate_test.py @@ -1,12 +1,12 @@ """Colocated unit tests for the platforms-mirror gate (#1004). Isolation: the reader is injected, so nothing here touches the repo's real -platforms.py / platforms.ts. The reader itself is exercised against a scratch -file. The table readers and the verdict in `problems.py` run for real, since -they are pure text-in / messages-out. +platforms.py / platforms.ts; `read.py` carries its own scratch-file test. The +table readers and the verdict in `problems.py` run for real, since they are +pure text-in / messages-out. """ -from checks.platforms_mirror.gate import read, run +from checks.platforms_mirror.gate import run PYTHON = '''\ from dataclasses import dataclass @@ -55,13 +55,6 @@ def invoke(**kwargs): return code, "\n".join(lines) -def describe_read(): - def it_reads_a_file_as_utf8_text(tmp_path): - path = tmp_path / "platforms.ts" - path.write_text("// caffè\n", encoding="utf-8") - assert read(str(path)) == "// caffè\n" - - def describe_run(): def it_reports_the_target_count_when_the_subset_agrees(): code, out = invoke() @@ -95,6 +88,17 @@ def it_fails_when_the_python_table_cannot_be_read(): assert "::error::platforms-mirror could not read a platform table:" in out assert "no `class Platform`" in out + def it_reads_both_paths_through_the_injected_reader(): + seen = [] + text = sources() + + def source(path): + seen.append(path) + return text(path) + + run("p.py", "t.ts", source=source, echo=lambda line: None) + assert seen == ["p.py", "t.ts"] + def it_fails_when_the_typescript_table_cannot_be_read(): code, out = invoke(typescript="export const OTHER = [];\n") assert code == 1 diff --git a/internals/checks/src/checks/platforms_mirror/read.py b/internals/checks/src/checks/platforms_mirror/read.py new file mode 100644 index 000000000..24178670d --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/read.py @@ -0,0 +1,8 @@ +"""A platform table's text, off disk.""" + +from __future__ import annotations + + +def read(path: str) -> str: + with open(path, encoding="utf-8") as handle: + return handle.read() diff --git a/internals/checks/src/checks/platforms_mirror/read_test.py b/internals/checks/src/checks/platforms_mirror/read_test.py new file mode 100644 index 000000000..bc7632714 --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/read_test.py @@ -0,0 +1,17 @@ +"""Colocated unit test for reading a platform table off disk (isolation -- a +scratch file, never the repo's real platforms.py / platforms.ts). +""" + +from checks.platforms_mirror.read import read + + +def describe_read(): + def it_reads_a_file_as_utf8_text(tmp_path): + path = tmp_path / "platforms.ts" + path.write_text("// caffè\n", encoding="utf-8") + assert read(str(path)) == "// caffè\n" + + def it_reads_the_whole_file(tmp_path): + path = tmp_path / "platforms.py" + path.write_text("one\ntwo\n", encoding="utf-8") + assert read(str(path)) == "one\ntwo\n" diff --git a/internals/checks/src/checks/platforms_mirror/requoted.py b/internals/checks/src/checks/platforms_mirror/requoted.py new file mode 100644 index 000000000..7faa050ac --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/requoted.py @@ -0,0 +1,16 @@ +"""A TypeScript string literal as a JSON one. + +Single-quoted and template forms are unquoted by hand and re-encoded, so the +result is something `json` will accept without the escapes changing meaning. +""" + +from __future__ import annotations + +import json + + +def requoted(text: str) -> str: + """A TypeScript string literal as a JSON one.""" + if text.startswith('"'): + return text + return json.dumps(text[1:-1].replace('\\"', '"').replace("\\'", "'")) diff --git a/internals/checks/src/checks/platforms_mirror/requoted_test.py b/internals/checks/src/checks/platforms_mirror/requoted_test.py new file mode 100644 index 000000000..a1e00715e --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/requoted_test.py @@ -0,0 +1,25 @@ +"""Colocated unit tests for re-quoting a TypeScript string literal (isolation -- +one literal in, one literal out). +""" + +from checks.platforms_mirror.requoted import requoted + + +def describe_requoted(): + def it_keeps_a_double_quoted_literal_verbatim(): + assert requoted('"linux"') == '"linux"' + + def it_rewrites_a_single_quoted_literal(): + assert requoted("'linux'") == '"linux"' + + def it_rewrites_a_template_literal(): + assert requoted("`linux`") == '"linux"' + + def it_unwraps_only_the_outermost_quotes(): + assert requoted("'aba'") == '"aba"' + + def it_unescapes_a_quote_the_typescript_form_had_to_escape(): + assert requoted(r"'a\'b'") == '"a\'b"' + + def it_keeps_an_escaped_double_quote_escaped(): + assert requoted(r"'a\"b'") == r'"a\"b"' diff --git a/internals/checks/src/checks/platforms_mirror/typescript_table.py b/internals/checks/src/checks/platforms_mirror/typescript_table.py index 8ffbb8560..0f804c6b3 100644 --- a/internals/checks/src/checks/platforms_mirror/typescript_table.py +++ b/internals/checks/src/checks/platforms_mirror/typescript_table.py @@ -10,47 +10,21 @@ import json import re +from .as_json import as_json from .parse import ParseError, TABLE_NAME - -# One alternation of "things a naive scan would cut in half": the three string -# forms first, so a `//` inside a string is matched as string rather than as the -# comment that follows it. -_TOKENS = re.compile( - r'"(?:\\.|[^"\\])*"' r"|'(?:\\.|[^'\\])*'" r"|`(?:\\.|[^`\\])*`" r"|//[^\n]*" r"|/\*.*?\*/", - re.S, -) - - -def _requoted(text: str) -> str: - """A TypeScript string literal as a JSON one.""" - if text.startswith('"'): - return text - return json.dumps(text[1:-1].replace('\\"', '"').replace("\\'", "'")) - - -def _without_comments(source: str) -> str: - def keep(match: re.Match) -> str: - text = match.group(0) - return "" if text.startswith(("//", "/*")) else _requoted(text) - - return _TOKENS.sub(keep, source) - - -def _as_json(text: str) -> str: - keyed = re.sub(r"([{,]\s*)([A-Za-z_$][\w$]*)\s*:", r'\1"\2":', text) - return re.sub(r",(\s*[}\]])", r"\1", keyed) +from .without_comments import without_comments def typescript_table(source: str) -> list[dict]: """Rows of the `PLATFORMS` array in a `platforms.ts`-shaped module.""" - cleaned = _without_comments(source) + cleaned = without_comments(source) marker = re.search(rf"\b{TABLE_NAME}\b[^=;]*=\s*(\[)", cleaned) if marker is None: raise ParseError(f"no `{TABLE_NAME} = [...]` assignment in the TypeScript source.") try: # `raw_decode` stops at the end of the array, so nothing here has to find # the closing bracket or care what follows it. - rows, _ = json.JSONDecoder().raw_decode(_as_json(cleaned[marker.start(1) :])) + rows, _ = json.JSONDecoder().raw_decode(as_json(cleaned[marker.start(1) :])) except json.JSONDecodeError as error: raise ParseError( f"`{TABLE_NAME}` is not a plain array of object literals ({error}); this check " diff --git a/internals/checks/src/checks/platforms_mirror/typescript_table_test.py b/internals/checks/src/checks/platforms_mirror/typescript_table_test.py index 64f4b61cc..b18406f97 100644 --- a/internals/checks/src/checks/platforms_mirror/typescript_table_test.py +++ b/internals/checks/src/checks/platforms_mirror/typescript_table_test.py @@ -32,21 +32,13 @@ def it_reads_the_array_through_comments_and_trailing_commas(): def it_normalizes_single_quoted_strings(): assert typescript_table("const PLATFORMS = [{ os: ['linux'] }];") == [{"os": ["linux"]}] - def it_unwraps_a_single_quoted_string_without_trimming_its_contents(): - assert typescript_table("const PLATFORMS = [{ os: ['aba'] }];") == [{"os": ["aba"]}] - - def it_unwraps_a_template_literal(): - assert typescript_table("const PLATFORMS = [{ os: [`aba`] }];") == [{"os": ["aba"]}] - - def it_keeps_a_double_quoted_string_verbatim(): - assert typescript_table('const PLATFORMS = [{ os: ["aba"] }];') == [{"os": ["aba"]}] - def it_finds_the_array_when_a_comment_mentions_a_bracket(): source = "// see PLATFORMS = [ elsewhere\nconst PLATFORMS = [{ os: ['a'] }];" assert typescript_table(source) == [{"os": ["a"]}] - def it_keeps_an_escape_sequence_inside_a_string(): - assert typescript_table(r'const PLATFORMS = [{ os: "a\"b" }];') == [{"os": 'a"b'}] + def it_reads_the_first_platforms_binding_it_finds(): + source = "const OTHER = [];\nexport const PLATFORMS = [{ os: ['a'] }];" + assert typescript_table(source) == [{"os": ["a"]}] def it_rejects_a_source_with_no_table(): with pytest.raises(ParseError, match="no `PLATFORMS"): diff --git a/internals/checks/src/checks/platforms_mirror/without_comments.py b/internals/checks/src/checks/platforms_mirror/without_comments.py new file mode 100644 index 000000000..6b24909e1 --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/without_comments.py @@ -0,0 +1,27 @@ +"""A TypeScript source with its comments gone and its strings JSON-quoted. + +Both happen in one pass because they cannot be separated: a `//` inside a +string is not a comment, and a quote inside a comment is not a string. +""" + +from __future__ import annotations + +import re + +from .requoted import requoted + +# One alternation of "things a naive scan would cut in half": the three string +# forms first, so a `//` inside a string is matched as string rather than as the +# comment that follows it. +_TOKENS = re.compile( + r'"(?:\\.|[^"\\])*"' r"|'(?:\\.|[^'\\])*'" r"|`(?:\\.|[^`\\])*`" r"|//[^\n]*" r"|/\*.*?\*/", + re.S, +) + + +def without_comments(source: str) -> str: + def keep(match: re.Match) -> str: + text = match.group(0) + return "" if text.startswith(("//", "/*")) else requoted(text) + + return _TOKENS.sub(keep, source) diff --git a/internals/checks/src/checks/platforms_mirror/without_comments_test.py b/internals/checks/src/checks/platforms_mirror/without_comments_test.py new file mode 100644 index 000000000..8f46615c0 --- /dev/null +++ b/internals/checks/src/checks/platforms_mirror/without_comments_test.py @@ -0,0 +1,25 @@ +"""Colocated unit tests for stripping comments and normalizing strings +(isolation -- source text in, source text out). +""" + +from checks.platforms_mirror.without_comments import without_comments + + +def describe_without_comments(): + def it_drops_a_line_comment(): + assert without_comments("a // gone\nb") == "a \nb" + + def it_drops_a_block_comment_across_lines(): + assert without_comments("a /* gone\ngone */ b") == "a b" + + def it_keeps_a_comment_marker_that_sits_inside_a_string(): + assert without_comments("['a // b']") == '["a // b"]' + + def it_normalizes_the_strings_it_keeps(): + assert without_comments("['linux']") == '["linux"]' + + def it_drops_a_line_comment_that_runs_to_the_end_of_the_source(): + assert without_comments("a // gone") == "a " + + def it_leaves_a_source_with_neither_alone(): + assert without_comments("[1, 2]") == "[1, 2]" From 66d4edb4d1f0c0f78e1cdae04ed38852376ddbe3 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Fri, 4 Sep 2026 22:04:44 -0400 Subject: [PATCH 2/2] e2e attestation for claude/1064-pm-tsgate-z8m5 --- .../e2e-attestations/claude-1064-pm-tsgate-z8m5.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 internals/checks/e2e-attestations/claude-1064-pm-tsgate-z8m5.json diff --git a/internals/checks/e2e-attestations/claude-1064-pm-tsgate-z8m5.json b/internals/checks/e2e-attestations/claude-1064-pm-tsgate-z8m5.json new file mode 100644 index 000000000..72a46b070 --- /dev/null +++ b/internals/checks/e2e-attestations/claude-1064-pm-tsgate-z8m5.json @@ -0,0 +1,7 @@ +{ + "command": "uv run python -m pytest tests/e2e -x -q", + "ran_at": 1788573884, + "exit_code": 0, + "commit": "adc459ef86b67bf5fcf43e1b0f627c9337c13120", + "branch": "claude/1064-pm-tsgate-z8m5" +}