Repository: apollographql/apollo-tooling
Pull request #2050 (2020-07-24 18:22:36)

Add `unionTypes` and `interfaceTypes` to the `json-modern` IR
This commit adds new top level properties called `unionTypes` and `interfaceTypes`, to the `json-modern` exported IR. These properties list all unions and their types, as well as all interfaces and their implementing types.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2611846..ea140289 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,7 @@
 - `apollo-codegen-typescript`
   - <First `apollo-codegen-typescript` related entry goes here>
 - `apollo-codegen-core`
-  - <First `apollo-codegen-core` related entry goes here>
+  - Add new `unionTypes` and `interfaceTypes` properties to the exported IR JSON (when using the `json-modern` target), that list all unions and their types, as well as all interfaces and their implementing types [#2050](https://github.com/apollographql/apollo-tooling/pull/2050).
 - `apollo-env`
   - <First `apollo-env` related entry goes here>
 - `apollo-graphql`
diff --git a/packages/apollo-codegen-core/src/compiler/index.ts b/packages/apollo-codegen-core/src/compiler/index.ts
index 690a946c..481b1b13 100644
--- a/packages/apollo-codegen-core/src/compiler/index.ts
+++ b/packages/apollo-codegen-core/src/compiler/index.ts
@@ -12,6 +12,8 @@ import {
   GraphQLSchema,
   GraphQLType,
   GraphQLCompositeType,
+  GraphQLUnionType,
+  GraphQLInterfaceType,
   DocumentNode,
   OperationDefinitionNode,
   FragmentDefinitionNode,
@@ -23,8 +25,8 @@ import {
   isEnumType,
   isInputObjectType,
   isScalarType,
-  NamedTypeNode,
-  ListTypeNode,
+  isUnionType,
+  isInterfaceType,
   TypeNode,
   parseType
 } from "graphql";
@@ -61,6 +63,11 @@ export interface CompilerContext {
   operations: { [operationName: string]: Operation };
   fragments: { [fragmentName: string]: Fragment };
   options: CompilerOptions;
+  unionTypes: GraphQLUnionType[];
+  interfaceTypes: Map<
+    GraphQLInterfaceType,
+    (GraphQLObjectType | GraphQLInterfaceType)[]
+  >;
 }
 
 export interface Operation {
@@ -206,14 +213,29 @@ export function compileToIR(
   }
 
   const typesUsed = compiler.typesUsed;
-
-  return { schema, typesUsed, operations, fragments, options };
+  const unionTypes = compiler.unionTypes;
+  const interfaceTypes = compiler.interfaceTypes;
+
+  return {
+    schema,
+    typesUsed,
+    operations,
+    fragments,
+    options,
+    unionTypes,
+    interfaceTypes
+  };
 }
 
 class Compiler {
   options: CompilerOptions;
   schema: GraphQLSchema;
   typesUsedSet: Set<GraphQLType>;
+  unionTypesSet: Set<GraphQLUnionType>;
+  interfaceTypesMap: Map<
+    GraphQLInterfaceType,
+    (GraphQLObjectType | GraphQLInterfaceType)[]
+  >;
 
   unresolvedFragmentSpreads: FragmentSpread[] = [];
 
@@ -222,6 +244,8 @@ class Compiler {
     this.options = options;
 
     this.typesUsedSet = new Set();
+    this.unionTypesSet = new Set();
+    this.interfaceTypesMap = new Map();
   }
 
   addTypeUsed(type: GraphQLType) {
@@ -245,6 +269,31 @@ class Compiler {
     return Array.from(this.typesUsedSet);
   }
 
+  addUnionType(type: GraphQLType) {
+    if (isUnionType(type)) {
+      if (this.unionTypesSet.has(type)) return;
+      this.unionTypesSet.add(type);
+    }
+  }
+
+  get unionTypes(): GraphQLUnionType[] {
+    return Array.from(this.unionTypesSet);
+  }
+
+  addInterfaceType(type: GraphQLType) {
+    if (isInterfaceType(type)) {
+      if (this.interfaceTypesMap.has(type)) return;
+      this.interfaceTypesMap.set(type, this.possibleTypesForType(type));
+    }
+  }
+
+  get interfaceTypes(): Map<
+    GraphQLInterfaceType,
+    (GraphQLObjectType | GraphQLInterfaceType)[]
+  > {
+    return this.interfaceTypesMap;
+  }
+
   compileOperation(operationDefinition: OperationDefinitionNode): Operation {
     if (!operationDefinition.name) {
       throw new Error("Operations should be named");
@@ -372,6 +421,8 @@ class Compiler {
 
         const unmodifiedFieldType = getNamedType(fieldType);
         this.addTypeUsed(unmodifiedFieldType);
+        this.addUnionType(unmodifiedFieldType);
+        this.addInterfaceType(unmodifiedFieldType);
 
         const { description, isDeprecated, deprecationReason } = fieldDef;
 
diff --git a/packages/apollo-codegen-core/src/compiler/legacyIR.ts b/packages/apollo-codegen-core/src/compiler/legacyIR.ts
index d2dc8535..9685dfbd 100644
--- a/packages/apollo-codegen-core/src/compiler/legacyIR.ts
+++ b/packages/apollo-codegen-core/src/compiler/legacyIR.ts
@@ -4,6 +4,8 @@ import {
   GraphQLObjectType,
   GraphQLCompositeType,
   GraphQLInputType,
+  GraphQLUnionType,
+  GraphQLInterfaceType,
   DocumentNode,
   TypeNode
 } from "graphql";
@@ -39,6 +41,11 @@ export interface LegacyCompilerContext {
   fragments: { [fragmentName: string]: LegacyFragment };
   typesUsed: GraphQLType[];
   options: CompilerOptions;
+  unionTypes: GraphQLUnionType[];
+  interfaceTypes: Map<
+    GraphQLInterfaceType,
+    (GraphQLObjectType | GraphQLInterfaceType)[]
+  >;
 }
 
 export interface LegacyOperation {
@@ -185,7 +192,9 @@ class LegacyIRTransformer {
       operations,
       fragments,
       typesUsed: this.context.typesUsed,
-      options: this.options
+      options: this.options,
+      unionTypes: this.context.unionTypes,
+      interfaceTypes: this.context.interfaceTypes
     };
 
     return legacyContext;
diff --git a/packages/apollo-codegen-core/src/serializeToJSON.ts b/packages/apollo-codegen-core/src/serializeToJSON.ts
index 90dd5a56..75758f31 100644
--- a/packages/apollo-codegen-core/src/serializeToJSON.ts
+++ b/packages/apollo-codegen-core/src/serializeToJSON.ts
@@ -4,9 +4,14 @@ import {
   GraphQLScalarType,
   GraphQLEnumType,
   GraphQLInputObjectType,
+  GraphQLUnionType,
+  GraphQLInterfaceType,
+  GraphQLObjectType,
   isEnumType,
   isInputObjectType,
   isScalarType,
+  isUnionType,
+  isInterfaceType,
   parseType
 } from "graphql";
 
@@ -25,13 +30,15 @@ interface serializeOptions {
 
 export default function serializeToJSON(
   context: LegacyCompilerContext | CompilerContext,
-  options: serializeOptions
+  options?: serializeOptions
 ) {
   return serializeAST(
     {
       operations: Object.values(context.operations),
       fragments: Object.values(context.fragments),
-      typesUsed: context.typesUsed.map(type => serializeType(type, options))
+      typesUsed: context.typesUsed.map(type => serializeType(type, options)),
+      unionTypes: context.unionTypes.map(type => serializeType(type, options)),
+      interfaceTypes: serializeInterfaceTypes(context.interfaceTypes)
     },
     "\t"
   );
@@ -51,13 +58,15 @@ export function serializeAST(ast: any, space?: string) {
   );
 }
 
-function serializeType(type: GraphQLType, options: serializeOptions) {
+function serializeType(type: GraphQLType, options?: serializeOptions) {
   if (isEnumType(type)) {
     return serializeEnumType(type);
   } else if (isInputObjectType(type)) {
     return serializeInputObjectType(type, options);
   } else if (isScalarType(type)) {
     return serializeScalarType(type);
+  } else if (isUnionType(type)) {
+    return serializeUnionType(type);
   } else {
     throw new Error(`Unexpected GraphQL type: ${type}`);
   }
@@ -113,3 +122,30 @@ function serializeScalarType(type: GraphQLScalarType) {
     description
   };
 }
+
+function serializeUnionType(type: GraphQLUnionType) {
+  const { name } = type;
+  return {
+    name,
+    types: type.getTypes()
+  };
+}
+
+function serializeInterfaceTypes(
+  interfaceTypes: Map<
+    GraphQLInterfaceType,
+    (GraphQLObjectType | GraphQLInterfaceType)[]
+  >
+) {
+  const types: {
+    name: string;
+    types: (GraphQLObjectType | GraphQLInterfaceType)[];
+  }[] = [];
+  for (let [interfaceType, implementors] of interfaceTypes) {
+    types.push({
+      name: interfaceType.toString(),
+      types: implementors
+    });
+  }
+  return types;
+}


Test patch:
diff --git a/packages/apollo-codegen-core/src/__tests__/__snapshots__/jsonOutput.ts.snap b/packages/apollo-codegen-core/src/__tests__/__snapshots__/jsonOutput.ts.snap
index 88906346..1d498567 100644
--- a/packages/apollo-codegen-core/src/__tests__/__snapshots__/jsonOutput.ts.snap
+++ b/packages/apollo-codegen-core/src/__tests__/__snapshots__/jsonOutput.ts.snap
@@ -151,7 +151,9 @@ exports[`JSON output should generate JSON output for a mutation with an enum and
 				}
 			]
 		}
-	]
+	],
+	\\"unionTypes\\": [],
+	\\"interfaceTypes\\": []
 }"
 `;
 
@@ -300,7 +302,17 @@ exports[`JSON output should generate JSON output for a query with a fragment spr
 			]
 		}
 	],
-	\\"typesUsed\\": []
+	\\"typesUsed\\": [],
+	\\"unionTypes\\": [],
+	\\"interfaceTypes\\": [
+		{
+			\\"name\\": \\"Character\\",
+			\\"types\\": [
+				\\"Human\\",
+				\\"Droid\\"
+			]
+		}
+	]
 }"
 `;
 
@@ -377,7 +389,17 @@ exports[`JSON output should generate JSON output for a query with a nested selec
 		}
 	],
 	\\"fragments\\": [],
-	\\"typesUsed\\": []
+	\\"typesUsed\\": [],
+	\\"unionTypes\\": [],
+	\\"interfaceTypes\\": [
+		{
+			\\"name\\": \\"Character\\",
+			\\"types\\": [
+				\\"Human\\",
+				\\"Droid\\"
+			]
+		}
+	]
 }"
 `;
 
@@ -468,6 +490,16 @@ exports[`JSON output should generate JSON output for a query with an enum variab
 				}
 			]
 		}
+	],
+	\\"unionTypes\\": [],
+	\\"interfaceTypes\\": [
+		{
+			\\"name\\": \\"Character\\",
+			\\"types\\": [
+				\\"Human\\",
+				\\"Droid\\"
+			]
+		}
 	]
 }"
 `;
@@ -538,7 +570,9 @@ exports[`JSON output should generate JSON output for a subscription 1`] = `
 		}
 	],
 	\\"fragments\\": [],
-	\\"typesUsed\\": []
+	\\"typesUsed\\": [],
+	\\"unionTypes\\": [],
+	\\"interfaceTypes\\": []
 }"
 `;
 
@@ -658,6 +692,265 @@ exports[`JSON output should generate JSON output for an input object type with d
 				}
 			]
 		}
+	],
+	\\"unionTypes\\": [],
+	\\"interfaceTypes\\": []
+}"
+`;
+
+exports[`JSON output should list all interfaces and their implementing types under a \`interfaceTypes\` property 1`] = `
+"{
+	\\"operations\\": [
+		{
+			\\"filePath\\": \\"GraphQL request\\",
+			\\"operationName\\": \\"HeroForEpisode\\",
+			\\"operationType\\": \\"query\\",
+			\\"rootType\\": \\"Query\\",
+			\\"variables\\": [],
+			\\"source\\": \\"query HeroForEpisode {\\\\n  hero(episode: JEDI) {\\\\n    __typename\\\\n    name\\\\n    ... on Droid {\\\\n      primaryFunction\\\\n    }\\\\n  }\\\\n}\\",
+			\\"fields\\": [
+				{
+					\\"responseName\\": \\"hero\\",
+					\\"fieldName\\": \\"hero\\",
+					\\"type\\": \\"Character\\",
+					\\"args\\": [
+						{
+							\\"name\\": \\"episode\\",
+							\\"value\\": \\"JEDI\\",
+							\\"type\\": \\"Episode\\"
+						}
+					],
+					\\"isConditional\\": false,
+					\\"isDeprecated\\": false,
+					\\"fields\\": [
+						{
+							\\"responseName\\": \\"__typename\\",
+							\\"fieldName\\": \\"__typename\\",
+							\\"type\\": \\"String!\\",
+							\\"isConditional\\": false,
+							\\"isDeprecated\\": false
+						},
+						{
+							\\"responseName\\": \\"name\\",
+							\\"fieldName\\": \\"name\\",
+							\\"type\\": \\"String!\\",
+							\\"isConditional\\": false,
+							\\"description\\": \\"The name of the character\\",
+							\\"isDeprecated\\": false
+						}
+					],
+					\\"fragmentSpreads\\": [],
+					\\"inlineFragments\\": [
+						{
+							\\"typeCondition\\": \\"Droid\\",
+							\\"possibleTypes\\": [
+								\\"Droid\\"
+							],
+							\\"fields\\": [
+								{
+									\\"responseName\\": \\"__typename\\",
+									\\"fieldName\\": \\"__typename\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"name\\",
+									\\"fieldName\\": \\"name\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"What others call this droid\\",
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"primaryFunction\\",
+									\\"fieldName\\": \\"primaryFunction\\",
+									\\"type\\": \\"String\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"This droid's primary function\\",
+									\\"isDeprecated\\": false
+								}
+							],
+							\\"fragmentSpreads\\": []
+						}
+					]
+				}
+			],
+			\\"fragmentSpreads\\": [],
+			\\"inlineFragments\\": [],
+			\\"fragmentsReferenced\\": [],
+			\\"sourceWithFragments\\": \\"query HeroForEpisode {\\\\n  hero(episode: JEDI) {\\\\n    __typename\\\\n    name\\\\n    ... on Droid {\\\\n      primaryFunction\\\\n    }\\\\n  }\\\\n}\\",
+			\\"operationId\\": \\"173a7ebf41abd014f3fc54ddd007f6182fca738bd44a1e2d1bb75503c8bf79f6\\"
+		}
+	],
+	\\"fragments\\": [],
+	\\"typesUsed\\": [],
+	\\"unionTypes\\": [],
+	\\"interfaceTypes\\": [
+		{
+			\\"name\\": \\"Character\\",
+			\\"types\\": [
+				\\"Human\\",
+				\\"Droid\\"
+			]
+		}
 	]
 }"
 `;
+
+exports[`JSON output should list all unions and their types under a \`unionTypes\` property 1`] = `
+"{
+	\\"operations\\": [
+		{
+			\\"filePath\\": \\"GraphQL request\\",
+			\\"operationName\\": \\"Search\\",
+			\\"operationType\\": \\"query\\",
+			\\"rootType\\": \\"Query\\",
+			\\"variables\\": [],
+			\\"source\\": \\"query Search {\\\\n  search(text: \\\\\\"an\\\\\\") {\\\\n    __typename\\\\n    ... on Human {\\\\n      name\\\\n      height\\\\n    }\\\\n    ... on Droid {\\\\n      name\\\\n      primaryFunction\\\\n    }\\\\n    ... on Starship {\\\\n      name\\\\n      length\\\\n    }\\\\n  }\\\\n}\\",
+			\\"fields\\": [
+				{
+					\\"responseName\\": \\"search\\",
+					\\"fieldName\\": \\"search\\",
+					\\"type\\": \\"[SearchResult]\\",
+					\\"args\\": [
+						{
+							\\"name\\": \\"text\\",
+							\\"value\\": \\"an\\",
+							\\"type\\": \\"String\\"
+						}
+					],
+					\\"isConditional\\": false,
+					\\"isDeprecated\\": false,
+					\\"fields\\": [
+						{
+							\\"responseName\\": \\"__typename\\",
+							\\"fieldName\\": \\"__typename\\",
+							\\"type\\": \\"String!\\",
+							\\"isConditional\\": false,
+							\\"isDeprecated\\": false
+						}
+					],
+					\\"fragmentSpreads\\": [],
+					\\"inlineFragments\\": [
+						{
+							\\"typeCondition\\": \\"Human\\",
+							\\"possibleTypes\\": [
+								\\"Human\\"
+							],
+							\\"fields\\": [
+								{
+									\\"responseName\\": \\"__typename\\",
+									\\"fieldName\\": \\"__typename\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"name\\",
+									\\"fieldName\\": \\"name\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"What this human calls themselves\\",
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"height\\",
+									\\"fieldName\\": \\"height\\",
+									\\"type\\": \\"Float\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"Height in the preferred unit, default is meters\\",
+									\\"isDeprecated\\": false
+								}
+							],
+							\\"fragmentSpreads\\": []
+						},
+						{
+							\\"typeCondition\\": \\"Droid\\",
+							\\"possibleTypes\\": [
+								\\"Droid\\"
+							],
+							\\"fields\\": [
+								{
+									\\"responseName\\": \\"__typename\\",
+									\\"fieldName\\": \\"__typename\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"name\\",
+									\\"fieldName\\": \\"name\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"What others call this droid\\",
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"primaryFunction\\",
+									\\"fieldName\\": \\"primaryFunction\\",
+									\\"type\\": \\"String\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"This droid's primary function\\",
+									\\"isDeprecated\\": false
+								}
+							],
+							\\"fragmentSpreads\\": []
+						},
+						{
+							\\"typeCondition\\": \\"Starship\\",
+							\\"possibleTypes\\": [
+								\\"Starship\\"
+							],
+							\\"fields\\": [
+								{
+									\\"responseName\\": \\"__typename\\",
+									\\"fieldName\\": \\"__typename\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"name\\",
+									\\"fieldName\\": \\"name\\",
+									\\"type\\": \\"String!\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"The name of the starship\\",
+									\\"isDeprecated\\": false
+								},
+								{
+									\\"responseName\\": \\"length\\",
+									\\"fieldName\\": \\"length\\",
+									\\"type\\": \\"Float\\",
+									\\"isConditional\\": false,
+									\\"description\\": \\"Length of the starship, along the longest axis\\",
+									\\"isDeprecated\\": false
+								}
+							],
+							\\"fragmentSpreads\\": []
+						}
+					]
+				}
+			],
+			\\"fragmentSpreads\\": [],
+			\\"inlineFragments\\": [],
+			\\"fragmentsReferenced\\": [],
+			\\"sourceWithFragments\\": \\"query Search {\\\\n  search(text: \\\\\\"an\\\\\\") {\\\\n    __typename\\\\n    ... on Human {\\\\n      name\\\\n      height\\\\n    }\\\\n    ... on Droid {\\\\n      name\\\\n      primaryFunction\\\\n    }\\\\n    ... on Starship {\\\\n      name\\\\n      length\\\\n    }\\\\n  }\\\\n}\\",
+			\\"operationId\\": \\"9887ff0652e14d678a66769b853c41293d4afb1d1338bbbdb9f84e66979605dd\\"
+		}
+	],
+	\\"fragments\\": [],
+	\\"typesUsed\\": [],
+	\\"unionTypes\\": [
+		{
+			\\"name\\": \\"SearchResult\\",
+			\\"types\\": [
+				\\"Human\\",
+				\\"Droid\\",
+				\\"Starship\\"
+			]
+		}
+	],
+	\\"interfaceTypes\\": []
+}"
+`;
diff --git a/packages/apollo-codegen-core/src/__tests__/jsonOutput.ts b/packages/apollo-codegen-core/src/__tests__/jsonOutput.ts
index 003946a2..99200bae 100644
--- a/packages/apollo-codegen-core/src/__tests__/jsonOutput.ts
+++ b/packages/apollo-codegen-core/src/__tests__/jsonOutput.ts
@@ -1,4 +1,4 @@
-import { GraphQLSchema, buildSchema, parse } from "graphql";
+import { GraphQLSchema, buildSchema, parse, GraphQLUnionType } from "graphql";
 import { compileToLegacyIR } from "../compiler/legacyIR";
 import serializeToJSON from "../serializeToJSON";
 
@@ -157,4 +157,58 @@ describe("JSON output", function() {
 
     expect(output).toMatchSnapshot();
   });
+
+  test("should list all unions and their types under a `unionTypes` property", function() {
+    const context = compileFromSource(`
+      query Search {
+        search(text: "an") {
+          __typename
+          ... on Human {
+            name
+            height
+          }
+          ... on Droid {
+            name
+            primaryFunction
+          }
+          ... on Starship {
+            name
+            length
+          }
+        }
+      }
+    `);
+
+    expect(context.unionTypes.length).toBe(1);
+    const type = context.unionTypes[0] as GraphQLUnionType;
+    expect(type.name).toBe("SearchResult");
+    expect(type.getTypes().length).toBe(3);
+
+    const output = serializeToJSON(context);
+    expect(output).toMatchSnapshot();
+  });
+
+  test("should list all interfaces and their implementing types under a `interfaceTypes` property", function() {
+    const context = compileFromSource(`
+      query HeroForEpisode {
+        hero(episode: JEDI) {
+          name
+          ... on Droid {
+            primaryFunction
+          }
+        }
+      }
+    `);
+
+    expect(context.interfaceTypes.size).toBe(1);
+    const [
+      interfaceType,
+      implementors
+    ] = context.interfaceTypes.entries().next().value;
+    expect(interfaceType.toString()).toBe("Character");
+    expect(implementors.length).toBe(2);
+
+    const output = serializeToJSON(context);
+    expect(output).toMatchSnapshot();
+  });
 });
diff --git a/packages/apollo/src/commands/client/__tests__/__snapshots__/generate.test.ts.snap b/packages/apollo/src/commands/client/__tests__/__snapshots__/generate.test.ts.snap
index 33977c16..dc46acde 100644
--- a/packages/apollo/src/commands/client/__tests__/__snapshots__/generate.test.ts.snap
+++ b/packages/apollo/src/commands/client/__tests__/__snapshots__/generate.test.ts.snap
@@ -96,6 +96,7 @@ export type SimpleQuery = {
 exports[`client:codegen writes json operations 1`] = `
 Object {
   "fragments": Array [],
+  "interfaceTypes": Array [],
   "operations": Array [
     Object {
       "fields": Array [
@@ -125,12 +126,14 @@ Object {
     },
   ],
   "typesUsed": Array [],
+  "unionTypes": Array [],
 }
 `;
 
 exports[`client:codegen writes json operations with typeNode (json-modern) 1`] = `
 Object {
   "fragments": Array [],
+  "interfaceTypes": Array [],
   "operations": Array [
     Object {
       "fields": Array [
@@ -170,6 +173,7 @@ Object {
     },
   ],
   "typesUsed": Array [],
+  "unionTypes": Array [],
 }
 `;