From 4a8e612c1c54287e7356fb21c2506b82f165a011 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Wed, 16 Apr 2025 16:35:40 -0400 Subject: [PATCH 1/7] Expose a platform-specific line separator Closes #448 --- core/api/kotlinx-io-core.api | 1 + core/api/kotlinx-io-core.klib.api | 2 ++ core/apple/src/-PlatformApple.kt | 13 +++++++++++++ core/common/src/Core.kt | 9 +++++++++ core/common/test/LineSeparatorTest.kt | 21 +++++++++++++++++++++ core/js/src/-PlatformJs.kt | 16 ++++++++++++++++ core/jvm/src/JvmCore.kt | 7 +++++++ core/mingw/src/-PlatformMingw.kt | 13 +++++++++++++ core/nodeFilesystemShared/src/node/os.kt | 5 +++++ core/unix/src/-PlatformUnix.kt | 13 +++++++++++++ core/wasmJs/src/-PlatformWasmJs.kt | 15 +++++++++++++++ core/wasmWasi/src/-PlatformWasmWasi.kt | 13 +++++++++++++ 12 files changed, 128 insertions(+) create mode 100644 core/apple/src/-PlatformApple.kt create mode 100644 core/common/test/LineSeparatorTest.kt create mode 100644 core/mingw/src/-PlatformMingw.kt create mode 100644 core/unix/src/-PlatformUnix.kt create mode 100644 core/wasmWasi/src/-PlatformWasmWasi.kt diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index e28bc59cd..25b6fa3a0 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -91,6 +91,7 @@ public abstract interface annotation class kotlinx/io/InternalIoApi : java/lang/ public final class kotlinx/io/JvmCoreKt { public static final fun asSink (Ljava/io/OutputStream;)Lkotlinx/io/RawSink; public static final fun asSource (Ljava/io/InputStream;)Lkotlinx/io/RawSource; + public static final fun getSystemLineSeparator ()Ljava/lang/String; } public abstract interface class kotlinx/io/RawSink : java/io/Flushable, java/lang/AutoCloseable { diff --git a/core/api/kotlinx-io-core.klib.api b/core/api/kotlinx-io-core.klib.api index 02d9050a7..c3e48dc9c 100644 --- a/core/api/kotlinx-io-core.klib.api +++ b/core/api/kotlinx-io-core.klib.api @@ -233,6 +233,8 @@ final val kotlinx.io.unsafe/SegmentReadContextImpl // kotlinx.io.unsafe/SegmentR final fun (): kotlinx.io.unsafe/SegmentReadContext // kotlinx.io.unsafe/SegmentReadContextImpl.|(){}[0] final val kotlinx.io.unsafe/SegmentWriteContextImpl // kotlinx.io.unsafe/SegmentWriteContextImpl|{}SegmentWriteContextImpl[0] final fun (): kotlinx.io.unsafe/SegmentWriteContext // kotlinx.io.unsafe/SegmentWriteContextImpl.|(){}[0] +final val kotlinx.io/SystemLineSeparator // kotlinx.io/SystemLineSeparator|{}SystemLineSeparator[0] + final fun (): kotlin/String // kotlinx.io/SystemLineSeparator.|(){}[0] final fun (kotlinx.io.files/Path).kotlinx.io.files/sink(): kotlinx.io/Sink // kotlinx.io.files/sink|sink@kotlinx.io.files.Path(){}[0] final fun (kotlinx.io.files/Path).kotlinx.io.files/source(): kotlinx.io/Source // kotlinx.io.files/source|source@kotlinx.io.files.Path(){}[0] diff --git a/core/apple/src/-PlatformApple.kt b/core/apple/src/-PlatformApple.kt new file mode 100644 index 000000000..1686fb31d --- /dev/null +++ b/core/apple/src/-PlatformApple.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2010-2025 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * The value of this property is always `"\n"`. + */ +public actual val SystemLineSeparator: String = "\n" diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index be72be491..05977a5c9 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -43,3 +43,12 @@ private class DiscardingSink : RawSink { override fun flush() {} override fun close() {} } + +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * The value of this property is platform-specific, but usually it is either `"\n"` or `"\r\n"`. + * + * See the documentation for a particular platform to get more information about the value of this property. + */ +public expect val SystemLineSeparator: String diff --git a/core/common/test/LineSeparatorTest.kt b/core/common/test/LineSeparatorTest.kt new file mode 100644 index 000000000..1d3b4a75f --- /dev/null +++ b/core/common/test/LineSeparatorTest.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2025 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +import kotlinx.io.files.isWindows +import kotlin.test.Test +import kotlin.test.assertEquals + +class LineSeparatorTest { + @Test + public fun testLineSeparator() { + if (isWindows) { + assertEquals("\r\n", SystemLineSeparator) + } else { + assertEquals("\n", SystemLineSeparator) + } + } +} diff --git a/core/js/src/-PlatformJs.kt b/core/js/src/-PlatformJs.kt index 7e198e25c..a21af1c69 100644 --- a/core/js/src/-PlatformJs.kt +++ b/core/js/src/-PlatformJs.kt @@ -5,6 +5,8 @@ package kotlinx.io +import kotlinx.io.node.os + public actual open class IOException : Exception { public actual constructor() : super() @@ -31,3 +33,17 @@ internal actual fun withCaughtException(block: () -> Unit): Throwable? { return t } } + +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol), + * in all other environments (like a web-browser), its value is always `"\n"`. + */ +public actual val SystemLineSeparator: String by lazy { + try { + os.EOL + } catch (_: Throwable) { + "\r\n" + } +} diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index e714f7740..a556c8916 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -109,3 +109,10 @@ internal val AssertionError.isAndroidGetsocknameError: Boolean get() { return cause != null && message?.contains("getsockname failed") ?: false } + +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * Returns the same value as [System.lineSeparator]. + */ +public actual val SystemLineSeparator: String = System.lineSeparator() diff --git a/core/mingw/src/-PlatformMingw.kt b/core/mingw/src/-PlatformMingw.kt new file mode 100644 index 000000000..c1ab9e3c7 --- /dev/null +++ b/core/mingw/src/-PlatformMingw.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2010-2025 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * The value of this property is always `"\r\n"`. + */ +public actual val SystemLineSeparator: String = "\r\n" diff --git a/core/nodeFilesystemShared/src/node/os.kt b/core/nodeFilesystemShared/src/node/os.kt index 431d60a8f..6a55a0691 100644 --- a/core/nodeFilesystemShared/src/node/os.kt +++ b/core/nodeFilesystemShared/src/node/os.kt @@ -16,6 +16,11 @@ internal external interface Os { * See https://nodejs.org/api/os.html#osplatform */ fun platform(): String + + /** + * See https://nodejs.org/api/os.html#oseol + */ + val EOL: String } internal expect val os: Os diff --git a/core/unix/src/-PlatformUnix.kt b/core/unix/src/-PlatformUnix.kt new file mode 100644 index 000000000..1686fb31d --- /dev/null +++ b/core/unix/src/-PlatformUnix.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2010-2025 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * The value of this property is always `"\n"`. + */ +public actual val SystemLineSeparator: String = "\n" diff --git a/core/wasmJs/src/-PlatformWasmJs.kt b/core/wasmJs/src/-PlatformWasmJs.kt index d93280360..9d71dd0aa 100644 --- a/core/wasmJs/src/-PlatformWasmJs.kt +++ b/core/wasmJs/src/-PlatformWasmJs.kt @@ -5,6 +5,8 @@ package kotlinx.io +import kotlinx.io.node.os + internal class JsException(message: String) : RuntimeException(message) internal actual fun withCaughtException(block: () -> Unit): Throwable? { @@ -23,3 +25,16 @@ private fun catchJsThrowable(block: () -> Unit): JsAny? = js("""{ } }""") +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol), + * in all other environments (like a web-browser), its value is always `"\n"`. + */ +public actual val SystemLineSeparator: String by lazy { + try { + os.EOL + } catch (_: Throwable) { + "\n" + } +} diff --git a/core/wasmWasi/src/-PlatformWasmWasi.kt b/core/wasmWasi/src/-PlatformWasmWasi.kt new file mode 100644 index 000000000..1686fb31d --- /dev/null +++ b/core/wasmWasi/src/-PlatformWasmWasi.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2010-2025 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +/** + * Sequence of characters used as a line separator by the underlying platform. + * + * The value of this property is always `"\n"`. + */ +public actual val SystemLineSeparator: String = "\n" From 20b64646dca11cfc1ecd2789afc4f11f56121151 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 19 Dec 2025 14:38:41 -0500 Subject: [PATCH 2/7] Return platform-specific path separator in any JS environment --- core/js/src/-PlatformJs.kt | 13 ++++++++++--- core/wasmJs/src/-PlatformWasmJs.kt | 14 +++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/core/js/src/-PlatformJs.kt b/core/js/src/-PlatformJs.kt index a21af1c69..5a95b22d4 100644 --- a/core/js/src/-PlatformJs.kt +++ b/core/js/src/-PlatformJs.kt @@ -5,6 +5,7 @@ package kotlinx.io +import kotlinx.browser.window import kotlinx.io.node.os public actual open class IOException : Exception { @@ -37,13 +38,19 @@ internal actual fun withCaughtException(block: () -> Unit): Throwable? { /** * Sequence of characters used as a line separator by the underlying platform. * - * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol), - * in all other environments (like a web-browser), its value is always `"\n"`. + * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol). + * In all other environments, it checks [Navigator.platform](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform) + * property and returns `"\r\n"` if the platform is Windows, and `"\n"` otherwise. */ public actual val SystemLineSeparator: String by lazy { try { os.EOL } catch (_: Throwable) { - "\r\n" + platformLineSeparator() } } + +private fun platformLineSeparator(): String { + val platform = window.navigator.platform + return if (platform.startsWith("Win", ignoreCase = true)) "\r\n" else "\n" +} diff --git a/core/wasmJs/src/-PlatformWasmJs.kt b/core/wasmJs/src/-PlatformWasmJs.kt index 9d71dd0aa..c1a6087ef 100644 --- a/core/wasmJs/src/-PlatformWasmJs.kt +++ b/core/wasmJs/src/-PlatformWasmJs.kt @@ -28,13 +28,21 @@ private fun catchJsThrowable(block: () -> Unit): JsAny? = js("""{ /** * Sequence of characters used as a line separator by the underlying platform. * - * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol), - * in all other environments (like a web-browser), its value is always `"\n"`. + * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol). + * In all other environments, it checks [Navigator.platform](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform) + * property and returns `"\r\n"` if the platform is Windows, and `"\n"` otherwise. */ public actual val SystemLineSeparator: String by lazy { try { os.EOL } catch (_: Throwable) { - "\n" + platformLineSeparator() } } + +private fun platformLineSeparator(): String { + return if (windowNavigatorPlatform().startsWith("Win", ignoreCase = true)) "\r\n" else "\n" +} + +@JsFun("() => window.navigator.platform") +private external fun windowNavigatorPlatform(): String From b6a2e560e94a99f7e2f7280e40de7434289edbf6 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 19 Dec 2025 15:42:01 -0500 Subject: [PATCH 3/7] For Web targets, derive SystemLineSeparator from Navigator.platform Remove dependency on NodeJs modules from SystemLineSeparator. Also, run wasmJs tests in browser, and migrate isWindows property to NodeJs-agnostic API. --- core/build.gradle.kts | 12 +++++++- core/common/src/-CommonPlatform.kt | 5 ++++ core/common/src/files/FileSystem.kt | 2 -- core/common/test/LineSeparatorTest.kt | 2 +- .../common/test/files/SmokeFileTestWindows.kt | 1 + core/js/src/-PlatformJs.kt | 29 ++++++++++--------- core/jvm/src/-JvmPlatform.kt | 2 ++ core/jvm/src/files/FileSystemJvm.kt | 2 -- .../jvm/test/files/SmokeFileTestWindowsJVM.kt | 1 + core/native/src/-NonJvmPlatform.kt | 5 ++++ core/native/src/files/FileSystemNative.kt | 3 -- .../src/files/FileSystemNodeJs.kt | 1 - .../test/files/SmokeFileTestWindowsNodeJs.kt | 1 + core/wasmJs/src/-PlatformWasmJs.kt | 28 ++++++++++-------- core/wasmWasi/src/-PlatformWasmWasi.kt | 7 +++++ core/wasmWasi/src/files/FileSystemWasm.kt | 3 -- core/wasmWasi/test/WasiFsTest.kt | 1 + 17 files changed, 67 insertions(+), 38 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 2799ec449..ddd997545 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,6 +3,8 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. */ +@file:OptIn(ExperimentalWasmDsl::class) + import org.gradle.internal.os.OperatingSystem import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl @@ -29,10 +31,18 @@ kotlin { useMocha { timeout = "300s" } + filter.setExcludePatterns("kotlinx.io.files.*") + } + } + } + wasmJs { + nodejs() + browser { + testTask { + filter.setExcludePatterns("kotlinx.io.files.*") } } } - @OptIn(ExperimentalWasmDsl::class) wasmWasi { nodejs { testTask { diff --git a/core/common/src/-CommonPlatform.kt b/core/common/src/-CommonPlatform.kt index a0c1e75d9..21db552a7 100644 --- a/core/common/src/-CommonPlatform.kt +++ b/core/common/src/-CommonPlatform.kt @@ -38,3 +38,8 @@ public expect open class EOFException : IOException { public constructor() public constructor(message: String?) } + +/** + * True if the underlying platform is Windows (assuming that it's possible to get this info). + */ +internal expect val isWindows: Boolean diff --git a/core/common/src/files/FileSystem.kt b/core/common/src/files/FileSystem.kt index 46c40af65..2a2ff704b 100644 --- a/core/common/src/files/FileSystem.kt +++ b/core/common/src/files/FileSystem.kt @@ -214,5 +214,3 @@ public expect class FileNotFoundException(message: String?) : IOException internal const val WindowsPathSeparator: Char = '\\' internal const val UnixPathSeparator: Char = '/' - -internal expect val isWindows: Boolean diff --git a/core/common/test/LineSeparatorTest.kt b/core/common/test/LineSeparatorTest.kt index 1d3b4a75f..ca03a9874 100644 --- a/core/common/test/LineSeparatorTest.kt +++ b/core/common/test/LineSeparatorTest.kt @@ -5,7 +5,7 @@ package kotlinx.io -import kotlinx.io.files.isWindows +import kotlinx.io.isWindows import kotlin.test.Test import kotlin.test.assertEquals diff --git a/core/common/test/files/SmokeFileTestWindows.kt b/core/common/test/files/SmokeFileTestWindows.kt index 23994807e..5c1178419 100644 --- a/core/common/test/files/SmokeFileTestWindows.kt +++ b/core/common/test/files/SmokeFileTestWindows.kt @@ -5,6 +5,7 @@ package kotlinx.io.files +import kotlinx.io.isWindows import kotlin.test.* class SmokeFileTestWindows { diff --git a/core/js/src/-PlatformJs.kt b/core/js/src/-PlatformJs.kt index 5a95b22d4..8ee661e3b 100644 --- a/core/js/src/-PlatformJs.kt +++ b/core/js/src/-PlatformJs.kt @@ -5,9 +5,6 @@ package kotlinx.io -import kotlinx.browser.window -import kotlinx.io.node.os - public actual open class IOException : Exception { public actual constructor() : super() @@ -38,19 +35,25 @@ internal actual fun withCaughtException(block: () -> Unit): Throwable? { /** * Sequence of characters used as a line separator by the underlying platform. * - * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol). - * In all other environments, it checks [Navigator.platform](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform) - * property and returns `"\r\n"` if the platform is Windows, and `"\n"` otherwise. + * Value of this property depends on [Navigator.platform](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform): + * it is `"\r\n"` on Windows, and `"\n"` on all other platforms. */ public actual val SystemLineSeparator: String by lazy { - try { - os.EOL - } catch (_: Throwable) { - platformLineSeparator() + if (isWindows) { + "\r\n" + } else { + "\n" } } -private fun platformLineSeparator(): String { - val platform = window.navigator.platform - return if (platform.startsWith("Win", ignoreCase = true)) "\r\n" else "\n" +internal actual val isWindows: Boolean by lazy { + getPlatformName().startsWith("Win", ignoreCase = true) } + +private fun getPlatformName(): String = js( + """ + (typeof navigator !== "undefined" && navigator.platform) + || (typeof window !== "undefined" && typeof window.navigator !== "undefined" && window.navigator.platform) + || "unknown" + """ +) diff --git a/core/jvm/src/-JvmPlatform.kt b/core/jvm/src/-JvmPlatform.kt index 92e17424a..0d65280ef 100644 --- a/core/jvm/src/-JvmPlatform.kt +++ b/core/jvm/src/-JvmPlatform.kt @@ -24,3 +24,5 @@ package kotlinx.io public actual typealias IOException = java.io.IOException public actual typealias EOFException = java.io.EOFException + +internal actual val isWindows: Boolean = System.getProperty("os.name")?.startsWith("Windows") ?: false diff --git a/core/jvm/src/files/FileSystemJvm.kt b/core/jvm/src/files/FileSystemJvm.kt index b85ebdb7e..d19e75216 100644 --- a/core/jvm/src/files/FileSystemJvm.kt +++ b/core/jvm/src/files/FileSystemJvm.kt @@ -113,5 +113,3 @@ public actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl() public actual val SystemTemporaryDirectory: Path = Path(System.getProperty("java.io.tmpdir")) public actual typealias FileNotFoundException = java.io.FileNotFoundException - -internal actual val isWindows: Boolean = System.getProperty("os.name")?.startsWith("Windows") ?: false diff --git a/core/jvm/test/files/SmokeFileTestWindowsJVM.kt b/core/jvm/test/files/SmokeFileTestWindowsJVM.kt index 76d6ec4bc..d873a68b8 100644 --- a/core/jvm/test/files/SmokeFileTestWindowsJVM.kt +++ b/core/jvm/test/files/SmokeFileTestWindowsJVM.kt @@ -5,6 +5,7 @@ package kotlinx.io.files +import kotlinx.io.isWindows import kotlin.test.Test import kotlin.test.assertEquals diff --git a/core/native/src/-NonJvmPlatform.kt b/core/native/src/-NonJvmPlatform.kt index 9294c3190..e25daf3c7 100644 --- a/core/native/src/-NonJvmPlatform.kt +++ b/core/native/src/-NonJvmPlatform.kt @@ -20,6 +20,8 @@ */ package kotlinx.io +import kotlin.experimental.ExperimentalNativeApi + public actual open class IOException : Exception { public actual constructor() : super() @@ -37,3 +39,6 @@ public actual open class EOFException : IOException { public constructor(message: String?, cause: Throwable?) : super(message, cause) } + +@OptIn(ExperimentalNativeApi::class) +internal actual val isWindows: Boolean = Platform.osFamily == OsFamily.WINDOWS diff --git a/core/native/src/files/FileSystemNative.kt b/core/native/src/files/FileSystemNative.kt index 1fa719b15..9a1ce9385 100644 --- a/core/native/src/files/FileSystemNative.kt +++ b/core/native/src/files/FileSystemNative.kt @@ -121,9 +121,6 @@ public actual open class FileNotFoundException actual constructor( // 777 in octal, rwx for all (owner, group and others). internal const val PermissionAllowAll: UShort = 511u -@OptIn(ExperimentalNativeApi::class) -internal actual val isWindows: Boolean = Platform.osFamily == OsFamily.WINDOWS - internal expect class OpaqueDirEntry : AutoCloseable { fun readdir(): String? override fun close() diff --git a/core/nodeFilesystemShared/src/files/FileSystemNodeJs.kt b/core/nodeFilesystemShared/src/files/FileSystemNodeJs.kt index 810b5ba02..ba36e42b0 100644 --- a/core/nodeFilesystemShared/src/files/FileSystemNodeJs.kt +++ b/core/nodeFilesystemShared/src/files/FileSystemNodeJs.kt @@ -128,4 +128,3 @@ public actual open class FileNotFoundException actual constructor( message: String?, ) : IOException(message) -internal actual val isWindows = os.platform() == "win32" diff --git a/core/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt b/core/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt index bb84a4d03..d85fdaa0d 100644 --- a/core/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt +++ b/core/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt @@ -5,6 +5,7 @@ package kotlinx.io.files +import kotlinx.io.isWindows import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull diff --git a/core/wasmJs/src/-PlatformWasmJs.kt b/core/wasmJs/src/-PlatformWasmJs.kt index c1a6087ef..0f584e1a6 100644 --- a/core/wasmJs/src/-PlatformWasmJs.kt +++ b/core/wasmJs/src/-PlatformWasmJs.kt @@ -28,21 +28,25 @@ private fun catchJsThrowable(block: () -> Unit): JsAny? = js("""{ /** * Sequence of characters used as a line separator by the underlying platform. * - * In NodeJS-compatible environments, this property derives value from [os.EOL](https://nodejs.org/api/os.html#oseol). - * In all other environments, it checks [Navigator.platform](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform) - * property and returns `"\r\n"` if the platform is Windows, and `"\n"` otherwise. + * Value of this property depends on [Navigator.platform](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform): + * it is `"\r\n"` on Windows, and `"\n"` on all other platforms. */ public actual val SystemLineSeparator: String by lazy { - try { - os.EOL - } catch (_: Throwable) { - platformLineSeparator() + if (isWindows) { + "\r\n" + } else { + "\n" } } -private fun platformLineSeparator(): String { - return if (windowNavigatorPlatform().startsWith("Win", ignoreCase = true)) "\r\n" else "\n" -} - -@JsFun("() => window.navigator.platform") +@JsFun(""" + () => + (typeof navigator !== "undefined" && navigator.platform) + || (typeof window !== "undefined" && typeof window.navigator !== "undefined" && window.navigator.platform) + || "unknown" +""") private external fun windowNavigatorPlatform(): String + +internal actual val isWindows by lazy { + windowNavigatorPlatform().startsWith("Win", ignoreCase = true) +} diff --git a/core/wasmWasi/src/-PlatformWasmWasi.kt b/core/wasmWasi/src/-PlatformWasmWasi.kt index 1686fb31d..4087fa4ad 100644 --- a/core/wasmWasi/src/-PlatformWasmWasi.kt +++ b/core/wasmWasi/src/-PlatformWasmWasi.kt @@ -11,3 +11,10 @@ package kotlinx.io * The value of this property is always `"\n"`. */ public actual val SystemLineSeparator: String = "\n" + +/** + * The property affects paths processing and [SystemLineSeparator]. + * In Wasi, paths are always '/'-delimited, so it does not really affect paths processing. + * As of [SystemLineSeparator], it seems like there's not so much we can do right now. + */ +internal actual val isWindows: Boolean get() = false diff --git a/core/wasmWasi/src/files/FileSystemWasm.kt b/core/wasmWasi/src/files/FileSystemWasm.kt index 252fbe30b..8ab0860c2 100644 --- a/core/wasmWasi/src/files/FileSystemWasm.kt +++ b/core/wasmWasi/src/files/FileSystemWasm.kt @@ -372,9 +372,6 @@ public actual open class FileNotFoundException actual constructor( message: String?, ) : IOException(message) -// The property affects only paths processing and in Wasi paths are always '/'-delimited. -internal actual val isWindows: Boolean get() = false - internal object PreOpens { data class PreOpen(val path: Path, val fd: Int) diff --git a/core/wasmWasi/test/WasiFsTest.kt b/core/wasmWasi/test/WasiFsTest.kt index 40a835be5..9296f666a 100644 --- a/core/wasmWasi/test/WasiFsTest.kt +++ b/core/wasmWasi/test/WasiFsTest.kt @@ -7,6 +7,7 @@ package kotlinx.io.files import kotlinx.io.IOException import kotlinx.io.buffered +import kotlinx.io.isWindows import kotlinx.io.readLine import kotlinx.io.writeString import kotlin.test.* From b516e57f53964e088a68fd09b030066c0d1473e6 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 19 Dec 2025 15:45:07 -0500 Subject: [PATCH 4/7] Renaming --- core/wasmJs/src/-PlatformWasmJs.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/wasmJs/src/-PlatformWasmJs.kt b/core/wasmJs/src/-PlatformWasmJs.kt index 0f584e1a6..d8ddd31f6 100644 --- a/core/wasmJs/src/-PlatformWasmJs.kt +++ b/core/wasmJs/src/-PlatformWasmJs.kt @@ -5,8 +5,6 @@ package kotlinx.io -import kotlinx.io.node.os - internal class JsException(message: String) : RuntimeException(message) internal actual fun withCaughtException(block: () -> Unit): Throwable? { @@ -45,8 +43,8 @@ public actual val SystemLineSeparator: String by lazy { || (typeof window !== "undefined" && typeof window.navigator !== "undefined" && window.navigator.platform) || "unknown" """) -private external fun windowNavigatorPlatform(): String +private external fun getPlatformName(): String internal actual val isWindows by lazy { - windowNavigatorPlatform().startsWith("Win", ignoreCase = true) + getPlatformName().startsWith("Win", ignoreCase = true) } From 3e3a6c8e83932f3127b9d0a5d1ee8aa0c7d63cd8 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 19 Dec 2025 15:56:29 -0500 Subject: [PATCH 5/7] Use mocha for WasmJs/Browser tests --- core/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ddd997545..ab5fe16d4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -39,6 +39,7 @@ kotlin { nodejs() browser { testTask { + useMocha {} filter.setExcludePatterns("kotlinx.io.files.*") } } From 990fdeca12a71d86060395a678475f5b94b6c705 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 19 Dec 2025 16:56:10 -0500 Subject: [PATCH 6/7] Disable browser test for WasmJs --- core/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ab5fe16d4..b47ed2b8e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -37,12 +37,16 @@ kotlin { } wasmJs { nodejs() + /* browser { testTask { - useMocha {} + useKarma { + // how to configure browsers available in CI? + } filter.setExcludePatterns("kotlinx.io.files.*") } } + */ } wasmWasi { nodejs { From 969de55177f15d699a0f72837bd4bc71ffdf58ee Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 19 Dec 2025 17:00:35 -0500 Subject: [PATCH 7/7] Simplify platform check --- core/js/src/-PlatformJs.kt | 4 +--- core/wasmJs/src/-PlatformWasmJs.kt | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/core/js/src/-PlatformJs.kt b/core/js/src/-PlatformJs.kt index 8ee661e3b..5f868d230 100644 --- a/core/js/src/-PlatformJs.kt +++ b/core/js/src/-PlatformJs.kt @@ -52,8 +52,6 @@ internal actual val isWindows: Boolean by lazy { private fun getPlatformName(): String = js( """ - (typeof navigator !== "undefined" && navigator.platform) - || (typeof window !== "undefined" && typeof window.navigator !== "undefined" && window.navigator.platform) - || "unknown" + (typeof navigator !== "undefined" && navigator.platform) || "unknown" """ ) diff --git a/core/wasmJs/src/-PlatformWasmJs.kt b/core/wasmJs/src/-PlatformWasmJs.kt index d8ddd31f6..b3fabfc3b 100644 --- a/core/wasmJs/src/-PlatformWasmJs.kt +++ b/core/wasmJs/src/-PlatformWasmJs.kt @@ -38,10 +38,7 @@ public actual val SystemLineSeparator: String by lazy { } @JsFun(""" - () => - (typeof navigator !== "undefined" && navigator.platform) - || (typeof window !== "undefined" && typeof window.navigator !== "undefined" && window.navigator.platform) - || "unknown" + () => (typeof navigator !== "undefined" && navigator.platform) || "unknown" """) private external fun getPlatformName(): String