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