Skip to content

Commit 476ac7d

Browse files
authored
Fix surrogates, options, and version bumps (#10)
* bump gradle version * version bumps - bump Kotlin, Kotest, KxSerialization, Jacoco - downgrade jvm to 8 (wider compatibility) * fix options not being configured soon enough * fixes - make sure JsonLiterals are parsed correctly - don't escape ' or " unless they're not the quote char - handle & test invalid surrogates (credit to Synt4xErr0r4 Synt4xErr0r4@aff8a0f) * update opt-in args
1 parent 68f9102 commit 476ac7d

File tree

11 files changed

+212
-118
lines changed

11 files changed

+212
-118
lines changed

Diff for: build.gradle.kts

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
22

33
plugins {
4-
kotlin("jvm") version "1.6.10"
4+
kotlin("jvm") version "1.7.10"
55
jacoco
66
`java-library`
77
id("me.qoomon.git-versioning") version "5.1.1"
@@ -11,7 +11,7 @@ plugins {
1111
dependencies {
1212
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
1313

14-
val kotlinxSerializationVersion = "1.3.1"
14+
val kotlinxSerializationVersion = "1.3.3"
1515
implementation(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:$kotlinxSerializationVersion"))
1616
api("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinxSerializationVersion")
1717
api("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
@@ -23,7 +23,7 @@ dependencies {
2323
because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions")
2424
}
2525

26-
val kotestVersion = "5.0.3"
26+
val kotestVersion = "5.3.1"
2727
testImplementation(platform("io.kotest:kotest-bom:$kotestVersion"))
2828
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
2929
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
@@ -51,23 +51,23 @@ java {
5151

5252
kotlin {
5353
jvmToolchain {
54-
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(11))
54+
languageVersion.set(JavaLanguageVersion.of(8))
5555
}
5656
}
5757

5858
tasks.withType<KotlinCompile>().configureEach {
5959

6060
kotlinOptions {
61-
jvmTarget = "11"
62-
apiVersion = "1.6"
63-
languageVersion = "1.6"
61+
jvmTarget = "1.8"
62+
apiVersion = "1.7"
63+
languageVersion = "1.7"
6464
}
6565

6666
kotlinOptions.freeCompilerArgs += listOf(
67-
"-Xopt-in=kotlin.RequiresOptIn",
68-
"-Xopt-in=kotlin.ExperimentalStdlibApi",
69-
"-Xopt-in=kotlin.time.ExperimentalTime",
70-
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
67+
"-opt-in=kotlin.RequiresOptIn",
68+
"-opt-in=kotlin.ExperimentalStdlibApi",
69+
"-opt-in=kotlin.time.ExperimentalTime",
70+
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
7171
)
7272
}
7373

@@ -78,7 +78,7 @@ tasks.withType<Test> {
7878
}
7979

8080
jacoco {
81-
toolVersion = "0.8.7"
81+
toolVersion = "0.8.8"
8282
}
8383

8484
tasks.withType<JacocoReport> {
@@ -103,7 +103,7 @@ tasks.withType<JavaCompile> {
103103
}
104104

105105
tasks.wrapper {
106-
gradleVersion = "7.3.3"
106+
gradleVersion = "7.5"
107107
distributionType = Wrapper.DistributionType.ALL
108108
}
109109
tasks.assemble { dependsOn(tasks.wrapper) }

Diff for: gradle/wrapper/gradle-wrapper.jar

1.19 KB
Binary file not shown.

Diff for: gradle/wrapper/gradle-wrapper.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

Diff for: gradlew

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/sh
22

33
#
4-
# Copyright © 2015-2021 the original authors.
4+
# Copyright © 2015-2021 the original authors.
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
3232
# Busybox and similar reduced shells will NOT work, because this script
3333
# requires all of these POSIX shell features:
3434
# * functions;
35-
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36-
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37-
# * compound commands having a testable exit status, especially «case»;
38-
# * various built-in commands including «command», «set», and «ulimit».
35+
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36+
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37+
# * compound commands having a testable exit status, especially «case»;
38+
# * various built-in commands including «command», «set», and «ulimit».
3939
#
4040
# Important for patching:
4141
#
@@ -205,6 +205,12 @@ set -- \
205205
org.gradle.wrapper.GradleWrapperMain \
206206
"$@"
207207

208+
# Stop when "xargs" is not available.
209+
if ! command -v xargs >/dev/null 2>&1
210+
then
211+
die "xargs is not available"
212+
fi
213+
208214
# Use "xargs" to parse quoted args.
209215
#
210216
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

Diff for: gradlew.bat

+8-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
@rem limitations under the License.
1515
@rem
1616

17-
@if "%DEBUG%" == "" @echo off
17+
@if "%DEBUG%"=="" @echo off
1818
@rem ##########################################################################
1919
@rem
2020
@rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
2525
if "%OS%"=="Windows_NT" setlocal
2626

2727
set DIRNAME=%~dp0
28-
if "%DIRNAME%" == "" set DIRNAME=.
28+
if "%DIRNAME%"=="" set DIRNAME=.
2929
set APP_BASE_NAME=%~n0
3030
set APP_HOME=%DIRNAME%
3131

@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
4040

4141
set JAVA_EXE=java.exe
4242
%JAVA_EXE% -version >NUL 2>&1
43-
if "%ERRORLEVEL%" == "0" goto execute
43+
if %ERRORLEVEL% equ 0 goto execute
4444

4545
echo.
4646
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
7575

7676
:end
7777
@rem End local scope for the variables with windows NT shell
78-
if "%ERRORLEVEL%"=="0" goto mainEnd
78+
if %ERRORLEVEL% equ 0 goto mainEnd
7979

8080
:fail
8181
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
8282
rem the _cmd.exe /c_ return code!
83-
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84-
exit /b 1
83+
set EXIT_CODE=%ERRORLEVEL%
84+
if %EXIT_CODE% equ 0 set EXIT_CODE=1
85+
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
86+
exit /b %EXIT_CODE%
8587

8688
:mainEnd
8789
if "%OS%"=="Windows_NT" endlocal

Diff for: src/main/kotlin/at/syntaxerror/json5/JSONOptions.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,5 @@ data class JSONOptions(
6464
*/
6565
var quoteSingle: Boolean = false,
6666

67-
var indentFactor: UInt = 2u
67+
var indentFactor: UInt = 2u,
6868
)

Diff for: src/main/kotlin/at/syntaxerror/json5/JSONParser.kt

+11-7
Original file line numberDiff line numberDiff line change
@@ -223,18 +223,20 @@ class JSONParser(
223223
return codepoint.toChar()
224224
}
225225

226-
private fun checkSurrogate(hi: Char, lo: Char) {
226+
private fun checkSurrogate(high: Char, low: Char) {
227227
if (j5.options.allowInvalidSurrogates) {
228228
return
229-
}
230-
if (!Character.isHighSurrogate(hi) || !Character.isLowSurrogate(lo)) {
231-
return
232-
}
233-
if (!Character.isSurrogatePair(hi, lo)) {
229+
} else if (
230+
(high.isHighSurrogate() && !low.isSurrogate())
231+
||
232+
(!high.isSurrogate() && low.isLowSurrogate())
233+
||
234+
!Character.isSurrogatePair(high, low)
235+
) {
234236
throw createSyntaxException(
235237
String.format(
236238
"Invalid surrogate pair: U+%04X and U+%04X",
237-
hi, lo
239+
high.code, low.code,
238240
)
239241
)
240242
}
@@ -254,6 +256,7 @@ class JSONParser(
254256
prev = n
255257
n = next()
256258
if (n == quote) {
259+
checkSurrogate(prev, 0.toChar())
257260
break
258261
}
259262
if (isLineTerminator(n) && n.code != 0x2028 && n.code != 0x2029) {
@@ -383,6 +386,7 @@ class JSONParser(
383386
n = unicodeEscape(true, isNotEmpty)
384387
} else if (!isMemberNameChar(n, isNotEmpty)) {
385388
back()
389+
checkSurrogate(prev, 0.toChar())
386390
break
387391
}
388392
checkSurrogate(prev, n)

Diff for: src/main/kotlin/at/syntaxerror/json5/JSONStringify.kt

+39-16
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ package at.syntaxerror.json5
2525

2626
import kotlinx.serialization.json.JsonArray
2727
import kotlinx.serialization.json.JsonObject
28+
import kotlinx.serialization.json.JsonPrimitive
29+
import kotlinx.serialization.json.booleanOrNull
30+
import kotlinx.serialization.json.contentOrNull
31+
import kotlinx.serialization.json.doubleOrNull
32+
import kotlinx.serialization.json.floatOrNull
33+
import kotlinx.serialization.json.intOrNull
34+
import kotlinx.serialization.json.longOrNull
2835

2936
/**
3037
* A utility class for serializing [JSONObjects][DecodeJson5Object] and [JSONArrays][DecodeJson5Array]
@@ -36,9 +43,10 @@ class JSONStringify(
3643
private val options: JSONOptions
3744
) {
3845

39-
private val quoteToken = if (options.quoteSingle) '\'' else '"'
40-
private val emptyString = "$quoteToken$quoteToken"
41-
private val indentFactor = options.indentFactor
46+
private val quoteToken: Char get() = if (options.quoteSingle) '\'' else '"'
47+
private val emptyString: String get() = "$quoteToken$quoteToken"
48+
private val indentFactor: UInt get() = options.indentFactor
49+
private val isIndented: Boolean get() = indentFactor > 0u
4250

4351
/**
4452
* Converts a JSONObject into its string representation. The indentation factor enables
@@ -47,7 +55,8 @@ class JSONStringify(
4755
* characters.
4856
*
4957
* `indentFactor = 2`:
50-
* ```
58+
*
59+
* ```json
5160
* {
5261
* "key0": "value0",
5362
* "key1": {
@@ -59,7 +68,7 @@ class JSONStringify(
5968
*
6069
* `indentFactor = 0`:
6170
*
62-
* ```
71+
* ```json
6372
* {"key0":"value0","key1":{"nested":123},"key2":false}
6473
* ```
6574
*/
@@ -68,7 +77,6 @@ class JSONStringify(
6877
indent: String = "",
6978
): String {
7079
val childIndent = indent + " ".repeat(indentFactor.toInt())
71-
val isIndented = indentFactor > 0u
7280

7381
val sb = StringBuilder()
7482
sb.append('{')
@@ -99,7 +107,8 @@ class JSONStringify(
99107
*
100108
*
101109
* `indentFactor = 2`:
102-
* ```
110+
*
111+
* ```json
103112
* [
104113
* "value",
105114
* {
@@ -110,7 +119,8 @@ class JSONStringify(
110119
* ```
111120
*
112121
* `indentFactor = 0`:
113-
* ```
122+
*
123+
* ```json
114124
* ["value",{"nested":123},false]
115125
* ```
116126
*/
@@ -119,7 +129,6 @@ class JSONStringify(
119129
indent: String = "",
120130
): String {
121131
val childIndent = indent + " ".repeat(indentFactor.toInt())
122-
val isIndented = indentFactor > 0u
123132

124133
val sb = StringBuilder()
125134
sb.append('[')
@@ -144,18 +153,21 @@ class JSONStringify(
144153
indent: String,
145154
): String {
146155
return when (value) {
147-
null -> "null"
148-
is JsonObject -> encodeObject(value, indent)
149-
is JsonArray -> encodeArray(value, indent)
150-
is String -> escapeString(value)
151-
is Double -> {
156+
null -> "null"
157+
158+
is JsonObject -> encodeObject(value, indent)
159+
is JsonArray -> encodeArray(value, indent)
160+
is JsonPrimitive -> encode(value.anyOrNull, indent)
161+
162+
is String -> escapeString(value)
163+
is Double -> {
152164
when {
153165
!options.allowNaN && value.isNaN() -> throw JSONException("Illegal NaN in JSON")
154166
!options.allowInfinity && value.isInfinite() -> throw JSONException("Illegal Infinity in JSON")
155167
else -> value.toString()
156168
}
157169
}
158-
else -> value.toString()
170+
else -> value.toString()
159171
}
160172
}
161173

@@ -172,6 +184,7 @@ class JSONStringify(
172184
) { c: Char ->
173185

174186
val formattedChar: String? = when (c) {
187+
in listOf('\'', '\"') -> if (c == quoteToken) "\\$quoteToken" else "$c"
175188
quoteToken -> "\\$quoteToken"
176189
in Json5EscapeSequence.escapableChars -> Json5EscapeSequence.asEscapedString(c)
177190
else -> when (c.category) {
@@ -181,12 +194,22 @@ class JSONStringify(
181194
CharCategory.CONTROL,
182195
CharCategory.PRIVATE_USE,
183196
CharCategory.SURROGATE,
184-
CharCategory.UNASSIGNED -> String.format("\\u%04X", c)
197+
CharCategory.UNASSIGNED -> String.format("\\u%04X", c.code)
185198
else -> null
186199
}
187200
}
188201
formattedChar ?: c.toString()
189202
}
190203
}
191204
}
205+
206+
companion object {
207+
private val JsonPrimitive.anyOrNull: Any?
208+
get() = intOrNull
209+
?: longOrNull
210+
?: doubleOrNull
211+
?: floatOrNull
212+
?: booleanOrNull
213+
?: contentOrNull
214+
}
192215
}

Diff for: src/main/kotlin/at/syntaxerror/json5/Json5EscapeSequence.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ enum class Json5EscapeSequence(
2020
;
2121

2222
companion object {
23-
private val mapCharToRepresentation = values().associate { it.char to it.escaped }
23+
private val mapCharToRepresentation: Map<Char, String> =
24+
values().associate { it.char to it.escaped }
2425

25-
val escapableChars = values().map { it.char }
26+
val escapableChars: List<Char> = values().map { it.char }
2627

2728
fun asEscapedString(char: Char): String? = mapCharToRepresentation[char]
2829

29-
fun isEscapable(char: Char) = mapCharToRepresentation.containsKey(char)
30+
fun isEscapable(char: Char): Boolean = mapCharToRepresentation.containsKey(char)
3031
}
3132
}

0 commit comments

Comments
 (0)