Skip to content

Commit 88e2c59

Browse files
authored
Do not check already checked fragments in checkCapitalizedFields, to avoid a StackOverflow (#6718)
1 parent a55267d commit 88e2c59

File tree

2 files changed

+28
-26
lines changed

2 files changed

+28
-26
lines changed

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/internal/checkCapitalizedFields.kt

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ internal fun checkCapitalizedFields(definitions: List<GQLDefinition>, checkFragm
2020

2121
definitions.forEach { definition ->
2222
when {
23-
definition is GQLOperationDefinition && !checkFragmentsOnly -> scope.checkCapitalizedFields(definition.selections)
24-
definition is GQLFragmentDefinition -> scope.checkCapitalizedFields(definition.selections)
23+
definition is GQLOperationDefinition && !checkFragmentsOnly -> scope.checkCapitalizedFields(definition.selections, emptyList())
24+
definition is GQLFragmentDefinition -> scope.checkCapitalizedFields(definition.selections, emptyList())
2525
}
2626
}
2727

@@ -31,7 +31,7 @@ internal fun checkCapitalizedFields(definitions: List<GQLDefinition>, checkFragm
3131
/**
3232
* Fields named with a capital first letter clash with the corresponding model name, unless flatten.
3333
*/
34-
private fun ValidationScope.checkCapitalizedFields(selections: List<GQLSelection>) {
34+
private fun ValidationScope.checkCapitalizedFields(selections: List<GQLSelection>, checkedFragments: List<String>) {
3535
selections.forEach {
3636
when (it) {
3737
is GQLField -> {
@@ -42,26 +42,34 @@ private fun ValidationScope.checkCapitalizedFields(selections: List<GQLSelection
4242
val alias = it.alias
4343
if (alias != null) {
4444
if (isFirstLetterUpperCase(alias)) {
45-
issues.add(UpperCaseField(message = """
45+
issues.add(
46+
UpperCaseField(
47+
message = """
4648
Capitalized alias '$alias' is not supported as it causes name clashes with the generated models. Use '${decapitalizeFirstLetter(alias)}' instead.
4749
""".trimIndent(),
48-
sourceLocation = it.sourceLocation)
50+
sourceLocation = it.sourceLocation
51+
)
4952
)
5053
}
5154
} else if (isFirstLetterUpperCase(it.name)) {
52-
issues.add(UpperCaseField(message = """
53-
Capitalized field '${it.name}' is not supported as it causes name clashes with the generated models. Use an alias instead or the 'flattenModels' or 'decapitalizeFields' compiler option.
54-
""".trimIndent(),
55-
sourceLocation = it.sourceLocation)
55+
issues.add(
56+
UpperCaseField(
57+
message = """
58+
Capitalized field '${it.name}' is not supported as it causes name clashes with the generated models. Use an alias instead or the 'flattenModels' or 'decapitalizeFields' compiler option.
59+
""".trimIndent(),
60+
sourceLocation = it.sourceLocation
61+
)
5662
)
5763
}
58-
checkCapitalizedFields(it.selections)
64+
checkCapitalizedFields(it.selections, checkedFragments)
5965
}
6066

61-
is GQLInlineFragment -> checkCapitalizedFields(it.selections)
67+
is GQLInlineFragment -> checkCapitalizedFields(it.selections, checkedFragments)
6268
// it might be that the fragment is defined in an upstream module. In that case, it is validated
6369
// already, no need to check it again
64-
is GQLFragmentSpread -> fragmentsByName[it.name]?.let { fragment -> checkCapitalizedFields(fragment.selections) }
70+
is GQLFragmentSpread -> if (!checkedFragments.contains(it.name)) {
71+
fragmentsByName[it.name]?.let { fragment -> checkCapitalizedFields(fragment.selections, checkedFragments + it.name) }
72+
}
6573
}
6674
}
6775
}
@@ -79,12 +87,14 @@ private fun decapitalizeFirstLetter(name: String): String {
7987
val builder = StringBuilder(name.length)
8088
var isDecapitalized = false
8189
name.forEach {
82-
builder.append(if (!isDecapitalized && it.isLetter()) {
83-
isDecapitalized = true
84-
it.toString().lowercase()
85-
} else {
86-
it.toString()
87-
})
90+
builder.append(
91+
if (!isDecapitalized && it.isLetter()) {
92+
isDecapitalized = true
93+
it.toString().lowercase()
94+
} else {
95+
it.toString()
96+
}
97+
)
8898
}
8999
return builder.toString()
90100
}

libraries/apollo-compiler/src/test/kotlin/com/apollographql/apollo/compiler/ExecutableValidationTest.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@ package com.apollographql.apollo.compiler
22

33
import com.apollographql.apollo.ast.parseAsGQLDocument
44
import com.apollographql.apollo.ast.validateAsExecutable
5-
import com.apollographql.apollo.ast.validateAsSchemaAndAddApolloDefinition
65
import com.apollographql.apollo.compiler.TestUtils.checkExpected
76
import com.apollographql.apollo.compiler.TestUtils.serialize
87
import com.apollographql.apollo.compiler.TestUtils.testParametersForGraphQLFilesIn
9-
import com.apollographql.apollo.compiler.internal.checkApolloReservedEnumValueNames
10-
import com.apollographql.apollo.compiler.internal.checkApolloTargetNameClashes
118
import com.apollographql.apollo.compiler.internal.checkCapitalizedFields
129
import okio.buffer
1310
import okio.source
@@ -30,12 +27,7 @@ class ExecutableValidationTest(name: String, private val graphQLFile: File) {
3027
if (graphQLFile.name == "capitalized_fields_disallowed.graphql") {
3128
checkCapitalizedFields(parseResult.value!!.definitions, checkFragmentsOnly = false)
3229
} else {
33-
emptyList()
34-
} +
35-
if (graphQLFile.name == "capitalized_fields_allowed_with_fragment_spread.graphql") {
3630
checkCapitalizedFields(parseResult.value!!.definitions, checkFragmentsOnly = true)
37-
} else {
38-
emptyList()
3931
}
4032
}
4133

0 commit comments

Comments
 (0)