@khanacademy/graphql-flow 1.2.0 → 3.0.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/.babelrc +1 -1
- package/.eslintrc.js +0 -1
- package/.github/workflows/changeset-release.yml +1 -1
- package/CHANGELOG.md +16 -0
- package/dist/cli/config.js +8 -7
- package/dist/cli/run.js +1 -2
- package/dist/enums.js +8 -9
- package/dist/generateResponseType.js +33 -41
- package/dist/generateTypeFiles.js +9 -23
- package/dist/generateVariablesType.js +15 -31
- package/dist/index.js +8 -15
- package/dist/parser/parse.js +6 -7
- package/dist/parser/resolve.js +1 -2
- package/dist/parser/utils.js +1 -2
- package/dist/schemaFromIntrospectionData.js +1 -2
- package/dist/types.js +1 -2
- package/dist/utils.js +43 -3
- package/package.json +9 -8
- package/{dist/__test__/generateTypeFileContents.test.js → src/__test__/generateTypeFileContents.test.ts} +38 -41
- package/{dist/__test__/graphql-flow.test.js → src/__test__/graphql-flow.test.ts} +232 -235
- package/src/__test__/{processPragmas.test.js → processPragmas.test.ts} +0 -1
- package/{dist/cli/__test__/config.test.js → src/cli/__test__/config.test.ts} +5 -6
- package/src/cli/{config.js → config.ts} +10 -15
- package/src/cli/{run.js → run.ts} +5 -4
- package/src/{enums.js → enums.ts} +20 -22
- package/src/{generateResponseType.js → generateResponseType.ts} +167 -182
- package/src/{generateTypeFiles.js → generateTypeFiles.ts} +20 -30
- package/src/{generateVariablesType.js → generateVariablesType.ts} +34 -44
- package/{dist/index.js.flow → src/index.ts} +32 -24
- package/{dist/parser/__test__/parse.test.js → src/parser/__test__/parse.test.ts} +12 -11
- package/src/parser/{parse.js → parse.ts} +65 -47
- package/{dist/parser/resolve.js.flow → src/parser/resolve.ts} +15 -11
- package/{dist/parser/utils.js.flow → src/parser/utils.ts} +0 -1
- package/{dist/schemaFromIntrospectionData.js.flow → src/schemaFromIntrospectionData.ts} +1 -4
- package/src/types.ts +97 -0
- package/src/utils.ts +73 -0
- package/tools/{find-files-with-gql.js → find-files-with-gql.ts} +2 -3
- package/tsconfig.json +110 -0
- package/types/flow-to-ts.d.ts +1 -0
- package/dist/__test__/example-schema.graphql +0 -67
- package/dist/__test__/processPragmas.test.js +0 -76
- package/dist/cli/config.js.flow +0 -84
- package/dist/cli/config.js.map +0 -1
- package/dist/cli/run.js.flow +0 -236
- package/dist/cli/run.js.map +0 -1
- package/dist/enums.js.flow +0 -98
- package/dist/enums.js.map +0 -1
- package/dist/generateResponseType.js.flow +0 -583
- package/dist/generateResponseType.js.map +0 -1
- package/dist/generateTypeFiles.js.flow +0 -191
- package/dist/generateTypeFiles.js.map +0 -1
- package/dist/generateVariablesType.js.flow +0 -156
- package/dist/generateVariablesType.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/parser/parse.js.flow +0 -417
- package/dist/parser/parse.js.map +0 -1
- package/dist/parser/resolve.js.map +0 -1
- package/dist/parser/utils.js.map +0 -1
- package/dist/schemaFromIntrospectionData.js.map +0 -1
- package/dist/types.js.flow +0 -88
- package/dist/types.js.map +0 -1
- package/dist/utils.js.flow +0 -50
- package/dist/utils.js.map +0 -1
- package/flow-typed/npm/@babel/types_vx.x.x.js +0 -5331
- package/flow-typed/npm/jest_v23.x.x.js +0 -1155
- package/flow-typed/overrides.js +0 -435
- package/src/__test__/generateTypeFileContents.test.js +0 -157
- package/src/__test__/graphql-flow.test.js +0 -639
- package/src/cli/__test__/config.test.js +0 -120
- package/src/cli/schema.json +0 -97
- package/src/index.js +0 -160
- package/src/parser/__test__/parse.test.js +0 -249
- package/src/parser/resolve.js +0 -119
- package/src/parser/utils.js +0 -25
- package/src/schemaFromIntrospectionData.js +0 -68
- package/src/types.js +0 -88
- package/src/utils.js +0 -50
- /package/{dist/cli/schema.json → schema.json} +0 -0
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import generate from '@babel/generator'; // eslint-disable-line flowtype-errors/uncovered
|
|
3
|
-
import type {
|
|
4
|
-
BabelNodeFlowType,
|
|
5
|
-
BabelNodeObjectTypeProperty,
|
|
6
|
-
} from '@babel/types';
|
|
7
2
|
import * as babelTypes from '@babel/types';
|
|
8
3
|
import type {OperationDefinitionNode, TypeNode} from 'graphql/language/ast';
|
|
9
4
|
import type {IntrospectionInputTypeRef} from 'graphql/utilities/introspectionQuery';
|
|
10
5
|
import {builtinScalars, enumTypeToFlow, scalarTypeToFlow} from './enums';
|
|
6
|
+
import {nullableType, isnNullableType, objectTypeFromProperties} from './utils';
|
|
11
7
|
import type {Context, Schema} from './types';
|
|
12
8
|
import {
|
|
13
9
|
liftLeadingPropertyComments,
|
|
@@ -18,18 +14,18 @@ import {
|
|
|
18
14
|
export const inputObjectToFlow = (
|
|
19
15
|
ctx: Context,
|
|
20
16
|
name: string,
|
|
21
|
-
):
|
|
17
|
+
): babelTypes.TSType => {
|
|
22
18
|
const inputObject = ctx.schema.inputObjectsByName[name];
|
|
23
19
|
if (!inputObject) {
|
|
24
20
|
ctx.errors.push(`Unknown input object ${name}`);
|
|
25
|
-
return babelTypes.
|
|
26
|
-
`Unknown input object ${name}
|
|
21
|
+
return babelTypes.tsLiteralType(
|
|
22
|
+
babelTypes.stringLiteral(`Unknown input object ${name}`),
|
|
27
23
|
);
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
return maybeAddDescriptionComment(
|
|
31
27
|
inputObject.description,
|
|
32
|
-
|
|
28
|
+
objectTypeFromProperties(
|
|
33
29
|
inputObject.inputFields.map((vbl) =>
|
|
34
30
|
maybeAddDescriptionComment(
|
|
35
31
|
vbl.description,
|
|
@@ -39,22 +35,21 @@ export const inputObjectToFlow = (
|
|
|
39
35
|
),
|
|
40
36
|
),
|
|
41
37
|
),
|
|
42
|
-
undefined /* indexers */,
|
|
43
|
-
undefined /* callProperties */,
|
|
44
|
-
undefined /* internalSlots */,
|
|
45
|
-
true /* exact */,
|
|
46
38
|
),
|
|
47
39
|
);
|
|
48
40
|
};
|
|
49
41
|
|
|
50
42
|
export const maybeOptionalObjectTypeProperty = (
|
|
51
43
|
name: string,
|
|
52
|
-
type: babelTypes.
|
|
53
|
-
):
|
|
44
|
+
type: babelTypes.TSType,
|
|
45
|
+
): babelTypes.TSPropertySignature => {
|
|
54
46
|
const prop = liftLeadingPropertyComments(
|
|
55
|
-
babelTypes.
|
|
47
|
+
babelTypes.tsPropertySignature(
|
|
48
|
+
babelTypes.identifier(name),
|
|
49
|
+
babelTypes.tsTypeAnnotation(type),
|
|
50
|
+
),
|
|
56
51
|
);
|
|
57
|
-
if (type
|
|
52
|
+
if (isnNullableType(type)) {
|
|
58
53
|
prop.optional = true;
|
|
59
54
|
}
|
|
60
55
|
return prop;
|
|
@@ -63,18 +58,18 @@ export const maybeOptionalObjectTypeProperty = (
|
|
|
63
58
|
export const inputRefToFlow = (
|
|
64
59
|
ctx: Context,
|
|
65
60
|
inputRef: IntrospectionInputTypeRef,
|
|
66
|
-
):
|
|
61
|
+
): babelTypes.TSType => {
|
|
67
62
|
if (inputRef.kind === 'NON_NULL') {
|
|
68
63
|
return _inputRefToFlow(ctx, inputRef.ofType);
|
|
69
64
|
}
|
|
70
65
|
const result = _inputRefToFlow(ctx, inputRef);
|
|
71
|
-
return transferLeadingComments(
|
|
72
|
-
result,
|
|
73
|
-
babelTypes.nullableTypeAnnotation(result),
|
|
74
|
-
);
|
|
66
|
+
return transferLeadingComments(result, nullableType(result));
|
|
75
67
|
};
|
|
76
68
|
|
|
77
|
-
const _inputRefToFlow = (
|
|
69
|
+
const _inputRefToFlow = (
|
|
70
|
+
ctx: Context,
|
|
71
|
+
inputRef: IntrospectionInputTypeRef,
|
|
72
|
+
): babelTypes.TSType => {
|
|
78
73
|
if (inputRef.kind === 'SCALAR') {
|
|
79
74
|
return scalarTypeToFlow(ctx, inputRef.name);
|
|
80
75
|
}
|
|
@@ -85,28 +80,27 @@ const _inputRefToFlow = (ctx: Context, inputRef: IntrospectionInputTypeRef) => {
|
|
|
85
80
|
return inputObjectToFlow(ctx, inputRef.name);
|
|
86
81
|
}
|
|
87
82
|
if (inputRef.kind === 'LIST') {
|
|
88
|
-
return babelTypes.
|
|
89
|
-
babelTypes.identifier('
|
|
90
|
-
babelTypes.
|
|
83
|
+
return babelTypes.tsTypeReference(
|
|
84
|
+
babelTypes.identifier('ReadonlyArray'),
|
|
85
|
+
babelTypes.tsTypeParameterInstantiation([
|
|
91
86
|
inputRefToFlow(ctx, inputRef.ofType),
|
|
92
87
|
]),
|
|
93
88
|
);
|
|
94
89
|
}
|
|
95
|
-
return babelTypes.
|
|
90
|
+
return babelTypes.tsLiteralType(
|
|
91
|
+
babelTypes.stringLiteral(JSON.stringify(inputRef)),
|
|
92
|
+
);
|
|
96
93
|
};
|
|
97
94
|
|
|
98
|
-
const variableToFlow = (ctx: Context, type: TypeNode) => {
|
|
95
|
+
const variableToFlow = (ctx: Context, type: TypeNode): babelTypes.TSType => {
|
|
99
96
|
if (type.kind === 'NonNullType') {
|
|
100
97
|
return _variableToFlow(ctx, type.type);
|
|
101
98
|
}
|
|
102
99
|
const result = _variableToFlow(ctx, type);
|
|
103
|
-
return transferLeadingComments(
|
|
104
|
-
result,
|
|
105
|
-
babelTypes.nullableTypeAnnotation(result),
|
|
106
|
-
);
|
|
100
|
+
return transferLeadingComments(result, nullableType(result));
|
|
107
101
|
};
|
|
108
102
|
|
|
109
|
-
const _variableToFlow = (ctx: Context, type: TypeNode) => {
|
|
103
|
+
const _variableToFlow = (ctx: Context, type: TypeNode): babelTypes.TSType => {
|
|
110
104
|
if (type.kind === 'NamedType') {
|
|
111
105
|
if (builtinScalars[type.name.value]) {
|
|
112
106
|
return scalarTypeToFlow(ctx, type.name.value);
|
|
@@ -116,22 +110,22 @@ const _variableToFlow = (ctx: Context, type: TypeNode) => {
|
|
|
116
110
|
}
|
|
117
111
|
const customScalarType = ctx.scalars[type.name.value];
|
|
118
112
|
if (customScalarType) {
|
|
119
|
-
return babelTypes.
|
|
113
|
+
return babelTypes.tsTypeReference(
|
|
120
114
|
babelTypes.identifier(customScalarType),
|
|
121
115
|
);
|
|
122
116
|
}
|
|
123
117
|
return inputObjectToFlow(ctx, type.name.value);
|
|
124
118
|
}
|
|
125
119
|
if (type.kind === 'ListType') {
|
|
126
|
-
return babelTypes.
|
|
127
|
-
babelTypes.identifier('
|
|
128
|
-
babelTypes.
|
|
120
|
+
return babelTypes.tsTypeReference(
|
|
121
|
+
babelTypes.identifier('ReadonlyArray'),
|
|
122
|
+
babelTypes.tsTypeParameterInstantiation([
|
|
129
123
|
variableToFlow(ctx, type.type),
|
|
130
124
|
]),
|
|
131
125
|
);
|
|
132
126
|
}
|
|
133
|
-
return babelTypes.
|
|
134
|
-
'UNKNOWN' + JSON.stringify(type),
|
|
127
|
+
return babelTypes.tsLiteralType(
|
|
128
|
+
babelTypes.stringLiteral('UNKNOWN' + JSON.stringify(type)),
|
|
135
129
|
);
|
|
136
130
|
};
|
|
137
131
|
|
|
@@ -140,17 +134,13 @@ export const generateVariablesType = (
|
|
|
140
134
|
item: OperationDefinitionNode,
|
|
141
135
|
ctx: Context,
|
|
142
136
|
): string => {
|
|
143
|
-
const variableObject =
|
|
137
|
+
const variableObject = objectTypeFromProperties(
|
|
144
138
|
(item.variableDefinitions || []).map((vbl) => {
|
|
145
139
|
return maybeOptionalObjectTypeProperty(
|
|
146
140
|
vbl.variable.name.value,
|
|
147
141
|
variableToFlow(ctx, vbl.type),
|
|
148
142
|
);
|
|
149
143
|
}),
|
|
150
|
-
undefined /* indexers */,
|
|
151
|
-
undefined /* callProperties */,
|
|
152
|
-
undefined /* internalSlots */,
|
|
153
|
-
true /* exact */,
|
|
154
144
|
);
|
|
155
145
|
return generate(variableObject).code; // eslint-disable-line flowtype-errors/uncovered
|
|
156
146
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import {isTruthy} from '@khanacademy/wonder-stuff-core';
|
|
1
2
|
/* eslint-disable no-console */
|
|
2
3
|
/* flow-uncovered-file */
|
|
3
|
-
// @flow
|
|
4
4
|
/**
|
|
5
5
|
* This tool generates flowtype definitions from graphql queries.
|
|
6
6
|
*
|
|
@@ -15,13 +15,13 @@ import {
|
|
|
15
15
|
generateResponseType,
|
|
16
16
|
} from './generateResponseType';
|
|
17
17
|
import {generateVariablesType} from './generateVariablesType';
|
|
18
|
-
import type {
|
|
18
|
+
import type {Node} from '@babel/types';
|
|
19
19
|
|
|
20
20
|
import type {Context, Schema, GenerateConfig} from './types';
|
|
21
21
|
|
|
22
22
|
const optionsToConfig = (
|
|
23
23
|
schema: Schema,
|
|
24
|
-
definitions:
|
|
24
|
+
definitions: ReadonlyArray<DefinitionNode>,
|
|
25
25
|
options?: GenerateConfig,
|
|
26
26
|
errors: Array<string> = [],
|
|
27
27
|
): Context => {
|
|
@@ -31,8 +31,8 @@ const optionsToConfig = (
|
|
|
31
31
|
scalars: options?.scalars ?? {},
|
|
32
32
|
typeScript: options?.typeScript ?? false,
|
|
33
33
|
omitFileExtensions: options?.omitFileExtensions ?? false,
|
|
34
|
-
};
|
|
35
|
-
const fragments = {};
|
|
34
|
+
} as const;
|
|
35
|
+
const fragments: Record<string, any> = {};
|
|
36
36
|
definitions.forEach((def) => {
|
|
37
37
|
if (def.kind === 'FragmentDefinition') {
|
|
38
38
|
fragments[def.name.value] = def;
|
|
@@ -46,8 +46,10 @@ const optionsToConfig = (
|
|
|
46
46
|
path: [],
|
|
47
47
|
experimentalEnumsMap: options?.experimentalEnums ? {} : undefined,
|
|
48
48
|
...internalOptions,
|
|
49
|
-
};
|
|
49
|
+
} as const;
|
|
50
50
|
|
|
51
|
+
// @ts-expect-error: TS2322 - The type 'readonly []' is 'readonly' and cannot be
|
|
52
|
+
// assigned to the mutable type 'string[]'.
|
|
51
53
|
return config;
|
|
52
54
|
};
|
|
53
55
|
|
|
@@ -59,17 +61,17 @@ export class FlowGenerationError extends Error {
|
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
export const documentToFlowTypes = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
export const documentToFlowTypes = (document: DocumentNode, schema: Schema, options?: GenerateConfig): ReadonlyArray<{
|
|
65
|
+
name: string
|
|
66
|
+
typeName: string
|
|
67
|
+
code: string
|
|
68
|
+
isFragment?: boolean
|
|
69
|
+
extraTypes: {
|
|
70
|
+
[key: string]: string
|
|
71
|
+
}
|
|
72
|
+
experimentalEnums: {
|
|
73
|
+
[key: string]: string
|
|
74
|
+
}
|
|
73
75
|
}> => {
|
|
74
76
|
const errors: Array<string> = [];
|
|
75
77
|
const config = optionsToConfig(
|
|
@@ -82,7 +84,7 @@ export const documentToFlowTypes = (
|
|
|
82
84
|
.map((item) => {
|
|
83
85
|
if (item.kind === 'FragmentDefinition') {
|
|
84
86
|
const name = item.name.value;
|
|
85
|
-
const types = {};
|
|
87
|
+
const types: Record<string, any> = {};
|
|
86
88
|
const code = `export type ${name} = ${generateFragmentType(
|
|
87
89
|
schema,
|
|
88
90
|
item,
|
|
@@ -114,7 +116,7 @@ export const documentToFlowTypes = (
|
|
|
114
116
|
(item.operation === 'query' || item.operation === 'mutation') &&
|
|
115
117
|
item.name
|
|
116
118
|
) {
|
|
117
|
-
const types = {};
|
|
119
|
+
const types: Record<string, any> = {};
|
|
118
120
|
const name = item.name.value;
|
|
119
121
|
const response = generateResponseType(schema, item, {
|
|
120
122
|
...config,
|
|
@@ -131,7 +133,7 @@ export const documentToFlowTypes = (
|
|
|
131
133
|
const typeName = `${name}Type`;
|
|
132
134
|
// TODO(jared): Maybe make this template configurable?
|
|
133
135
|
// We'll see what's required to get webapp on board.
|
|
134
|
-
const code = `export type ${typeName} = {
|
|
136
|
+
const code = `export type ${typeName} = {\n variables: ${variables},\n response: ${response}\n};`;
|
|
135
137
|
|
|
136
138
|
const extraTypes = codegenExtraTypes(types);
|
|
137
139
|
const experimentalEnums = codegenExtraTypes(
|
|
@@ -141,17 +143,23 @@ export const documentToFlowTypes = (
|
|
|
141
143
|
return {name, typeName, code, extraTypes, experimentalEnums};
|
|
142
144
|
}
|
|
143
145
|
})
|
|
144
|
-
.filter(
|
|
146
|
+
.filter(isTruthy);
|
|
145
147
|
if (errors.length) {
|
|
146
148
|
throw new FlowGenerationError(errors);
|
|
147
149
|
}
|
|
148
150
|
return result;
|
|
149
151
|
};
|
|
150
152
|
|
|
151
|
-
function codegenExtraTypes(
|
|
152
|
-
|
|
153
|
+
function codegenExtraTypes(
|
|
154
|
+
types: {
|
|
155
|
+
[key: string]: Node
|
|
156
|
+
},
|
|
157
|
+
): {
|
|
158
|
+
[key: string]: string
|
|
153
159
|
} {
|
|
154
|
-
const extraTypes: {
|
|
160
|
+
const extraTypes: {
|
|
161
|
+
[key: string]: string
|
|
162
|
+
} = {};
|
|
155
163
|
Object.keys(types).forEach((k: string) => {
|
|
156
164
|
// eslint-disable-next-line flowtype-errors/uncovered
|
|
157
165
|
extraTypes[k] = generate(types[k]).code;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
|
|
3
1
|
import {processFiles} from '../parse';
|
|
4
2
|
import {resolveDocuments} from '../resolve';
|
|
5
3
|
|
|
6
4
|
import {print} from 'graphql/language/printer';
|
|
7
5
|
|
|
8
6
|
const fixtureFiles: {
|
|
9
|
-
[key: string]: string | {
|
|
7
|
+
[key: string]: string | {
|
|
8
|
+
text: string
|
|
9
|
+
resolvedPath: string
|
|
10
|
+
}
|
|
10
11
|
} = {
|
|
11
12
|
'/firstFile.js': `
|
|
12
13
|
// Note that you can import graphql-tag as
|
|
@@ -151,14 +152,14 @@ const getFileSource = (name: string) => {
|
|
|
151
152
|
describe('processing fragments in various ways', () => {
|
|
152
153
|
it('should work', () => {
|
|
153
154
|
const files = processFiles(['/thirdFile.js'], getFileSource);
|
|
154
|
-
Object.keys(files).forEach((k) => {
|
|
155
|
+
Object.keys(files).forEach((k: any) => {
|
|
155
156
|
expect(files[k].errors).toEqual([]);
|
|
156
157
|
});
|
|
157
158
|
const {resolved, errors} = resolveDocuments(files);
|
|
158
159
|
expect(errors).toEqual([]);
|
|
159
|
-
const printed = {};
|
|
160
|
+
const printed: Record<string, any> = {};
|
|
160
161
|
Object.keys(resolved).map(
|
|
161
|
-
(k) => (printed[k] = print(resolved[k].document).trim()),
|
|
162
|
+
(k: any) => (printed[k] = print(resolved[k].document).trim()),
|
|
162
163
|
);
|
|
163
164
|
expect(printed).toMatchInlineSnapshot(`
|
|
164
165
|
Object {
|
|
@@ -216,7 +217,7 @@ describe('processing fragments in various ways', () => {
|
|
|
216
217
|
|
|
217
218
|
it('should flag things it doesnt support', () => {
|
|
218
219
|
const files = processFiles(['/invalidThings.js'], getFileSource);
|
|
219
|
-
expect(files['/invalidThings.js'].errors.map((m) => m.message))
|
|
220
|
+
expect(files['/invalidThings.js'].errors.map((m: any) => m.message))
|
|
220
221
|
.toMatchInlineSnapshot(`
|
|
221
222
|
Array [
|
|
222
223
|
"Unable to resolve someExternalFragment",
|
|
@@ -228,11 +229,11 @@ describe('processing fragments in various ways', () => {
|
|
|
228
229
|
|
|
229
230
|
it('should flag resolution errors', () => {
|
|
230
231
|
const files = processFiles(['/invalidReferences.js'], getFileSource);
|
|
231
|
-
Object.keys(files).forEach((k) => {
|
|
232
|
+
Object.keys(files).forEach((k: any) => {
|
|
232
233
|
expect(files[k].errors).toEqual([]);
|
|
233
234
|
});
|
|
234
235
|
const {resolved, errors} = resolveDocuments(files);
|
|
235
|
-
expect(errors.map((m) => m.message)).toMatchInlineSnapshot(`
|
|
236
|
+
expect(errors.map((m: any) => m.message)).toMatchInlineSnapshot(`
|
|
236
237
|
Array [
|
|
237
238
|
"Circular import /circular.js -> /invalidReferences.js -> /circular.js",
|
|
238
239
|
"/circular.js has no valid gql export doesntExist",
|
|
@@ -240,9 +241,9 @@ describe('processing fragments in various ways', () => {
|
|
|
240
241
|
"Recursive template dependency! /circular.js:5 ~ 1,2 -> /invalidReferences.js:15 ~ 1,2 -> /circular.js:5",
|
|
241
242
|
]
|
|
242
243
|
`);
|
|
243
|
-
const printed = {};
|
|
244
|
+
const printed: Record<string, any> = {};
|
|
244
245
|
Object.keys(resolved).map(
|
|
245
|
-
(k) => (printed[k] = print(resolved[k].document).trim()),
|
|
246
|
+
(k: any) => (printed[k] = print(resolved[k].document).trim()),
|
|
246
247
|
);
|
|
247
248
|
expect(printed).toMatchInlineSnapshot(`Object {}`);
|
|
248
249
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import {isTruthy} from '@khanacademy/wonder-stuff-core';
|
|
2
2
|
import type {
|
|
3
3
|
BabelNodeImportDeclaration,
|
|
4
4
|
BabelNodeVariableDeclarator,
|
|
@@ -42,40 +42,54 @@ import {getPathWithExtension} from './utils';
|
|
|
42
42
|
* in `gqlOp<Type>()` or not (to inform an auto-wrapper of the future)
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
export type Template = {
|
|
46
|
-
literals: Array<string
|
|
47
|
-
expressions: Array<Document | Import
|
|
48
|
-
loc: Loc
|
|
49
|
-
|
|
50
|
-
export type Loc = {
|
|
45
|
+
export type Template = {
|
|
46
|
+
literals: Array<string>
|
|
47
|
+
expressions: Array<Document | Import>
|
|
48
|
+
loc: Loc
|
|
49
|
+
};
|
|
50
|
+
export type Loc = {
|
|
51
|
+
start: number
|
|
52
|
+
end: number
|
|
53
|
+
path: string
|
|
54
|
+
line: number
|
|
55
|
+
};
|
|
51
56
|
|
|
52
|
-
export type Document = {
|
|
53
|
-
type: 'document'
|
|
54
|
-
source: Template
|
|
55
|
-
|
|
56
|
-
export type Import = {
|
|
57
|
-
type: 'import'
|
|
58
|
-
name: string
|
|
59
|
-
path: string
|
|
60
|
-
loc: Loc
|
|
61
|
-
|
|
57
|
+
export type Document = {
|
|
58
|
+
type: 'document'
|
|
59
|
+
source: Template
|
|
60
|
+
};
|
|
61
|
+
export type Import = {
|
|
62
|
+
type: 'import'
|
|
63
|
+
name: string
|
|
64
|
+
path: string
|
|
65
|
+
loc: Loc
|
|
66
|
+
};
|
|
62
67
|
|
|
63
|
-
export type Operation = {
|
|
64
|
-
source: Template
|
|
68
|
+
export type Operation = {
|
|
69
|
+
source: Template
|
|
65
70
|
// TODO: Determine if an operation is already wrapped
|
|
66
71
|
// in `gqlOp` so we can automatically wrap if needed.
|
|
67
72
|
// needsWrapping: boolean,
|
|
68
|
-
|
|
73
|
+
};
|
|
69
74
|
|
|
70
|
-
export type FileResult = {
|
|
71
|
-
path: string
|
|
72
|
-
operations: Array<Operation
|
|
73
|
-
exports: {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
export type FileResult = {
|
|
76
|
+
path: string
|
|
77
|
+
operations: Array<Operation>
|
|
78
|
+
exports: {
|
|
79
|
+
[key: string]: Document | Import
|
|
80
|
+
}
|
|
81
|
+
locals: {
|
|
82
|
+
[key: string]: Document | Import
|
|
83
|
+
}
|
|
84
|
+
errors: Array<{
|
|
85
|
+
loc: Loc
|
|
86
|
+
message: string
|
|
87
|
+
}>
|
|
88
|
+
};
|
|
77
89
|
|
|
78
|
-
export type Files = {
|
|
90
|
+
export type Files = {
|
|
91
|
+
[path: string]: FileResult
|
|
92
|
+
};
|
|
79
93
|
|
|
80
94
|
/**
|
|
81
95
|
* Finds all referenced imports that might possibly be relevant
|
|
@@ -86,7 +100,7 @@ export type Files = {[path: string]: FileResult};
|
|
|
86
100
|
* from a graphql template are treated as relevant.
|
|
87
101
|
*/
|
|
88
102
|
const listExternalReferences = (file: FileResult): Array<string> => {
|
|
89
|
-
const paths = {};
|
|
103
|
+
const paths: Record<string, any> = {};
|
|
90
104
|
const add = (v: Document | Import, followImports: boolean) => {
|
|
91
105
|
if (v.type === 'import') {
|
|
92
106
|
if (followImports) {
|
|
@@ -126,7 +140,10 @@ const listExternalReferences = (file: FileResult): Array<string> => {
|
|
|
126
140
|
|
|
127
141
|
export const processFile = (
|
|
128
142
|
filePath: string,
|
|
129
|
-
contents: string | {
|
|
143
|
+
contents: string | {
|
|
144
|
+
text: string
|
|
145
|
+
resolvedPath: string
|
|
146
|
+
},
|
|
130
147
|
): FileResult => {
|
|
131
148
|
const dir = path.dirname(filePath);
|
|
132
149
|
const result: FileResult = {
|
|
@@ -140,7 +157,7 @@ export const processFile = (
|
|
|
140
157
|
typeof contents === 'string' ? filePath : contents.resolvedPath;
|
|
141
158
|
const text = typeof contents === 'string' ? contents : contents.text;
|
|
142
159
|
const plugins = resolved.match(/\.tsx?$/)
|
|
143
|
-
? ['typescript', filePath.endsWith('x') ? 'jsx' : null].filter(
|
|
160
|
+
? ['typescript', filePath.endsWith('x') ? 'jsx' : null].filter(isTruthy)
|
|
144
161
|
: [['flow', {enums: true}], 'jsx'];
|
|
145
162
|
/* eslint-disable flowtype-errors/uncovered */
|
|
146
163
|
const ast: BabelNodeFile = parse(text, {
|
|
@@ -150,7 +167,9 @@ export const processFile = (
|
|
|
150
167
|
});
|
|
151
168
|
/* eslint-enable flowtype-errors/uncovered */
|
|
152
169
|
const gqlTagNames = [];
|
|
153
|
-
const seenTemplates: {
|
|
170
|
+
const seenTemplates: {
|
|
171
|
+
[key: number]: Document | false
|
|
172
|
+
} = {};
|
|
154
173
|
|
|
155
174
|
ast.program.body.forEach((toplevel) => {
|
|
156
175
|
if (toplevel.type === 'ImportDeclaration') {
|
|
@@ -176,7 +195,7 @@ export const processFile = (
|
|
|
176
195
|
const importPath = source.value.startsWith('.')
|
|
177
196
|
? path.resolve(path.join(dir, source.value))
|
|
178
197
|
: source.value;
|
|
179
|
-
toplevel.specifiers?.forEach((spec) => {
|
|
198
|
+
toplevel.specifiers?.forEach((spec: unknown) => {
|
|
180
199
|
if (
|
|
181
200
|
spec.type === 'ExportSpecifier' &&
|
|
182
201
|
spec.exported.type === 'Identifier'
|
|
@@ -195,7 +214,7 @@ export const processFile = (
|
|
|
195
214
|
}
|
|
196
215
|
});
|
|
197
216
|
} else {
|
|
198
|
-
toplevel.specifiers?.forEach((spec) => {
|
|
217
|
+
toplevel.specifiers?.forEach((spec: unknown) => {
|
|
199
218
|
if (spec.type === 'ExportSpecifier') {
|
|
200
219
|
const local = result.locals[spec.local.name];
|
|
201
220
|
if (local && spec.exported.type === 'Identifier') {
|
|
@@ -285,7 +304,7 @@ export const processFile = (
|
|
|
285
304
|
|
|
286
305
|
/* eslint-disable flowtype-errors/uncovered */
|
|
287
306
|
traverse(ast, {
|
|
288
|
-
TaggedTemplateExpression(path) {
|
|
307
|
+
TaggedTemplateExpression(path: unknown) {
|
|
289
308
|
visitTpl(path.node, (name) => {
|
|
290
309
|
const binding = path.scope.getBinding(name);
|
|
291
310
|
const start = binding.path.node.init
|
|
@@ -306,10 +325,10 @@ export const processFile = (
|
|
|
306
325
|
const processTemplate = (
|
|
307
326
|
tpl: BabelNodeTaggedTemplateExpression,
|
|
308
327
|
result: FileResult,
|
|
309
|
-
getTemplate?: (name: string) => Document | null,
|
|
310
328
|
// getBinding?: (name: string) => Binding,
|
|
311
329
|
// seenTemplates,
|
|
312
|
-
|
|
330
|
+
getTemplate?: (name: string) => Document | null,
|
|
331
|
+
): Template | null | undefined => {
|
|
313
332
|
// 'cooked' is the string as runtime javascript will see it.
|
|
314
333
|
const literals = tpl.quasi.quasis.map((q) => q.value.cooked || '');
|
|
315
334
|
const expressions = tpl.quasi.expressions.map(
|
|
@@ -347,7 +366,7 @@ const processTemplate = (
|
|
|
347
366
|
}
|
|
348
367
|
return {
|
|
349
368
|
literals,
|
|
350
|
-
expressions: expressions.filter(
|
|
369
|
+
expressions: expressions.filter(isTruthy),
|
|
351
370
|
loc: {
|
|
352
371
|
line: tpl.loc?.start.line ?? -1,
|
|
353
372
|
start: tpl.start ?? -1,
|
|
@@ -357,18 +376,16 @@ const processTemplate = (
|
|
|
357
376
|
};
|
|
358
377
|
};
|
|
359
378
|
|
|
360
|
-
const getLocals = (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
myPath: string,
|
|
364
|
-
): ?{[key: string]: Import} => {
|
|
379
|
+
const getLocals = (dir: unknown, toplevel: BabelNodeImportDeclaration, myPath: string): {
|
|
380
|
+
[key: string]: Import
|
|
381
|
+
} | null | undefined => {
|
|
365
382
|
if (toplevel.importKind === 'type') {
|
|
366
383
|
return null;
|
|
367
384
|
}
|
|
368
385
|
const importPath = toplevel.source.value.startsWith('.')
|
|
369
386
|
? path.resolve(path.join(dir, toplevel.source.value))
|
|
370
387
|
: toplevel.source.value;
|
|
371
|
-
const locals = {};
|
|
388
|
+
const locals: Record<string, any> = {};
|
|
372
389
|
toplevel.specifiers.forEach((spec) => {
|
|
373
390
|
if (spec.type === 'ImportDefaultSpecifier') {
|
|
374
391
|
locals[spec.local.name] = {
|
|
@@ -394,9 +411,10 @@ const getLocals = (
|
|
|
394
411
|
|
|
395
412
|
export const processFiles = (
|
|
396
413
|
filePaths: Array<string>,
|
|
397
|
-
getFileSource: (
|
|
398
|
-
|
|
399
|
-
|
|
414
|
+
getFileSource: (path: string) => string | {
|
|
415
|
+
text: string
|
|
416
|
+
resolvedPath: string
|
|
417
|
+
},
|
|
400
418
|
): Files => {
|
|
401
419
|
const files: Files = {};
|
|
402
420
|
const toProcess = filePaths.slice();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import gql from 'graphql-tag';
|
|
3
2
|
import {getPathWithExtension} from './utils';
|
|
4
3
|
import type {DocumentNode} from 'graphql/language/ast';
|
|
@@ -6,14 +5,15 @@ import type {FileResult, Files, Import, Template, Document} from './parse';
|
|
|
6
5
|
|
|
7
6
|
export type Resolved = {
|
|
8
7
|
[key: string]: {
|
|
9
|
-
document: DocumentNode
|
|
10
|
-
raw: Template
|
|
11
|
-
}
|
|
8
|
+
document: DocumentNode
|
|
9
|
+
raw: Template
|
|
10
|
+
}
|
|
12
11
|
};
|
|
13
12
|
|
|
14
|
-
export const resolveDocuments = (
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export const resolveDocuments = (files: Files): {
|
|
14
|
+
resolved: Resolved
|
|
15
|
+
errors: FileResult['errors']
|
|
16
|
+
} => {
|
|
17
17
|
const resolved: Resolved = {};
|
|
18
18
|
const errors: FileResult['errors'] = [];
|
|
19
19
|
Object.keys(files).forEach((path) => {
|
|
@@ -35,8 +35,10 @@ const resolveImport = (
|
|
|
35
35
|
expr: Import,
|
|
36
36
|
files: Files,
|
|
37
37
|
errors: FileResult['errors'],
|
|
38
|
-
seen: {
|
|
39
|
-
|
|
38
|
+
seen: {
|
|
39
|
+
[key: string]: true
|
|
40
|
+
},
|
|
41
|
+
): Document | null | undefined => {
|
|
40
42
|
const absPath: string = getPathWithExtension(expr.path);
|
|
41
43
|
if (seen[absPath]) {
|
|
42
44
|
errors.push({
|
|
@@ -72,8 +74,10 @@ const resolveGqlTemplate = (
|
|
|
72
74
|
files: Files,
|
|
73
75
|
errors: FileResult['errors'],
|
|
74
76
|
resolved: Resolved,
|
|
75
|
-
seen: {
|
|
76
|
-
|
|
77
|
+
seen: {
|
|
78
|
+
[key: string]: Template
|
|
79
|
+
},
|
|
80
|
+
): DocumentNode | null | undefined => {
|
|
77
81
|
const key = template.loc.path + ':' + template.loc.line;
|
|
78
82
|
if (seen[key]) {
|
|
79
83
|
errors.push({
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
/**
|
|
3
2
|
* Takes the introspectionQuery response and parses it into the "Schema"
|
|
4
3
|
* type that we use to look up types, interfaces, etc.
|
|
@@ -6,9 +5,7 @@
|
|
|
6
5
|
import type {IntrospectionQuery} from 'graphql';
|
|
7
6
|
import type {Schema} from './types';
|
|
8
7
|
|
|
9
|
-
export const schemaFromIntrospectionData = (
|
|
10
|
-
schema: IntrospectionQuery,
|
|
11
|
-
): Schema => {
|
|
8
|
+
export const schemaFromIntrospectionData = (schema: IntrospectionQuery): Schema => {
|
|
12
9
|
const result: Schema = {
|
|
13
10
|
interfacesByName: {},
|
|
14
11
|
typesByName: {},
|