@biffo/cli 0.253.9 → 0.253.11
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.
|
@@ -254,13 +254,33 @@ def _template_literals(text: str):
|
|
|
254
254
|
|
|
255
255
|
_PARAM_SEGMENT = re.compile(r"\{[^}]*\}")
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
|
|
257
|
+
# ONE alternation, scanned left to right, so whichever comment construct opens
|
|
258
|
+
# FIRST consumes the other (#1374). Two separate passes cannot express that,
|
|
259
|
+
# and the order they ran in was a live fail-open:
|
|
260
|
+
#
|
|
261
|
+
# text = _BLOCK_COMMENT.sub(...) # ran first
|
|
262
|
+
# return _LINE_COMMENT.sub(...) # ran second
|
|
263
|
+
#
|
|
264
|
+
# A `/*` appearing as PROSE inside a `//` comment was therefore read as a real
|
|
265
|
+
# block-comment opener, and everything up to the file's next literal `*/` —
|
|
266
|
+
# in JSX, typically a `{/* ... */}` far below — was deleted. `tabsii-geo`'s
|
|
267
|
+
# `MapView.tsx` carried `// exclude everything under basemap/*) from the S3
|
|
268
|
+
# deploy`, which swallowed ~180 lines including BOTH of that repo's real call
|
|
269
|
+
# sites. The guard extracted 0 paths and PASSED.
|
|
270
|
+
#
|
|
271
|
+
# `re.sub` takes the leftmost match, so a single alternation gets this right in
|
|
272
|
+
# both directions: a `//` opening first consumes any `/*` on its line, and a
|
|
273
|
+
# `/*` opening first consumes any `//` inside it.
|
|
274
|
+
#
|
|
275
|
+
# The `//` branch requires whitespace or start-of-line before it, NOT a bare
|
|
276
|
+
# `//`, which would also truncate a `'https://…'` string literal — see the
|
|
277
|
+
# module docstring's "Comments are stripped first" section for the real false
|
|
278
|
+
# positives (tabsii-crm, tabsii-lms) that rule exists to stop.
|
|
279
|
+
#
|
|
280
|
+
# An UNTERMINATED `/*` matches nothing and is left in place, deliberately:
|
|
281
|
+
# deleting to end-of-file on a malformed comment is the very blindness this
|
|
282
|
+
# fix exists to remove.
|
|
283
|
+
_COMMENT = re.compile(r"/\*.*?\*/|(?:^|(?<=\s))//[^\n]*", re.DOTALL | re.MULTILINE)
|
|
264
284
|
|
|
265
285
|
API_PREFIX = "/api/v1/"
|
|
266
286
|
|
|
@@ -269,14 +289,17 @@ def _strip_comments(text: str) -> str:
|
|
|
269
289
|
"""Remove `/* ... */` and `// ...` so a comment merely mentioning a path
|
|
270
290
|
— in prose, as documentation — is never mistaken for a call site.
|
|
271
291
|
|
|
272
|
-
A
|
|
273
|
-
|
|
292
|
+
A comment is replaced by the same number of newlines it contained, not
|
|
293
|
+
deleted outright — this codebase leans heavily on multi-line `/** */`
|
|
274
294
|
JSDoc, and dropping those newlines would shift every subsequent line
|
|
275
295
|
number, misdirecting a failure message at the wrong line for anything
|
|
276
|
-
below one.
|
|
296
|
+
below one. A `//` comment contains no newline, so the same substitution
|
|
297
|
+
correctly erases it to nothing.
|
|
298
|
+
|
|
299
|
+
See `_COMMENT` for why this is one pass rather than two, and for the
|
|
300
|
+
fail-open (#1374) that the two-pass version caused.
|
|
277
301
|
"""
|
|
278
|
-
|
|
279
|
-
return _LINE_COMMENT.sub("", text)
|
|
302
|
+
return _COMMENT.sub(lambda m: "\n" * m.group(0).count("\n"), text)
|
|
280
303
|
|
|
281
304
|
|
|
282
305
|
@dataclass(frozen=True)
|
|
@@ -643,6 +666,62 @@ def path_is_registered(fastapi_app: FastAPI, normalized: str) -> bool:
|
|
|
643
666
|
return False
|
|
644
667
|
|
|
645
668
|
|
|
669
|
+
def test_the_extractor_is_not_blind() -> None:
|
|
670
|
+
"""Zero extracted paths must mean "none there", never "could not see".
|
|
671
|
+
|
|
672
|
+
`test_every_frontend_api_v1_path_is_registered_on_the_bff` cannot tell the
|
|
673
|
+
difference on its own: it iterates whatever the extractor returned, so an
|
|
674
|
+
extractor that returns nothing passes it trivially. Every other safeguard
|
|
675
|
+
in this file — `unresolved` failing rather than skipping, the reported
|
|
676
|
+
denominator, the declared external allowlist — protects against a path
|
|
677
|
+
that is SEEN and cannot be resolved. None of them protects against text
|
|
678
|
+
the extractor never receives.
|
|
679
|
+
|
|
680
|
+
That is not hypothetical. #1374: `_strip_comments` ran its block-comment
|
|
681
|
+
pass before its line-comment pass, so a `/*` in prose inside a `//`
|
|
682
|
+
comment opened a phantom block that deleted ~180 lines of `tabsii-geo`'s
|
|
683
|
+
`MapView.tsx`, including both of its real call sites. The suite reported
|
|
684
|
+
`0 found ... 0 did not match` and passed. It was caught only because the
|
|
685
|
+
sweep that adopted this guard required a raw-grep cross-check of the
|
|
686
|
+
extracted count rather than trusting a green test.
|
|
687
|
+
|
|
688
|
+
So the cross-check is now part of the guard. Comparing against the RAW
|
|
689
|
+
text — before any stripping — is the point: the raw scan is the one thing
|
|
690
|
+
a bug in the stripping cannot affect.
|
|
691
|
+
|
|
692
|
+
**What this does and does not catch.** It catches TOTAL blindness over the
|
|
693
|
+
file set, which is the shape a stripping bug takes when it swallows a
|
|
694
|
+
span. It does not catch partial loss — three call sites eaten while a
|
|
695
|
+
fourth survives still passes here. Stated plainly rather than overclaimed;
|
|
696
|
+
the one-pass `_COMMENT` scanner is what addresses the partial case, and
|
|
697
|
+
this is the backstop for the next bug of a kind nobody has thought of.
|
|
698
|
+
|
|
699
|
+
A repo with genuinely no in-source API calls still passes, because the raw
|
|
700
|
+
scan reads exactly the same file set the extractor does — test files
|
|
701
|
+
excluded. `tabsii-app` is the real example: its only `/api/v1` text lives
|
|
702
|
+
in `lib/api-client.test.ts`, so raw and extracted are both 0 and that is
|
|
703
|
+
an honest clean.
|
|
704
|
+
"""
|
|
705
|
+
files = _frontend_source_files()
|
|
706
|
+
raw_hits = [(f, f.read_text(encoding="utf-8").count(API_PREFIX)) for f in files]
|
|
707
|
+
raw_total = sum(n for _, n in raw_hits)
|
|
708
|
+
|
|
709
|
+
extracted = 0
|
|
710
|
+
for file in files:
|
|
711
|
+
extracted += len(extract_api_paths(file.read_text(encoding="utf-8"), file))
|
|
712
|
+
|
|
713
|
+
if raw_total and not extracted:
|
|
714
|
+
offenders = "\n".join(f" {_relative(f)}: {n} raw occurrence(s)" for f, n in raw_hits if n)
|
|
715
|
+
raise AssertionError(
|
|
716
|
+
f"The extractor found 0 `{API_PREFIX}` path(s) across "
|
|
717
|
+
f"{len(files)} frontend source file(s), but the raw text of those "
|
|
718
|
+
f"same files contains {raw_total}. That is blindness, not a clean "
|
|
719
|
+
f"pass — something is consuming the source before extraction "
|
|
720
|
+
f"reaches it (#1374 was exactly this: a `/*` in prose inside a "
|
|
721
|
+
f"`//` comment swallowing the rest of the file).\n{offenders}"
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
|
|
646
725
|
def test_every_frontend_api_v1_path_is_registered_on_the_bff() -> None:
|
|
647
726
|
extracted: list[ExtractedPath] = []
|
|
648
727
|
for file in _frontend_source_files():
|
|
@@ -806,6 +885,63 @@ class TestExtractApiPaths:
|
|
|
806
885
|
assert len(found) == 1
|
|
807
886
|
assert found[0].normalized == "/api/v1/whoami"
|
|
808
887
|
|
|
888
|
+
def test_a_block_opener_in_prose_inside_a_line_comment_eats_nothing(self) -> None:
|
|
889
|
+
"""#1374, reduced from `tabsii-geo`'s `MapView.tsx` verbatim.
|
|
890
|
+
|
|
891
|
+
The `basemap/*)` in that comment is prose — a glob in an English
|
|
892
|
+
sentence about an S3 deploy exclusion. The two-pass stripper read its
|
|
893
|
+
`/*` as a real block-comment opener and deleted everything up to the
|
|
894
|
+
next literal `*/`, which in a `.tsx` file is the JSX comment below.
|
|
895
|
+
Both real call sites vanished and the suite reported a clean pass.
|
|
896
|
+
"""
|
|
897
|
+
found = extract_api_paths(
|
|
898
|
+
"// exclude everything under basemap/*) from the S3 deploy\n"
|
|
899
|
+
"api.get('/api/v1/units')\n"
|
|
900
|
+
"api.get('/api/v1/regions')\n"
|
|
901
|
+
"{/* an ordinary JSX comment */}\n"
|
|
902
|
+
"api.get('/api/v1/brands')\n",
|
|
903
|
+
Path("MapView.tsx"),
|
|
904
|
+
)
|
|
905
|
+
assert [p.normalized for p in found] == [
|
|
906
|
+
"/api/v1/units",
|
|
907
|
+
"/api/v1/regions",
|
|
908
|
+
"/api/v1/brands",
|
|
909
|
+
]
|
|
910
|
+
|
|
911
|
+
def test_a_line_comment_marker_inside_a_block_comment_eats_nothing(self) -> None:
|
|
912
|
+
"""The mirror case, which a naive "just swap the two passes" fix
|
|
913
|
+
breaks: a `//` inside a block comment must not end the block early and
|
|
914
|
+
leave its `*/` behind as live source."""
|
|
915
|
+
found = extract_api_paths(
|
|
916
|
+
"/* see //example.com for why\n"
|
|
917
|
+
" this used to call /api/v1/legacy */\n"
|
|
918
|
+
"api.get('/api/v1/whoami')\n",
|
|
919
|
+
Path("x.ts"),
|
|
920
|
+
)
|
|
921
|
+
assert [p.normalized for p in found] == ["/api/v1/whoami"]
|
|
922
|
+
|
|
923
|
+
def test_an_unterminated_block_comment_does_not_eat_the_rest_of_the_file(
|
|
924
|
+
self,
|
|
925
|
+
) -> None:
|
|
926
|
+
"""Deleting to end-of-file on a malformed comment is the same
|
|
927
|
+
blindness in a different costume, so an unterminated `/*` is left in
|
|
928
|
+
place and the code after it is still read."""
|
|
929
|
+
found = extract_api_paths(
|
|
930
|
+
"/* oops, never closed\napi.get('/api/v1/whoami')\n",
|
|
931
|
+
Path("x.ts"),
|
|
932
|
+
)
|
|
933
|
+
assert [p.normalized for p in found] == ["/api/v1/whoami"]
|
|
934
|
+
|
|
935
|
+
def test_comment_stripping_still_preserves_line_numbers(self) -> None:
|
|
936
|
+
"""The one-pass scanner must keep the property the two-pass version
|
|
937
|
+
had: a failure message points at the real line."""
|
|
938
|
+
found = extract_api_paths(
|
|
939
|
+
"/* a\n multi\n line\n comment */\napi.get('/api/v1/whoami')\n",
|
|
940
|
+
Path("x.ts"),
|
|
941
|
+
)
|
|
942
|
+
assert len(found) == 1
|
|
943
|
+
assert found[0].line == 5
|
|
944
|
+
|
|
809
945
|
def test_a_url_containing_double_slash_is_not_mistaken_for_a_comment(self) -> None:
|
|
810
946
|
found = extract_api_paths(
|
|
811
947
|
"const base = 'https://example.com'\napi.get('/api/v1/whoami')",
|