@khanacademy/graphql-flow 3.4.2 → 4.0.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/.github/workflows/changeset-release.yml +1 -1
- package/.github/workflows/pr-checks.yml +6 -6
- package/CHANGELOG.md +16 -0
- package/dist/cli/run.js +1 -1
- package/dist/parser/parse.js +74 -21
- package/dist/parser/resolve.js +14 -1
- package/dist/parser/resolveImport.js +58 -0
- package/dist/parser/utils.js +2 -15
- package/package.json +71 -65
- package/schema.json +1 -17
- package/src/cli/run.ts +1 -1
- package/src/parser/__test__/parse.test.ts +196 -2
- package/src/parser/__test__/resolveImport.test.ts +98 -0
- package/src/parser/__test__/utils.test.ts +10 -47
- package/src/parser/parse.ts +89 -29
- package/src/parser/resolve.ts +18 -1
- package/src/parser/resolveImport.ts +65 -0
- package/src/parser/utils.ts +1 -15
- package/src/types.ts +0 -4
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import {describe, it, expect, afterEach} from "@jest/globals";
|
|
2
5
|
|
|
3
6
|
import {Config} from "../../types";
|
|
4
7
|
import {processFiles} from "../parse";
|
|
@@ -6,6 +9,32 @@ import {resolveDocuments} from "../resolve";
|
|
|
6
9
|
|
|
7
10
|
import {print} from "graphql/language/printer";
|
|
8
11
|
|
|
12
|
+
jest.mock("../resolveImport", () => ({
|
|
13
|
+
resetImportCache: jest.fn(),
|
|
14
|
+
resolveImportPath: jest
|
|
15
|
+
.fn()
|
|
16
|
+
.mockImplementation((sourceFile: string, importPath: string) => {
|
|
17
|
+
const path = require("path");
|
|
18
|
+
if (importPath === "graphql-tag") {
|
|
19
|
+
return "/repo/node_modules/graphql-tag/index.js";
|
|
20
|
+
}
|
|
21
|
+
if (importPath === "monorepo-package/fragment") {
|
|
22
|
+
return "/repo/node_modules/monorepo-package/fragment.js";
|
|
23
|
+
}
|
|
24
|
+
if (importPath.startsWith(".")) {
|
|
25
|
+
return path.resolve(path.dirname(sourceFile), importPath);
|
|
26
|
+
}
|
|
27
|
+
if (path.isAbsolute(importPath)) {
|
|
28
|
+
return importPath;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
9
38
|
const fixtureFiles: {
|
|
10
39
|
[key: string]:
|
|
11
40
|
| string
|
|
@@ -14,6 +43,27 @@ const fixtureFiles: {
|
|
|
14
43
|
resolvedPath: string;
|
|
15
44
|
};
|
|
16
45
|
} = {
|
|
46
|
+
"/repo/node_modules/monorepo-package/fragment.js": `
|
|
47
|
+
import gql from 'graphql-tag';
|
|
48
|
+
|
|
49
|
+
export const sharedFragment = gql\`
|
|
50
|
+
fragment SharedFields on Something {
|
|
51
|
+
id
|
|
52
|
+
}
|
|
53
|
+
\`;
|
|
54
|
+
`,
|
|
55
|
+
"/repo/packages/app/App.js": `
|
|
56
|
+
import gql from 'graphql-tag';
|
|
57
|
+
import {sharedFragment} from 'monorepo-package/fragment';
|
|
58
|
+
export const appQuery = gql\`
|
|
59
|
+
query AppQuery {
|
|
60
|
+
viewer {
|
|
61
|
+
...SharedFields
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
\${sharedFragment}
|
|
65
|
+
\`;
|
|
66
|
+
`,
|
|
17
67
|
"/firstFile.js": `
|
|
18
68
|
// Note that you can import graphql-tag as
|
|
19
69
|
// something other than gql.
|
|
@@ -57,6 +107,31 @@ const fixtureFiles: {
|
|
|
57
107
|
\`;
|
|
58
108
|
export {secondFragment};`,
|
|
59
109
|
|
|
110
|
+
"/starExportSource.js": `
|
|
111
|
+
import gql from 'graphql-tag';
|
|
112
|
+
export const starFragment = gql\`
|
|
113
|
+
fragment StarFragment on Star {
|
|
114
|
+
id
|
|
115
|
+
}
|
|
116
|
+
\`;
|
|
117
|
+
`,
|
|
118
|
+
"/starExportReexport.js": `
|
|
119
|
+
export * from './starExportSource.js';
|
|
120
|
+
`,
|
|
121
|
+
"/starExportConsumer.js": `
|
|
122
|
+
import gql from 'graphql-tag';
|
|
123
|
+
import {starFragment} from './starExportReexport.js';
|
|
124
|
+
|
|
125
|
+
export const starQuery = gql\`
|
|
126
|
+
query StarQuery {
|
|
127
|
+
stars {
|
|
128
|
+
...StarFragment
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
\${starFragment}
|
|
132
|
+
\`;
|
|
133
|
+
`,
|
|
134
|
+
|
|
60
135
|
"/thirdFile.js": `
|
|
61
136
|
import {fromFirstFile, alsoFirst, secondFragment} from './secondFile.js';
|
|
62
137
|
import gql from 'graphql-tag';
|
|
@@ -281,7 +356,7 @@ describe("processing fragments in various ways", () => {
|
|
|
281
356
|
expect(files["/invalidThings.js"].errors.map((m: any) => m.message))
|
|
282
357
|
.toMatchInlineSnapshot(`
|
|
283
358
|
Array [
|
|
284
|
-
"Unable to resolve someExternalFragment",
|
|
359
|
+
"Unable to resolve import someExternalFragment from \\"somewhere\\" at /invalidThings.js:4.",
|
|
285
360
|
"Unable to resolve someUndefinedFragment",
|
|
286
361
|
"Template literal interpolation must be an identifier",
|
|
287
362
|
]
|
|
@@ -338,4 +413,123 @@ describe("processing fragments in various ways", () => {
|
|
|
338
413
|
);
|
|
339
414
|
expect(printed).toMatchInlineSnapshot(`Object {}`);
|
|
340
415
|
});
|
|
416
|
+
|
|
417
|
+
it("should resolve fragments re-exported via export all", () => {
|
|
418
|
+
// Arrange
|
|
419
|
+
const config: Config = {
|
|
420
|
+
crawl: {
|
|
421
|
+
root: "/here/we/crawl",
|
|
422
|
+
},
|
|
423
|
+
generate: {
|
|
424
|
+
match: [/\.fixture\.js$/],
|
|
425
|
+
exclude: [
|
|
426
|
+
"_test\\.js$",
|
|
427
|
+
"\\bcourse-editor-package\\b",
|
|
428
|
+
"\\.fixture\\.js$",
|
|
429
|
+
"\\b__flowtests__\\b",
|
|
430
|
+
"\\bcourse-editor\\b",
|
|
431
|
+
],
|
|
432
|
+
readOnlyArray: false,
|
|
433
|
+
regenerateCommand: "make gqlflow",
|
|
434
|
+
scalars: {
|
|
435
|
+
JSONString: "string",
|
|
436
|
+
KALocale: "string",
|
|
437
|
+
NaiveDateTime: "string",
|
|
438
|
+
},
|
|
439
|
+
splitTypes: true,
|
|
440
|
+
generatedDirectory: "__graphql-types__",
|
|
441
|
+
exportAllObjectTypes: true,
|
|
442
|
+
schemaFilePath: "./composed_schema.graphql",
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
// Act
|
|
446
|
+
const files = processFiles(
|
|
447
|
+
["/starExportConsumer.js"],
|
|
448
|
+
config,
|
|
449
|
+
getFileSource,
|
|
450
|
+
);
|
|
451
|
+
const {resolved} = resolveDocuments(files, config);
|
|
452
|
+
const printed: Record<string, any> = {};
|
|
453
|
+
Object.keys(resolved).map(
|
|
454
|
+
(k: any) => (printed[k] = print(resolved[k].document).trim()),
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
// Assert
|
|
458
|
+
expect(printed).toMatchInlineSnapshot(`
|
|
459
|
+
Object {
|
|
460
|
+
"/starExportConsumer.js:5": "query StarQuery {
|
|
461
|
+
stars {
|
|
462
|
+
...StarFragment
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
fragment StarFragment on Star {
|
|
467
|
+
id
|
|
468
|
+
}",
|
|
469
|
+
"/starExportSource.js:3": "fragment StarFragment on Star {
|
|
470
|
+
id
|
|
471
|
+
}",
|
|
472
|
+
}
|
|
473
|
+
`);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("should resolve fragments imported from monorepo packages", () => {
|
|
477
|
+
// Arrange
|
|
478
|
+
const config: Config = {
|
|
479
|
+
crawl: {
|
|
480
|
+
root: "/here/we/crawl",
|
|
481
|
+
},
|
|
482
|
+
generate: {
|
|
483
|
+
match: [/\.fixture\.js$/],
|
|
484
|
+
exclude: [
|
|
485
|
+
"_test\\.js$",
|
|
486
|
+
"\\bcourse-editor-package\\b",
|
|
487
|
+
"\\.fixture\\.js$",
|
|
488
|
+
"\\b__flowtests__\\b",
|
|
489
|
+
"\\bcourse-editor\\b",
|
|
490
|
+
],
|
|
491
|
+
readOnlyArray: false,
|
|
492
|
+
regenerateCommand: "make gqlflow",
|
|
493
|
+
scalars: {
|
|
494
|
+
JSONString: "string",
|
|
495
|
+
KALocale: "string",
|
|
496
|
+
NaiveDateTime: "string",
|
|
497
|
+
},
|
|
498
|
+
splitTypes: true,
|
|
499
|
+
generatedDirectory: "__graphql-types__",
|
|
500
|
+
exportAllObjectTypes: true,
|
|
501
|
+
schemaFilePath: "./composed_schema.graphql",
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// Act
|
|
506
|
+
const files = processFiles(
|
|
507
|
+
["/repo/packages/app/App.js"],
|
|
508
|
+
config,
|
|
509
|
+
getFileSource,
|
|
510
|
+
);
|
|
511
|
+
const {resolved} = resolveDocuments(files, config);
|
|
512
|
+
const printed: Record<string, any> = {};
|
|
513
|
+
Object.keys(resolved).map(
|
|
514
|
+
(k: any) => (printed[k] = print(resolved[k].document).trim()),
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
// Assert
|
|
518
|
+
expect(printed).toMatchInlineSnapshot(`
|
|
519
|
+
Object {
|
|
520
|
+
"/repo/node_modules/monorepo-package/fragment.js:4": "fragment SharedFields on Something {
|
|
521
|
+
id
|
|
522
|
+
}",
|
|
523
|
+
"/repo/packages/app/App.js:4": "query AppQuery {
|
|
524
|
+
viewer {
|
|
525
|
+
...SharedFields
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
fragment SharedFields on Something {
|
|
530
|
+
id
|
|
531
|
+
}",
|
|
532
|
+
}
|
|
533
|
+
`);
|
|
534
|
+
});
|
|
341
535
|
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import {afterEach, beforeEach, describe, expect, it} from "@jest/globals";
|
|
5
|
+
import {resolveImportPath, resetImportCache} from "../resolveImport";
|
|
6
|
+
import {ResolverFactory} from "rspack-resolver";
|
|
7
|
+
|
|
8
|
+
const mockSync = jest.fn();
|
|
9
|
+
const mockCreateMatchPath = jest.fn();
|
|
10
|
+
const mockLoadConfig = jest.fn();
|
|
11
|
+
|
|
12
|
+
jest.mock("rspack-resolver", () => ({
|
|
13
|
+
ResolverFactory: jest.fn().mockImplementation(() => ({
|
|
14
|
+
sync: (...args: Array<unknown>) => mockSync(...args),
|
|
15
|
+
})),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock("tsconfig-paths", () => ({
|
|
19
|
+
createMatchPath: jest
|
|
20
|
+
.fn()
|
|
21
|
+
.mockImplementation((...args: Array<unknown>) =>
|
|
22
|
+
mockCreateMatchPath(...args),
|
|
23
|
+
),
|
|
24
|
+
loadConfig: jest
|
|
25
|
+
.fn()
|
|
26
|
+
.mockImplementation((...args: Array<unknown>) =>
|
|
27
|
+
mockLoadConfig(...args),
|
|
28
|
+
),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
describe("resolveImportPath", () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mockSync.mockReset();
|
|
34
|
+
mockCreateMatchPath.mockReset();
|
|
35
|
+
mockLoadConfig.mockReset();
|
|
36
|
+
(ResolverFactory as unknown as jest.Mock).mockClear();
|
|
37
|
+
resetImportCache();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns the resolved path from the resolver", () => {
|
|
45
|
+
// Arrange
|
|
46
|
+
const matchPath = jest.fn().mockReturnValue("/repo/src/alias/thing.ts");
|
|
47
|
+
mockLoadConfig.mockReturnValue({
|
|
48
|
+
resultType: "success",
|
|
49
|
+
absoluteBaseUrl: "/repo",
|
|
50
|
+
paths: {"@/*": ["src/*"]},
|
|
51
|
+
});
|
|
52
|
+
mockCreateMatchPath.mockReturnValue(matchPath);
|
|
53
|
+
mockSync.mockReturnValue({path: "/repo/src/alias/thing.ts"});
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
const result = resolveImportPath("/repo/src/file.ts", "@/alias/thing");
|
|
57
|
+
|
|
58
|
+
// Assert
|
|
59
|
+
expect(result).toBe("/repo/src/alias/thing.ts");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("loads tsconfig from the source file directory", () => {
|
|
63
|
+
// Arrange
|
|
64
|
+
const matchPath = jest.fn().mockReturnValue("/repo/src/alias/thing.ts");
|
|
65
|
+
mockLoadConfig.mockReturnValue({
|
|
66
|
+
resultType: "success",
|
|
67
|
+
absoluteBaseUrl: "/repo",
|
|
68
|
+
paths: {"@/*": ["src/*"]},
|
|
69
|
+
});
|
|
70
|
+
mockCreateMatchPath.mockReturnValue(matchPath);
|
|
71
|
+
mockSync.mockReturnValue({path: "/repo/src/alias/thing.ts"});
|
|
72
|
+
|
|
73
|
+
// Act
|
|
74
|
+
resolveImportPath("/repo/src/file.ts", "@/alias/thing");
|
|
75
|
+
|
|
76
|
+
// Assert
|
|
77
|
+
expect(mockLoadConfig).toHaveBeenCalledWith("/repo/src");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("caches tsconfig matchPath per source directory", () => {
|
|
81
|
+
// Arrange
|
|
82
|
+
const matchPath = jest.fn().mockReturnValue(undefined);
|
|
83
|
+
mockLoadConfig.mockReturnValue({
|
|
84
|
+
resultType: "success",
|
|
85
|
+
absoluteBaseUrl: "/repo",
|
|
86
|
+
paths: {},
|
|
87
|
+
});
|
|
88
|
+
mockCreateMatchPath.mockReturnValue(matchPath);
|
|
89
|
+
mockSync.mockReturnValue({path: "/repo/node_modules/pkg/index.js"});
|
|
90
|
+
|
|
91
|
+
// Act
|
|
92
|
+
resolveImportPath("/repo/src/a.ts", "pkg");
|
|
93
|
+
resolveImportPath("/repo/src/b.ts", "pkg");
|
|
94
|
+
|
|
95
|
+
// Assert
|
|
96
|
+
expect(mockLoadConfig).toHaveBeenCalledTimes(1);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,41 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
1
4
|
import fs from "fs";
|
|
2
|
-
import {describe, it, expect
|
|
3
|
-
import type {Config} from "../../types";
|
|
4
|
-
|
|
5
|
+
import {describe, it, expect} from "@jest/globals";
|
|
5
6
|
import {getPathWithExtension} from "../utils";
|
|
6
7
|
|
|
7
|
-
const generate = {
|
|
8
|
-
match: [/\.fixture\.js$/],
|
|
9
|
-
exclude: [
|
|
10
|
-
"_test\\.js$",
|
|
11
|
-
"\\bcourse-editor-package\\b",
|
|
12
|
-
"\\.fixture\\.js$",
|
|
13
|
-
"\\b__flowtests__\\b",
|
|
14
|
-
"\\bcourse-editor\\b",
|
|
15
|
-
],
|
|
16
|
-
readOnlyArray: false,
|
|
17
|
-
regenerateCommand: "make gqlflow",
|
|
18
|
-
scalars: {
|
|
19
|
-
JSONString: "string",
|
|
20
|
-
KALocale: "string",
|
|
21
|
-
NaiveDateTime: "string",
|
|
22
|
-
},
|
|
23
|
-
splitTypes: true,
|
|
24
|
-
generatedDirectory: "__graphql-types__",
|
|
25
|
-
exportAllObjectTypes: true,
|
|
26
|
-
schemaFilePath: "./composed_schema.graphql",
|
|
27
|
-
} as const;
|
|
28
|
-
|
|
29
|
-
const config: Config = {
|
|
30
|
-
crawl: {
|
|
31
|
-
root: "/here/we/crawl",
|
|
32
|
-
},
|
|
33
|
-
generate: [
|
|
34
|
-
{...generate, match: [/^static/], exportAllObjectTypes: false},
|
|
35
|
-
generate,
|
|
36
|
-
],
|
|
37
|
-
};
|
|
38
|
-
|
|
39
8
|
describe("getPathWithExtension", () => {
|
|
40
9
|
it("should handle a basic missing extension", () => {
|
|
41
10
|
// Arrange
|
|
@@ -44,7 +13,7 @@ describe("getPathWithExtension", () => {
|
|
|
44
13
|
);
|
|
45
14
|
|
|
46
15
|
// Act
|
|
47
|
-
const result = getPathWithExtension("/path/to/file"
|
|
16
|
+
const result = getPathWithExtension("/path/to/file");
|
|
48
17
|
|
|
49
18
|
// Assert
|
|
50
19
|
expect(result).toBe("/path/to/file.js");
|
|
@@ -55,26 +24,20 @@ describe("getPathWithExtension", () => {
|
|
|
55
24
|
jest.spyOn(fs, "existsSync").mockImplementation((path) => false);
|
|
56
25
|
|
|
57
26
|
// Act
|
|
58
|
-
const result = getPathWithExtension("/path/to/file"
|
|
27
|
+
const result = getPathWithExtension("/path/to/file");
|
|
59
28
|
|
|
60
29
|
// Assert
|
|
61
30
|
expect(result).toBe(null);
|
|
62
31
|
});
|
|
63
32
|
|
|
64
|
-
it("
|
|
33
|
+
it("returns the original path when an extension is already present", () => {
|
|
65
34
|
// Arrange
|
|
66
|
-
|
|
67
|
-
typeof path === "string" ? path.endsWith(".js") : false,
|
|
68
|
-
);
|
|
69
|
-
const tmpConfig: Config = {
|
|
70
|
-
...config,
|
|
71
|
-
alias: [{find: "~", replacement: "../../some/prefix"}],
|
|
72
|
-
};
|
|
35
|
+
const input = "/dir/file.tsx";
|
|
73
36
|
|
|
74
37
|
// Act
|
|
75
|
-
const result = getPathWithExtension(
|
|
38
|
+
const result = getPathWithExtension(input);
|
|
76
39
|
|
|
77
40
|
// Assert
|
|
78
|
-
expect(result).toBe(
|
|
41
|
+
expect(result).toBe(input);
|
|
79
42
|
});
|
|
80
43
|
});
|
package/src/parser/parse.ts
CHANGED
|
@@ -8,10 +8,12 @@ import type {
|
|
|
8
8
|
|
|
9
9
|
import {parse, ParserPlugin} from "@babel/parser";
|
|
10
10
|
import traverse from "@babel/traverse";
|
|
11
|
+
import type {NodePath} from "@babel/traverse";
|
|
11
12
|
|
|
12
13
|
import path from "path";
|
|
13
14
|
|
|
14
|
-
import {
|
|
15
|
+
import {resolveImportPath} from "./resolveImport";
|
|
16
|
+
import {getPathWithExtension} from "./utils";
|
|
15
17
|
import {Config} from "../types";
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -63,6 +65,8 @@ export type Import = {
|
|
|
63
65
|
type: "import";
|
|
64
66
|
name: string;
|
|
65
67
|
path: string;
|
|
68
|
+
rawPath?: string;
|
|
69
|
+
resolvedPath?: string | null;
|
|
66
70
|
loc: Loc;
|
|
67
71
|
};
|
|
68
72
|
|
|
@@ -79,6 +83,7 @@ export type FileResult = {
|
|
|
79
83
|
exports: {
|
|
80
84
|
[key: string]: Document | Import;
|
|
81
85
|
};
|
|
86
|
+
exportAlls: Array<Import>;
|
|
82
87
|
locals: {
|
|
83
88
|
[key: string]: Document | Import;
|
|
84
89
|
};
|
|
@@ -86,6 +91,12 @@ export type FileResult = {
|
|
|
86
91
|
loc: Loc;
|
|
87
92
|
message: string;
|
|
88
93
|
}>;
|
|
94
|
+
unresolvedImports?: {
|
|
95
|
+
[key: string]: {
|
|
96
|
+
source: string;
|
|
97
|
+
loc: Loc;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
89
100
|
};
|
|
90
101
|
|
|
91
102
|
export type Files = {
|
|
@@ -100,15 +111,12 @@ export type Files = {
|
|
|
100
111
|
* potentially relevant, and of course any values referenced
|
|
101
112
|
* from a graphql template are treated as relevant.
|
|
102
113
|
*/
|
|
103
|
-
const listExternalReferences = (
|
|
104
|
-
file: FileResult,
|
|
105
|
-
config: Config,
|
|
106
|
-
): Array<string> => {
|
|
114
|
+
const listExternalReferences = (file: FileResult): Array<string> => {
|
|
107
115
|
const paths: Record<string, any> = {};
|
|
108
116
|
const add = (v: Document | Import, followImports: boolean) => {
|
|
109
117
|
if (v.type === "import") {
|
|
110
118
|
if (followImports) {
|
|
111
|
-
const absPath = getPathWithExtension(v.path
|
|
119
|
+
const absPath = getPathWithExtension(v.path);
|
|
112
120
|
if (absPath) {
|
|
113
121
|
paths[absPath] = true;
|
|
114
122
|
}
|
|
@@ -141,6 +149,7 @@ const listExternalReferences = (
|
|
|
141
149
|
),
|
|
142
150
|
),
|
|
143
151
|
);
|
|
152
|
+
file.exportAlls.forEach((expr) => add(expr, true));
|
|
144
153
|
return Object.keys(paths);
|
|
145
154
|
};
|
|
146
155
|
|
|
@@ -154,13 +163,14 @@ export const processFile = (
|
|
|
154
163
|
},
|
|
155
164
|
config: Config,
|
|
156
165
|
): FileResult => {
|
|
157
|
-
const dir = path.dirname(filePath);
|
|
158
166
|
const result: FileResult = {
|
|
159
167
|
path: filePath,
|
|
160
168
|
operations: [],
|
|
161
169
|
exports: {},
|
|
170
|
+
exportAlls: [],
|
|
162
171
|
locals: {},
|
|
163
172
|
errors: [],
|
|
173
|
+
unresolvedImports: {},
|
|
164
174
|
};
|
|
165
175
|
const text = typeof contents === "string" ? contents : contents.text;
|
|
166
176
|
const plugins: Array<ParserPlugin> = filePath.endsWith("x")
|
|
@@ -178,17 +188,42 @@ export const processFile = (
|
|
|
178
188
|
|
|
179
189
|
ast.program.body.forEach((toplevel) => {
|
|
180
190
|
if (toplevel.type === "ImportDeclaration") {
|
|
181
|
-
const
|
|
191
|
+
const isUnresolvedModule =
|
|
192
|
+
!toplevel.source.value.startsWith(".") &&
|
|
193
|
+
!path.isAbsolute(toplevel.source.value) &&
|
|
194
|
+
toplevel.source.value !== "graphql-tag";
|
|
195
|
+
if (isUnresolvedModule) {
|
|
196
|
+
toplevel.specifiers.forEach((spec) => {
|
|
197
|
+
if (
|
|
198
|
+
spec.type === "ImportSpecifier" ||
|
|
199
|
+
spec.type === "ImportDefaultSpecifier"
|
|
200
|
+
) {
|
|
201
|
+
result.unresolvedImports![spec.local.name] = {
|
|
202
|
+
source: toplevel.source.value,
|
|
203
|
+
loc: {
|
|
204
|
+
start: spec.start ?? -1,
|
|
205
|
+
end: spec.end ?? -1,
|
|
206
|
+
line: spec.loc?.start.line ?? -1,
|
|
207
|
+
path: filePath,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const newLocals = getLocals(toplevel, filePath);
|
|
182
214
|
if (newLocals) {
|
|
183
215
|
Object.keys(newLocals).forEach((k) => {
|
|
184
216
|
const local = newLocals[k];
|
|
185
|
-
|
|
217
|
+
const isGraphqlTagImport =
|
|
218
|
+
local.rawPath === "graphql-tag" ||
|
|
219
|
+
(local.resolvedPath?.includes(
|
|
220
|
+
`${path.sep}node_modules${path.sep}graphql-tag`,
|
|
221
|
+
) ??
|
|
222
|
+
false);
|
|
223
|
+
if (path.isAbsolute(local.path)) {
|
|
186
224
|
result.locals[k] = local;
|
|
187
225
|
}
|
|
188
|
-
if (
|
|
189
|
-
local.path === "graphql-tag" &&
|
|
190
|
-
local.name === "default"
|
|
191
|
-
) {
|
|
226
|
+
if (isGraphqlTagImport && local.name === "default") {
|
|
192
227
|
gqlTagNames.push(k);
|
|
193
228
|
}
|
|
194
229
|
});
|
|
@@ -197,9 +232,8 @@ export const processFile = (
|
|
|
197
232
|
if (toplevel.type === "ExportNamedDeclaration") {
|
|
198
233
|
if (toplevel.source) {
|
|
199
234
|
const source = toplevel.source;
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
: source.value;
|
|
235
|
+
const resolvedPath = resolveImportPath(filePath, source.value);
|
|
236
|
+
const importPath = resolvedPath ?? source.value;
|
|
203
237
|
toplevel.specifiers?.forEach((spec) => {
|
|
204
238
|
if (
|
|
205
239
|
spec.type === "ExportSpecifier" &&
|
|
@@ -229,6 +263,23 @@ export const processFile = (
|
|
|
229
263
|
});
|
|
230
264
|
}
|
|
231
265
|
}
|
|
266
|
+
if (toplevel.type === "ExportAllDeclaration" && toplevel.source) {
|
|
267
|
+
const source = toplevel.source;
|
|
268
|
+
const importPath = source.value.startsWith(".")
|
|
269
|
+
? path.resolve(path.join(path.dirname(filePath), source.value))
|
|
270
|
+
: source.value;
|
|
271
|
+
result.exportAlls.push({
|
|
272
|
+
type: "import",
|
|
273
|
+
name: "*",
|
|
274
|
+
path: importPath,
|
|
275
|
+
loc: {
|
|
276
|
+
start: toplevel.start ?? -1,
|
|
277
|
+
end: toplevel.end ?? -1,
|
|
278
|
+
line: toplevel.loc?.start.line ?? -1,
|
|
279
|
+
path: filePath,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
}
|
|
232
283
|
|
|
233
284
|
const processDeclarator = (
|
|
234
285
|
decl: VariableDeclarator,
|
|
@@ -308,8 +359,9 @@ export const processFile = (
|
|
|
308
359
|
};
|
|
309
360
|
|
|
310
361
|
traverse(ast as any, {
|
|
311
|
-
TaggedTemplateExpression(path) {
|
|
312
|
-
|
|
362
|
+
TaggedTemplateExpression(path: NodePath) {
|
|
363
|
+
const node = path.node as TaggedTemplateExpression;
|
|
364
|
+
visitTpl(node, (name) => {
|
|
313
365
|
const binding = path.scope.getBinding(name);
|
|
314
366
|
if (!binding) {
|
|
315
367
|
return null;
|
|
@@ -358,10 +410,18 @@ const processTemplate = (
|
|
|
358
410
|
const found = getTemplate(expr.name);
|
|
359
411
|
return found;
|
|
360
412
|
}
|
|
361
|
-
result.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
413
|
+
const unresolved = result.unresolvedImports?.[expr.name];
|
|
414
|
+
if (unresolved) {
|
|
415
|
+
result.errors.push({
|
|
416
|
+
loc: unresolved.loc,
|
|
417
|
+
message: `Unable to resolve import ${expr.name} from "${unresolved.source}" at ${unresolved.loc.path}:${unresolved.loc.line}.`,
|
|
418
|
+
});
|
|
419
|
+
} else {
|
|
420
|
+
result.errors.push({
|
|
421
|
+
loc,
|
|
422
|
+
message: `Unable to resolve ${expr.name}`,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
365
425
|
return null;
|
|
366
426
|
}
|
|
367
427
|
return result.locals[expr.name];
|
|
@@ -384,10 +444,8 @@ const processTemplate = (
|
|
|
384
444
|
};
|
|
385
445
|
|
|
386
446
|
const getLocals = (
|
|
387
|
-
dir: string,
|
|
388
447
|
toplevel: ImportDeclaration,
|
|
389
448
|
myPath: string,
|
|
390
|
-
config: Config,
|
|
391
449
|
):
|
|
392
450
|
| {
|
|
393
451
|
[key: string]: Import;
|
|
@@ -397,10 +455,8 @@ const getLocals = (
|
|
|
397
455
|
if (toplevel.importKind === "type") {
|
|
398
456
|
return null;
|
|
399
457
|
}
|
|
400
|
-
const
|
|
401
|
-
const importPath =
|
|
402
|
-
? path.resolve(path.join(dir, fixedPath))
|
|
403
|
-
: fixedPath;
|
|
458
|
+
const resolvedPath = resolveImportPath(myPath, toplevel.source.value);
|
|
459
|
+
const importPath = resolvedPath ?? toplevel.source.value;
|
|
404
460
|
const locals: Record<string, any> = {};
|
|
405
461
|
toplevel.specifiers.forEach((spec) => {
|
|
406
462
|
if (spec.type === "ImportDefaultSpecifier") {
|
|
@@ -408,6 +464,8 @@ const getLocals = (
|
|
|
408
464
|
type: "import",
|
|
409
465
|
name: "default",
|
|
410
466
|
path: importPath,
|
|
467
|
+
rawPath: toplevel.source.value,
|
|
468
|
+
resolvedPath,
|
|
411
469
|
loc: {start: spec.start, end: spec.end, path: myPath},
|
|
412
470
|
};
|
|
413
471
|
} else if (spec.type === "ImportSpecifier") {
|
|
@@ -418,6 +476,8 @@ const getLocals = (
|
|
|
418
476
|
? spec.imported.name
|
|
419
477
|
: spec.imported.value,
|
|
420
478
|
path: importPath,
|
|
479
|
+
rawPath: toplevel.source.value,
|
|
480
|
+
resolvedPath,
|
|
421
481
|
loc: {start: spec.start, end: spec.end, path: myPath},
|
|
422
482
|
};
|
|
423
483
|
}
|
|
@@ -449,7 +509,7 @@ export const processFiles = (
|
|
|
449
509
|
}
|
|
450
510
|
const result = processFile(next, source, config);
|
|
451
511
|
files[next] = result;
|
|
452
|
-
listExternalReferences(result
|
|
512
|
+
listExternalReferences(result).forEach((path) => {
|
|
453
513
|
if (!files[path] && !toProcess.includes(path)) {
|
|
454
514
|
toProcess.push(path);
|
|
455
515
|
}
|