Skip to content

Commit d897b3c

Browse files
committed
fix: handle ~= and != version operators in Python dependency name parsing
getDependencyName() only recognized >, <, and = as version operator characters, causing ~ from ~= (compatibility) and ! from != (exclusion) to be included in the package name (e.g. "urllib3~", "click!"). Replaced the three-index approach with a loop that recognizes all PEP 508 version operator characters (>, <, =, ~, !). Fixes TC-4041 Assisted-by: Claude Code
1 parent 2786440 commit d897b3c

2 files changed

Lines changed: 32 additions & 18 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/utils/PythonControllerBase.java

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -388,31 +388,24 @@ public static String getDependencyName(String dep) {
388388
requirement = requirement.substring(0, extrasStart) + requirement.substring(extrasEnd + 1);
389389
}
390390
}
391-
int rightTriangleBracket = requirement.indexOf(">");
392-
int leftTriangleBracket = requirement.indexOf("<");
393-
int equalsSign = requirement.indexOf("=");
394-
int minimumIndex = getFirstSign(rightTriangleBracket, leftTriangleBracket, equalsSign);
391+
// Find the first PEP 508 version operator character (~=, !=, ==, >=, <=, >, <, ===)
392+
int firstOperatorChar = -1;
393+
for (int i = 0; i < requirement.length(); i++) {
394+
char c = requirement.charAt(i);
395+
if (c == '>' || c == '<' || c == '=' || c == '~' || c == '!') {
396+
firstOperatorChar = i;
397+
break;
398+
}
399+
}
395400
String depName;
396-
if (rightTriangleBracket == -1 && leftTriangleBracket == -1 && equalsSign == -1) {
401+
if (firstOperatorChar == -1) {
397402
depName = requirement;
398403
} else {
399-
depName = requirement.substring(0, minimumIndex);
404+
depName = requirement.substring(0, firstOperatorChar);
400405
}
401406
return depName.trim();
402407
}
403408

404-
private static int getFirstSign(
405-
int rightTriangleBracket, int leftTriangleBracket, int equalsSign) {
406-
rightTriangleBracket = rightTriangleBracket == -1 ? 999 : rightTriangleBracket;
407-
leftTriangleBracket = leftTriangleBracket == -1 ? 999 : leftTriangleBracket;
408-
equalsSign = equalsSign == -1 ? 999 : equalsSign;
409-
return equalsSign < leftTriangleBracket && equalsSign < rightTriangleBracket
410-
? equalsSign
411-
: (leftTriangleBracket < equalsSign && leftTriangleBracket < rightTriangleBracket
412-
? leftTriangleBracket
413-
: rightTriangleBracket);
414-
}
415-
416409
static List<String> splitPipShowLines(String pipShowOutput) {
417410
return Arrays.stream(
418411
pipShowOutput.split(System.lineSeparator() + "---" + System.lineSeparator()))

src/test/java/io/github/guacsec/trustifyda/utils/PythonControllerRealEnvTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,16 @@ void get_Dependency_Name_with_markers() {
301301
PythonControllerRealEnv.getDependencyName("certifi==2023.7.22 ; python_version >= \"3\""));
302302
}
303303

304+
/** Verifies getDependencyName handles compatibility (~=) and exclusion (!=) operators. */
305+
@Test
306+
void get_Dependency_Name_with_compatibility_and_exclusion_operators() {
307+
assertEquals("urllib3", PythonControllerRealEnv.getDependencyName("urllib3~=1.26.0"));
308+
assertEquals("click", PythonControllerRealEnv.getDependencyName("click!=7.1.1"));
309+
assertEquals("certifi", PythonControllerRealEnv.getDependencyName("certifi>=2021.0.0"));
310+
assertEquals("package", PythonControllerRealEnv.getDependencyName("package~=2.0"));
311+
assertEquals("package", PythonControllerRealEnv.getDependencyName("package!=1.0.0"));
312+
}
313+
304314
/** Verifies getDependencyName strips PEP 508 extras from requirements. */
305315
@Test
306316
void get_Dependency_Name_with_extras() {
@@ -313,6 +323,17 @@ void get_Dependency_Name_with_extras() {
313323
"package[extra1]>=1.0 ; python_version >= \"3.8\""));
314324
}
315325

326+
/** Verifies getDependencyName handles extras combined with special version operators. */
327+
@Test
328+
void get_Dependency_Name_with_extras_and_special_operators() {
329+
assertEquals(
330+
"requests", PythonControllerRealEnv.getDependencyName("requests[security,socks]==2.25.1"));
331+
assertEquals("httpx", PythonControllerRealEnv.getDependencyName("httpx [http2] >=0.23.0"));
332+
assertEquals(
333+
"package",
334+
PythonControllerRealEnv.getDependencyName("package[extra]~=1.0 ; python_version >= \"3\""));
335+
}
336+
316337
@Test
317338
void automaticallyInstallPackageOnEnvironment() {
318339
assertFalse(pythonControllerRealEnv.automaticallyInstallPackageOnEnvironment());

0 commit comments

Comments
 (0)