KT-49721 Avoid matching from the same position after a zero-width match
In JS, RegExp can return a match before the specified start index, if it has matched at that index, but it is in the middle of a surrogate pair. Account for that when advancing to the next position after a zero-width match so that it doesn't get to the middle of SP.
This commit is contained in:
@@ -30,4 +30,6 @@ public actual val isFloat32RangeEnforced: Boolean get() = true
|
|||||||
|
|
||||||
public actual val supportsSuppressedExceptions: Boolean get() = true
|
public actual val supportsSuppressedExceptions: Boolean get() = true
|
||||||
|
|
||||||
public actual val supportsNamedCapturingGroup: Boolean get() = false
|
public actual val supportsNamedCapturingGroup: Boolean get() = false
|
||||||
|
|
||||||
|
public actual val regexSplitUnicodeCodePointHandling: Boolean get() = true
|
||||||
@@ -18,4 +18,6 @@ public expect val isFloat32RangeEnforced: Boolean
|
|||||||
|
|
||||||
public expect val supportsSuppressedExceptions: Boolean
|
public expect val supportsSuppressedExceptions: Boolean
|
||||||
|
|
||||||
public expect val supportsNamedCapturingGroup: Boolean
|
public expect val supportsNamedCapturingGroup: Boolean
|
||||||
|
|
||||||
|
public expect val regexSplitUnicodeCodePointHandling: Boolean
|
||||||
@@ -359,7 +359,20 @@ private fun RegExp.findNext(input: String, from: Int, nextPattern: RegExp): Matc
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun next(): MatchResult? =
|
override fun next(): MatchResult? =
|
||||||
nextPattern.findNext(input, if (range.isEmpty()) range.start + 1 else range.endInclusive + 1, nextPattern)
|
nextPattern.findNext(input, if (range.isEmpty()) advanceToNextCharacter(range.start) else range.endInclusive + 1, nextPattern)
|
||||||
|
|
||||||
|
private fun advanceToNextCharacter(index: Int): Int {
|
||||||
|
if (index < input.lastIndex) {
|
||||||
|
val code1 = input.asDynamic().charCodeAt(index).unsafeCast<Int>()
|
||||||
|
if (code1 in 0xD800..0xDBFF) {
|
||||||
|
val code2 = input.asDynamic().charCodeAt(index + 1).unsafeCast<Int>()
|
||||||
|
if (code2 in 0xDC00..0xDFFF) {
|
||||||
|
return index + 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,4 +28,6 @@ public actual val isFloat32RangeEnforced: Boolean = false
|
|||||||
actual val supportsSuppressedExceptions: Boolean get() = true
|
actual val supportsSuppressedExceptions: Boolean get() = true
|
||||||
|
|
||||||
// TODO: implement named group reference in replacement expression
|
// TODO: implement named group reference in replacement expression
|
||||||
public actual val supportsNamedCapturingGroup: Boolean get() = false
|
public actual val supportsNamedCapturingGroup: Boolean get() = false
|
||||||
|
|
||||||
|
public actual val regexSplitUnicodeCodePointHandling: Boolean get() = true
|
||||||
@@ -41,4 +41,6 @@ public actual val isFloat32RangeEnforced: Boolean = true
|
|||||||
|
|
||||||
public actual val supportsSuppressedExceptions: Boolean get() = !isJava6
|
public actual val supportsSuppressedExceptions: Boolean get() = !isJava6
|
||||||
|
|
||||||
public actual val supportsNamedCapturingGroup: Boolean get() = !isJava6
|
public actual val supportsNamedCapturingGroup: Boolean get() = !isJava6
|
||||||
|
|
||||||
|
public actual val regexSplitUnicodeCodePointHandling: Boolean get() = false
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
package test.text
|
package test.text
|
||||||
|
|
||||||
|
import test.regexSplitUnicodeCodePointHandling
|
||||||
import test.supportsNamedCapturingGroup
|
import test.supportsNamedCapturingGroup
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
@@ -354,6 +355,11 @@ class RegexTest {
|
|||||||
|
|
||||||
testSplitEquals("".split(""), "", emptyMatch)
|
testSplitEquals("".split(""), "", emptyMatch)
|
||||||
|
|
||||||
|
testSplitEquals(
|
||||||
|
if (regexSplitUnicodeCodePointHandling) listOf("", "\uD83D\uDE04", "\uD801", "") else listOf("", "\uD83D", "\uDE04", "\uD801", ""),
|
||||||
|
"\uD83D\uDE04\uD801", emptyMatch
|
||||||
|
)
|
||||||
|
|
||||||
val emptyMatchBeforeT = "(?=t)".toRegex()
|
val emptyMatchBeforeT = "(?=t)".toRegex()
|
||||||
|
|
||||||
testSplitEquals(listOf("", "tes", "t"), input, emptyMatchBeforeT)
|
testSplitEquals(listOf("", "tes", "t"), input, emptyMatchBeforeT)
|
||||||
@@ -397,4 +403,16 @@ class RegexTest {
|
|||||||
assertFailsWith<NoSuchElementException> { splits.next() }
|
assertFailsWith<NoSuchElementException> { splits.next() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun findAllEmoji() {
|
||||||
|
val input = "\uD83D\uDE04\uD801x"
|
||||||
|
val regex = ".".toRegex()
|
||||||
|
|
||||||
|
val matches = regex.findAll(input).toList()
|
||||||
|
val values = matches.map { it.value }
|
||||||
|
val ranges = matches.map { it.range }
|
||||||
|
|
||||||
|
assertEquals(listOf("\uD83D\uDE04", "\uD801", "x"), values)
|
||||||
|
assertEquals(listOf(0..1, 2..2, 3..3), ranges)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,3 +31,5 @@ actual val supportsSuppressedExceptions: Boolean get() = false
|
|||||||
|
|
||||||
// TODO: implement named group reference in replacement expression
|
// TODO: implement named group reference in replacement expression
|
||||||
public actual val supportsNamedCapturingGroup: Boolean get() = false
|
public actual val supportsNamedCapturingGroup: Boolean get() = false
|
||||||
|
|
||||||
|
public actual val regexSplitUnicodeCodePointHandling: Boolean get() = TODO()
|
||||||
Reference in New Issue
Block a user