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

Differentiate escaping of Java or Kotlin keywords #3630

Merged
merged 7 commits into from
Dec 1, 2021
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.apollographql.apollo3.compiler

// Reference:
// https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
private val JAVA_RESERVED_WORDS = arrayOf(
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default",
"do", "double", "else", "enum", "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements",
Expand All @@ -8,15 +10,16 @@ private val JAVA_RESERVED_WORDS = arrayOf(
"transient", "try", "true", "void", "volatile", "while"
)

private val KOTLIN_RESERVED_WORDS = arrayOf(
"as", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "interface", "is", "null", "object", "package",
"return", "super", "this", "throw", "true", "try", "typealias", "typeof", "val", "var", "when", "while", "yield", "it", "field"
)
// Note: reserved keywords escaping is handled by KotlinPoet, but "yield" is missing from its list at the moment
// so we handle it ourselves (see https://github.com/apollographql/apollo-android/issues/1957)
private val KOTLIN_RESERVED_WORDS = arrayOf("yield")

private val RESERVED_ENUM_VALUE_NAMES = arrayOf("name", "ordinal")
// Reference:
// https://kotlinlang.org/docs/enum-classes.html#working-with-enum-constants:~:text=properties%20for%20obtaining%20its%20name%20and%20position
private val KOTLIN_RESERVED_ENUM_VALUE_NAMES = arrayOf("name", "ordinal")

fun String.escapeJavaReservedWord() = if (this in JAVA_RESERVED_WORDS) "${this}_" else this

fun String.escapeKotlinReservedWord() = if (this in (JAVA_RESERVED_WORDS + KOTLIN_RESERVED_WORDS)) "${this}_" else this
fun String.escapeKotlinReservedWord() = if (this in KOTLIN_RESERVED_WORDS) "`${this}`" else this

fun String.escapeKotlinReservedEnumValueNames() = if (this in (JAVA_RESERVED_WORDS + KOTLIN_RESERVED_WORDS + RESERVED_ENUM_VALUE_NAMES)) "${this}_" else this
fun String.escapeKotlinReservedEnumValueNames() = if (this in (KOTLIN_RESERVED_WORDS + KOTLIN_RESERVED_ENUM_VALUE_NAMES)) "${this}_" else this
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@


package com.apollographql.apollo3.compiler.codegen

import com.apollographql.apollo3.compiler.PackageNameGenerator
import com.apollographql.apollo3.compiler.capitalizeFirstLetter
import com.apollographql.apollo3.compiler.decapitalizeFirstLetter
import com.apollographql.apollo3.compiler.escapeKotlinReservedEnumValueNames
import com.apollographql.apollo3.compiler.escapeKotlinReservedWord
import com.apollographql.apollo3.compiler.ir.IrFieldInfo
import com.apollographql.apollo3.compiler.ir.IrListType
import com.apollographql.apollo3.compiler.ir.IrNonNullType
Expand All @@ -18,11 +14,9 @@ import com.apollographql.apollo3.compiler.singularize
/**
* The central place where the names/packages of the different classes are decided and escape rules done.
*
* Inputs should always be GraphQL identifiers and outputs are valid Kotlin identifiers.
*
* Inputs should always be GraphQL identifiers and outputs are valid Kotlin/Java identifiers.
*/

class CodegenLayout(
abstract class CodegenLayout(
private val packageNameGenerator: PackageNameGenerator,
schemaPackageName: String,
private val useSemanticNaming: Boolean,
Expand Down Expand Up @@ -58,10 +52,6 @@ class CodegenLayout(

internal fun enumName(name: String) = regularIdentifier(name)

// We used to write upper case enum values but the server can define different values with different cases
// See https://github.com/apollographql/apollo-android/issues/3035
internal fun enumValueName(name: String) = regularIdentifier(name)
internal fun sealedClassValueName(name: String) = name.escapeKotlinReservedEnumValueNames()
internal fun enumResponseAdapterName(name: String) = enumName(name) + "_ResponseAdapter"

internal fun operationName(operation: IrOperation): String {
Expand Down Expand Up @@ -97,9 +87,12 @@ class CodegenLayout(
internal fun schemaName() = "__Schema"

// ------------------------ Helpers ---------------------------------
private fun regularIdentifier(name: String) = name.escapeKotlinReservedWord()

abstract fun escapeReservedWord(word: String): String

internal fun regularIdentifier(name: String) = escapeReservedWord(name)
private fun capitalizedIdentifier(name: String): String {
return name.capitalizeFirstLetter().escapeKotlinReservedWord()
return escapeReservedWord(name.capitalizeFirstLetter())
}

fun rootSelectionsPropertyName() = "root"
Expand All @@ -113,6 +106,7 @@ class CodegenLayout(
it.capitalizeFirstLetter()
}.joinToString("")
}

fun lowerCamelCaseIgnoringNonLetters(strings: Collection<String>): String {
return strings.map {
it.decapitalizeFirstLetter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.apollographql.apollo3.compiler.codegen.java

import com.apollographql.apollo3.compiler.APOLLO_VERSION
import com.apollographql.apollo3.compiler.PackageNameGenerator
import com.apollographql.apollo3.compiler.codegen.CodegenLayout
import com.apollographql.apollo3.compiler.codegen.ResolverInfo
import com.apollographql.apollo3.compiler.codegen.java.adapter.EnumResponseAdapterBuilder
import com.apollographql.apollo3.compiler.codegen.java.file.CustomScalarBuilder
Expand All @@ -20,12 +19,11 @@ import com.apollographql.apollo3.compiler.codegen.java.file.OperationBuilder
import com.apollographql.apollo3.compiler.codegen.java.file.OperationResponseAdapterBuilder
import com.apollographql.apollo3.compiler.codegen.java.file.OperationSelectionsBuilder
import com.apollographql.apollo3.compiler.codegen.java.file.OperationVariablesAdapterBuilder
import com.apollographql.apollo3.compiler.codegen.java.file.UnionBuilder
import com.apollographql.apollo3.compiler.codegen.java.file.SchemaBuilder
import com.apollographql.apollo3.compiler.codegen.java.file.UnionBuilder
import com.apollographql.apollo3.compiler.ir.Ir
import com.apollographql.apollo3.compiler.operationoutput.OperationOutput
import com.apollographql.apollo3.compiler.operationoutput.findOperationId
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.JavaFile
import java.io.File
Expand Down Expand Up @@ -61,7 +59,7 @@ class JavaCodeGen(
JavaResolver(resolverInfo.entries, acc)
}

val layout = CodegenLayout(
val layout = JavaCodegenLayout(
useSemanticNaming = useSemanticNaming,
packageNameGenerator = packageNameGenerator,
schemaPackageName = schemaPackageName
Expand Down Expand Up @@ -212,4 +210,4 @@ fun CodeBlock.isNotEmpty() = isEmpty().not()

internal const val T = "${'$'}T"
internal const val L = "${'$'}L"
internal const val S = "${'$'}S"
internal const val S = "${'$'}S"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.apollographql.apollo3.compiler.codegen.java

import com.apollographql.apollo3.compiler.PackageNameGenerator
import com.apollographql.apollo3.compiler.codegen.CodegenLayout
import com.apollographql.apollo3.compiler.escapeJavaReservedWord

class JavaCodegenLayout(
packageNameGenerator: PackageNameGenerator,
schemaPackageName: String,
useSemanticNaming: Boolean,
) : CodegenLayout(packageNameGenerator, schemaPackageName, useSemanticNaming) {
override fun escapeReservedWord(word: String): String = word.escapeJavaReservedWord()

// We used to write upper case enum values but the server can define different values with different cases
// See https://github.com/apollographql/apollo-android/issues/3035
internal fun enumValueName(name: String) = regularIdentifier(name)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.apollographql.apollo3.compiler.codegen.java

import com.apollographql.apollo3.compiler.codegen.CodegenLayout

class JavaContext(
val layout : CodegenLayout,
val resolver: JavaResolver
)
val layout: JavaCodegenLayout,
val resolver: JavaResolver,
)
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,4 @@ class ModelBuilder(
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.apollographql.apollo3.compiler.codegen.kotlin
import com.apollographql.apollo3.compiler.APOLLO_VERSION
import com.apollographql.apollo3.compiler.PackageNameGenerator
import com.apollographql.apollo3.compiler.TargetLanguage
import com.apollographql.apollo3.compiler.codegen.CodegenLayout
import com.apollographql.apollo3.compiler.codegen.ResolverInfo
import com.apollographql.apollo3.compiler.codegen.kotlin.file.CustomScalarBuilder
import com.apollographql.apollo3.compiler.codegen.kotlin.file.EnumAsEnumBuilder
Expand Down Expand Up @@ -69,7 +68,7 @@ class KotlinCodeGen(
KotlinResolver(resolverInfo.entries, acc)
}

val layout = CodegenLayout(
val layout = KotlinCodegenLayout(
useSemanticNaming = useSemanticNaming,
packageNameGenerator = packageNameGenerator,
schemaPackageName = schemaPackageName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.apollographql.apollo3.compiler.codegen.kotlin

import com.apollographql.apollo3.compiler.PackageNameGenerator
import com.apollographql.apollo3.compiler.codegen.CodegenLayout
import com.apollographql.apollo3.compiler.escapeKotlinReservedEnumValueNames
import com.apollographql.apollo3.compiler.escapeKotlinReservedWord

class KotlinCodegenLayout(
packageNameGenerator: PackageNameGenerator,
schemaPackageName: String,
useSemanticNaming: Boolean,
) : CodegenLayout(packageNameGenerator, schemaPackageName, useSemanticNaming) {

override fun escapeReservedWord(word: String): String = word.escapeKotlinReservedWord()

/**
* Enum value name to use when generating enums as sealed classes
*/
internal fun enumAsSealedClassValueName(name: String) = regularIdentifier(name)

/**
* Enum value name to use when generating enums as enums
*/
internal fun enumAsEnumValueName(name: String) = name.escapeKotlinReservedEnumValueNames()
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.apollographql.apollo3.compiler.codegen.kotlin

import com.apollographql.apollo3.compiler.TargetLanguage
import com.apollographql.apollo3.compiler.codegen.CodegenLayout

class KotlinContext(
val layout: CodegenLayout,
val layout: KotlinCodegenLayout,
val resolver: KotlinResolver,
val targetLanguageVersion: TargetLanguage,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ internal fun readFromResponseCodeBlock(
}

CodeBlock.of(
"var·%L:·%T·=·%L",
"var·%N:·%T·=·%L",
context.layout.variableName(property.info.responseName),
context.resolver.resolveIrType(property.info.type).copy(nullable = !property.info.type.isOptional()),
variableInitializer
Expand All @@ -68,7 +68,7 @@ internal fun readFromResponseCodeBlock(
.add(
regularProperties.mapIndexed { index, property ->
CodeBlock.of(
"%L·->·%L·=·%L.$fromJson($reader, $customScalarAdapters)",
"%L·->·%N·=·%L.$fromJson($reader, $customScalarAdapters)",
index,
context.layout.variableName(property.info.responseName),
context.resolver.adapterInitializer(property.info.type, property.requiresBuffering)
Expand Down Expand Up @@ -103,7 +103,7 @@ internal fun readFromResponseCodeBlock(
.apply {
if (property.condition != BooleanExpression.True) {
add(
"var·%L:·%T·=·null\n",
"var·%N:·%T·=·null\n",
context.layout.variableName(property.info.responseName),
context.resolver.resolveIrType(property.info.type).copy(nullable = !property.info.type.isOptional()),
)
Expand Down Expand Up @@ -140,7 +140,7 @@ internal fun readFromResponseCodeBlock(
""
}
CodeBlock.of(
"%L·=·%L%L",
"%N·=·%N%L",
context.layout.propertyName(property.info.responseName),
context.layout.variableName(property.info.responseName),
maybeAssertNotNull
Expand Down Expand Up @@ -183,8 +183,9 @@ private fun IrProperty.writeToResponseCodeBlock(context: KotlinContext): CodeBlo
val adapterInitializer = context.resolver.adapterInitializer(info.type, requiresBuffering)
builder.addStatement("${writer}.name(%S)", info.responseName)
builder.addStatement(
"%L.${Identifier.toJson}($writer, $customScalarAdapters, $value.$propertyName)",
adapterInitializer
"%L.${Identifier.toJson}($writer, $customScalarAdapters, $value.%N)",
adapterInitializer,
propertyName,
)
} else {
val adapterInitializer = context.resolver.resolveModelAdapter(info.type.modelPath())
Expand All @@ -193,11 +194,12 @@ private fun IrProperty.writeToResponseCodeBlock(context: KotlinContext): CodeBlo
* Output types do not distinguish between null and absent
*/
if (this.info.type !is IrNonNullType) {
builder.beginControlFlow("if·($value.$propertyName·!=·null)")
builder.beginControlFlow("if·($value.%N·!=·null)", propertyName)
}
builder.addStatement(
"%L.${Identifier.toJson}($writer, $customScalarAdapters, $value.$propertyName)",
adapterInitializer
"%L.${Identifier.toJson}($writer, $customScalarAdapters, $value.%N)",
adapterInitializer,
propertyName,
)
if (this.info.type !is IrNonNullType) {
builder.endControlFlow()
Expand Down Expand Up @@ -225,4 +227,4 @@ internal fun CodeBlock.obj(buffered: Boolean): CodeBlock {
MemberName("com.apollographql.apollo3.api", "obj"),
params
).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.apollographql.apollo3.compiler.codegen.kotlin.CgOutputFileBuilder
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDeprecation
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDescription
import com.apollographql.apollo3.compiler.escapeKotlinReservedEnumValueNames
import com.apollographql.apollo3.compiler.ir.IrEnum
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FunSpec
Expand Down Expand Up @@ -51,7 +50,7 @@ class EnumAsEnumBuilder(
.addType(companionTypeSpec())
.apply {
values.forEach { value ->
addEnumConstant(layout.sealedClassValueName(value.name), value.enumConstTypeSpec())
addEnumConstant(layout.enumAsEnumValueName(value.name), value.enumConstTypeSpec())
}
addEnumConstant("UNKNOWN__", unknownValueTypeSpec())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.apollographql.apollo3.compiler.codegen.Identifier.knownValues
import com.apollographql.apollo3.compiler.codegen.Identifier.safeValueOf
import com.apollographql.apollo3.compiler.codegen.kotlin.CgFile
import com.apollographql.apollo3.compiler.codegen.kotlin.CgOutputFileBuilder
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinSymbols
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinSymbols
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.deprecatedAnnotation
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDescription
import com.apollographql.apollo3.compiler.ir.IrEnum
Expand Down Expand Up @@ -74,7 +74,7 @@ class EnumAsSealedBuilder(
}

private fun IrEnum.Value.toObjectTypeSpec(superClass: TypeName): TypeSpec {
return TypeSpec.objectBuilder(layout.enumValueName(name))
return TypeSpec.objectBuilder(layout.enumAsSealedClassValueName(name))
.applyIf(description?.isNotBlank() == true) { addKdoc("%L\n", description!!) }
.applyIf(deprecationReason != null) { addAnnotation(deprecatedAnnotation(deprecationReason!!)) }
.superclass(superClass)
Expand Down Expand Up @@ -151,7 +151,7 @@ class EnumAsSealedBuilder(
}

private fun IrEnum.Value.valueClassName(): ClassName {
return ClassName(packageName, simpleName, layout.enumValueName(name))
return ClassName(packageName, simpleName, layout.enumAsSealedClassValueName(name))
}

private fun unknownValueClassName(): ClassName {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.apollographql.apollo3.compiler.codegen.kotlin.file

import com.apollographql.apollo3.ast.GQLType
import com.apollographql.apollo3.compiler.codegen.ClassNames
import com.apollographql.apollo3.compiler.codegen.CodegenLayout
import com.apollographql.apollo3.compiler.codegen.Identifier
import com.apollographql.apollo3.compiler.codegen.Identifier.Data
import com.apollographql.apollo3.compiler.codegen.Identifier.block
Expand All @@ -11,9 +10,10 @@ import com.apollographql.apollo3.compiler.codegen.Identifier.fromJson
import com.apollographql.apollo3.compiler.codegen.Identifier.testResolver
import com.apollographql.apollo3.compiler.codegen.kotlin.CgFile
import com.apollographql.apollo3.compiler.codegen.kotlin.CgTestFileBuilder
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinSymbols
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinCodegenLayout
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinMemberNames
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinSymbols
import com.apollographql.apollo3.compiler.codegen.kotlin.test.TBuilderBuilder
import com.apollographql.apollo3.compiler.decapitalizeFirstLetter
import com.apollographql.apollo3.compiler.ir.IrModel
Expand Down Expand Up @@ -206,7 +206,7 @@ private fun resolveNameClashes(usedNames: MutableSet<String>, modelName: String)
}


internal fun IrModel.toTBuilder(layout: CodegenLayout): TBuilder {
internal fun IrModel.toTBuilder(layout: KotlinCodegenLayout): TBuilder {
val nestedBuilders = modelGroups.flatMap { it.toTBuilders(layout) }
return TBuilder(
kotlinName = layout.testBuilder(modelName),
Expand All @@ -217,7 +217,7 @@ internal fun IrModel.toTBuilder(layout: CodegenLayout): TBuilder {
)
}

internal fun IrModelGroup.toTBuilders(layout: CodegenLayout): List<TBuilder> {
internal fun IrModelGroup.toTBuilders(layout: KotlinCodegenLayout): List<TBuilder> {
return models.filter { !it.isInterface }.map {
it.toTBuilder(layout)
}
Expand Down
Loading