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

fix: Selection set initializer reserved parameter name #257

Merged
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 @@ -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