From f2b1839a7d3e6a0eed397ffd4f86e9e9a8e19b49 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Tue, 11 Jun 2019 14:48:06 +0200 Subject: [PATCH] SemVer: support for prerelease segment - post review fixes and additional tests GitOrigin-RevId: 076ccaae4af5f6fbb1d8c4dbb6111579ccc54be5 --- .../openapi/util/text/StringUtil.java | 11 +--- .../src/com/intellij/util/text/SemVer.java | 23 +++----- .../com/intellij/util/text/SemVerTest.java | 45 +++++++++----- .../intellij/util/text/StringUtilTest.java | 58 +++++++++++++++---- 4 files changed, 88 insertions(+), 49 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index d90c1bf3fd4e..02e8294cab1b 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -2850,18 +2850,13 @@ public class StringUtil extends StringUtilRt { int length1 = s1.length(); int length2 = s2.length(); int i = 0; - int j = 0; - for (; i < length1 && j < length2; i++, j++) { - int diff = compare(s1.charAt(i), s2.charAt(j), ignoreCase); + for (; i < length1 && i < length2; i++) { + int diff = compare(s1.charAt(i), s2.charAt(i), ignoreCase); if (diff != 0) { return diff; } } - if (i < length1) return +1; - if (j < length2) return -1; - if (length1 != length2) return length1 - length2; - - return ignoreCase ? compare(s1, s2, false) : 0; + return length1 - length2; } @Contract(pure = true) diff --git a/platform/util/src/com/intellij/util/text/SemVer.java b/platform/util/src/com/intellij/util/text/SemVer.java index 5893cfe06088..57dd86c1e23c 100644 --- a/platform/util/src/com/intellij/util/text/SemVer.java +++ b/platform/util/src/com/intellij/util/text/SemVer.java @@ -21,6 +21,8 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; +import static com.intellij.openapi.util.text.StringUtil.isNotNegativeNumber; + /** * Holds Semantic Version. */ @@ -151,13 +153,15 @@ public final class SemVer implements Comparable { CharSequence segment1 = new CharSequenceSubSequence(pre1, start1, end1); CharSequence segment2 = new CharSequenceSubSequence(pre2, start2, end2); - if (isNumeric(segment1)) { - if (!isNumeric(segment2)) { + if (isNotNegativeNumber(segment1)) { + if (!isNotNegativeNumber(segment2)) { + // According to SemVer specification numeric segments has lower precedence + // than non-numeric segments return -1; } diff = compareNumeric(segment1, segment2); } - else if (isNumeric(segment2)) { + else if (isNotNegativeNumber(segment2)) { return 1; } else { @@ -184,23 +188,12 @@ public final class SemVer implements Comparable { int length1 = segment1.length(); int length2 = segment2.length(); int diff = Integer.compare(length1, length2); - for (int i = 0; i <= length1 && diff == 0; i++) { + for (int i = 0; i < length1 && diff == 0; i++) { diff = segment1.charAt(i) - segment2.charAt(i); } return diff; } - private static boolean isNumeric(CharSequence segment) { - int length = segment.length(); - for (int i = 0; i < length; i++) { - int ch = segment.charAt(i); - if (ch < '0' || ch > '9') { - return false; - } - } - return true; - } - @Nullable public static SemVer parseFromText(@Nullable String text) { if (text != null) { diff --git a/platform/util/testSrc/com/intellij/util/text/SemVerTest.java b/platform/util/testSrc/com/intellij/util/text/SemVerTest.java index 395ce47c6c9c..be4670e8b1b0 100644 --- a/platform/util/testSrc/com/intellij/util/text/SemVerTest.java +++ b/platform/util/testSrc/com/intellij/util/text/SemVerTest.java @@ -15,6 +15,7 @@ */ package com.intellij.util.text; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Assert; import org.junit.Test; @@ -50,27 +51,37 @@ public class SemVerTest { assertThat(parse("11.123.0-a.b.c-1")).isEqualByComparingTo(parse("11.123.0-a.b.c-1")); assertPrecedence("0.10.0", "1.0.0"); - assertPrecedence("1.0.0","2.10.0"); + assertPrecedence("1.0.0", "2.10.0"); assertPrecedence("0.5.1000", "0.30.0"); - assertPrecedence("0.30.10","0.100.0"); + assertPrecedence("0.30.10", "0.100.0"); assertPrecedence("2.9.100", "2.9.123-test"); - assertPrecedence("2.9.123-test","2.9.124"); - assertPrecedence("2.9.123","2.9.124-test"); + assertPrecedence("2.9.123-test", "2.9.124"); + assertPrecedence("2.9.123", "2.9.124-test"); - assertPrecedence("1.2.3-a","1.2.3"); + assertPrecedence("1.2.3-a", "1.2.3"); - assertPrecedence("1.2.3-12","1.2.3-a"); - assertPrecedence("1.2.3-22","1.2.3-100"); - assertPrecedence("1.2.3-22","1.2.3-31"); + assertPrecedence("1.2.3-12", "1.2.3-a"); + assertPrecedence("1.2.3-22", "1.2.3-100"); + assertPrecedence("1.2.3-22", "1.2.3-31"); + assertPrecedence("1.2.3-22", "1.2.3-222"); - assertPrecedence("1.2.3-a.b.c","1.2.3-a.b.d"); - assertPrecedence("1.2.3-a.b.c","1.2.3-a.b.c.a"); + assertPrecedence("1.2.3-a.b.c", "1.2.3-a.b.d"); + assertPrecedence("1.2.3-a.b.c", "1.2.3-a.b.c.a"); - assertPrecedence("1.2.3-a.b.1","1.2.3-a.b.c"); - assertPrecedence("1.2.3-a.b.1","1.2.3-a.c.1"); - assertPrecedence("1.2.3-a.cbc.100","1.2.3-a.cca.1"); - assertPrecedence("1.2.3-a.cb.1","1.2.3-a.cba.1"); + assertPrecedence("1.2.3-a.b.1", "1.2.3-a.b.c"); + assertPrecedence("1.2.3-a.b.1", "1.2.3-a.c.1"); + assertPrecedence("1.2.3-a.cbc.100", "1.2.3-a.cca.1"); + assertPrecedence("1.2.3-a.cb.1", "1.2.3-a.cba.1"); + + // Example from SemVer documentation https://semver.org/#spec-item-11 + assertPrecedence("1.0.0-alpha", "1.0.0-alpha.1"); + assertPrecedence("1.0.0-alpha.1", "1.0.0-alpha.beta"); + assertPrecedence("1.0.0-alpha.beta", "1.0.0-beta"); + assertPrecedence("1.0.0-beta", "1.0.0-beta.2"); + assertPrecedence("1.0.0-beta.2", "1.0.0-beta.11"); + assertPrecedence("1.0.0-beta.11", "1.0.0-rc.1"); + assertPrecedence("1.0.0-rc.1", "1.0.0"); Assert.assertTrue(parse("4.12.5").isGreaterOrEqualThan(4, 12, 5)); Assert.assertTrue(parse("4.12.5-a").isGreaterOrEqualThan(4, 12, 5)); @@ -95,7 +106,11 @@ public class SemVerTest { assertThat(v1).isNotEqualTo(v2); } - private static void assertParsed(String version, int expectedMajor, int expectedMinor, int expectedPatch, @Nullable String expectedPreRelease) { + private static void assertParsed(@NotNull String version, + int expectedMajor, + int expectedMinor, + int expectedPatch, + @Nullable String expectedPreRelease) { assertThat(parse(version)).isEqualTo(new SemVer(version, expectedMajor, expectedMinor, expectedPatch, expectedPreRelease)); } diff --git a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java index 07afecb1b2e8..94fee88d55ab 100644 --- a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java +++ b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java @@ -9,6 +9,7 @@ import com.intellij.openapi.util.text.LineColumn; import com.intellij.openapi.util.text.NaturalComparator; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.LineSeparator; +import com.intellij.util.TripleFunction; import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.util.XmlStringUtil; import org.jdom.Verifier; @@ -199,16 +200,16 @@ public class StringUtilTest { @Test public void testNaturalCompare() { - final List numbers = Arrays.asList("1a000001", "000001a1", "001a0001", "0001A001" , "00001a01", "01a00001"); + final List numbers = Arrays.asList("1a000001", "000001a1", "001a0001", "0001A001", "00001a01", "01a00001"); numbers.sort(NaturalComparator.INSTANCE); - assertEquals(Arrays.asList("1a000001", "01a00001", "001a0001", "0001A001" , "00001a01", "000001a1"), numbers); + assertEquals(Arrays.asList("1a000001", "01a00001", "001a0001", "0001A001", "00001a01", "000001a1"), numbers); final List test = Arrays.asList("test011", "test10", "test10a", "test010"); test.sort(NaturalComparator.INSTANCE); assertEquals(Arrays.asList("test10", "test10a", "test010", "test011"), test); final List strings = Arrays.asList("Test99", "tes0", "test0", "testing", "test", "test99", "test011", "test1", - "test 3", "test2", "test10a", "test10", "1.2.10.5", "1.2.9.1"); + "test 3", "test2", "test10a", "test10", "1.2.10.5", "1.2.9.1"); strings.sort(NaturalComparator.INSTANCE); assertEquals(Arrays.asList("1.2.9.1", "1.2.10.5", "tes0", "test", "test0", "test1", "test2", "test 3", "test10", "test10a", "test011", "Test99", "test99", "testing"), strings); @@ -216,13 +217,14 @@ public class StringUtilTest { final List strings2 = Arrays.asList("t1", "t001", "T2", "T002", "T1", "t2"); strings2.sort(NaturalComparator.INSTANCE); assertEquals(Arrays.asList("T1", "t1", "t001", "T2", "t2", "T002"), strings2); - assertEquals(1 ,StringUtil.naturalCompare("7403515080361171695", "07403515080361171694")); + assertEquals(1, StringUtil.naturalCompare("7403515080361171695", "07403515080361171694")); assertEquals(-14, StringUtil.naturalCompare("_firstField", "myField1")); //idea-80853 final List strings3 = Arrays.asList("C148A_InsomniaCure", "C148B_Escape", "C148C_TersePrincess", "C148D_BagOfMice", "C148E_Porcelain"); strings3.sort(NaturalComparator.INSTANCE); - assertEquals(Arrays.asList("C148A_InsomniaCure", "C148B_Escape", "C148C_TersePrincess", "C148D_BagOfMice", "C148E_Porcelain"), strings3); + assertEquals(Arrays.asList("C148A_InsomniaCure", "C148B_Escape", "C148C_TersePrincess", "C148D_BagOfMice", "C148E_Porcelain"), + strings3); final List l = Arrays.asList("a0002", "a0 2", "a001"); l.sort(NaturalComparator.INSTANCE); @@ -374,7 +376,8 @@ public class StringUtilTest { @SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale") public void testReplaceReturnReplacementIfTextEqualsToReplacedText() { String str = "/tmp"; - assertSame(str, StringUtil.replace("$PROJECT_FILE$", "$PROJECT_FILE$".toLowerCase().toUpperCase() /* ensure new String instance */, str)); + assertSame(str, + StringUtil.replace("$PROJECT_FILE$", "$PROJECT_FILE$".toLowerCase().toUpperCase() /* ensure new String instance */, str)); } @Test @@ -453,6 +456,36 @@ public class StringUtilTest { assertTrue(StringUtil.contains("12", "2")); } + @Test + public void testCompareCharSequence() { + TripleFunction assertPrecedence = + (lesser, greater, ignoreCase) -> { + assertTrue(StringUtil.compare(lesser, greater, ignoreCase) < 0); + assertTrue(StringUtil.compare(greater, lesser, ignoreCase) > 0); + return true; + }; + TripleFunction assertEquality = + (lesser, greater, ignoreCase) -> { + assertEquals(0, StringUtil.compare(lesser, greater, ignoreCase)); + assertEquals(0, StringUtil.compare(greater, lesser, ignoreCase)); + return true; + }; + + assertPrecedence.fun("A","b", true); + assertPrecedence.fun("a","aa", true); + assertPrecedence.fun("abb","abC", true); + + assertPrecedence.fun("A","a", false); + assertPrecedence.fun("Aa","a", false); + assertPrecedence.fun("a","aa", false); + assertPrecedence.fun("-","A", false); + + assertEquality.fun("a","A",true); + assertEquality.fun("aa12b","Aa12B",true); + + assertEquality.fun("aa12b","aa12b",false); + } + @Test public void testDetectSeparators() { assertNull(StringUtil.detectSeparators("")); @@ -549,7 +582,7 @@ public class StringUtilTest { assertEquals("2 m 3 s", StringUtil.formatDurationApproximate(123456)); assertEquals("1 h 1 m", StringUtil.formatDurationApproximate(3659009)); assertEquals("2 h", StringUtil.formatDurationApproximate(7199000)); - assertEquals("1 d", StringUtil.formatDurationApproximate((23*60*60 + 59*60 + 59) * 1000L)); + assertEquals("1 d", StringUtil.formatDurationApproximate((23 * 60 * 60 + 59 * 60 + 59) * 1000L)); assertEquals("1 yr 1 mo", StringUtil.formatDurationApproximate(33786061001L)); } @@ -558,7 +591,8 @@ public class StringUtilTest { assertEquals("", XmlStringUtil.wrapInCDATA("abc")); assertEquals("]]>", XmlStringUtil.wrapInCDATA("abc]]>")); assertEquals("def]]>", XmlStringUtil.wrapInCDATA("abc]]>def")); - assertEquals("]]]>]]]>", XmlStringUtil.wrapInCDATA("123]]>]]>]]]>]]]>", + XmlStringUtil.wrapInCDATA("123]]>]]>