Skip to content

Commit f05363d

Browse files
committed
fix(version-scanner): tighten version boundaries, add lookaheads, allow subscript whitespace
1 parent 375f30f commit f05363d

2 files changed

Lines changed: 61 additions & 10 deletions

File tree

scripts/version_scanner/regex_config.yaml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ rules:
2525
- |
2626
python_requires\s*=\s*['"]==3\.{minor}(?:\.\d+)?['"]
2727
- |
28-
python_requires\s*=\s*['"]>=3\.{minor}(?:\.\d+)?['"]
28+
python_requires\s*=\s*['"]>=3\.{minor}(?:\.0)?['"]
2929
- |
30-
python_requires\s*=\s*['"]<=3\.{minor}(?:\.\d+)?['"]
30+
python_requires\s*=\s*['"]<=3\.{minor}(?:\.0)?['"]
3131
# Matches >3.6 (equivalent to >=3.7)
3232
- |
33-
python_requires\s*=\s*['"]>3\.{minor_minus_one}(?:\.\d+)?['"]
33+
python_requires\s*=\s*['"]>3\.{minor_minus_one}(?:\.0)?['"]
3434
# Matches <3.8 (equivalent to <=3.7)
3535
- |
36-
python_requires\s*=\s*['"]<3\.{minor_plus_one}(?:\.\d+)?['"]
36+
python_requires\s*=\s*['"]<3\.{minor_plus_one}(?:\.0)?['"]
3737
3838
- name: sys_version_info
3939
description: Finds sys.version_info checks in code.
@@ -80,17 +80,17 @@ rules:
8080
- |
8181
sys\.version_info\.minor\s*<\s*{minor_plus_one}(?!\d)
8282
- |
83-
sys\.version_info\[1\]\s*==\s*{minor}(?!\d)
83+
sys\.version_info\[\s*1\s*\]\s*==\s*{minor}(?!\d)
8484
- |
85-
sys\.version_info\[1\]\s*>=\s*{minor}(?!\d)
85+
sys\.version_info\[\s*1\s*\]\s*>=\s*{minor}(?!\d)
8686
- |
87-
sys\.version_info\[1\]\s*<=\s*{minor}(?!\d)
87+
sys\.version_info\[\s*1\s*\]\s*<=\s*{minor}(?!\d)
8888
# Matches sys.version_info[1] > 6 (equivalent to >=7)
8989
- |
90-
sys\.version_info\[1\]\s*>\s*{minor_minus_one}(?!\d)
90+
sys\.version_info\[\s*1\s*\]\s*>\s*{minor_minus_one}(?!\d)
9191
# Matches sys.version_info[1] < 8 (equivalent to <=7)
9292
- |
93-
sys\.version_info\[1\]\s*<\s*{minor_plus_one}(?!\d)
93+
sys\.version_info\[\s*1\s*\]\s*<\s*{minor_plus_one}(?!\d)
9494
9595
- name: python_env_short
9696
description: Finds short python environment names often used in tox or nox.
@@ -133,6 +133,6 @@ rules:
133133
- "protobuf!=3.7"
134134
rules:
135135
- |
136-
{name}\s*(?:==|>=|<=|~=|!=)\s*{version}
136+
{name}\s*(?:==|>=|<=|~=|!=)\s*{version}(?!\d)
137137
138138

scripts/version_scanner/tests/unit/test_version_scanner.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,57 @@ def test_regex_examples_from_config():
443443
break
444444
assert matched, f"Example '{example}' in group '{name}' did not match any pattern."
445445

446+
447+
def test_regex_negative_cases():
448+
"""Verify regex patterns prevent false positives (lookaheads, patch bounds) and support whitespace."""
449+
config_path = "regex_config.yaml"
450+
with open(config_path, 'r') as f:
451+
config = yaml.safe_load(f)
452+
453+
rules_list = config.get("rules", [])
454+
455+
# Target version 3.7
456+
vars = {
457+
"name": "protobuf",
458+
"major": "3",
459+
"minor": "7",
460+
"version": "3.7",
461+
"minor_plus_one": "8",
462+
"minor_minus_one": "6"
463+
}
464+
465+
# Find specific rule groups
466+
dep_req_group = next(r for r in rules_list if r["name"] == "dependency_requirement")
467+
python_cmd_group = next(r for r in rules_list if r["name"] == "explicit_python_command")
468+
python_req_group = next(r for r in rules_list if r["name"] == "python_requires")
469+
sys_info_group = next(r for r in rules_list if r["name"] == "sys_version_info")
470+
471+
# 1. Verify dependency_requirement looks ahead correctly (no partial match)
472+
dep_pattern = re.compile(dep_req_group["rules"][0].strip().format(**vars), re.IGNORECASE)
473+
assert dep_pattern.search("protobuf==3.7")
474+
assert not dep_pattern.search("protobuf==3.72")
475+
476+
# 2. Verify explicit_python_command negative lookahead
477+
cmd_pattern = re.compile(python_cmd_group["rules"][0].strip().format(**vars), re.IGNORECASE)
478+
assert cmd_pattern.search("python3.7")
479+
assert not cmd_pattern.search("python3.72")
480+
481+
# 3. Verify python_requires optional patch limits boundary rules to .0
482+
# Boundary rule 1: >=3.7 (python_requires = '>=3.7.0' is OK, but >=3.7.1 is not equivalent and should be skipped)
483+
req_ge_pattern = re.compile(python_req_group["rules"][1].strip().format(**vars), re.IGNORECASE)
484+
assert req_ge_pattern.search("python_requires = '>=3.7'")
485+
assert req_ge_pattern.search("python_requires = '>=3.7.0'")
486+
assert not req_ge_pattern.search("python_requires = '>=3.7.1'")
487+
488+
# 4. Verify sys_version_info[1] allows optional whitespace
489+
# Matches sys.version_info[ 1 ]
490+
sys_sub_pattern = re.compile(sys_info_group["rules"][10].strip().format(**vars), re.IGNORECASE) # sys.version_info[1] == 7
491+
assert sys_sub_pattern.search("sys.version_info[1] == 7")
492+
assert sys_sub_pattern.search("sys.version_info[ 1 ] == 7")
493+
assert sys_sub_pattern.search("sys.version_info[1 ] == 7")
494+
assert sys_sub_pattern.search("sys.version_info[ 1] == 7")
495+
496+
446497
def test_main_exit_code_1():
447498
"""Test that main() calls sys.exit(1) when matches are found."""
448499
# We can mock scan_repository to return a dummy match

0 commit comments

Comments
 (0)