Copilot commented on code in PR #15696:
URL: https://github.com/apache/grails-core/pull/15696#discussion_r3322085705


##########
grails-bootstrap/src/main/groovy/grails/plugins/VersionComparator.groovy:
##########
@@ -18,79 +18,66 @@
  */
 package grails.plugins
 
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
 import groovy.transform.CompileStatic
 
 /**
- * A comparator capable of sorting versions from from newest to oldest
+ * A comparator capable of sorting versions from newest to oldest.
+ *

Review Comment:
   The class-level Javadoc says this comparator sorts versions "from newest to 
oldest", but the comparator contract/tests show it orders from oldest to newest 
(e.g., `versions.sort(false, comparator)` in the spec expects 
ascending/oldest-first order). Please align the documentation with the actual 
ordering to avoid misuse by future callers.



##########
grails-bootstrap/src/main/groovy/grails/plugins/VersionComparator.groovy:
##########
@@ -112,4 +99,97 @@ class VersionComparator implements Comparator<String> {
     protected boolean isSnapshot(String version) {
         SNAPSHOT_SUFFIXES.any { String it -> version?.endsWith(it) }
     }
+
+    /**
+     * Splits a version into its leading numeric components and an optional 
trailing qualifier.
+     * The first token that is not purely numeric ends the numeric section. A 
token of the form
+     * {@code <digits>-<qualifier>} (for example {@code 0-RC1} from {@code 
7.0.0-RC1}) contributes
+     * its leading digits to the numeric section and the remainder becomes the 
qualifier.
+     */
+    private static ParsedVersion parse(String version) {
+        List<Integer> numbers = []
+        String qualifier = null
+        if (version) {
+            for (String token : version.split(/\./)) {
+                if (DIGITS.matcher(token).matches()) {
+                    numbers.add(Integer.parseInt(token))
+                    continue
+                }
+                Matcher matcher = NUMERIC_PREFIX.matcher(token)
+                if (matcher.matches()) {
+                    numbers.add(Integer.parseInt(matcher.group(1)))
+                    qualifier = normalizeQualifier(matcher.group(2))
+                } else {
+                    qualifier = normalizeQualifier(token)
+                }
+                break
+            }
+        }
+        return new ParsedVersion(numbers, qualifier)
+    }
+
+    private static int compareNumbers(List<Integer> a, List<Integer> b) {
+        int max = Math.max(a.size(), b.size())
+        for (int i = 0; i < max; i++) {
+            int left = i < a.size() ? a.get(i) : 0
+            int right = i < b.size() ? b.get(i) : 0
+            int result = Integer.compare(left, right)
+            if (result != 0) {
+                return result
+            }
+        }
+        return 0
+    }
+
+    private static int compareQualifiers(String q1, String q2) {
+        int tier = Integer.compare(qualifierTier(q1), qualifierTier(q2))
+        if (tier != 0) {
+            return tier
+        }
+        return Integer.compare(qualifierNumber(q1), qualifierNumber(q2))
+    }

Review Comment:
   `compareQualifiers` treats unknown qualifiers as `TIER_FINAL` as intended, 
but then still compares their trailing digits. That makes an unrecognised 
qualifier like `7.0.0-FOO2` sort *newer* than the final `7.0.0`, which 
contradicts the documented/previous behavior that unknown qualifiers should be 
treated as a final release (i.e., equal to no qualifier). Only milestone/RC 
qualifiers should participate in numeric suffix ordering once tiers are equal.



##########
grails-bootstrap/src/main/groovy/grails/plugins/VersionComparator.groovy:
##########
@@ -112,4 +99,97 @@ class VersionComparator implements Comparator<String> {
     protected boolean isSnapshot(String version) {
         SNAPSHOT_SUFFIXES.any { String it -> version?.endsWith(it) }
     }
+
+    /**
+     * Splits a version into its leading numeric components and an optional 
trailing qualifier.
+     * The first token that is not purely numeric ends the numeric section. A 
token of the form
+     * {@code <digits>-<qualifier>} (for example {@code 0-RC1} from {@code 
7.0.0-RC1}) contributes
+     * its leading digits to the numeric section and the remainder becomes the 
qualifier.
+     */
+    private static ParsedVersion parse(String version) {
+        List<Integer> numbers = []
+        String qualifier = null
+        if (version) {
+            for (String token : version.split(/\./)) {
+                if (DIGITS.matcher(token).matches()) {
+                    numbers.add(Integer.parseInt(token))
+                    continue
+                }
+                Matcher matcher = NUMERIC_PREFIX.matcher(token)
+                if (matcher.matches()) {
+                    numbers.add(Integer.parseInt(matcher.group(1)))
+                    qualifier = normalizeQualifier(matcher.group(2))
+                } else {
+                    qualifier = normalizeQualifier(token)
+                }
+                break
+            }
+        }
+        return new ParsedVersion(numbers, qualifier)
+    }
+
+    private static int compareNumbers(List<Integer> a, List<Integer> b) {
+        int max = Math.max(a.size(), b.size())
+        for (int i = 0; i < max; i++) {
+            int left = i < a.size() ? a.get(i) : 0
+            int right = i < b.size() ? b.get(i) : 0
+            int result = Integer.compare(left, right)
+            if (result != 0) {
+                return result
+            }
+        }
+        return 0
+    }
+
+    private static int compareQualifiers(String q1, String q2) {
+        int tier = Integer.compare(qualifierTier(q1), qualifierTier(q2))
+        if (tier != 0) {
+            return tier
+        }
+        return Integer.compare(qualifierNumber(q1), qualifierNumber(q2))
+    }

Review Comment:
   `compareQualifiers` treats unknown qualifiers as `TIER_FINAL` as intended, 
but then still compares their trailing digits. That makes an unrecognised 
qualifier like `7.0.0-FOO2` sort *newer* than the final `7.0.0`, which 
contradicts the documented/previous behavior that unknown qualifiers should be 
treated as a final release (i.e., equal to no qualifier). Only milestone/RC 
qualifiers should participate in numeric suffix ordering once tiers are equal.



##########
grails-bootstrap/src/main/groovy/grails/plugins/VersionComparator.groovy:
##########
@@ -18,79 +18,66 @@
  */
 package grails.plugins
 
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
 import groovy.transform.CompileStatic
 
 /**
- * A comparator capable of sorting versions from from newest to oldest
+ * A comparator capable of sorting versions from newest to oldest.
+ *

Review Comment:
   The class-level Javadoc says this comparator sorts versions "from newest to 
oldest", but the comparator contract/tests show it orders from oldest to newest 
(e.g., `versions.sort(false, comparator)` in the spec expects 
ascending/oldest-first order). Please align the documentation with the actual 
ordering to avoid misuse by future callers.



##########
grails-core/src/test/groovy/grails/plugins/VersionComparatorSpec.groovy:
##########
@@ -44,5 +44,48 @@ class VersionComparatorSpec extends Specification {
         "3.0.0"                | "3.0.0"                || 0
         "4.0.1"                | "3.1.110"              || 1
         "4.0.1"                | "3.0.0.BUILD-SNAPSHOT" || 1
+
+        // A pre-release (milestone/rc/snapshot) is older than the final 
release of the same number
+        "7.0.0-M1"             | "7.0.0"                || -1
+        "7.0.0"                | "7.0.0-M1"             || 1
+        "7.0.0-RC1"            | "7.0.0"                || -1
+        "7.0.0-SNAPSHOT"       | "7.0.0"                || -1
+        "7.0.0"                | "7.0.0-SNAPSHOT"       || 1
+
+        // The numeric version is compared before the qualifier, so the patch 
number is never lost
+        "7.0.5-M1"             | "7.0.0"                || 1
+        "7.0.0"                | "7.0.5-M1"             || -1
+        "7.0.1-M1"             | "7.0.0"                || 1
+        "7.0.0-RC1"            | "6.9.9"                || 1
+        "6.9.9"                | "7.0.0-RC1"            || -1
+
+        // Milestones and release candidates are ordered by their number, 
numerically not lexically
+        "7.0.0-M1"             | "7.0.0-M2"             || -1
+        "7.0.0-M2"             | "7.0.0-M1"             || 1
+        "7.0.0-RC1"            | "7.0.0-RC2"            || -1
+        "7.0.0-RC10"           | "7.0.0-RC2"            || 1
+
+        // Qualifier tiers: milestone < release candidate < snapshot < final
+        "7.0.0-M2"             | "7.0.0-RC1"            || -1
+        "7.0.0-RC1"            | "7.0.0-SNAPSHOT"       || -1
+        "7.0.0-M9"             | "7.0.0-SNAPSHOT"       || -1
+
+        // The dotted and hyphenated qualifier forms are equivalent, and 
matching is case insensitive
+        "7.0.0.M1"             | "7.0.0-M1"             || 0
+        "7.0.0.RC1"            | "7.0.0-RC1"            || 0
+        "7.0.0.BUILD-SNAPSHOT" | "7.0.0-SNAPSHOT"       || 0
+        "7.0.0-rc3"            | "7.0.0-RC3"            || 0

Review Comment:
   The updated comparator intentionally treats unrecognised qualifiers as a 
final release, but there isn't a spec case asserting this behavior (especially 
with trailing digits, which previously could be mis-ordered). Adding an 
explicit assertion here would prevent regressions like `7.0.0-FOO2` comparing 
newer than `7.0.0`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to