|
14 | 14 | import java.util.concurrent.CompletableFuture; |
15 | 15 | import java.util.concurrent.ExecutorService; |
16 | 16 | import java.util.concurrent.Executors; |
| 17 | +import java.util.regex.Matcher; |
| 18 | +import java.util.regex.Pattern; |
17 | 19 |
|
18 | 20 | import com.google.gson.Gson; |
19 | 21 | import com.semmle.js.dependencies.packument.PackageJson; |
@@ -55,17 +57,34 @@ private void addConstraint(Constraint constraint) { |
55 | 57 | } |
56 | 58 | } |
57 | 59 |
|
| 60 | + private static final Pattern semVerToken = Pattern.compile("[~^<>=|&-]+|\\d+(?:\\.[\\dx]+)+(?:-[\\w.-]*)?"); |
| 61 | + |
58 | 62 | /** |
59 | 63 | * Returns the first version number mentioned in the given constraints, excluding upper bounds such as `< 2.0.0`, |
60 | 64 | * or `null` if no such version number was found. |
61 | 65 | * <p> |
62 | 66 | * To help ensure deterministic version resolution, we prefer the version mentioned in the constraint, rather than |
63 | 67 | * the latest version satisfying the constraint (as the latter can change in time). |
64 | 68 | */ |
65 | | - private SemVer getPreferredVersionFromConstraints(List<VersionConstraint> constraints) { |
66 | | - for (VersionConstraint constraint : constraints) { |
67 | | - if (!constraint.getOperator().equals("<") && constraint.getVersion() != null) { |
68 | | - return constraint.getVersion(); |
| 69 | + public static SemVer getPreferredVersionFromVersionSpec(String versionSpec) { |
| 70 | + versionSpec = versionSpec.trim(); |
| 71 | + boolean isFirst = true; |
| 72 | + Matcher m = semVerToken.matcher(versionSpec); |
| 73 | + while (m.find()) { |
| 74 | + if (isFirst && m.start() != 0) { |
| 75 | + return null; // Not a version range |
| 76 | + } |
| 77 | + isFirst = false; |
| 78 | + String text = m.group(); |
| 79 | + if (text.equals("<")) { |
| 80 | + // Skip next token to ignore upper bound constraints like `< 2.0.0`. |
| 81 | + if (!m.find()) break; |
| 82 | + } |
| 83 | + if (text.charAt(0) >= '0' && text.charAt(0) <= '9') { |
| 84 | + SemVer semVer = SemVer.tryParse(text.replace("x", "0")); |
| 85 | + if (semVer != null) { |
| 86 | + return semVer; |
| 87 | + } |
69 | 88 | } |
70 | 89 | } |
71 | 90 | return null; |
@@ -103,8 +122,8 @@ private CompletableFuture<Void> fetchRelevantPackages(PackageJson pack, int dept |
103 | 122 | if (packagesInRepo.contains(targetName)) { |
104 | 123 | return; |
105 | 124 | } |
106 | | - List<VersionConstraint> constraints = VersionConstraint.parseVersionConstraints(targetVersions); |
107 | | - SemVer preferredVersion = getPreferredVersionFromConstraints(constraints); |
| 125 | + SemVer preferredVersion = getPreferredVersionFromVersionSpec(targetVersions); |
| 126 | + System.out.println("Prefer " + preferredVersion + " from " + targetVersions); |
108 | 127 | if (preferredVersion == null) return; |
109 | 128 | futures.add(fetcher.getPackument(targetName).exceptionally(ex -> null).thenCompose(targetPackument -> { |
110 | 129 | if (targetPackument == null) { |
@@ -198,7 +217,7 @@ public CompletableFuture<Void> installDependencies(PackageJson rootPackage, Path |
198 | 217 | }); |
199 | 218 | } |
200 | 219 |
|
201 | | - /** Entry point which installs dependencies from a given `package.json`, used for testing andbenchmarking. */ |
| 220 | + /** Entry point which installs dependencies from a given `package.json`, used for testing and benchmarking. */ |
202 | 221 | public static void main(String[] args) throws IOException { |
203 | 222 | ExecutorService executors = Executors.newFixedThreadPool(50); |
204 | 223 | try { |
|
0 commit comments