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

Add NameAllocator API to control keyword pre-allocation #1803

Merged
merged 4 commits into from
Jan 12, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kotlinpoet/api/kotlinpoet.api
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ public final class com/squareup/kotlinpoet/MemberName$Companion {

public final class com/squareup/kotlinpoet/NameAllocator {
public fun <init> ()V
public fun <init> (Z)V
public final fun copy ()Lcom/squareup/kotlinpoet/NameAllocator;
public final fun get (Ljava/lang/Object;)Ljava/lang/String;
public final fun newName (Ljava/lang/String;)Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,39 @@ public class NameAllocator private constructor(
private val allocatedNames: MutableSet<String>,
private val tagToName: MutableMap<Any, String>,
) {
public constructor() : this(mutableSetOf(), mutableMapOf())
public constructor() : this(preallocateKeywords = true)

/**
* @param preallocateKeywords If true, all Kotlin keywords will be preallocated. Requested names which
* collide with keywords will be suffixed with underscores to avoid being used as identifiers:
*
* ```kotlin
* val nameAllocator = NameAllocator(preallocateKeywords = true)
* println(nameAllocator.newName("when")) // prints "when_"
* ```
*
* If false, keywords will not get any special treatment:
*
* ```kotlin
* val nameAllocator = NameAllocator(preallocateKeywords = false)
* println(nameAllocator.newName("when")) // prints "when"
* ```
*
* Note that you can use the `%N` placeholder when emitting a name produced by [NameAllocator] to
* ensure it's properly escaped for use as an identifier:
*
* ```kotlin
* val nameAllocator = NameAllocator(preallocateKeywords = false)
* println(CodeBlock.of("%N", nameAllocator.newName("when"))) // prints "`when`"
* ```
*
* The default behaviour of [NameAllocator] is to preallocate keywords - this is the behaviour you'll
* get when using the no-arg constructor.
*/
public constructor(preallocateKeywords: Boolean) : this(
allocatedNames = if (preallocateKeywords) KEYWORDS.toMutableSet() else mutableSetOf(),
tagToName = mutableMapOf(),
)

/**
* Return a new name using `suggestion` that will not be a Java identifier or clash with other
Expand All @@ -89,7 +121,7 @@ public class NameAllocator private constructor(
tag: Any = UUID.randomUUID().toString(),
): String {
var result = toJavaIdentifier(suggestion)
while (result.isKeyword || !allocatedNames.add(result)) {
while (!allocatedNames.add(result)) {
result += "_"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ private val IDENTIFIER_REGEX =
internal val String.isIdentifier get() = IDENTIFIER_REGEX.matches(this)

// https://kotlinlang.org/docs/reference/keyword-reference.html
private val KEYWORDS = setOf(
internal val KEYWORDS = setOf(
// Hard keywords
"as",
"break",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ class NameAllocatorTest {
assertThat(nameAllocator[1]).isEqualTo("when_")
}

@Test fun kotlinKeywordNotPreAllocated() {
val nameAllocator = NameAllocator(preallocateKeywords = false)
assertThat(nameAllocator.newName("when", 1)).isEqualTo("when")
assertThat(nameAllocator[1]).isEqualTo("when")
}

@Test fun tagReuseForbidden() {
val nameAllocator = NameAllocator()
nameAllocator.newName("foo", 1)
Expand Down