@khanacademy/graphql-flow 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +15 -4
- package/.github/workflows/changeset-release.yml +3 -5
- package/.github/workflows/pr-checks.yml +17 -17
- package/.prettierrc +2 -2
- package/.vscode/settings.json +6 -0
- package/CHANGELOG.md +12 -0
- package/dist/cli/config.js +6 -15
- package/dist/cli/run.js +31 -37
- package/dist/enums.js +9 -9
- package/dist/generateResponseType.js +36 -40
- package/dist/generateTypeFiles.js +10 -10
- package/dist/generateVariablesType.js +12 -13
- package/dist/index.js +4 -6
- package/dist/parser/parse.js +47 -53
- package/dist/parser/resolve.js +16 -16
- package/dist/parser/utils.js +25 -11
- package/dist/schemaFromIntrospectionData.js +5 -5
- package/dist/utils.js +8 -8
- package/package.json +12 -13
- package/src/__test__/generateTypeFileContents.test.ts +26 -25
- package/src/__test__/graphql-flow.test.ts +32 -33
- package/src/__test__/processPragmas.test.ts +14 -13
- package/src/cli/__test__/config.test.ts +51 -56
- package/src/cli/config.ts +23 -20
- package/src/cli/run.ts +45 -46
- package/src/enums.ts +17 -17
- package/src/generateResponseType.ts +120 -91
- package/src/generateTypeFiles.ts +24 -22
- package/src/generateVariablesType.ts +20 -20
- package/src/index.ts +28 -29
- package/src/parser/__test__/parse.test.ts +114 -23
- package/src/parser/__test__/utils.test.ts +80 -0
- package/src/parser/parse.ts +122 -106
- package/src/parser/resolve.ts +61 -34
- package/src/parser/utils.ts +28 -11
- package/src/schemaFromIntrospectionData.ts +10 -8
- package/src/types.ts +57 -53
- package/src/utils.ts +30 -16
- package/tools/find-files-with-gql.ts +7 -11
package/src/parser/parse.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import {isTruthy} from
|
|
1
|
+
import {isTruthy} from "@khanacademy/wonder-stuff-core";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from
|
|
3
|
+
ImportDeclaration,
|
|
4
|
+
VariableDeclarator,
|
|
5
|
+
TaggedTemplateExpression,
|
|
6
|
+
File,
|
|
7
|
+
} from "@babel/types";
|
|
8
8
|
|
|
9
|
-
import {parse} from
|
|
10
|
-
import traverse from
|
|
9
|
+
import {parse, ParserPlugin} from "@babel/parser";
|
|
10
|
+
import traverse from "@babel/traverse";
|
|
11
11
|
|
|
12
|
-
import path from
|
|
12
|
+
import path from "path";
|
|
13
13
|
|
|
14
|
-
import {getPathWithExtension} from
|
|
14
|
+
import {getPathWithExtension} from "./utils";
|
|
15
|
+
import {Config} from "../types";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* This file is responsible for finding all gql`-annotated
|
|
@@ -43,52 +44,52 @@ import {getPathWithExtension} from './utils';
|
|
|
43
44
|
*/
|
|
44
45
|
|
|
45
46
|
export type Template = {
|
|
46
|
-
literals: Array<string
|
|
47
|
-
expressions: Array<Document | Import
|
|
48
|
-
loc: Loc
|
|
47
|
+
literals: Array<string>;
|
|
48
|
+
expressions: Array<Document | Import>;
|
|
49
|
+
loc: Loc;
|
|
49
50
|
};
|
|
50
51
|
export type Loc = {
|
|
51
|
-
start: number
|
|
52
|
-
end: number
|
|
53
|
-
path: string
|
|
54
|
-
line: number
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
path: string;
|
|
55
|
+
line: number;
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
export type Document = {
|
|
58
|
-
type:
|
|
59
|
-
source: Template
|
|
59
|
+
type: "document";
|
|
60
|
+
source: Template;
|
|
60
61
|
};
|
|
61
62
|
export type Import = {
|
|
62
|
-
type:
|
|
63
|
-
name: string
|
|
64
|
-
path: string
|
|
65
|
-
loc: Loc
|
|
63
|
+
type: "import";
|
|
64
|
+
name: string;
|
|
65
|
+
path: string;
|
|
66
|
+
loc: Loc;
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
export type Operation = {
|
|
69
|
-
source: Template
|
|
70
|
+
source: Template;
|
|
70
71
|
// TODO: Determine if an operation is already wrapped
|
|
71
72
|
// in `gqlOp` so we can automatically wrap if needed.
|
|
72
73
|
// needsWrapping: boolean,
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
export type FileResult = {
|
|
76
|
-
path: string
|
|
77
|
-
operations: Array<Operation
|
|
77
|
+
path: string;
|
|
78
|
+
operations: Array<Operation>;
|
|
78
79
|
exports: {
|
|
79
|
-
[key: string]: Document | Import
|
|
80
|
-
}
|
|
80
|
+
[key: string]: Document | Import;
|
|
81
|
+
};
|
|
81
82
|
locals: {
|
|
82
|
-
[key: string]: Document | Import
|
|
83
|
-
}
|
|
83
|
+
[key: string]: Document | Import;
|
|
84
|
+
};
|
|
84
85
|
errors: Array<{
|
|
85
|
-
loc: Loc
|
|
86
|
-
message: string
|
|
87
|
-
}
|
|
86
|
+
loc: Loc;
|
|
87
|
+
message: string;
|
|
88
|
+
}>;
|
|
88
89
|
};
|
|
89
90
|
|
|
90
91
|
export type Files = {
|
|
91
|
-
[path: string]: FileResult
|
|
92
|
+
[path: string]: FileResult;
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
/**
|
|
@@ -99,13 +100,18 @@ export type Files = {
|
|
|
99
100
|
* potentially relevant, and of course any values referenced
|
|
100
101
|
* from a graphql template are treated as relevant.
|
|
101
102
|
*/
|
|
102
|
-
const listExternalReferences = (
|
|
103
|
+
const listExternalReferences = (
|
|
104
|
+
file: FileResult,
|
|
105
|
+
config: Config,
|
|
106
|
+
): Array<string> => {
|
|
103
107
|
const paths: Record<string, any> = {};
|
|
104
108
|
const add = (v: Document | Import, followImports: boolean) => {
|
|
105
|
-
if (v.type ===
|
|
109
|
+
if (v.type === "import") {
|
|
106
110
|
if (followImports) {
|
|
107
|
-
const absPath = getPathWithExtension(v.path);
|
|
108
|
-
|
|
111
|
+
const absPath = getPathWithExtension(v.path, config);
|
|
112
|
+
if (absPath) {
|
|
113
|
+
paths[absPath] = true;
|
|
114
|
+
}
|
|
109
115
|
}
|
|
110
116
|
} else {
|
|
111
117
|
v.source.expressions.forEach((expr) => add(expr, true));
|
|
@@ -140,10 +146,12 @@ const listExternalReferences = (file: FileResult): Array<string> => {
|
|
|
140
146
|
|
|
141
147
|
export const processFile = (
|
|
142
148
|
filePath: string,
|
|
143
|
-
contents:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
contents:
|
|
150
|
+
| string
|
|
151
|
+
| {
|
|
152
|
+
text: string;
|
|
153
|
+
resolvedPath: string;
|
|
154
|
+
},
|
|
147
155
|
): FileResult => {
|
|
148
156
|
const dir = path.dirname(filePath);
|
|
149
157
|
const result: FileResult = {
|
|
@@ -153,55 +161,51 @@ export const processFile = (
|
|
|
153
161
|
locals: {},
|
|
154
162
|
errors: [],
|
|
155
163
|
};
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
:
|
|
162
|
-
/* eslint-disable flowtype-errors/uncovered */
|
|
163
|
-
const ast: BabelNodeFile = parse(text, {
|
|
164
|
-
sourceType: 'module',
|
|
164
|
+
const text = typeof contents === "string" ? contents : contents.text;
|
|
165
|
+
const plugins: Array<ParserPlugin> = filePath.endsWith("x")
|
|
166
|
+
? ["typescript", "jsx"]
|
|
167
|
+
: ["typescript"];
|
|
168
|
+
const ast: File = parse(text, {
|
|
169
|
+
sourceType: "module",
|
|
165
170
|
allowImportExportEverywhere: true,
|
|
166
171
|
plugins: plugins,
|
|
167
172
|
});
|
|
168
|
-
|
|
169
|
-
const gqlTagNames = [];
|
|
173
|
+
const gqlTagNames: Array<string> = [];
|
|
170
174
|
const seenTemplates: {
|
|
171
|
-
[key: number]: Document | false
|
|
175
|
+
[key: number]: Document | false;
|
|
172
176
|
} = {};
|
|
173
177
|
|
|
174
178
|
ast.program.body.forEach((toplevel) => {
|
|
175
|
-
if (toplevel.type ===
|
|
179
|
+
if (toplevel.type === "ImportDeclaration") {
|
|
176
180
|
const newLocals = getLocals(dir, toplevel, filePath);
|
|
177
181
|
if (newLocals) {
|
|
178
182
|
Object.keys(newLocals).forEach((k) => {
|
|
179
183
|
const local = newLocals[k];
|
|
180
|
-
if (local.path.startsWith(
|
|
184
|
+
if (local.path.startsWith("/")) {
|
|
181
185
|
result.locals[k] = local;
|
|
182
186
|
}
|
|
183
187
|
if (
|
|
184
|
-
local.path ===
|
|
185
|
-
local.name ===
|
|
188
|
+
local.path === "graphql-tag" &&
|
|
189
|
+
local.name === "default"
|
|
186
190
|
) {
|
|
187
191
|
gqlTagNames.push(k);
|
|
188
192
|
}
|
|
189
193
|
});
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
|
-
if (toplevel.type ===
|
|
196
|
+
if (toplevel.type === "ExportNamedDeclaration") {
|
|
193
197
|
if (toplevel.source) {
|
|
194
198
|
const source = toplevel.source;
|
|
195
|
-
const importPath = source.value.startsWith(
|
|
199
|
+
const importPath = source.value.startsWith(".")
|
|
196
200
|
? path.resolve(path.join(dir, source.value))
|
|
197
201
|
: source.value;
|
|
198
|
-
toplevel.specifiers?.forEach((spec
|
|
202
|
+
toplevel.specifiers?.forEach((spec) => {
|
|
199
203
|
if (
|
|
200
|
-
spec.type ===
|
|
201
|
-
spec.exported.type ===
|
|
204
|
+
spec.type === "ExportSpecifier" &&
|
|
205
|
+
spec.exported.type === "Identifier"
|
|
202
206
|
) {
|
|
203
207
|
result.exports[spec.exported.name] = {
|
|
204
|
-
type:
|
|
208
|
+
type: "import",
|
|
205
209
|
name: spec.local.name,
|
|
206
210
|
path: importPath,
|
|
207
211
|
loc: {
|
|
@@ -214,10 +218,10 @@ export const processFile = (
|
|
|
214
218
|
}
|
|
215
219
|
});
|
|
216
220
|
} else {
|
|
217
|
-
toplevel.specifiers?.forEach((spec
|
|
218
|
-
if (spec.type ===
|
|
221
|
+
toplevel.specifiers?.forEach((spec) => {
|
|
222
|
+
if (spec.type === "ExportSpecifier") {
|
|
219
223
|
const local = result.locals[spec.local.name];
|
|
220
|
-
if (local && spec.exported.type ===
|
|
224
|
+
if (local && spec.exported.type === "Identifier") {
|
|
221
225
|
result.exports[spec.exported.name] = local;
|
|
222
226
|
}
|
|
223
227
|
}
|
|
@@ -226,23 +230,23 @@ export const processFile = (
|
|
|
226
230
|
}
|
|
227
231
|
|
|
228
232
|
const processDeclarator = (
|
|
229
|
-
decl:
|
|
233
|
+
decl: VariableDeclarator,
|
|
230
234
|
isExported: boolean,
|
|
231
235
|
) => {
|
|
232
|
-
if (decl.id.type !==
|
|
236
|
+
if (decl.id.type !== "Identifier" || !decl.init) {
|
|
233
237
|
return;
|
|
234
238
|
}
|
|
235
239
|
const {init} = decl;
|
|
236
240
|
const id = decl.id.name;
|
|
237
241
|
if (
|
|
238
|
-
init.type ===
|
|
239
|
-
init.tag.type ===
|
|
242
|
+
init.type === "TaggedTemplateExpression" &&
|
|
243
|
+
init.tag.type === "Identifier"
|
|
240
244
|
) {
|
|
241
245
|
if (gqlTagNames.includes(init.tag.name)) {
|
|
242
246
|
const tpl = processTemplate(init, result);
|
|
243
247
|
if (tpl) {
|
|
244
248
|
const document = (result.locals[id] = {
|
|
245
|
-
type:
|
|
249
|
+
type: "document",
|
|
246
250
|
source: tpl,
|
|
247
251
|
});
|
|
248
252
|
seenTemplates[init.start ?? -1] = document;
|
|
@@ -254,7 +258,7 @@ export const processFile = (
|
|
|
254
258
|
}
|
|
255
259
|
}
|
|
256
260
|
}
|
|
257
|
-
if (init.type ===
|
|
261
|
+
if (init.type === "Identifier" && result.locals[init.name]) {
|
|
258
262
|
result.locals[id] = result.locals[init.name];
|
|
259
263
|
if (isExported) {
|
|
260
264
|
result.exports[id] = result.locals[init.name];
|
|
@@ -262,15 +266,15 @@ export const processFile = (
|
|
|
262
266
|
}
|
|
263
267
|
};
|
|
264
268
|
|
|
265
|
-
if (toplevel.type ===
|
|
269
|
+
if (toplevel.type === "VariableDeclaration") {
|
|
266
270
|
toplevel.declarations.forEach((decl) => {
|
|
267
271
|
processDeclarator(decl, false);
|
|
268
272
|
});
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
if (
|
|
272
|
-
toplevel.type ===
|
|
273
|
-
toplevel.declaration?.type ===
|
|
276
|
+
toplevel.type === "ExportNamedDeclaration" &&
|
|
277
|
+
toplevel.declaration?.type === "VariableDeclaration"
|
|
274
278
|
) {
|
|
275
279
|
toplevel.declaration.declarations.forEach((decl) => {
|
|
276
280
|
processDeclarator(decl, true);
|
|
@@ -279,21 +283,21 @@ export const processFile = (
|
|
|
279
283
|
});
|
|
280
284
|
|
|
281
285
|
const visitTpl = (
|
|
282
|
-
node:
|
|
286
|
+
node: TaggedTemplateExpression,
|
|
283
287
|
getBinding: (name: string) => Document | null,
|
|
284
288
|
) => {
|
|
285
289
|
if (seenTemplates[node.start ?? -1] != null) {
|
|
286
290
|
return;
|
|
287
291
|
}
|
|
288
292
|
if (
|
|
289
|
-
node.tag.type !==
|
|
293
|
+
node.tag.type !== "Identifier" ||
|
|
290
294
|
!gqlTagNames.includes(node.tag.name)
|
|
291
295
|
) {
|
|
292
296
|
return;
|
|
293
297
|
}
|
|
294
298
|
const tpl = processTemplate(node, result, getBinding);
|
|
295
299
|
if (tpl) {
|
|
296
|
-
seenTemplates[node.start ?? -1] = {type:
|
|
300
|
+
seenTemplates[node.start ?? -1] = {type: "document", source: tpl};
|
|
297
301
|
result.operations.push({
|
|
298
302
|
source: tpl,
|
|
299
303
|
});
|
|
@@ -302,35 +306,37 @@ export const processFile = (
|
|
|
302
306
|
}
|
|
303
307
|
};
|
|
304
308
|
|
|
305
|
-
/* eslint-disable flowtype-errors/uncovered */
|
|
306
309
|
traverse(ast, {
|
|
307
|
-
TaggedTemplateExpression(path
|
|
310
|
+
TaggedTemplateExpression(path) {
|
|
308
311
|
visitTpl(path.node, (name) => {
|
|
309
312
|
const binding = path.scope.getBinding(name);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
+
if (!binding) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const start =
|
|
317
|
+
"init" in binding.path.node && binding.path.node.init
|
|
318
|
+
? binding.path.node.init.start
|
|
319
|
+
: null;
|
|
313
320
|
if (start && seenTemplates[start]) {
|
|
314
|
-
return seenTemplates[start];
|
|
321
|
+
return seenTemplates[start] || null;
|
|
315
322
|
}
|
|
316
323
|
return null;
|
|
317
324
|
});
|
|
318
325
|
},
|
|
319
326
|
});
|
|
320
|
-
/* eslint-enable flowtype-errors/uncovered */
|
|
321
327
|
|
|
322
328
|
return result;
|
|
323
329
|
};
|
|
324
330
|
|
|
325
331
|
const processTemplate = (
|
|
326
|
-
tpl:
|
|
332
|
+
tpl: TaggedTemplateExpression,
|
|
327
333
|
result: FileResult,
|
|
328
334
|
// getBinding?: (name: string) => Binding,
|
|
329
335
|
// seenTemplates,
|
|
330
336
|
getTemplate?: (name: string) => Document | null,
|
|
331
337
|
): Template | null | undefined => {
|
|
332
338
|
// 'cooked' is the string as runtime javascript will see it.
|
|
333
|
-
const literals = tpl.quasi.quasis.map((q) => q.value.cooked ||
|
|
339
|
+
const literals = tpl.quasi.quasis.map((q) => q.value.cooked || "");
|
|
334
340
|
const expressions = tpl.quasi.expressions.map(
|
|
335
341
|
(expr): null | Document | Import => {
|
|
336
342
|
const loc: Loc = {
|
|
@@ -339,7 +345,7 @@ const processTemplate = (
|
|
|
339
345
|
line: expr.loc?.start.line ?? -1,
|
|
340
346
|
path: result.path,
|
|
341
347
|
};
|
|
342
|
-
if (expr.type !==
|
|
348
|
+
if (expr.type !== "Identifier") {
|
|
343
349
|
result.errors.push({
|
|
344
350
|
loc,
|
|
345
351
|
message: `Template literal interpolation must be an identifier`,
|
|
@@ -376,29 +382,36 @@ const processTemplate = (
|
|
|
376
382
|
};
|
|
377
383
|
};
|
|
378
384
|
|
|
379
|
-
const getLocals = (
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
385
|
+
const getLocals = (
|
|
386
|
+
dir: string,
|
|
387
|
+
toplevel: ImportDeclaration,
|
|
388
|
+
myPath: string,
|
|
389
|
+
):
|
|
390
|
+
| {
|
|
391
|
+
[key: string]: Import;
|
|
392
|
+
}
|
|
393
|
+
| null
|
|
394
|
+
| undefined => {
|
|
395
|
+
if (toplevel.importKind === "type") {
|
|
383
396
|
return null;
|
|
384
397
|
}
|
|
385
|
-
const importPath = toplevel.source.value.startsWith(
|
|
398
|
+
const importPath = toplevel.source.value.startsWith(".")
|
|
386
399
|
? path.resolve(path.join(dir, toplevel.source.value))
|
|
387
400
|
: toplevel.source.value;
|
|
388
401
|
const locals: Record<string, any> = {};
|
|
389
402
|
toplevel.specifiers.forEach((spec) => {
|
|
390
|
-
if (spec.type ===
|
|
403
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
391
404
|
locals[spec.local.name] = {
|
|
392
|
-
type:
|
|
393
|
-
name:
|
|
405
|
+
type: "import",
|
|
406
|
+
name: "default",
|
|
394
407
|
path: importPath,
|
|
395
408
|
loc: {start: spec.start, end: spec.end, path: myPath},
|
|
396
409
|
};
|
|
397
|
-
} else if (spec.type ===
|
|
410
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
398
411
|
locals[spec.local.name] = {
|
|
399
|
-
type:
|
|
412
|
+
type: "import",
|
|
400
413
|
name:
|
|
401
|
-
spec.imported.type ===
|
|
414
|
+
spec.imported.type === "Identifier"
|
|
402
415
|
? spec.imported.name
|
|
403
416
|
: spec.imported.value,
|
|
404
417
|
path: importPath,
|
|
@@ -411,21 +424,24 @@ const getLocals = (dir: unknown, toplevel: BabelNodeImportDeclaration, myPath: s
|
|
|
411
424
|
|
|
412
425
|
export const processFiles = (
|
|
413
426
|
filePaths: Array<string>,
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
427
|
+
config: Config,
|
|
428
|
+
getFileSource: (path: string) =>
|
|
429
|
+
| string
|
|
430
|
+
| {
|
|
431
|
+
text: string;
|
|
432
|
+
resolvedPath: string;
|
|
433
|
+
},
|
|
418
434
|
): Files => {
|
|
419
435
|
const files: Files = {};
|
|
420
436
|
const toProcess = filePaths.slice();
|
|
421
437
|
while (toProcess.length) {
|
|
422
438
|
const next = toProcess.shift();
|
|
423
|
-
if (files[next]) {
|
|
439
|
+
if (!next || files[next]) {
|
|
424
440
|
continue;
|
|
425
441
|
}
|
|
426
442
|
const result = processFile(next, getFileSource(next));
|
|
427
443
|
files[next] = result;
|
|
428
|
-
listExternalReferences(result).forEach((path) => {
|
|
444
|
+
listExternalReferences(result, config).forEach((path) => {
|
|
429
445
|
if (!files[path] && !toProcess.includes(path)) {
|
|
430
446
|
toProcess.push(path);
|
|
431
447
|
}
|
package/src/parser/resolve.ts
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
|
-
import gql from
|
|
2
|
-
import {getPathWithExtension} from
|
|
3
|
-
import type {DocumentNode} from
|
|
4
|
-
import type {FileResult, Files, Import, Template, Document} from
|
|
1
|
+
import gql from "graphql-tag";
|
|
2
|
+
import {getPathWithExtension} from "./utils";
|
|
3
|
+
import type {DocumentNode} from "graphql/language/ast";
|
|
4
|
+
import type {FileResult, Files, Import, Template, Document} from "./parse";
|
|
5
|
+
import {Config} from "../types";
|
|
5
6
|
|
|
6
7
|
export type Resolved = {
|
|
7
8
|
[key: string]: {
|
|
8
|
-
document: DocumentNode
|
|
9
|
-
raw: Template
|
|
10
|
-
}
|
|
9
|
+
document: DocumentNode;
|
|
10
|
+
raw: Template;
|
|
11
|
+
};
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export const resolveDocuments = (
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
export const resolveDocuments = (
|
|
15
|
+
files: Files,
|
|
16
|
+
config: Config,
|
|
17
|
+
): {
|
|
18
|
+
resolved: Resolved;
|
|
19
|
+
errors: FileResult["errors"];
|
|
16
20
|
} => {
|
|
17
21
|
const resolved: Resolved = {};
|
|
18
|
-
const errors: FileResult[
|
|
22
|
+
const errors: FileResult["errors"] = [];
|
|
19
23
|
Object.keys(files).forEach((path) => {
|
|
20
24
|
const file = files[path];
|
|
21
25
|
file.operations.forEach((op) => {
|
|
22
|
-
resolveGqlTemplate(op.source, files, errors, resolved, {});
|
|
26
|
+
resolveGqlTemplate(op.source, files, errors, resolved, {}, config);
|
|
23
27
|
});
|
|
24
28
|
Object.keys(file.locals).forEach((k) => {
|
|
25
29
|
const local = file.locals[k];
|
|
26
|
-
if (local.type ===
|
|
27
|
-
resolveGqlTemplate(
|
|
30
|
+
if (local.type === "document") {
|
|
31
|
+
resolveGqlTemplate(
|
|
32
|
+
local.source,
|
|
33
|
+
files,
|
|
34
|
+
errors,
|
|
35
|
+
resolved,
|
|
36
|
+
{},
|
|
37
|
+
config,
|
|
38
|
+
);
|
|
28
39
|
}
|
|
29
40
|
});
|
|
30
41
|
});
|
|
@@ -34,17 +45,18 @@ export const resolveDocuments = (files: Files): {
|
|
|
34
45
|
const resolveImport = (
|
|
35
46
|
expr: Import,
|
|
36
47
|
files: Files,
|
|
37
|
-
errors: FileResult[
|
|
48
|
+
errors: FileResult["errors"],
|
|
38
49
|
seen: {
|
|
39
|
-
[key: string]: true
|
|
50
|
+
[key: string]: true;
|
|
40
51
|
},
|
|
52
|
+
config: Config,
|
|
41
53
|
): Document | null | undefined => {
|
|
42
|
-
const absPath: string = getPathWithExtension(expr.path);
|
|
54
|
+
const absPath: string = getPathWithExtension(expr.path, config);
|
|
43
55
|
if (seen[absPath]) {
|
|
44
56
|
errors.push({
|
|
45
57
|
loc: expr.loc,
|
|
46
58
|
message: `Circular import ${Object.keys(seen).join(
|
|
47
|
-
|
|
59
|
+
" -> ",
|
|
48
60
|
)} -> ${absPath}`,
|
|
49
61
|
});
|
|
50
62
|
return null;
|
|
@@ -63,8 +75,8 @@ const resolveImport = (
|
|
|
63
75
|
return null;
|
|
64
76
|
}
|
|
65
77
|
const value = res.exports[expr.name];
|
|
66
|
-
if (value.type ===
|
|
67
|
-
return resolveImport(value, files, errors, seen);
|
|
78
|
+
if (value.type === "import") {
|
|
79
|
+
return resolveImport(value, files, errors, seen, config);
|
|
68
80
|
}
|
|
69
81
|
return value;
|
|
70
82
|
};
|
|
@@ -72,13 +84,14 @@ const resolveImport = (
|
|
|
72
84
|
const resolveGqlTemplate = (
|
|
73
85
|
template: Template,
|
|
74
86
|
files: Files,
|
|
75
|
-
errors: FileResult[
|
|
87
|
+
errors: FileResult["errors"],
|
|
76
88
|
resolved: Resolved,
|
|
77
89
|
seen: {
|
|
78
|
-
[key: string]: Template
|
|
90
|
+
[key: string]: Template;
|
|
79
91
|
},
|
|
92
|
+
config: Config,
|
|
80
93
|
): DocumentNode | null | undefined => {
|
|
81
|
-
const key = template.loc.path +
|
|
94
|
+
const key = template.loc.path + ":" + template.loc.line;
|
|
82
95
|
if (seen[key]) {
|
|
83
96
|
errors.push({
|
|
84
97
|
loc: template.loc,
|
|
@@ -86,12 +99,12 @@ const resolveGqlTemplate = (
|
|
|
86
99
|
.map(
|
|
87
100
|
(k) =>
|
|
88
101
|
k +
|
|
89
|
-
|
|
102
|
+
" ~ " +
|
|
90
103
|
seen[k].expressions.length +
|
|
91
|
-
|
|
104
|
+
"," +
|
|
92
105
|
seen[k].literals.length,
|
|
93
106
|
)
|
|
94
|
-
.join(
|
|
107
|
+
.join(" -> ")} -> ${key}`,
|
|
95
108
|
});
|
|
96
109
|
return null;
|
|
97
110
|
}
|
|
@@ -100,17 +113,31 @@ const resolveGqlTemplate = (
|
|
|
100
113
|
return resolved[key].document;
|
|
101
114
|
}
|
|
102
115
|
const expressions = template.expressions.map((expr) => {
|
|
103
|
-
if (expr.type ===
|
|
104
|
-
const document = resolveImport(expr, files, errors, {});
|
|
116
|
+
if (expr.type === "import") {
|
|
117
|
+
const document = resolveImport(expr, files, errors, {}, config);
|
|
105
118
|
return document
|
|
106
|
-
? resolveGqlTemplate(
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
? resolveGqlTemplate(
|
|
120
|
+
document.source,
|
|
121
|
+
files,
|
|
122
|
+
errors,
|
|
123
|
+
resolved,
|
|
124
|
+
{
|
|
125
|
+
...seen,
|
|
126
|
+
},
|
|
127
|
+
config,
|
|
128
|
+
)
|
|
109
129
|
: null;
|
|
110
130
|
}
|
|
111
|
-
return resolveGqlTemplate(
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
return resolveGqlTemplate(
|
|
132
|
+
expr.source,
|
|
133
|
+
files,
|
|
134
|
+
errors,
|
|
135
|
+
resolved,
|
|
136
|
+
{
|
|
137
|
+
...seen,
|
|
138
|
+
},
|
|
139
|
+
config,
|
|
140
|
+
);
|
|
114
141
|
});
|
|
115
142
|
if (expressions.includes(null)) {
|
|
116
143
|
return null;
|
package/src/parser/utils.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
import fs from
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import {Config} from "../types";
|
|
2
3
|
|
|
3
|
-
export const getPathWithExtension = (
|
|
4
|
+
export const getPathWithExtension = (
|
|
5
|
+
pathWithoutExtension: string,
|
|
6
|
+
config: Config,
|
|
7
|
+
): string => {
|
|
8
|
+
// Process the path so that we can handle aliases.
|
|
9
|
+
if (config.alias) {
|
|
10
|
+
for (const {find, replacement} of config.alias) {
|
|
11
|
+
pathWithoutExtension = pathWithoutExtension.replace(
|
|
12
|
+
find,
|
|
13
|
+
replacement,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
if (
|
|
5
18
|
/\.(less|css|png|gif|jpg|jpeg|js|jsx|ts|tsx|mjs)$/.test(
|
|
6
19
|
pathWithoutExtension,
|
|
@@ -8,17 +21,21 @@ export const getPathWithExtension = (pathWithoutExtension: string): string => {
|
|
|
8
21
|
) {
|
|
9
22
|
return pathWithoutExtension;
|
|
10
23
|
}
|
|
11
|
-
if (fs.existsSync(pathWithoutExtension +
|
|
12
|
-
return pathWithoutExtension +
|
|
24
|
+
if (fs.existsSync(pathWithoutExtension + ".js")) {
|
|
25
|
+
return pathWithoutExtension + ".js";
|
|
13
26
|
}
|
|
14
|
-
if (fs.existsSync(pathWithoutExtension +
|
|
15
|
-
return pathWithoutExtension +
|
|
27
|
+
if (fs.existsSync(pathWithoutExtension + ".jsx")) {
|
|
28
|
+
return pathWithoutExtension + ".jsx";
|
|
16
29
|
}
|
|
17
|
-
if (fs.existsSync(pathWithoutExtension +
|
|
18
|
-
return pathWithoutExtension +
|
|
30
|
+
if (fs.existsSync(pathWithoutExtension + ".tsx")) {
|
|
31
|
+
return pathWithoutExtension + ".tsx";
|
|
19
32
|
}
|
|
20
|
-
if (fs.existsSync(pathWithoutExtension +
|
|
21
|
-
return pathWithoutExtension +
|
|
33
|
+
if (fs.existsSync(pathWithoutExtension + ".ts")) {
|
|
34
|
+
return pathWithoutExtension + ".ts";
|
|
22
35
|
}
|
|
23
|
-
|
|
36
|
+
// NOTE(john): This is a bit of a hack, but it's necessary for when we
|
|
37
|
+
// have a file that doesn't exist. This will happen when we delete all of
|
|
38
|
+
// the type files before re-running graphql-flow again. We want to ensure
|
|
39
|
+
// that we don't error out in this case.
|
|
40
|
+
return "";
|
|
24
41
|
};
|