Skip to content

Commit

Permalink
fix: Selection set initializer reserved parameter name (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
calvincestari authored Feb 5, 2024
1 parent 5647c14 commit a335842
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1757,7 +1757,7 @@ class SelectionSetTemplate_Initializers_Tests: XCTestCase {
expect(actual).to(equalLineByLine(expected, atLine: 24, ignoringExtraLines: true))
}

// MARK: - Include/Skip Tests
// MARK: Include/Skip Tests

func test__render_given_fieldWithInclusionCondition_rendersInitializerWithOptionalParameter() async throws {
// given
Expand Down Expand Up @@ -2136,4 +2136,110 @@ class SelectionSetTemplate_Initializers_Tests: XCTestCase {
expect(allAnimals_ifA_actual).to(equalLineByLine(
allAnimals_ifA_expected, atLine: 23, ignoringExtraLines: true))
}

// MARK: Parameter Name Tests

func test__render__givenReservedFieldName_shouldGenerateParameterNameWithAlias() async throws {
// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}
type Animal {
name: String
self: String # <- reserved name
}
"""

document = """
query TestOperation {
allAnimals {
name
self
}
}
"""

let expected =
"""
public init(
name: String? = nil,
`self` _self: String? = nil
) {
self.init(_dataDict: DataDict(
data: [
"__typename": TestSchema.Objects.Animal.typename,
"name": name,
"self": _self,
],
fulfilledFragments: [
ObjectIdentifier(TestOperationQuery.Data.AllAnimal.self)
]
))
}
"""

// when
try await buildSubjectAndOperation()

let allAnimals = try XCTUnwrap(
operation[field: "query"]?[field: "allAnimals"]?.selectionSet
)

let actual = subject.test_render(childEntity: allAnimals.computed)

// then
expect(actual).to(equalLineByLine(expected, atLine: 18, ignoringExtraLines: true))
}

func test__render__givenFieldName_generatesParameterNameWithoutAlias() async throws {
// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}
type Animal {
name: String
}
"""

document = """
query TestOperation {
allAnimals {
name
}
}
"""

let expected =
"""
public init(
name: String? = nil
) {
self.init(_dataDict: DataDict(
data: [
"__typename": TestSchema.Objects.Animal.typename,
"name": name,
],
fulfilledFragments: [
ObjectIdentifier(TestOperationQuery.Data.AllAnimal.self)
]
))
}
"""

// when
try await buildSubjectAndOperation()

let allAnimals = try XCTUnwrap(
operation[field: "query"]?[field: "allAnimals"]?.selectionSet
)

let actual = subject.test_render(childEntity: allAnimals.computed)

// then
expect(actual).to(equalLineByLine(expected, atLine: 16, ignoringExtraLines: true))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,59 @@ extension String {
private func escapeIf(in set: Set<String>) -> String {
set.contains(self) ? "`\(self)`" : self
}

/// Renders the string as the property name for a field accessor on a generated `SelectionSet`.
/// This escapes the names of properties that would conflict with Swift reserved keywords.
func renderAsFieldPropertyName(
config: ApolloCodegenConfiguration
) -> String {

private func aliasIf(in set: Set<String>) -> String {
set.contains(self) ? "_\(self)" : self
}

private func escapeWithAliasIf(in set: Set<String>) -> String {
set.contains(self) ? "`\(self)` _\(self)" : self
}

private func renderedAsPropertyName(config: ApolloCodegenConfiguration) -> String {
var propertyName = self

switch config.options.conversionStrategies.fieldAccessors {
case .camelCase:
propertyName = propertyName.convertToCamelCase()
case .idiomatic:
break
}

propertyName = propertyName.isAllUppercased ? propertyName.lowercased() : propertyName.firstLowercased
return propertyName.escapeIf(in: SwiftKeywords.FieldAccessorNamesToEscape)
return propertyName
}


/// Renders the string as the property name for a field accessor on a generated `SelectionSet`.
/// This escapes the names of properties that would conflict with Swift reserved keywords.
func renderAsFieldPropertyName(config: ApolloCodegenConfiguration) -> String {
let propertyName = renderedAsPropertyName(config: config)
.escapeIf(in: SwiftKeywords.FieldAccessorNamesToEscape)

return propertyName
}

/// Renders the string as the parameter name for an initializer on a generated `SelectionSet`.
/// This escapes the names of parameters that would conflict with Swift reserved keywords and
/// makes them available under a parameter name alias to avoid further conflicts; see issue #3330
/// as an example.
func renderAsInitializerParameterName(config: ApolloCodegenConfiguration) -> String {
let propertyName = renderedAsPropertyName(config: config)
.escapeWithAliasIf(in: SwiftKeywords.FieldAccessorNamesToEscape)

return propertyName
}

/// Renders the string as the parameter name for an initializer on a generated `SelectionSet`.
/// This underscores the names of initializer parameters that would conflict with Swift reserved
/// keywords; see issue #3330 as an example.
func renderAsInitializerParameterAccessorName(config: ApolloCodegenConfiguration) -> String {
let propertyName = renderedAsPropertyName(config: config)
.aliasIf(in: SwiftKeywords.FieldAccessorNamesToEscape)

return propertyName
}

func renderAsInputObjectName(
config: ApolloCodegenConfiguration
) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,8 @@ struct SelectionSetTemplate {
) -> TemplateString {
let isOptional: Bool = field.type.isNullable || field.isConditionallyIncluded(in: scope)
return """
\(field.responseKey.renderAsFieldPropertyName(config: config.config)): \(typeName(for: field, forceOptional: isOptional))\
\(field.responseKey.renderAsInitializerParameterName(config: config.config)): \
\(typeName(for: field, forceOptional: isOptional))\
\(if: isOptional, " = nil")
"""
}
Expand Down Expand Up @@ -641,7 +642,7 @@ struct SelectionSetTemplate {
}()

return """
"\(field.responseKey)": \(field.responseKey.renderAsFieldPropertyName(config: config.config))\
"\(field.responseKey)": \(field.responseKey.renderAsInitializerParameterAccessorName(config: config.config))\
\(if: isEntityField, "._fieldData")
"""
}
Expand Down

0 comments on commit a335842

Please sign in to comment.