diff --git a/BUILD.bazel b/BUILD.bazel
index 0b44e2cedc99..a293175b3e92 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -364,6 +364,8 @@ jvm_library(
"//plugins/jsonpath",
"//plugins/jsonpath:jsonpath_test_lib",
"//plugins/ui-designer/jps-plugin/tests:tests_test_lib",
+ "//platform/non-modal-welcome-screen",
+ "//platform/non-modal-welcome-screen:non-modal-welcome-screen_test_lib",
"//platform/util/coroutines:coroutines-tests_test_lib",
"//platform/util/progress:progress-tests_test_lib",
"//platform/testFramework/junit5.jimfs",
diff --git a/intellij.idea.community.main.iml b/intellij.idea.community.main.iml
index de7598f76638..52403e4b0582 100644
--- a/intellij.idea.community.main.iml
+++ b/intellij.idea.community.main.iml
@@ -217,6 +217,7 @@
+
diff --git a/platform/non-modal-welcome-screen/BUILD.bazel b/platform/non-modal-welcome-screen/BUILD.bazel
index fc9580456521..f0e85073a2ef 100644
--- a/platform/non-modal-welcome-screen/BUILD.bazel
+++ b/platform/non-modal-welcome-screen/BUILD.bazel
@@ -36,4 +36,47 @@ jvm_library(
],
plugins = ["@lib//:compose-plugin"]
)
-### auto-generated section `build intellij.platform.ide.nonModalWelcomeScreen` end
\ No newline at end of file
+
+jvm_library(
+ name = "non-modal-welcome-screen_test_lib",
+ visibility = ["//visibility:public"],
+ srcs = glob(["testSrc/**/*.kt", "testSrc/**/*.java", "testSrc/**/*.form"], allow_empty = True),
+ associates = [":non-modal-welcome-screen"],
+ deps = [
+ "//platform/analysis-api:analysis",
+ "//platform/editor-ui-api:editor-ui",
+ "//platform/compose",
+ "//platform/compose:compose_test_lib",
+ "//platform/core-api:core",
+ "//jps/model-api:model",
+ "//platform/lang-core",
+ "//libraries/compose-runtime-desktop",
+ "//platform/projectModel-api:projectModel",
+ "//platform/testFramework",
+ "//platform/testFramework:testFramework_test_lib",
+ "//platform/util:util-ui",
+ "//platform/platform-api:ide",
+ "//platform/lang-impl",
+ "//platform/core-ui",
+ "//platform/ide-core-impl",
+ "//platform/platform-impl:ide-impl",
+ "@lib//:junit5",
+ "//platform/statistics",
+ "//platform/statistics:statistics_test_lib",
+ "//libraries/kotlinx/serialization/core",
+ "//platform/kernel/shared:kernel",
+ "//platform/platform-impl/rpc",
+ "//platform/project/shared:project",
+ ],
+ plugins = ["@lib//:compose-plugin"]
+)
+### auto-generated section `build intellij.platform.ide.nonModalWelcomeScreen` end
+
+### auto-generated section `test intellij.platform.ide.nonModalWelcomeScreen` start
+load("@community//build:tests-options.bzl", "jps_test")
+
+jps_test(
+ name = "non-modal-welcome-screen_test",
+ runtime_deps = [":non-modal-welcome-screen_test_lib"]
+)
+### auto-generated section `test intellij.platform.ide.nonModalWelcomeScreen` end
\ No newline at end of file
diff --git a/platform/non-modal-welcome-screen/intellij.platform.ide.nonModalWelcomeScreen.iml b/platform/non-modal-welcome-screen/intellij.platform.ide.nonModalWelcomeScreen.iml
index 2178cd6dd828..2252ce1bf20b 100644
--- a/platform/non-modal-welcome-screen/intellij.platform.ide.nonModalWelcomeScreen.iml
+++ b/platform/non-modal-welcome-screen/intellij.platform.ide.nonModalWelcomeScreen.iml
@@ -30,6 +30,7 @@
+
diff --git a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/newFileDialog/WelcomeScreenNewFileDialog.kt b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/newFileDialog/WelcomeScreenNewFileDialog.kt
index affbe6154d0b..af805407ded5 100644
--- a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/newFileDialog/WelcomeScreenNewFileDialog.kt
+++ b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/newFileDialog/WelcomeScreenNewFileDialog.kt
@@ -22,10 +22,12 @@ import com.intellij.util.ui.FormBuilder
import org.jetbrains.annotations.ApiStatus
import java.awt.event.FocusAdapter
import java.awt.event.FocusEvent
+import java.nio.file.InvalidPathException
import java.nio.file.Path
import javax.swing.JComponent
import javax.swing.JList
import javax.swing.event.DocumentEvent
+import kotlin.io.path.invariantSeparatorsPathString
@ApiStatus.Internal
class WelcomeScreenNewFileDialog private constructor(
@@ -33,8 +35,24 @@ class WelcomeScreenNewFileDialog private constructor(
private val builder: Builder,
) : DialogWrapper(project, true) {
- private companion object {
+ internal companion object {
private const val MAX_PATH_LENGTH = 70
+
+ /**
+ * Normalizes a directory path for use with [DirectoryUtil.mkdirs].
+ *
+ * This method ensures the path:
+ * - Uses forward slashes (/) as separators, which is required by [DirectoryUtil.mkdirs]
+ * - Has redundant path elements (like `.` and `..`) resolved
+ *
+ * This is necessary because on Windows, [Path.toString] returns paths with backslashes,
+ * but [DirectoryUtil.mkdirs] requires forward slashes.
+ *
+ * @see IJPL-217109
+ */
+ fun normalizeDirectoryPath(path: String): String {
+ return Path.of(path).normalize().invariantSeparatorsPathString
+ }
}
private val targetDirectoryField: ComponentWithBrowseButton = ComponentWithBrowseButton(ExtendableTextField(), null)
@@ -155,7 +173,7 @@ class WelcomeScreenNewFileDialog private constructor(
val targetDirectoryName = targetDirectoryField.childComponent.text
- if (targetDirectoryName.isEmpty()) {
+ if (targetDirectoryName.isNullOrEmpty()) {
Messages.showErrorDialog(
project,
NonModalWelcomeScreenBundle.message("welcome.screen.create.file.dialog.no.target.directory.specified"),
@@ -168,12 +186,14 @@ class WelcomeScreenNewFileDialog private constructor(
ApplicationManager.getApplication().runWriteAction {
try {
targetDirectory = DirectoryUtil.mkdirs(
- PsiManager.getInstance(project),
- Path.of(targetDirectoryName).normalize().toString()
+ PsiManager.getInstance(project),
+ normalizeDirectoryPath(targetDirectoryName)
)
}
catch (_: IncorrectOperationException) {
}
+ catch (_: InvalidPathException) {
+ }
}
}, NonModalWelcomeScreenBundle.message("welcome.screen.create.file.dialog.create.directory"), null)
diff --git a/platform/non-modal-welcome-screen/testSrc/com/intellij/platform/ide/nonModalWelcomeScreen/newFileDialog/WelcomeScreenNewFileDialogTest.kt b/platform/non-modal-welcome-screen/testSrc/com/intellij/platform/ide/nonModalWelcomeScreen/newFileDialog/WelcomeScreenNewFileDialogTest.kt
new file mode 100644
index 000000000000..0cfae0c357f8
--- /dev/null
+++ b/platform/non-modal-welcome-screen/testSrc/com/intellij/platform/ide/nonModalWelcomeScreen/newFileDialog/WelcomeScreenNewFileDialogTest.kt
@@ -0,0 +1,99 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ide.nonModalWelcomeScreen.newFileDialog
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.condition.EnabledOnOs
+import org.junit.jupiter.api.condition.OS
+import java.nio.file.FileSystems
+
+/**
+ * Tests for [WelcomeScreenNewFileDialog.normalizeDirectoryPath].
+ *
+ * The method ensures paths use forward slashes, which is required by [DirectoryUtil.mkdirs].
+ *
+ * **Note:** Windows-specific tests use [@EnabledOnOs] and will be skipped on other platforms.
+ * They will run on Windows agents in TeamCity.
+ *
+ * @see IJPL-217109
+ */
+class WelcomeScreenNewFileDialogTest {
+
+ /**
+ * Regression test for IJPL-217109: [DirectoryUtil.mkdirs] requires forward slashes.
+ * Simulates user input with platform-native separators (backslashes on Windows).
+ */
+ @Test
+ fun `normalizeDirectoryPath uses forward slashes`() {
+ val separator = FileSystems.getDefault().separator
+ val userInput = listOf("Users", "test", "NewProject").joinToString(separator)
+
+ val normalized = WelcomeScreenNewFileDialog.normalizeDirectoryPath(userInput)
+
+ assertFalse(normalized.contains("\\"),
+ "Path for DirectoryUtil must not contain backslashes: $normalized")
+ }
+
+ @Test
+ fun `normalizeDirectoryPath resolves parent directory references`() {
+ val pathWithDots = "home/user/../user/project"
+
+ val normalized = WelcomeScreenNewFileDialog.normalizeDirectoryPath(pathWithDots)
+
+ assertFalse(normalized.contains(".."), "Normalized path should not contain '..': $normalized")
+ assertEquals("home/user/project", normalized)
+ }
+
+ @Test
+ fun `normalizeDirectoryPath resolves current directory references`() {
+ val pathWithDot = "home/./user/./project"
+
+ val normalized = WelcomeScreenNewFileDialog.normalizeDirectoryPath(pathWithDot)
+
+ assertEquals("home/user/project", normalized)
+ }
+
+ /**
+ * Regression test for IJPL-217109: Windows backslash paths must be converted to forward slashes.
+ * This test runs ONLY on Windows agents in TeamCity.
+ */
+ @Test
+ @EnabledOnOs(OS.WINDOWS)
+ fun `normalizeDirectoryPath converts Windows backslashes to forward slashes`() {
+ val windowsPath = "C:\\Users\\test\\project"
+
+ val normalized = WelcomeScreenNewFileDialog.normalizeDirectoryPath(windowsPath)
+
+ assertEquals("C:/Users/test/project", normalized)
+ }
+
+ /**
+ * Tests Windows paths with mixed separators (common when copy-pasting paths).
+ * This test runs ONLY on Windows agents in TeamCity.
+ */
+ @Test
+ @EnabledOnOs(OS.WINDOWS)
+ fun `normalizeDirectoryPath handles Windows mixed separators`() {
+ val mixedPath = "C:/Users\\test/project\\src"
+
+ val normalized = WelcomeScreenNewFileDialog.normalizeDirectoryPath(mixedPath)
+
+ assertEquals("C:/Users/test/project/src", normalized)
+ assertFalse(normalized.contains("\\"), "Path should not contain backslashes")
+ }
+
+ /**
+ * Tests that trailing backslashes are handled correctly on Windows.
+ * This test runs ONLY on Windows agents in TeamCity.
+ */
+ @Test
+ @EnabledOnOs(OS.WINDOWS)
+ fun `normalizeDirectoryPath handles Windows trailing separators`() {
+ val pathWithTrailing = "C:\\Users\\test\\"
+
+ val normalized = WelcomeScreenNewFileDialog.normalizeDirectoryPath(pathWithTrailing)
+
+ assertEquals("C:/Users/test", normalized)
+ }
+}