From 202894c314c0a3b83a17085481e176cb0821d414 Mon Sep 17 00:00:00 2001 From: BobaFetters <4425109+BobaFetters@users.noreply.github.com> Date: Mon, 8 May 2023 13:17:21 -0400 Subject: [PATCH] Updating casing of variable names and keys (#2996) --- Sources/ApolloCodegenLib/TemplateString.swift | 4 + .../Templates/InputObjectTemplate.swift | 10 +-- .../OperationTemplateRenderer.swift | 8 +- .../String+SwiftNameEscaping.swift | 11 +-- .../Templates/SelectionSetTemplate.swift | 6 +- .../Templates/InputObjectTemplateTests.swift | 60 +++++++++++++ ...nDefinition_VariableDefinition_Tests.swift | 84 +++++++++++++++++++ .../SelectionSetTemplateTests.swift | 77 +++++++++++++++++ ...ectionSetTemplate_Initializers_Tests.swift | 55 ++++++++++++ 9 files changed, 296 insertions(+), 19 deletions(-) diff --git a/Sources/ApolloCodegenLib/TemplateString.swift b/Sources/ApolloCodegenLib/TemplateString.swift index 005b47d3fd..a33548e4e3 100644 --- a/Sources/ApolloCodegenLib/TemplateString.swift +++ b/Sources/ApolloCodegenLib/TemplateString.swift @@ -389,4 +389,8 @@ extension String { return prefix(through: indexToChangeCase).lowercased() + suffix(from: index(after: indexToChangeCase)) } + + var isAllUppercased: Bool { + return self == self.uppercased() + } } diff --git a/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift b/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift index d6cc9b9901..be4624c452 100644 --- a/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/InputObjectTemplate.swift @@ -91,7 +91,7 @@ struct InputObjectTemplate: TemplateRenderer { ) -> TemplateString { TemplateString(""" \(fields.map({ - "\($1.name.asInputParameterName): \($1.renderInputValueType(includeDefault: true, config: config.config))" + "\($1.name.asFieldPropertyName): \($1.renderInputValueType(includeDefault: true, config: config.config))" }), separator: ",\n") """) } @@ -100,7 +100,7 @@ struct InputObjectTemplate: TemplateRenderer { _ fields: GraphQLInputFieldDictionary ) -> TemplateString { TemplateString(""" - \(fields.map({ "\"\($1.name)\": \($1.name.asInputParameterName)" }), separator: ",\n") + \(fields.map({ "\"\($1.name)\": \($1.name.asFieldPropertyName)" }), separator: ",\n") """) } @@ -108,9 +108,9 @@ struct InputObjectTemplate: TemplateRenderer { """ \(documentation: field.documentation, config: config) \(deprecationReason: field.deprecationReason, config: config) - public var \(field.name.asInputParameterName): \(field.renderInputValueType(config: config.config)) { - get { __data["\(field.name.firstLowercased)"] } - set { __data["\(field.name.firstLowercased)"] = newValue } + public var \(field.name.asFieldPropertyName): \(field.renderInputValueType(config: config.config)) { + get { __data["\(field.name)"] } + set { __data["\(field.name)"] = newValue } } """ } diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/OperationTemplateRenderer.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/OperationTemplateRenderer.swift index ce38251062..ec64c5c048 100644 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/OperationTemplateRenderer.swift +++ b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/OperationTemplateRenderer.swift @@ -13,7 +13,7 @@ extension OperationTemplateRenderer { return """ \(`init`)(\(list: variables.map(VariableParameter))) { \(variables.map { - let name = $0.name.asInputParameterName + let name = $0.name.asFieldPropertyName return "self.\(name) = \(name)" }, separator: "\n") } @@ -24,7 +24,7 @@ extension OperationTemplateRenderer { _ variables: [CompilationResult.VariableDefinition] ) -> TemplateString { """ - \(variables.map { "public var \($0.name.asInputParameterName): \($0.type.rendered(as: .inputValue, config: config.config))"}, separator: "\n") + \(variables.map { "public var \($0.name.asFieldPropertyName): \($0.type.rendered(as: .inputValue, config: config.config))"}, separator: "\n") """ } @@ -32,7 +32,7 @@ extension OperationTemplateRenderer { _ variable: CompilationResult.VariableDefinition ) -> TemplateString { """ - \(variable.name.asInputParameterName): \(variable.type.rendered(as: .inputValue, config: config.config))\ + \(variable.name.asFieldPropertyName): \(variable.type.rendered(as: .inputValue, config: config.config))\ \(if: variable.defaultValue != nil, " = " + variable.renderVariableDefaultValue(config: config.config)) """ } @@ -46,7 +46,7 @@ extension OperationTemplateRenderer { } return """ - public var __variables: \(if: !graphQLOperation, "GraphQLOperation.")Variables? { [\(list: variables.map { "\"\($0.name)\": \($0.name.asInputParameterName)"})] } + public var __variables: \(if: !graphQLOperation, "GraphQLOperation.")Variables? { [\(list: variables.map { "\"\($0.name)\": \($0.name.asFieldPropertyName)"})] } """ } diff --git a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift index 1db9a975d8..ceaefd36f3 100644 --- a/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift +++ b/Sources/ApolloCodegenLib/Templates/RenderingHelpers/String+SwiftNameEscaping.swift @@ -4,8 +4,9 @@ import Foundation extension String { /// 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. - var asFieldAccessorPropertyName: String { - escapeIf(in: SwiftKeywords.FieldAccessorNamesToEscape) + var asFieldPropertyName: String { + let str = self.isAllUppercased ? self.lowercased() : self.firstLowercased + return str.escapeIf(in: SwiftKeywords.FieldAccessorNamesToEscape) } var asEnumCaseName: String { @@ -16,11 +17,7 @@ extension String { SwiftKeywords.SelectionSetTypeNamesToSuffix.contains(self) ? "\(self)_SelectionSet" : self } - - var asInputParameterName: String { - escapeIf(in: SwiftKeywords.InputParameterNamesToEscape).firstLowercased - } - + var asTestMockFieldPropertyName: String { escapeIf(in: SwiftKeywords.TestMockFieldNamesToEscape) } diff --git a/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift b/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift index f037c653b2..e36406ca86 100644 --- a/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift +++ b/Sources/ApolloCodegenLib/Templates/SelectionSetTemplate.swift @@ -295,7 +295,7 @@ struct SelectionSetTemplate { return """ \(documentation: field.underlyingField.documentation, config: config) \(deprecationReason: field.underlyingField.deprecationReason, config: config) - public var \(field.responseKey.firstLowercased.asFieldAccessorPropertyName): \ + public var \(field.responseKey.asFieldPropertyName): \ \(typeName(for: field, forceOptional: field.isConditionallyIncluded(in: scope))) {\ \(if: isMutable, """ @@ -426,7 +426,7 @@ struct SelectionSetTemplate { ) -> TemplateString { let isOptional: Bool = field.type.isNullable || field.isConditionallyIncluded(in: scope) return """ - \(field.responseKey.asInputParameterName): \(typeName(for: field, forceOptional: isOptional))\ + \(field.responseKey.asFieldPropertyName): \(typeName(for: field, forceOptional: isOptional))\ \(if: isOptional, " = nil") """ } @@ -459,7 +459,7 @@ struct SelectionSetTemplate { }() return """ - "\(field.responseKey)": \(field.responseKey.asInputParameterName)\ + "\(field.responseKey)": \(field.responseKey.asFieldPropertyName)\ \(if: isEntityField, "._fieldData") """ } diff --git a/Tests/ApolloCodegenTests/CodeGeneration/Templates/InputObjectTemplateTests.swift b/Tests/ApolloCodegenTests/CodeGeneration/Templates/InputObjectTemplateTests.swift index e2dc58145e..6c675e25ea 100644 --- a/Tests/ApolloCodegenTests/CodeGeneration/Templates/InputObjectTemplateTests.swift +++ b/Tests/ApolloCodegenTests/CodeGeneration/Templates/InputObjectTemplateTests.swift @@ -182,6 +182,66 @@ class InputObjectTemplateTests: XCTestCase { // then expect(actual).to(equalLineByLine(expected, atLine: 8, ignoringExtraLines: false)) } + + func test__render__givenSingleFieldTypeInMixedCase__generatesParameterAndInitializerWithCorrectCasing() throws { + // given + buildSubject(fields: [ + GraphQLInputField.mock("Field", type: .scalar(.string()), defaultValue: nil) + ]) + + let expected = """ + public init( + field: GraphQLNullable = nil + ) { + __data = InputDict([ + "Field": field + ]) + } + + public var field: GraphQLNullable { + get { __data["Field"] } + set { __data["Field"] = newValue } + } + } + + """ + + // when + let actual = renderSubject() + + // then + expect(actual).to(equalLineByLine(expected, atLine: 8, ignoringExtraLines: false)) + } + + func test__render__givenSingleFieldTypeInAllUppercase__generatesParameterAndInitializerWithCorrectCasing() throws { + // given + buildSubject(fields: [ + GraphQLInputField.mock("FIELDNAME", type: .scalar(.string()), defaultValue: nil) + ]) + + let expected = """ + public init( + fieldname: GraphQLNullable = nil + ) { + __data = InputDict([ + "FIELDNAME": fieldname + ]) + } + + public var fieldname: GraphQLNullable { + get { __data["FIELDNAME"] } + set { __data["FIELDNAME"] = newValue } + } + } + + """ + + // when + let actual = renderSubject() + + // then + expect(actual).to(equalLineByLine(expected, atLine: 8, ignoringExtraLines: false)) + } func test__render__givenAllPossibleSchemaInputFieldTypes__generatesCorrectParametersAndInitializer() throws { // given diff --git a/Tests/ApolloCodegenTests/CodeGeneration/Templates/OperationDefinition_VariableDefinition_Tests.swift b/Tests/ApolloCodegenTests/CodeGeneration/Templates/OperationDefinition_VariableDefinition_Tests.swift index 4b13ab0cb3..754efe6147 100644 --- a/Tests/ApolloCodegenTests/CodeGeneration/Templates/OperationDefinition_VariableDefinition_Tests.swift +++ b/Tests/ApolloCodegenTests/CodeGeneration/Templates/OperationDefinition_VariableDefinition_Tests.swift @@ -37,6 +37,90 @@ class OperationDefinition_VariableDefinition_Tests: XCTestCase { // then expect(actual).to(equal(expected)) } + + func test__renderOperationVariableProperty_givenAllUppercaseVariableName_generatesCorrectlyCasedVariable() throws { + // given + subject = .mock("VARIABLE", type: .string(), defaultValue: nil) + + let expected = "public var variable: GraphQLNullable" + + // when + buildTemplate() + let actual = template.VariableProperties([subject]).description + + // then + expect(actual).to(equal(expected)) + } + + func test__renderOperationVariableProperty_givenMixedCaseVariableName_generatesCorrectlyCasedVariable() throws { + // given + subject = .mock("VariableName", type: .string(), defaultValue: nil) + + let expected = "public var variableName: GraphQLNullable" + + // when + buildTemplate() + let actual = template.VariableProperties([subject]).description + + // then + expect(actual).to(equal(expected)) + } + + func test__renderOperationVariableParameter_givenAllUppercaseVariableName_generatesCorrectlyCasedVariable() throws { + // given + subject = .mock("VARIABLE", type: .string(), defaultValue: nil) + + let expected = "variable: GraphQLNullable" + + // when + buildTemplate() + let actual = template.VariableParameter(subject).description + + // then + expect(actual).to(equal(expected)) + } + + func test__renderOperationVariableParameter_givenMixedCaseVariableName_generatesCorrectlyCasedVariable() throws { + // given + subject = .mock("VariableName", type: .string(), defaultValue: nil) + + let expected = "variableName: GraphQLNullable" + + // when + buildTemplate() + let actual = template.VariableParameter(subject).description + + // then + expect(actual).to(equal(expected)) + } + + func test__renderOperationVariableAccessor_givenAllUppercaseVariableName_generatesCorrectlyCasedVariable() throws { + // given + subject = .mock("VARIABLE", type: .string(), defaultValue: nil) + + let expected = "public var __variables: Variables? { [\"VARIABLE\": variable] }" + + // when + buildTemplate() + let actual = template.VariableAccessors([subject]).description + + // then + expect(actual).to(equal(expected)) + } + + func test__renderOperationVariableAccessor_givenMixedCaseVariableName_generatesCorrectlyCasedVariable() throws { + // given + subject = .mock("VariableName", type: .string(), defaultValue: nil) + + let expected = "public var __variables: Variables? { [\"VariableName\": variableName] }" + + // when + buildTemplate() + let actual = template.VariableAccessors([subject]).description + + // then + expect(actual).to(equal(expected)) + } func test__renderOperationVariableParameter_includeDefaultTrue__givenAllInputFieldTypes_nilDefaultValues__generatesCorrectParametersWithoutInitializer() throws { // given diff --git a/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift b/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift index 19b3da91b3..b033658f9c 100644 --- a/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift +++ b/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplateTests.swift @@ -495,6 +495,45 @@ class SelectionSetTemplateTests: XCTestCase { // then expect(actual).to(equalLineByLine(expected, atLine: 7, ignoringExtraLines: true)) } + + func test__render_selections__givenAllUppercase_generatesCorrectCasing() throws { + // given + schemaSDL = """ + type Query { + allAnimals: [Animal!] + } + + type Animal { + FIELDNAME: String + } + """ + + document = """ + query TestOperation { + allAnimals { + FIELDNAME + } + } + """ + + let expected = """ + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("FIELDNAME", String?.self), + ] } + """ + + // when + try buildSubjectAndOperation() + let allAnimals = try XCTUnwrap( + operation[field: "query"]?[field: "allAnimals"] as? IR.EntityField + ) + + let actual = subject.render(field: allAnimals) + + // then + expect(actual).to(equalLineByLine(expected, atLine: 7, ignoringExtraLines: true)) + } func test__render_selections__givenCustomScalar_rendersFieldSelectionsWithNamespaceInAllConfigurations() throws { // given @@ -2405,6 +2444,42 @@ class SelectionSetTemplateTests: XCTestCase { // then expect(actual).to(equalLineByLine(expected, atLine: 12, ignoringExtraLines: true)) } + + func test__render_fieldAccessors__givenFieldWithAllUpperCaseName_rendersFieldAccessorWithLowercaseName() throws { + // given + schemaSDL = """ + type Query { + AllAnimals: [Animal!] + } + + type Animal { + FIELDNAME: String! + } + """ + + document = """ + query TestOperation { + AllAnimals { + FIELDNAME + } + } + """ + + let expected = """ + public var fieldname: String { __data["FIELDNAME"] } + """ + + // when + try buildSubjectAndOperation() + let allAnimals = try XCTUnwrap( + operation[field: "query"]?[field: "AllAnimals"] as? IR.EntityField + ) + + let actual = subject.render(field: allAnimals) + + // then + expect(actual).to(equalLineByLine(expected, atLine: 12, ignoringExtraLines: true)) + } func test__render_fieldAccessors__givenFieldWithAlias_rendersAllFieldAccessors() throws { // given @@ -4068,6 +4143,8 @@ class SelectionSetTemplateTests: XCTestCase { // then expect(actual).to(equalLineByLine(expected, atLine: 8, ignoringExtraLines: true)) } + + // MARK: - Inline Fragment Accessors diff --git a/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplate_Initializers_Tests.swift b/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplate_Initializers_Tests.swift index 1eb24e9b89..f7870a4545 100644 --- a/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplate_Initializers_Tests.swift +++ b/Tests/ApolloCodegenTests/CodeGeneration/Templates/SelectionSet/SelectionSetTemplate_Initializers_Tests.swift @@ -416,6 +416,61 @@ class SelectionSetTemplate_Initializers_Tests: XCTestCase { // then expect(actual).to(equalLineByLine(expected, atLine: 62, ignoringExtraLines: true)) } + + func test__render_given_differentCasedFields_rendersInitializerWithCorrectCasing() throws { + // given + schemaSDL = """ + type Query { + allAnimals: [Animal!] + } + + type Animal { + FIELDONE: String! + FieldTwo: String! + fieldthree: String! + } + """ + + document = """ + query TestOperation { + allAnimals { + FIELDONE + FieldTwo + fieldthree + } + } + """ + + let expected = """ + public init( + fieldone: String, + fieldTwo: String, + fieldthree: String + ) { + self.init(_dataDict: DataDict(data: [ + "__typename": TestSchema.Objects.Animal.typename, + "FIELDONE": fieldone, + "FieldTwo": fieldTwo, + "fieldthree": fieldthree, + "__fulfilled": Set([ + ObjectIdentifier(Self.self) + ]) + ])) + } + """ + + // when + try buildSubjectAndOperation() + + let allAnimals = try XCTUnwrap( + operation[field: "query"]?[field: "allAnimals"] as? IR.EntityField + ) + + let actual = subject.render(field: allAnimals) + + // then + expect(actual).to(equalLineByLine(expected, atLine: 20, ignoringExtraLines: true)) + } func test__render_given_fieldWithAlias_rendersInitializer() throws { // given