Repository: apollographql/apollo-codegen
Pull request #324 (2017-12-18 21:41:27)

Use typescript interfaces where possible
When using typescript, it is often more powerful/easier to use an `interface` as you can destructure it for reuse.  The easiest example is taking query data that is passed to react widgets, where you want those react widgets to remains strongly typed.  As a ts `type`, you cannot `Pick` from it, whereas an interface, you can `Pick<MyQuery, 'user'>` as a widget type or simply use `MyQuery['user']` to access the type inside the interface.


rasdaniil: @rosskevin Thanks for very enlightening note on TS interfaces!

rosskevin: Tests pass locally on node 9.3 - question out on chat as to what node versions should be tested - not sure if this is a node version dependent failure on CI.
lewisf: i'm going to take a look at this in a bit -- and just verify that it works with other use cases.

if you look in `javascript/typescript`, which will be the target moving forward, i suspect we want to make the same change there

Patch:
diff --git a/src/javascript/flow/printer.ts b/src/javascript/flow/printer.ts
index d52e2739..6aea1fbd 100644
--- a/src/javascript/flow/printer.ts
+++ b/src/javascript/flow/printer.ts
@@ -57,7 +57,10 @@ export default class Printer {
    * ```
    */
   private fixCommas(documentPart: string) {
-    const lines = documentPart.split('\n');
+    const lines = documentPart
+      .split('\n')
+      .filter(Boolean);  // filter out lines that have no content
+
     let currentLine = 0;
     let nextLine;
     const newDocumentParts = [];
diff --git a/src/javascript/typescript/printer.ts b/src/javascript/typescript/printer.ts
index ace10db1..f81b8ce3 100644
--- a/src/javascript/typescript/printer.ts
+++ b/src/javascript/typescript/printer.ts
@@ -57,7 +57,10 @@ export default class Printer {
    * ```
    */
   private fixCommas(documentPart: string) {
-    const lines = documentPart.split('\n');
+    const lines = documentPart
+      .split('\n')
+      .filter(Boolean);  // filter out lines that have no content
+
     let currentLine = 0;
     let nextLine;
     const newDocumentParts = [];
@@ -91,24 +94,27 @@ export default class Printer {
       currentLine++;
     }
 
-    return newDocumentParts.reduce((memo: string[], part) => {
-      const {
-        main,
-        comment
-      } = part;
+    return newDocumentParts
+      .reduce((memo: string[], part) => {
+        const {
+          main,
+          comment
+        } = part;
 
-      let line;
-      if (comment !== null) {
-        const spacesBetween = maxCommentColumn - main.length;
-        line = `${main}${' '.repeat(spacesBetween)} // ${comment}`
-      } else {
-        line = main;
-      }
+        let line;
+        if (comment !== null) {
+          const spacesBetween = maxCommentColumn - main.length;
+          line = `${main}${' '.repeat(spacesBetween)} // ${comment.trim()}`
+        } else {
+          line = main;
+        }
+        console.log(line);
 
-      return [
-        ...memo,
-        line
-      ];
-    }, []).join('\n');
+        return [
+          ...memo,
+          line
+        ];
+      }, [])
+      .join('\n');
   }
 }
diff --git a/src/typescript/language.ts b/src/typescript/language.ts
index 1a86d308..b82b8084 100644
--- a/src/typescript/language.ts
+++ b/src/typescript/language.ts
@@ -30,7 +30,12 @@ export function interfaceDeclaration(generator: CodeGenerator, {
   closure: () => void) {
   generator.printNewlineIfNeeded();
   generator.printNewline();
-  generator.print(`export type ${interfaceName} = `);
+  if (noBrackets) {
+    generator.print(`export type ${interfaceName} = `);
+  } else {
+    // simple types are exposed with a more user friendly `interface`
+    generator.print(`export interface ${interfaceName} `);
+  }
   generator.pushScope({ typeName: interfaceName });
   if (noBrackets) {
     generator.withinBlock(closure, '', '');


Test patch:
diff --git a/test/typescript/__snapshots__/codeGeneration.js.snap b/test/typescript/__snapshots__/codeGeneration.js.snap
index 15b73ec7..9765994c 100644
--- a/test/typescript/__snapshots__/codeGeneration.js.snap
+++ b/test/typescript/__snapshots__/codeGeneration.js.snap
@@ -12,11 +12,11 @@ export enum Episode {
 }
 
 
-export type HeroAndFriendsNamesQueryVariables = {
+export interface HeroAndFriendsNamesQueryVariables {
   episode?: Episode | null,
 };
 
-export type HeroAndFriendsNamesQuery = {
+export interface HeroAndFriendsNamesQuery {
   hero: ( {
       __typename: \\"Human\\",
       // The name of the character
@@ -68,7 +68,7 @@ exports[`TypeScript code generation #generateSource() should generate fragmented
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type HeroAndFriendsNamesQuery = {
+export interface HeroAndFriendsNamesQuery {
   hero: ( {
       __typename: \\"Human\\",
       // The name of the character
@@ -146,7 +146,7 @@ export enum Episode {
 }
 
 
-export type ReviewInput = {
+export interface ReviewInput {
   // 0-5 stars
   stars: number,
   // Comment about the movie, optional
@@ -155,18 +155,18 @@ export type ReviewInput = {
   favorite_color?: ColorInput | null,
 };
 
-export type ColorInput = {
+export interface ColorInput {
   red: number,
   green: number,
   blue: number,
 };
 
-export type ReviewMovieMutationVariables = {
+export interface ReviewMovieMutationVariables {
   episode?: Episode | null,
   review?: ReviewInput | null,
 };
 
-export type ReviewMovieMutation = {
+export interface ReviewMovieMutation {
   createReview:  {
     __typename: \\"Review\\",
     // The number of stars this review gave, 1-5
@@ -182,7 +182,7 @@ exports[`TypeScript code generation #generateSource() should generate query oper
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type HeroAndDetailsQuery = {
+export interface HeroAndDetailsQuery {
   hero: ( {
       __typename: \\"Human\\",
       // What this human calls themselves
@@ -224,11 +224,11 @@ export enum Episode {
 }
 
 
-export type HeroAndFriendsNamesQueryVariables = {
+export interface HeroAndFriendsNamesQueryVariables {
   episode?: Episode | null,
 };
 
-export type HeroAndFriendsNamesQuery = {
+export interface HeroAndFriendsNamesQuery {
   hero: ( {
       __typename: \\"Human\\",
       // The name of the character
@@ -269,7 +269,7 @@ exports[`TypeScript code generation #generateSource() should generate simple nes
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type StarshipCoordsQuery = {
+export interface StarshipCoordsQuery {
   starship:  {
     __typename: \\"Starship\\",
     coordinates: Array< Array< number > > | null,
@@ -282,7 +282,7 @@ exports[`TypeScript code generation #generateSource() should generate simple que
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type HeroNameQuery = {
+export interface HeroNameQuery {
   hero: ( {
       __typename: \\"Human\\",
       // The name of the character
@@ -309,11 +309,11 @@ export enum Episode {
 }
 
 
-export type HeroNameQueryVariables = {
+export interface HeroNameQueryVariables {
   episode?: Episode | null,
 };
 
-export type HeroNameQuery = {
+export interface HeroNameQuery {
   hero: ( {
       __typename: \\"Human\\",
       // The name of the character
@@ -340,7 +340,7 @@ export enum EnumCommentTestCase {
 }
 
 
-export type CustomScalarQuery = {
+export interface CustomScalarQuery {
   commentTest:  {
     __typename: \\"CommentTest\\",
     enumCommentTest: EnumCommentTestCase | null,
@@ -353,7 +353,7 @@ exports[`TypeScript code generation #generateSource() should handle interfaces a
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type CustomScalarQuery = {
+export interface CustomScalarQuery {
   interfaceTest: ( {
       __typename: \\"ImplA\\",
       prop: string,
@@ -372,7 +372,7 @@ exports[`TypeScript code generation #generateSource() should handle multi-line c
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type CustomScalarQuery = {
+export interface CustomScalarQuery {
   commentTest:  {
     __typename: \\"CommentTest\\",
     // This is a multi-line
@@ -387,7 +387,7 @@ exports[`TypeScript code generation #generateSource() should handle single line
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type CustomScalarQuery = {
+export interface CustomScalarQuery {
   commentTest:  {
     __typename: \\"CommentTest\\",
     // This is a single-line comment
@@ -401,7 +401,7 @@ exports[`TypeScript code generation #generateSource() should handle unions at ro
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type CustomScalarQuery = {
+export interface CustomScalarQuery {
   unionTest: ( {
       __typename: \\"PartialA\\",
       prop: string,
@@ -418,7 +418,7 @@ exports[`TypeScript code generation #generateSource() should have __typename val
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type HeroNameQuery = {
+export interface HeroNameQuery {
   hero: ( {
       __typename: \\"Human\\",
       // The name of the character
@@ -448,7 +448,7 @@ exports[`TypeScript code generation #generateSource() should have __typename val
 "/* tslint:disable */
 //  This file was automatically generated and should not be edited.
 
-export type DroidNameQuery = {
+export interface DroidNameQuery {
   droid:  {
     __typename: \\"Droid\\",
     // What others call this droid
@@ -456,7 +456,7 @@ export type DroidNameQuery = {
   } | null,
 };
 
-export type DroidWithNameFragment = {
+export interface DroidWithNameFragment {
   __typename: \\"Droid\\",
   // What others call this droid
   name: string,