Repository: apollographql/apollo-codegen (ts)

Issue:
Issues with ts-modern target
Trying to generate types using the `ts-modern` target I get the following error:

```
$ apollo-codegen generate packages/casalova-web-client/src/client/**/*.graphql --schema ./packages/casalova-web-client/src/universal/shared/graphql-schema.json --target ts-modern --output ./packages/casalova-web-client/src/universal/graphql-schema.d.ts --addTypename
TypeError: Property types[1] of UnionTypeAnnotation expected node to be of a type ["FlowType"] but instead got "TSUndefinedKeyword"
    at validate (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/node_modules/@babel/types/lib/definitions/utils.js:136:13)
    at validator (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/node_modules/@babel/types/lib/definitions/utils.js:95:7)
    at Object.validate (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/node_modules/@babel/types/lib/definitions/utils.js:191:10)
    at validate (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/node_modules/@babel/types/lib/validators/validate.js:15:9)
    at builder (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/node_modules/@babel/types/lib/builders/builder.js:40:27)
    at Object.UnionTypeAnnotation (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/node_modules/@babel/types/lib/builders/generated/index.js:1188:27)
    at TypescriptAPIGenerator.makeNullableAnnotation (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/src/javascript/typescript/language.ts:169:14)
    at t.objectTypeAnnotation.fields.map (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/src/javascript/typescript/language.ts:104:20)
    at Array.map (<anonymous>)
    at TypescriptAPIGenerator.objectTypeAnnotation (/Users/clayne/workspace/casalova-web-two/node_modules/apollo-codegen/src/javascript/typescript/language.ts:94:14)
```

Pull request description:
[ts-modern] Upgrading @babel/types and @babel/generator to 7.0.0-beta.38
fix #352 

Patch:
diff --git a/package-lock.json b/package-lock.json
index 3eaceeb4..61c34830 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,21 +16,33 @@
       }
     },
     "@babel/generator": {
-      "version": "7.0.0-beta.35",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.35.tgz",
-      "integrity": "sha512-5QO6oYlc5xpbZEBFcxdUfzNvtjTwKfttfqkkZZ8FCs7CQwmtqzZMzFvYc+pa8QmMxUF5D8c1+XI/mOy3lYjK7Q==",
+      "version": "7.0.0-beta.38",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.38.tgz",
+      "integrity": "sha512-aOHQPhsEyaB6p2n+AK981+onHoc+Ork9rcAQVSUJR33wUkGiWRpu6/C685knRyIZVsKeSdG5Q4xMiYeFUhuLzA==",
       "requires": {
-        "@babel/types": "7.0.0-beta.35",
+        "@babel/types": "7.0.0-beta.38",
         "jsesc": "2.5.1",
         "lodash": "4.17.4",
         "source-map": "0.5.7",
         "trim-right": "1.0.1"
+      },
+      "dependencies": {
+        "@babel/types": {
+          "version": "7.0.0-beta.38",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.38.tgz",
+          "integrity": "sha512-SAtyEjmA7KiEoL2eAOAUM6M9arQJGWxJKK0S9x0WyPOosHS420RXoxPhn57u/8orRnK8Kxm0nHQQNTX203cP1Q==",
+          "requires": {
+            "esutils": "2.0.2",
+            "lodash": "4.17.4",
+            "to-fast-properties": "2.0.0"
+          }
+        }
       }
     },
     "@babel/types": {
-      "version": "7.0.0-beta.35",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.35.tgz",
-      "integrity": "sha512-y9XT11CozHDgjWcTdxmhSj13rJVXpa5ZXwjjOiTedjaM0ba5ItqdS02t31EhPl7HtOWxsZkYCCUNrSfrOisA6w==",
+      "version": "7.0.0-beta.38",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.38.tgz",
+      "integrity": "sha512-SAtyEjmA7KiEoL2eAOAUM6M9arQJGWxJKK0S9x0WyPOosHS420RXoxPhn57u/8orRnK8Kxm0nHQQNTX203cP1Q==",
       "requires": {
         "esutils": "2.0.2",
         "lodash": "4.17.4",
diff --git a/package.json b/package.json
index 2fff4307..89c3b1a4 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,6 @@
     "npm": ">=5.0"
   },
   "devDependencies": {
-    "@babel/types": "^7.0.0-beta.35",
     "@types/babel-generator": "^6.25.1",
     "@types/babel-types": "^7.0.0",
     "@types/babylon": "^6.16.2",
@@ -42,7 +41,8 @@
     "typescript": "^2.6.2"
   },
   "dependencies": {
-    "@babel/generator": "^7.0.0-beta.35",
+    "@babel/generator": "7.0.0-beta.38",
+    "@babel/types": "7.0.0-beta.38",
     "change-case": "^3.0.1",
     "common-tags": "^1.5.1",
     "core-js": "^2.5.3",
diff --git a/src/generate.ts b/src/generate.ts
index 8a214c14..017dfde4 100644
--- a/src/generate.ts
+++ b/src/generate.ts
@@ -136,7 +136,7 @@ interface OperationIdsMap {
 
 function writeOperationIdsMap(context: any) {
   let operationIdsMap: { [id: string]: OperationIdsMap } = {};
-  Object.values(context.operations).forEach(operation => {
+  Object.keys(context.operations).map(k => context.operations[k]).forEach(operation => {
     operationIdsMap[operation.operationId] = {
       name: operation.operationName,
       source: operation.sourceWithFragments
diff --git a/src/swift/codeGeneration.ts b/src/swift/codeGeneration.ts
index be0165e7..86c76ef1 100644
--- a/src/swift/codeGeneration.ts
+++ b/src/swift/codeGeneration.ts
@@ -490,13 +490,14 @@ export class SwiftAPIGenerator extends SwiftGenerator<CompilerContext> {
     }
   }
 
-  propertyAssignmentForField(field: { responseKey: string; propertyName: string; type: GraphQLType }) {
-    const { responseKey, propertyName, type } = field;
+  propertyAssignmentForField(field: Field & Property & Struct) {
+    const { responseKey, propertyName, type, structName } = field;
     const valueExpression = isCompositeType(getNamedType(type))
       ? this.helpers.mapExpressionForType(
           type,
           identifier => `${identifier}.snapshot`,
-          escapeIdentifierIfNeeded(propertyName)
+          escapeIdentifierIfNeeded(propertyName),
+          structName
         )
       : escapeIdentifierIfNeeded(propertyName);
     return `"${responseKey}": ${valueExpression}`;
@@ -521,15 +522,17 @@ export class SwiftAPIGenerator extends SwiftGenerator<CompilerContext> {
           this.printOnNewline('get');
           this.withinBlock(() => {
             const snapshotTypeName = this.helpers.typeNameFromGraphQLType(type, 'Snapshot', false);
-            let getter;
+            let identifier;
             if (isOptional) {
-              getter = `return (snapshot["${responseKey}"] as? ${snapshotTypeName})`;
+              identifier = `(snapshot["${responseKey}"] as? ${snapshotTypeName})`;
             } else {
-              getter = `return (snapshot["${responseKey}"] as! ${snapshotTypeName})`;
+              identifier = `(snapshot["${responseKey}"] as! ${snapshotTypeName})`;
             }
-            getter += this.helpers.mapExpressionForType(
+            let getter = 'return ' + this.helpers.mapExpressionForType(
               type,
-              identifier => `${structName}(snapshot: ${identifier})`
+              identifier => `${structName}(snapshot: ${identifier})`,
+							identifier,
+							'Snapshot'
             );
             this.printOnNewline(getter);
           });
@@ -538,7 +541,8 @@ export class SwiftAPIGenerator extends SwiftGenerator<CompilerContext> {
             let newValueExpression = this.helpers.mapExpressionForType(
               type,
               identifier => `${identifier}.snapshot`,
-              'newValue'
+              'newValue',
+              structName
             );
             this.printOnNewline(`snapshot.updateValue(${newValueExpression}, forKey: "${responseKey}")`);
           });
diff --git a/src/swift/helpers.ts b/src/swift/helpers.ts
index af4295cd..fb678098 100644
--- a/src/swift/helpers.ts
+++ b/src/swift/helpers.ts
@@ -38,7 +38,7 @@ export class Helpers {
 
   // Types
 
-  typeNameFromGraphQLType(type: GraphQLType, unmodifiedTypeName?: string, isOptional?: boolean): string {
+  typeNameFromGraphQLType(type: GraphQLType, unmodifiedTypeName?: string, isOptional?: boolean, isSingular: boolean = false): string {
     if (type instanceof GraphQLNonNull) {
       return this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName, false);
     } else if (isOptional === undefined) {
@@ -47,7 +47,11 @@ export class Helpers {
 
     let typeName;
     if (type instanceof GraphQLList) {
-      typeName = '[' + this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName) + ']';
+      if (isSingular) {
+      	typeName = this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName);
+      } else {
+      	typeName = '[' + this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName) + ']';
+      }
     } else if (type instanceof GraphQLScalarType) {
       typeName = this.typeNameForScalarType(type);
     } else {
@@ -175,7 +179,7 @@ export class Helpers {
   propertiesForSelectionSet(
     selectionSet: SelectionSet,
     namespace?: string
-  ): (Field & Property)[] | undefined {
+  ): (Field & Property & Struct)[] | undefined {
     const properties = collectAndMergeFields(selectionSet, true)
       .filter(field => field.name !== '__typename')
       .map(field => this.propertyFromField(field, namespace));
@@ -231,7 +235,8 @@ export class Helpers {
   mapExpressionForType(
     type: GraphQLType,
     expression: (identifier: string) => string,
-    identifier = ''
+    identifier: string = '',
+    subtypeName: string
   ): string {
     let isOptional;
     if (type instanceof GraphQLNonNull) {
@@ -240,19 +245,25 @@ export class Helpers {
     } else {
       isOptional = true;
     }
-
     if (type instanceof GraphQLList) {
       if (isOptional) {
-        return `${identifier}.flatMap { $0.map { ${this.mapExpressionForType(
+        const prologue = subtypeName === undefined ? '' : `(i: ${subtypeName}) in `
+        const subidentifier = subtypeName === undefined ? '$0' : 'i'
+        return `${identifier}?.map({ ${prologue}${this.mapExpressionForType(
           type.ofType,
           expression,
-          '$0'
-        )} } }`;
+          subidentifier,
+          this.typeNameFromGraphQLType(type.ofType, undefined, false, true)
+        )} })`;
       } else {
-        return `${identifier}.map { ${this.mapExpressionForType(type.ofType, expression, '$0')} }`;
+        const prologue = subtypeName === undefined ? '' : `(j: ${subtypeName}) in `
+        const subidentifier = subtypeName === undefined ? '$0' : 'j'
+        return `${identifier}.map { ${prologue}${this.mapExpressionForType(type.ofType, expression, subidentifier, this.typeNameFromGraphQLType(type, undefined, false, true))} }`;
       }
     } else if (isOptional) {
-      return `${identifier}.flatMap { ${expression('$0')} }`;
+      const prologue = subtypeName === undefined ? '' : `(j: ${subtypeName}) in `
+      const subidentifier = subtypeName === undefined ? '$0' : 'j'
+      return `${identifier}.flatMap { ${prologue}${expression(subidentifier)} }`;
     } else {
       return expression(identifier);
     }
diff --git a/src/validation.ts b/src/validation.ts
index 5cb77219..96e0b147 100644
--- a/src/validation.ts
+++ b/src/validation.ts
@@ -2,6 +2,7 @@ import {
   validate,
   specifiedRules,
   NoUnusedFragmentsRule,
+  KnownDirectivesRule,
   GraphQLError,
   FieldNode,
   ValidationContext,
@@ -13,7 +14,7 @@ import {
 import { ToolError, logError } from './errors';
 
 export function validateQueryDocument(schema: GraphQLSchema, document: DocumentNode) {
-  const specifiedRulesToBeRemoved = [NoUnusedFragmentsRule];
+  const specifiedRulesToBeRemoved = [NoUnusedFragmentsRule, KnownDirectivesRule];
 
   const rules = [
     NoAnonymousQueries,


Test patch:
diff --git a/src/javascript/flow/__tests__/__snapshots__/codeGeneration.ts.snap b/src/javascript/flow/__tests__/__snapshots__/codeGeneration.ts.snap
index f878d107..0b18db1b 100644
--- a/src/javascript/flow/__tests__/__snapshots__/codeGeneration.ts.snap
+++ b/src/javascript/flow/__tests__/__snapshots__/codeGeneration.ts.snap
@@ -6,6 +6,7 @@ Object {
     "fileContents": "
 
 /* @flow */
+/* eslint-disable */
 // This file was automatically generated and should not be edited.
 
 // ====================================================
@@ -59,6 +60,7 @@ export type Episode = \\"EMPIRE\\" | \\"JEDI\\" | \\"NEWHOPE\\";
     "fileContents": "
 
 /* @flow */
+/* eslint-disable */
 // This file was automatically generated and should not be edited.
 
 // ====================================================
@@ -88,6 +90,7 @@ export type Episode = \\"EMPIRE\\" | \\"JEDI\\" | \\"NEWHOPE\\";
     "fileContents": "
 
 /* @flow */
+/* eslint-disable */
 // This file was automatically generated and should not be edited.
 
 // ====================================================
diff --git a/test/fixtures/starwars/UnknownDirective.graphql b/test/fixtures/starwars/UnknownDirective.graphql
new file mode 100644
index 00000000..2fe50f1e
--- /dev/null
+++ b/test/fixtures/starwars/UnknownDirective.graphql
@@ -0,0 +1,5 @@
+query HeroName {
+  hero @connection {
+    name
+  }
+}
diff --git a/test/swift/__snapshots__/codeGeneration.ts.snap b/test/swift/__snapshots__/codeGeneration.ts.snap
index b130a5ef..b98f63f7 100644
--- a/test/swift/__snapshots__/codeGeneration.ts.snap
+++ b/test/swift/__snapshots__/codeGeneration.ts.snap
@@ -29,7 +29,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(createReview: CreateReview? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Mutation\\", \\"createReview\\": createReview.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Mutation\\", \\"createReview\\": createReview.flatMap { (j: CreateReview) in j.snapshot }])
     }
 
     public var createReview: CreateReview? {
@@ -107,7 +107,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -233,7 +233,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -383,7 +383,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -481,7 +481,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -554,7 +554,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -775,11 +775,11 @@ exports[`Swift code generation #structDeclarationForFragment() should generate a
   }
 
   public static func makeHuman(name: String, friends: [Friend?]? = nil) -> HeroDetails {
-    return HeroDetails(snapshot: [\\"__typename\\": \\"Human\\", \\"name\\": name, \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return HeroDetails(snapshot: [\\"__typename\\": \\"Human\\", \\"name\\": name, \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   public static func makeDroid(name: String, friends: [Friend?]? = nil) -> HeroDetails {
-    return HeroDetails(snapshot: [\\"__typename\\": \\"Droid\\", \\"name\\": name, \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return HeroDetails(snapshot: [\\"__typename\\": \\"Droid\\", \\"name\\": name, \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   /// The name of the character
@@ -795,10 +795,10 @@ exports[`Swift code generation #structDeclarationForFragment() should generate a
   /// The friends of the character, or an empty list if they have none
   public var friends: [Friend?]? {
     get {
-      return (snapshot[\\"friends\\"] as? [Snapshot?]).flatMap { $0.map { $0.flatMap { Friend(snapshot: $0) } } }
+      return (snapshot[\\"friends\\"] as? [Snapshot?])?.map({ (i: Snapshot) in i.flatMap { (j: Character) in Friend(snapshot: j) } })
     }
     set {
-      snapshot.updateValue(newValue.flatMap { $0.map { $0.flatMap { $0.snapshot } } }, forKey: \\"friends\\")
+      snapshot.updateValue(newValue?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } }), forKey: \\"friends\\")
     }
   }
 
@@ -933,20 +933,20 @@ exports[`Swift code generation #structDeclarationForSelectionSet() should genera
   }
 
   public static func makeHuman(friends: [Friend?]? = nil) -> Hero {
-    return Hero(snapshot: [\\"__typename\\": \\"Human\\", \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return Hero(snapshot: [\\"__typename\\": \\"Human\\", \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   public static func makeDroid(friends: [Friend?]? = nil) -> Hero {
-    return Hero(snapshot: [\\"__typename\\": \\"Droid\\", \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return Hero(snapshot: [\\"__typename\\": \\"Droid\\", \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   /// The friends of the character, or an empty list if they have none
   public var friends: [Friend?]? {
     get {
-      return (snapshot[\\"friends\\"] as? [Snapshot?]).flatMap { $0.map { $0.flatMap { Friend(snapshot: $0) } } }
+      return (snapshot[\\"friends\\"] as? [Snapshot?])?.map({ (i: Snapshot) in i.flatMap { (j: Character) in Friend(snapshot: j) } })
     }
     set {
-      snapshot.updateValue(newValue.flatMap { $0.map { $0.flatMap { $0.snapshot } } }, forKey: \\"friends\\")
+      snapshot.updateValue(newValue?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } }), forKey: \\"friends\\")
     }
   }
 
diff --git a/test/swift/codeGeneration.ts b/test/swift/codeGeneration.ts
index 898214b3..0041f8f9 100644
--- a/test/swift/codeGeneration.ts
+++ b/test/swift/codeGeneration.ts
@@ -251,7 +251,7 @@ describe('Swift code generation', () => {
           propertyName: 'propertyName',
           type: new GraphQLList(schema.getType('Droid'))
         })
-      ).toBe('"response_key": propertyName.flatMap { $0.map { $0.flatMap { $0.snapshot } } }');
+      ).toBe('"response_key": propertyName?.map({ $0.flatMap { (j: Droid) in j.snapshot } })');
     });
 
     it('should generate expression for nullable list of non-null composites', () => {
@@ -261,7 +261,7 @@ describe('Swift code generation', () => {
           propertyName: 'propertyName',
           type: new GraphQLList(new GraphQLNonNull(schema.getType('Droid')))
         })
-      ).toBe('"response_key": propertyName.flatMap { $0.map { $0.snapshot } }');
+      ).toBe('"response_key": propertyName?.map({ $0.snapshot })');
     });
 
     it('should generate expression for non-null list of nullable composites', () => {
@@ -271,7 +271,7 @@ describe('Swift code generation', () => {
           propertyName: 'propertyName',
           type: new GraphQLNonNull(new GraphQLList(schema.getType('Droid')))
         })
-      ).toBe('"response_key": propertyName.map { $0.flatMap { $0.snapshot } }');
+      ).toBe('"response_key": propertyName.map { $0.flatMap { (j: Droid?) in j.snapshot } }');
     });
 
     it('should generate expression for non-null list of non-null composites', () => {
diff --git a/test/validation.ts b/test/validation.ts
index 4207a116..bda32cbf 100644
--- a/test/validation.ts
+++ b/test/validation.ts
@@ -32,4 +32,9 @@ describe('Validation', () => {
       'Validation of GraphQL query document failed'
     );
   });
+
+  test(`shouldn't throw an error for an unknown directive`, () => {
+    const document = loadQueryDocument('UnknownDirective.graphql');
+
+    validateQueryDocument(schema, document);
 });