Skip to content

Commit 84a69fd

Browse files
committed
fix(entrypoints): handle extras brackets and comments in dependency array scan (#27)
Bracket-depth counting replaces the non-greedy regex so a nested [...] from extras (celery[redis]) no longer truncates the dependency array early, and comments are stripped before scanning so a commented-out line is no longer detected as a live dependency.
1 parent fadf43f commit 84a69fd

2 files changed

Lines changed: 80 additions & 6 deletions

File tree

codeanalyzer/entrypoints/detect.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99

1010
import re
1111
from pathlib import Path
12-
from typing import Set
12+
from typing import Optional, Set
1313

1414
from codeanalyzer.entrypoints.rules import RuleSet
1515
from codeanalyzer.schema.py_schema import PyApplication
1616

1717
_REQ = re.compile(r"^\s*['\"]?([A-Za-z0-9_.\-]+)")
18-
_DEPS_ARRAY = re.compile(r"dependencies\s*=\s*\[(.*?)\]", re.DOTALL)
18+
_DEPS_START = re.compile(r"dependencies\s*=\s*\[")
1919
_PKG = re.compile(r"['\"]([A-Za-z0-9][A-Za-z0-9_.\-]*)")
2020

2121

@@ -45,10 +45,11 @@ def _manifest_packages(project_dir: Path) -> Set[str]:
4545
out: Set[str] = set()
4646
pyproject = project_dir / "pyproject.toml"
4747
if pyproject.exists():
48-
# PEP 621 `[project] dependencies = [...]` -- single- or multi-line.
49-
m = _DEPS_ARRAY.search(pyproject.read_text())
50-
if m:
51-
for pm in _PKG.finditer(m.group(1)):
48+
# PEP 621 `[project] dependencies = [...]` -- single- or multi-line,
49+
# possibly containing nested `[...]` extras (`celery[redis]`).
50+
span = _deps_array_span(_strip_comments(pyproject.read_text()))
51+
if span is not None:
52+
for pm in _PKG.finditer(span):
5253
out.add(pm.group(1).split("[", 1)[0].lower())
5354
requirements = project_dir / "requirements.txt"
5455
if requirements.exists():
@@ -57,3 +58,52 @@ def _manifest_packages(project_dir: Path) -> Set[str]:
5758
if m:
5859
out.add(m.group(1).split("[", 1)[0].lower())
5960
return out
61+
62+
63+
def _strip_comments(text: str) -> str:
64+
"""Drop everything from an unquoted ``#`` to end of line.
65+
66+
# ponytail: quote tracking resets each line, so a `#` inside a
67+
# triple-quoted string spanning lines could be mis-stripped. TOML
68+
# dependency arrays don't use those in practice; revisit if they do.
69+
"""
70+
out_lines = []
71+
for line in text.splitlines():
72+
in_str = None
73+
cut = len(line)
74+
for i, ch in enumerate(line):
75+
if in_str:
76+
if ch == in_str:
77+
in_str = None
78+
elif ch in ("'", '"'):
79+
in_str = ch
80+
elif ch == "#":
81+
cut = i
82+
break
83+
out_lines.append(line[:cut])
84+
return "\n".join(out_lines)
85+
86+
87+
def _deps_array_span(text: str) -> Optional[str]:
88+
"""Return the contents between the `dependencies = [` and its matching
89+
`]`, counting bracket depth so a nested `[...]` (extras, e.g.
90+
`celery[redis]`) doesn't close the span early."""
91+
m = _DEPS_START.search(text)
92+
if not m:
93+
return None
94+
depth = 1
95+
in_str = None
96+
i = m.end()
97+
while i < len(text) and depth > 0:
98+
ch = text[i]
99+
if in_str:
100+
if ch == in_str:
101+
in_str = None
102+
elif ch in ("'", '"'):
103+
in_str = ch
104+
elif ch == "[":
105+
depth += 1
106+
elif ch == "]":
107+
depth -= 1
108+
i += 1
109+
return text[m.end() : i - 1]

test/test_entrypoint_detect.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,27 @@ def test_manifest_entry_alone_is_sufficient(tmp_path: Path):
3333
)
3434
got = detected_frameworks(_app("os"), tmp_path, load_rules())
3535
assert "celery" in got
36+
37+
38+
def test_extras_bracket_in_dependency_does_not_truncate_the_array(tmp_path: Path):
39+
(tmp_path / "pyproject.toml").write_text(
40+
'[project]\nname = "x"\n'
41+
'dependencies = ["celery[redis]>=5", "flask>=2.0"]\n'
42+
)
43+
got = detected_frameworks(_app("os"), tmp_path, load_rules())
44+
assert "celery" in got
45+
assert "flask" in got
46+
47+
48+
def test_commented_out_dependency_is_not_detected(tmp_path: Path):
49+
(tmp_path / "pyproject.toml").write_text(
50+
"[project]\n"
51+
'name = "x"\n'
52+
"dependencies = [\n"
53+
' # "celery>=5",\n'
54+
' "flask>=2.0",\n'
55+
"]\n"
56+
)
57+
got = detected_frameworks(_app("os"), tmp_path, load_rules())
58+
assert "celery" not in got
59+
assert "flask" in got

0 commit comments

Comments
 (0)