diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f0f3babe0..c34dc76f08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - `apollo-codegen-scala` - - `apollo-codegen-swift` - - + - Fix issue where type names were not being properly escaped [iOS 193](https://github.com/apollographql/apollo-ios/issues/193) - `apollo-codegen-typescript` - - `apollo-env` diff --git a/packages/apollo-codegen-swift/src/__tests__/__snapshots__/codeGeneration.ts.snap b/packages/apollo-codegen-swift/src/__tests__/__snapshots__/codeGeneration.ts.snap index e36a7c98e1..47d5942af3 100644 --- a/packages/apollo-codegen-swift/src/__tests__/__snapshots__/codeGeneration.ts.snap +++ b/packages/apollo-codegen-swift/src/__tests__/__snapshots__/codeGeneration.ts.snap @@ -1,5 +1,175 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Swift code generation #classDeclarationForOperation() should correctly escape a mutli-line string literal 1`] = ` +"public final class CreateReviewMutation: GraphQLMutation { + public let operationDefinition = + \\"mutation CreateReview($episode: Episode) {\\\\n createReview(episode: $episode, review: {stars: 5, commentary: \\\\\\"\\\\\\"\\\\\\"\\\\n Wow!\\\\n \\\\n This movie ROCKED!\\\\n \\\\\\"\\\\\\"\\\\\\"}) {\\\\n stars\\\\n commentary\\\\n }\\\\n}\\" + + public let operationName = \\"CreateReview\\" + + public var episode: Episode? + + public init(episode: Episode? = nil) { + self.episode = episode + } + + public var variables: GraphQLMap? { + return [\\"episode\\": episode] + } + + public struct Data: GraphQLSelectionSet { + public static let possibleTypes = [\\"Mutation\\"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField(\\"createReview\\", arguments: [\\"episode\\": GraphQLVariable(\\"episode\\"), \\"review\\": [\\"stars\\": 5, \\"commentary\\": \\"Wow!\\\\n\\\\n This movie ROCKED!\\"]], type: .object(CreateReview.selections)), + ] + + public private(set) var resultMap: ResultMap + + public init(unsafeResultMap: ResultMap) { + self.resultMap = unsafeResultMap + } + + public init(createReview: CreateReview? = nil) { + self.init(unsafeResultMap: [\\"__typename\\": \\"Mutation\\", \\"createReview\\": createReview.flatMap { (value: CreateReview) -> ResultMap in value.resultMap }]) + } + + public var createReview: CreateReview? { + get { + return (resultMap[\\"createReview\\"] as? ResultMap).flatMap { CreateReview(unsafeResultMap: $0) } + } + set { + resultMap.updateValue(newValue?.resultMap, forKey: \\"createReview\\") + } + } + + public struct CreateReview: GraphQLSelectionSet { + public static let possibleTypes = [\\"Review\\"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField(\\"stars\\", type: .nonNull(.scalar(Int.self))), + GraphQLField(\\"commentary\\", type: .scalar(String.self)), + ] + + public private(set) var resultMap: ResultMap + + public init(unsafeResultMap: ResultMap) { + self.resultMap = unsafeResultMap + } + + public init(stars: Int, commentary: String? = nil) { + self.init(unsafeResultMap: [\\"__typename\\": \\"Review\\", \\"stars\\": stars, \\"commentary\\": commentary]) + } + + /// The number of stars this review gave, 1-5 + public var stars: Int { + get { + return resultMap[\\"stars\\"]! as! Int + } + set { + resultMap.updateValue(newValue, forKey: \\"stars\\") + } + } + + /// Comment about the movie + public var commentary: String? { + get { + return resultMap[\\"commentary\\"] as? String + } + set { + resultMap.updateValue(newValue, forKey: \\"commentary\\") + } + } + } + } +}" +`; + +exports[`Swift code generation #classDeclarationForOperation() should correctly escape a mutli-line string literal with backslashes 1`] = ` +"public final class CreateReviewMutation: GraphQLMutation { + public let operationDefinition = + \\"mutation CreateReview($episode: Episode) {\\\\n createReview(episode: $episode, review: {stars: 5, commentary: \\\\\\"\\\\\\"\\\\\\"\\\\n Wow!\\\\n \\\\n This movie \\\\ ROCKED!\\\\n \\\\\\"\\\\\\"\\\\\\"}) {\\\\n stars\\\\n commentary\\\\n }\\\\n}\\" + + public let operationName = \\"CreateReview\\" + + public var episode: Episode? + + public init(episode: Episode? = nil) { + self.episode = episode + } + + public var variables: GraphQLMap? { + return [\\"episode\\": episode] + } + + public struct Data: GraphQLSelectionSet { + public static let possibleTypes = [\\"Mutation\\"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField(\\"createReview\\", arguments: [\\"episode\\": GraphQLVariable(\\"episode\\"), \\"review\\": [\\"stars\\": 5, \\"commentary\\": \\"Wow!\\\\n\\\\n This movie \\\\\\\\ ROCKED!\\"]], type: .object(CreateReview.selections)), + ] + + public private(set) var resultMap: ResultMap + + public init(unsafeResultMap: ResultMap) { + self.resultMap = unsafeResultMap + } + + public init(createReview: CreateReview? = nil) { + self.init(unsafeResultMap: [\\"__typename\\": \\"Mutation\\", \\"createReview\\": createReview.flatMap { (value: CreateReview) -> ResultMap in value.resultMap }]) + } + + public var createReview: CreateReview? { + get { + return (resultMap[\\"createReview\\"] as? ResultMap).flatMap { CreateReview(unsafeResultMap: $0) } + } + set { + resultMap.updateValue(newValue?.resultMap, forKey: \\"createReview\\") + } + } + + public struct CreateReview: GraphQLSelectionSet { + public static let possibleTypes = [\\"Review\\"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField(\\"stars\\", type: .nonNull(.scalar(Int.self))), + GraphQLField(\\"commentary\\", type: .scalar(String.self)), + ] + + public private(set) var resultMap: ResultMap + + public init(unsafeResultMap: ResultMap) { + self.resultMap = unsafeResultMap + } + + public init(stars: Int, commentary: String? = nil) { + self.init(unsafeResultMap: [\\"__typename\\": \\"Review\\", \\"stars\\": stars, \\"commentary\\": commentary]) + } + + /// The number of stars this review gave, 1-5 + public var stars: Int { + get { + return resultMap[\\"stars\\"]! as! Int + } + set { + resultMap.updateValue(newValue, forKey: \\"stars\\") + } + } + + /// Comment about the movie + public var commentary: String? { + get { + return resultMap[\\"commentary\\"] as? String + } + set { + resultMap.updateValue(newValue, forKey: \\"commentary\\") + } + } + } + } +}" +`; + exports[`Swift code generation #classDeclarationForOperation() should generate a class declaration for a mutation with variables 1`] = ` "public final class CreateReviewMutation: GraphQLMutation { public let operationDefinition = diff --git a/packages/apollo-codegen-swift/src/__tests__/codeGeneration.ts b/packages/apollo-codegen-swift/src/__tests__/codeGeneration.ts index d8729f5b3f..50583f0c50 100644 --- a/packages/apollo-codegen-swift/src/__tests__/codeGeneration.ts +++ b/packages/apollo-codegen-swift/src/__tests__/codeGeneration.ts @@ -90,6 +90,48 @@ describe("Swift code generation", () => { expect(generator.output).toMatchSnapshot(); }); + it("should correctly escape a mutli-line string literal", () => { + const { operations } = compile(` + mutation CreateReview($episode: Episode) { + createReview(episode: $episode, review: {stars: 5, commentary: + """ + Wow! + + This movie ROCKED! + """ + }) { + stars + commentary + } + } + `); + + generator.classDeclarationForOperation(operations["CreateReview"]); + + expect(generator.output).toMatchSnapshot(); + }); + + it("should correctly escape a mutli-line string literal with backslashes", () => { + const { operations } = compile(` + mutation CreateReview($episode: Episode) { + createReview(episode: $episode, review: {stars: 5, commentary: + """ + Wow! + + This movie \\ ROCKED! + """ + }) { + stars + commentary + } + } + `); + + generator.classDeclarationForOperation(operations["CreateReview"]); + + expect(generator.output).toMatchSnapshot(); + }); + it(`should generate a class declaration for a query with a fragment spread nested in an inline fragment`, () => { const { operations } = compile(` query Hero { diff --git a/packages/apollo-codegen-swift/src/__tests__/language.ts b/packages/apollo-codegen-swift/src/__tests__/language.ts index 48adfdf647..9f0303ba7d 100644 --- a/packages/apollo-codegen-swift/src/__tests__/language.ts +++ b/packages/apollo-codegen-swift/src/__tests__/language.ts @@ -49,6 +49,34 @@ describe("Swift code generation: Basic language constructs", () => { `); }); + it(`should generate a class declaration with proper escaping`, () => { + generator.classDeclaration( + { className: "Type", modifiers: ["public", "final"] }, + () => { + generator.propertyDeclaration({ + propertyName: "name", + typeName: "String" + }); + generator.propertyDeclaration({ + propertyName: "age", + typeName: "Int" + }); + generator.propertyDeclaration({ + propertyName: "self", + typeName: "Self" + }); + } + ); + + expect(generator.output).toBe(stripIndent` + public final class \`Type\` { + public var name: String + public var age: Int + public var \`self\`: \`Self\` + } + `); + }); + it(`should generate a struct declaration`, () => { generator.structDeclaration({ structName: "Hero" }, () => { generator.propertyDeclaration({ @@ -100,12 +128,17 @@ describe("Swift code generation: Basic language constructs", () => { propertyName: "yearOfBirth", typeName: "Int" }); + generator.propertyDeclaration({ + propertyName: "self", + typeName: "Self" + }); }); expect(generator.output).toBe(stripIndent` public struct \`Type\` { public var name: String public var yearOfBirth: Int + public var \`self\`: \`Self\` } `); }); @@ -153,6 +186,10 @@ describe("Swift code generation: Basic language constructs", () => { propertyName: "age", typeName: "Int" }); + generator.protocolPropertyDeclaration({ + propertyName: "default", + typeName: "Boolean" + }); } ); @@ -160,6 +197,7 @@ describe("Swift code generation: Basic language constructs", () => { public protocol HeroDetails: HasName { var name: String { get } var age: Int { get } + var \`default\`: Boolean { get } } `); }); diff --git a/packages/apollo-codegen-swift/src/language.ts b/packages/apollo-codegen-swift/src/language.ts index 7c35b9e8da..2e7762cf06 100644 --- a/packages/apollo-codegen-swift/src/language.ts +++ b/packages/apollo-codegen-swift/src/language.ts @@ -128,7 +128,8 @@ export class SwiftGenerator extends CodeGenerator< ) { this.printNewlineIfNeeded(); this.printOnNewline( - wrap("", join(modifiers, " "), " ") + `class ${className}` + wrap("", join(modifiers, " "), " ") + + `class ${escapeIdentifierIfNeeded(className)}` ); this.print(wrap(": ", join([superClass, ...adoptedProtocols], ", "))); this.pushScope({ typeName: className }); @@ -164,7 +165,9 @@ export class SwiftGenerator extends CodeGenerator< propertyDeclaration({ propertyName, typeName, description }: Property) { this.comment(description); this.printOnNewline( - `public var ${escapeIdentifierIfNeeded(propertyName)}: ${typeName}` + `public var ${escapeIdentifierIfNeeded( + propertyName + )}: ${escapeIdentifierIfNeeded(typeName)}` ); } @@ -186,7 +189,9 @@ export class SwiftGenerator extends CodeGenerator< } protocolPropertyDeclaration({ propertyName, typeName }: Property) { - this.printOnNewline(`var ${propertyName}: ${typeName} { get }`); + this.printOnNewline( + `var ${escapeIdentifierIfNeeded(propertyName)}: ${typeName} { get }` + ); } protocolPropertyDeclarations(properties: Property[]) {