Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Escape special characters in file names #1481

Merged
merged 1 commit into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,23 @@ open class DokkaLocationProvider(

companion object {
internal val reservedFilenames = setOf("index", "con", "aux", "lst", "prn", "nul", "eof", "inp", "out")
//Taken from: https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
internal val reservedCharacters = setOf('|', '>', '<', '*', ':', '"', '?', '%')

internal fun identifierToFilename(name: String): String {
if (name.isEmpty()) return "--root--"
val escaped = name.replace("[<>]".toRegex(), "-")
val lowercase = escaped.replace("[A-Z]".toRegex()) { matchResult -> "-" + matchResult.value.toLowerCase() }
return if (lowercase in reservedFilenames) "--$lowercase--" else lowercase
return sanitizeFileName(name, reservedFilenames, reservedCharacters)
}
}
}

internal fun sanitizeFileName(name: String, reservedFileNames: Set<String>, reservedCharacters: Set<Char>): String {
val lowercase = name.replace("[A-Z]".toRegex()) { matchResult -> "-" + matchResult.value.toLowerCase() }
val withoutReservedFileNames = if (lowercase in reservedFileNames) "--$lowercase--" else lowercase
return reservedCharacters.fold(withoutReservedFileNames){
acc, character ->
if(character in acc) acc.replace(character.toString(), "[${character.toInt()}]")
else acc
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@ package locationProvider

import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider
import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

class DefaultLocationProviderTest: AbstractCoreTest() {
@Test
fun `#644 same directory for module and package`() {
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
}
class DefaultLocationProviderTest : AbstractCoreTest() {
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
}
}
}

@Test
fun `#644 same directory for module and package`() {
testInline(
"""
|/src/main/kotlin/basic/Test.kt
Expand All @@ -38,4 +43,79 @@ class DefaultLocationProviderTest: AbstractCoreTest() {
}
}
}

@Test
fun `should escape illegal pipe character in file name`() {
/*
Currently even kotlin doesn't escape pipe characters in file names so it is impossible to have a
class named || on windows
*/
testInline(
"""
|/src/main/kotlin/basic/Test.kt
|
|class Test {
| fun `||`() { }
|}
""".trimMargin(),
configuration
) {
var context: DokkaContext? = null
pluginsSetupStage = {
context = it
}

pagesGenerationStage = { module ->
val lp = DokkaLocationProvider(module, context!!)
val functionWithPipes = module.dfs { it.name == "||" }
assertNotNull(functionWithPipes, "Failed to find a page for a function named ||")
assertEquals(lp.resolve(functionWithPipes), "[root]/-test/[124][124].html")
}
}
}

@ParameterizedTest
@MethodSource
fun runEscapeTestForCharacter(data: TestData) {
testInline(
"""
|/src/main/kotlin/basic/Test.kt
|
|class Test {
| fun `${data.tested}`() { }
|}
""".trimMargin(),
configuration
) {
var context: DokkaContext? = null
pluginsSetupStage = {
context = it
}

pagesGenerationStage = { module ->
val lp = DokkaLocationProvider(module, context!!)
val functionWithPipes = module.dfs { it.name == "${data.tested}" }
assertNotNull(functionWithPipes, "Failed to find a page for a function named ${data.tested}")
assertEquals(lp.resolve(functionWithPipes), "[root]/-test/${data.expectedReplacement}.html")
}
}
}

data class TestData(val tested: Char, val expectedReplacement: String)

companion object TestDataSources {
@JvmStatic
fun runEscapeTestForCharacter(): List<TestData> = listOf(
'|' to "[124]",
'>' to "[62]",
'<' to "[60]",
'*' to "[42]",
':' to "[58]",
'"' to "[34]",
'?' to "[63]",
'%' to "[37]"
).map {
TestData(it.first, it.second)
}
}
}