@@ -534,12 +534,14 @@ def _is_fstring(tok_string: str) -> bool:
534534 return q > 0 and "f" in tok_string [:q ].lower ()
535535
536536
537- def _strip_noncode (content : str ) -> str :
537+ def _strip_noncode (content : str , blank_comments : bool = True ) -> str :
538538 """Blank comments and bare docstrings so IOC patterns see code only.
539539
540540 Removed regions become spaces (newlines kept) so line numbers stay exact for
541541 _extract_evidence. Fails open on tokenizer errors (the raw text is still
542- fully scanned, so a real detection is never lost).
542+ fully scanned, so a real detection is never lost). ``blank_comments=False``
543+ keeps comments (only strings/docstrings blanked) to isolate the span that
544+ exec() could actually run.
543545 """
544546 try :
545547 toks = list (tokenize .generate_tokens (io .StringIO (content ).readline ))
@@ -552,8 +554,9 @@ def _strip_noncode(content: str) -> str:
552554 for i , tok in enumerate (toks ):
553555 ttype = tok .type
554556 if ttype == tokenize .COMMENT :
555- spans .append ((* tok .start , * tok .end ))
556- continue # do not advance prev_significant; comments are transparent
557+ if blank_comments :
558+ spans .append ((* tok .start , * tok .end ))
559+ continue # transparent; never advances prev_significant
557560 if (
558561 ttype == tokenize .STRING
559562 and prev_significant in _LINE_START_TOKENS
@@ -619,10 +622,11 @@ def _hidden_payload_findings(
619622 scanning yet ``exec(__doc__)`` / ``exec(<str>)`` could still run it."""
620623 if not RE_EXEC_EVAL .search (stripped ):
621624 return []
622- # The text code-only scanning removed (docstrings/comments/strings), with real
623- # code spaced out. _strip_noncode blanks in place and preserves length, so a
624- # char was removed exactly where stripped differs from original.
625- removed = "" .join (o if o != s else " " for o , s in zip (original , stripped ))
625+ # Only docstrings/strings run via exec(__doc__)/exec(<str>); comments cannot.
626+ # Isolate that span: keep comments as real code, take what string-blanking
627+ # removed (length-preserved, so offsets stay exact for _extract_evidence).
628+ code = _strip_noncode (original , blank_comments = False )
629+ removed = "" .join (o if o != s else " " for o , s in zip (original , code ))
626630 out = []
627631
628632 def _hidden (pat ):
@@ -1705,20 +1709,23 @@ def _requires_dist_names(meta: dict) -> list[str]:
17051709 return specs
17061710
17071711
1708- def _requires_dist_for (name : str , version : str | None , project_meta : dict ) -> list [str ]:
1712+ def _requires_dist_for (
1713+ name : str , version : str | None , project_meta : dict , errors : list [str ] | None = None
1714+ ) -> list [str ]:
17091715 """Declared deps for the pinned version, read from that release's metadata
17101716 (its ``requires_dist`` can differ from latest). Unpinned uses the
17111717 project-level (latest) document. A pinned version whose own metadata cannot
1712- be fetched returns [] rather than substituting latest's deps, which would
1713- scan the wrong tree."""
1718+ be fetched returns [] (never latest's deps) and, when ``errors`` is given,
1719+ records an incomplete- scan error so a partial tree is not read as "no deps" ."""
17141720 if not version :
17151721 return _requires_dist_names (project_meta )
17161722 vmeta = _pypi_json (name , version )
17171723 if vmeta is None :
1718- print (
1719- f" [WARN] no metadata for pinned { name } =={ version } ; not substituting latest deps" ,
1720- file = sys .stderr ,
1721- )
1724+ msg = f"metadata fetch failed for pinned { name } =={ version } ; dependency scan incomplete"
1725+ if errors is None :
1726+ print (f" [WARN] { msg } " , file = sys .stderr )
1727+ else :
1728+ errors .append (msg )
17221729 return []
17231730 return _requires_dist_names (vmeta )
17241731
@@ -1842,7 +1849,7 @@ def _resolve_per_spec_with_deps(
18421849 if fpath is None :
18431850 download_errors .append (serr or f"sdist fetch failed for { name } " )
18441851 continue
1845- sdist_dep_followups .extend (_requires_dist_for (name , version , meta ))
1852+ sdist_dep_followups .extend (_requires_dist_for (name , version , meta , download_errors ))
18461853 continue
18471854 # Has a wheel but the full transitive tree won't co-resolve
18481855 # (ResolutionImpossible) -- typically a package the requirement file
@@ -1876,7 +1883,7 @@ def _resolve_per_spec_with_deps(
18761883 # which --no-deps skips. Recover the declared deps so that class is
18771884 # still scanned (each is fetched as a wheel or direct sdist below).
18781885 if meta is not None :
1879- sdist_dep_followups .extend (_requires_dist_for (name , version , meta ))
1886+ sdist_dep_followups .extend (_requires_dist_for (name , version , meta , download_errors ))
18801887 continue
18811888 # --no-deps also failed: last-ditch sdist fetch at the pinned version.
18821889 if meta is not None :
0 commit comments