Skip to content

Commit d3c50f5

Browse files
authored
Merge branch 'xolvio:master' into codgenv2
2 parents f5475de + 2275fc5 commit d3c50f5

File tree

9 files changed

+171
-22
lines changed

9 files changed

+171
-22
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ name: Release
22
on: push
33
jobs:
44
test:
5-
runs-on: ubuntu-16.04
5+
runs-on: ubuntu-latest
66
steps:
77
- uses: actions/checkout@v2
88
- uses: actions/setup-node@v1
99
with:
10-
node-version: '12'
10+
node-version: '14'
1111
- run: yarn
1212
- run: yarn test
1313
- run: yarn end-to-end-test

scaffold/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@
6868
"testdouble-jest": "2.0.0",
6969
"ts-jest": "26.1.3",
7070
"ts-node": "8.10.2",
71-
"typescript": "3.9.7"
71+
"typescript": "4.7.4"
7272
}
7373
}

src/generate/generate-module.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import getScalars from './parse-graphql/getScalars';
1515
import { saveRenderedTemplate } from './helpers/saveRenderedTemplate';
1616
import { findProjectMainPath } from './helpers/findProjectMainPath';
1717
import { execQuietly } from './helpers/execQuietly';
18+
import getUnions from './parse-graphql/getUnions';
1819

1920
const debug = configureDebug('generate-module');
2021

@@ -247,36 +248,38 @@ export const executeGeneration = async (appPrefix = '~app', generatedPrefix = '~
247248

248249
const federatedEntities = getFederatedEntities(schemaString);
249250
const interfaces = getInterfaces(schemaString);
251+
const unions = getUnions(schemaString);
252+
250253
// Leaving this for now
251254
// eslint-disable-next-line no-param-reassign
252255
schemaString = schemaString.replace(/extend type/g, 'type');
253256
const source = new Source(schemaString);
254257
const schema = buildSchema(source, { assumeValidSDL: true });
255258
shelljs.mkdir('-p', `${projectMainPath}/src/${graphqlFileRootPath}/types/`);
256259

257-
const createInterfaceType = (interfaceName: string) => {
260+
const createResolveType = (resolverTypeName: string) => {
258261
const templateName = './templates/typeTypeResolvers.handlebars';
259262
const capitalizedFieldName = capitalize('__resolveType');
260263
const context = {
261-
typeName: interfaceName,
264+
typeName: resolverTypeName,
262265
fieldName: '__resolveType',
263266
moduleName: name,
264267
resolveReferenceType: true,
265268
capitalizedFieldName,
266269
generatedPrefix,
267270
};
268271
const filePath = `${projectMainPath}/src/${graphqlFileRootPath}/types/`;
269-
const fileName = `${interfaceName}${capitalizedFieldName}.ts`;
272+
const fileName = `${resolverTypeName}${capitalizedFieldName}.ts`;
270273
const keepIfExists = true;
271274

272275
saveRenderedTemplate(templateName, context, filePath, fileName, keepIfExists);
273276
};
274277

275-
const createInterfaceSpec = (interfaceName: string) => {
278+
const createResolveTypeSpec = (resoverTypeName: string) => {
276279
const templateName = './templates/typeTypeResolvers.spec.handlebars';
277280
const capitalizedFieldName = capitalize('__resolveType');
278281
const context = {
279-
typeName: interfaceName,
282+
typeName: resoverTypeName,
280283
fieldName: '__resolveType',
281284
moduleName: name,
282285
hasArguments: false,
@@ -285,17 +288,17 @@ export const executeGeneration = async (appPrefix = '~app', generatedPrefix = '~
285288
generatedPrefix,
286289
};
287290
const filePath = `${projectMainPath}/src/${graphqlFileRootPath}/types/`;
288-
const fileName = `${interfaceName}${capitalizedFieldName}.spec.ts`;
291+
const fileName = `${resoverTypeName}${capitalizedFieldName}.spec.ts`;
289292
const keepIfExists = true;
290293

291294
saveRenderedTemplate(templateName, context, filePath, fileName, keepIfExists);
292295
};
293296

294-
const createInterfaceSpecWrapper = (interfaceName: string) => {
297+
const createResolveTypeSpecWrapper = (resolverTypeName: string) => {
295298
const templateName = './templates/typeTypeResolversSpecWrapper.handlebars';
296299
const capitalizedFieldName = capitalize('__resolveType');
297300
const context = {
298-
typeName: interfaceName,
301+
typeName: resolverTypeName,
299302
fieldName: '__resolveType',
300303
moduleName: name,
301304
hasArguments: false,
@@ -306,20 +309,31 @@ export const executeGeneration = async (appPrefix = '~app', generatedPrefix = '~
306309
graphqlFileRootPath,
307310
};
308311
const filePath = `${projectMainPath}/generated/graphql/helpers/`;
309-
const fileName = `${interfaceName}${capitalizedFieldName}SpecWrapper.ts`;
312+
const fileName = `${resolverTypeName}${capitalizedFieldName}SpecWrapper.ts`;
310313
const keepIfExists = false;
311314

312315
saveRenderedTemplate(templateName, context, filePath, fileName, keepIfExists);
313316
};
314317
interfaces.forEach((interfaceName) => {
315-
createInterfaceType(interfaceName);
316-
createInterfaceSpec(interfaceName);
317-
createInterfaceSpecWrapper(interfaceName);
318+
createResolveType(interfaceName);
319+
createResolveTypeSpec(interfaceName);
320+
createResolveTypeSpecWrapper(interfaceName);
318321
typeResolvers.push({
319322
typeName: interfaceName,
320323
fieldName: [{ name: '__resolveType', capitalizedName: capitalize('__resolveType') }],
321324
});
322325
});
326+
327+
unions.forEach((unionName) => {
328+
createResolveType(unionName);
329+
createResolveTypeSpec(unionName);
330+
createResolveTypeSpecWrapper(unionName);
331+
typeResolvers.push({
332+
typeName: unionName,
333+
fieldName: [{ name: '__resolveType', capitalizedName: capitalize('__resolveType') }],
334+
});
335+
});
336+
323337
type FilteredType = { name: { value: string }; resolveReferenceType: boolean; arguments?: string[] };
324338
typeDefinitions.forEach((typeDef) => {
325339
let filtered: FilteredType[] = [];
@@ -333,7 +347,10 @@ export const executeGeneration = async (appPrefix = '~app', generatedPrefix = '~
333347
filtered = type.astNode.fields.filter((field) =>
334348
field.directives.find(
335349
(d: { name: { value: string } }) =>
336-
d.name.value === 'computed' || d.name.value === 'link' || d.name.value === 'requires',
350+
d.name.value === 'computed' ||
351+
d.name.value === 'link' ||
352+
d.name.value === 'requires' ||
353+
d.name.value === 'map',
337354
),
338355
);
339356
}

src/generate/parse-graphql/getInterfaces.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,34 @@ test('get the interfaces', () => {
3131

3232
expect(res).toEqual(['Home']);
3333
});
34+
test('should throw error if duplicate interface names are found', () => {
35+
const schemaString = gql`
36+
type TodoItem @key(fields: "id") {
37+
id: ID!
38+
list: List
39+
}
40+
41+
interface Home {
42+
address: string
43+
}
44+
45+
extend type List {
46+
id: ID!
47+
todos: [TodoItem!]!
48+
incompleteCount: Int!
49+
}
50+
51+
type InMemory {
52+
id: ID!
53+
}
54+
55+
type Query {
56+
homes: [Home]
57+
}
58+
interface Home {
59+
address: string
60+
}
61+
`;
62+
63+
expect(() => getInterfaces(schemaString)).toThrow('Duplicate interface name found: Home');
64+
});
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import gql from 'graphql-tag';
2+
import validateUniqueName from './validateUniqueName';
23

34
export default (graphqlString: string) => {
45
const graphqlAST = gql`
56
${graphqlString}
67
`;
78

8-
return (
9-
graphqlAST.definitions
10-
.filter((d) => ['InterfaceTypeDefinition'].indexOf(d.kind) > -1)
11-
// @ts-ignore
12-
.map((f) => f.name.value)
13-
);
9+
const interfacesTypeDefs = graphqlAST.definitions
10+
.filter((d) => ['InterfaceTypeDefinition'].indexOf(d.kind) > -1)
11+
// @ts-ignore
12+
.map((f) => f.name.value);
13+
14+
validateUniqueName(interfacesTypeDefs, (name: string) => {
15+
throw new Error(`Duplicate interface name found: ${name}`);
16+
});
17+
18+
return interfacesTypeDefs;
1419
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import getUnions from './getUnions';
2+
3+
const gql = (a: TemplateStringsArray) => a[0];
4+
5+
test('get the unions', () => {
6+
const schemaString = gql`
7+
type Query {
8+
homes: [Home]
9+
}
10+
11+
type Cottage {
12+
id: ID!
13+
address: String!
14+
}
15+
16+
type Villa {
17+
id: ID!
18+
address: String!
19+
owner: String!
20+
}
21+
22+
union Home = Cottage | Villa
23+
`;
24+
25+
const res = getUnions(schemaString);
26+
27+
expect(res).toEqual(['Home']);
28+
});
29+
test('should throw exception if duplicate union names are found', () => {
30+
const schemaString = gql`
31+
type Query {
32+
homes: [Home]
33+
}
34+
35+
type Cottage {
36+
id: ID!
37+
address: String!
38+
}
39+
40+
type Villa {
41+
id: ID!
42+
address: String!
43+
owner: String!
44+
}
45+
46+
union Home = Cottage | Villa
47+
union Home = Cottage | Villa
48+
`;
49+
50+
expect(() => getUnions(schemaString)).toThrow('Duplicate union name found: Home');
51+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import gql from 'graphql-tag';
2+
import validateUniqueName from './validateUniqueName';
3+
4+
export default (graphqlString: string) => {
5+
const graphqlAST = gql`
6+
${graphqlString}
7+
`;
8+
9+
const unionTypeDefinitions = graphqlAST.definitions
10+
.filter((d) => ['UnionTypeDefinition'].indexOf(d.kind) > -1)
11+
// @ts-ignore
12+
.map((f) => f.name.value);
13+
14+
validateUniqueName(unionTypeDefinitions, (name: string) => {
15+
throw new Error(`Duplicate union name found: ${name}`);
16+
});
17+
return unionTypeDefinitions;
18+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import validateUniqueName from './validateUniqueName';
2+
3+
test('should throw error if duplicate entry found', () => {
4+
const names = ['apple', 'banana', 'orange', 'banana'];
5+
expect(() =>
6+
validateUniqueName(names, (name: any) => {
7+
throw new Error(`duplicate record found: ${name}`);
8+
}),
9+
).toThrow('duplicate record found: banana');
10+
});
11+
12+
test('should not throw error if no duplicate entry found', () => {
13+
const names = ['apple', 'banana', 'orange'];
14+
expect(() =>
15+
validateUniqueName(names, () => {
16+
throw new Error('duplicate record found');
17+
}),
18+
).not.toThrow();
19+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function (names: string[], exceptionHandler: Function) {
2+
const nameCounts: Map<string, number> = new Map();
3+
names.forEach((name) => {
4+
const val = (nameCounts.get(name) ?? 0) + 1;
5+
if (val > 1) exceptionHandler(name);
6+
nameCounts.set(name, val);
7+
});
8+
}

0 commit comments

Comments
 (0)