@graphql-inspector/core 3.1.4 → 3.4.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/README.md +5 -3
- package/{dist/ast → ast}/document.d.ts +0 -0
- package/{dist/coverage → coverage}/index.d.ts +0 -0
- package/{dist/coverage → coverage}/output/json.d.ts +0 -0
- package/{dist/diff → diff}/argument.d.ts +0 -0
- package/{dist/diff → diff}/changes/argument.d.ts +0 -0
- package/{dist/diff → diff}/changes/change.d.ts +0 -0
- package/{dist/diff → diff}/changes/directive.d.ts +0 -0
- package/{dist/diff → diff}/changes/enum.d.ts +0 -0
- package/{dist/diff → diff}/changes/field.d.ts +0 -0
- package/{dist/diff → diff}/changes/input.d.ts +0 -0
- package/{dist/diff → diff}/changes/object.d.ts +0 -0
- package/{dist/diff → diff}/changes/schema.d.ts +0 -0
- package/{dist/diff → diff}/changes/type.d.ts +0 -0
- package/{dist/diff → diff}/changes/union.d.ts +0 -0
- package/{dist/diff → diff}/directive.d.ts +0 -0
- package/{dist/diff → diff}/enum.d.ts +0 -0
- package/{dist/diff → diff}/field.d.ts +0 -0
- package/{dist/diff → diff}/index.d.ts +0 -0
- package/{dist/diff → diff}/input.d.ts +0 -0
- package/{dist/diff → diff}/interface.d.ts +0 -0
- package/{dist/diff → diff}/object.d.ts +0 -0
- package/{dist/diff → diff}/onComplete/types.d.ts +0 -0
- package/{dist/diff → diff}/rules/config.d.ts +0 -0
- package/{dist/diff → diff}/rules/consider-usage.d.ts +0 -0
- package/{dist/diff → diff}/rules/dangerous-breaking.d.ts +0 -0
- package/{dist/diff → diff}/rules/ignore-description-changes.d.ts +0 -0
- package/{dist/diff → diff}/rules/index.d.ts +0 -0
- package/{dist/diff → diff}/rules/safe-unreachable.d.ts +0 -0
- package/{dist/diff → diff}/rules/suppress-removal-of-deprecated-field.d.ts +0 -0
- package/{dist/diff → diff}/rules/types.d.ts +0 -0
- package/{dist/diff → diff}/schema.d.ts +0 -0
- package/{dist/diff → diff}/union.d.ts +0 -0
- package/index.d.ts +12 -0
- package/{dist/index.js → index.js} +325 -157
- package/{dist/index.mjs → index.mjs} +322 -159
- package/package.json +29 -35
- package/{dist/similar → similar}/index.d.ts +0 -0
- package/{dist/utils → utils}/apollo.d.ts +0 -0
- package/{dist/utils → utils}/compare.d.ts +0 -0
- package/{dist/utils → utils}/graphql.d.ts +0 -0
- package/utils/isDeprecated.d.ts +2 -0
- package/{dist/utils → utils}/path.d.ts +0 -0
- package/{dist/utils → utils}/string.d.ts +0 -0
- package/validate/alias-count.d.ts +10 -0
- package/validate/complexity.d.ts +16 -0
- package/validate/directive-count.d.ts +10 -0
- package/{dist/validate → validate}/index.d.ts +17 -2
- package/{dist/validate → validate}/query-depth.d.ts +2 -1
- package/validate/token-count.d.ts +12 -0
- package/dist/LICENSE +0 -21
- package/dist/README.md +0 -55
- package/dist/index.d.ts +0 -7
- package/dist/package.json +0 -46
- package/dist/utils/isDeprecated.d.ts +0 -2
- package/utils/testing.ts +0 -11
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
|
-
import { Kind, TypeInfo, visit, visitWithTypeInfo, GraphQLError, getNamedType, isScalarType, isInterfaceType, isObjectType, isUnionType, isInputObjectType, isListType, isNonNullType, isWrappingType, isEnumType, parse, extendSchema, print, validate as validate$1, printType } from 'graphql';
|
|
2
|
+
import { Kind, TypeInfo, visit, visitWithTypeInfo, GraphQLError, getNamedType, isScalarType, isInterfaceType, isObjectType, isUnionType, isInputObjectType, isListType, isNonNullType, isWrappingType, isEnumType, parse, extendSchema, TokenKind, print, validate as validate$1, printType } from 'graphql';
|
|
3
3
|
import inspect from 'object-inspect';
|
|
4
4
|
import { DepGraph } from 'dependency-graph';
|
|
5
|
+
import { Parser } from 'graphql/language/parser';
|
|
5
6
|
|
|
6
7
|
function keyMap(list, keyFn) {
|
|
7
8
|
return list.reduce((map, item) => {
|
|
@@ -43,7 +44,7 @@ function isVoid(a) {
|
|
|
43
44
|
return typeof a === 'undefined' || a === null;
|
|
44
45
|
}
|
|
45
46
|
function diffArrays(a, b) {
|
|
46
|
-
return a.filter(
|
|
47
|
+
return a.filter(c => !b.some(d => isEqual(d, c)));
|
|
47
48
|
}
|
|
48
49
|
function compareLists(oldList, newList, callbacks) {
|
|
49
50
|
const oldMap = keyMap(oldList, ({ name }) => name);
|
|
@@ -94,7 +95,7 @@ function isDeprecated(fieldOrEnumValue) {
|
|
|
94
95
|
if (fieldOrEnumValue.deprecationReason != null) {
|
|
95
96
|
return true;
|
|
96
97
|
}
|
|
97
|
-
if ((_b = (_a = fieldOrEnumValue.astNode) === null || _a === void 0 ? void 0 : _a.directives) === null || _b === void 0 ? void 0 : _b.some(
|
|
98
|
+
if ((_b = (_a = fieldOrEnumValue.astNode) === null || _a === void 0 ? void 0 : _a.directives) === null || _b === void 0 ? void 0 : _b.some(directive => directive.name.value === 'deprecated')) {
|
|
98
99
|
return true;
|
|
99
100
|
}
|
|
100
101
|
return false;
|
|
@@ -109,8 +110,7 @@ function safeChangeForField(oldType, newType) {
|
|
|
109
110
|
return safeChangeForField(ofType, newType.ofType);
|
|
110
111
|
}
|
|
111
112
|
if (isListType(oldType)) {
|
|
112
|
-
return ((isListType(newType) &&
|
|
113
|
-
safeChangeForField(oldType.ofType, newType.ofType)) ||
|
|
113
|
+
return ((isListType(newType) && safeChangeForField(oldType.ofType, newType.ofType)) ||
|
|
114
114
|
(isNonNullType(newType) && safeChangeForField(oldType, newType.ofType)));
|
|
115
115
|
}
|
|
116
116
|
return false;
|
|
@@ -123,7 +123,7 @@ function safeChangeForInputValue(oldType, newType) {
|
|
|
123
123
|
return safeChangeForInputValue(oldType.ofType, newType.ofType);
|
|
124
124
|
}
|
|
125
125
|
if (isNonNullType(oldType)) {
|
|
126
|
-
const ofType = isNonNullType(newType) ? newType : newType;
|
|
126
|
+
const ofType = isNonNullType(newType) ? newType.ofType : newType;
|
|
127
127
|
return safeChangeForInputValue(oldType.ofType, ofType);
|
|
128
128
|
}
|
|
129
129
|
return false;
|
|
@@ -145,7 +145,7 @@ function getTypePrefix(type) {
|
|
|
145
145
|
return kindsMap[kind.toString()];
|
|
146
146
|
}
|
|
147
147
|
function isPrimitive(type) {
|
|
148
|
-
return
|
|
148
|
+
return ['String', 'Int', 'Float', 'Boolean', 'ID'].indexOf(typeof type === 'string' ? type : type.name) !== -1;
|
|
149
149
|
}
|
|
150
150
|
function isForIntrospection(type) {
|
|
151
151
|
return ([
|
|
@@ -170,7 +170,9 @@ function findDeprecatedUsages(schema, ast) {
|
|
|
170
170
|
if (reason) {
|
|
171
171
|
const fieldDef = typeInfo.getFieldDef();
|
|
172
172
|
if (fieldDef) {
|
|
173
|
-
errors.push(new GraphQLError(`The argument '${argument === null || argument === void 0 ? void 0 : argument.name}' of '${fieldDef.name}' is deprecated. ${reason}`, [
|
|
173
|
+
errors.push(new GraphQLError(`The argument '${argument === null || argument === void 0 ? void 0 : argument.name}' of '${fieldDef.name}' is deprecated. ${reason}`, [
|
|
174
|
+
node,
|
|
175
|
+
]));
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
}
|
|
@@ -200,7 +202,7 @@ function findDeprecatedUsages(schema, ast) {
|
|
|
200
202
|
}
|
|
201
203
|
function removeFieldIfDirectives(node, directiveNames) {
|
|
202
204
|
if (node.directives) {
|
|
203
|
-
if (node.directives.some(
|
|
205
|
+
if (node.directives.some(d => directiveNames.indexOf(d.name.value) !== -1)) {
|
|
204
206
|
return null;
|
|
205
207
|
}
|
|
206
208
|
}
|
|
@@ -208,7 +210,7 @@ function removeFieldIfDirectives(node, directiveNames) {
|
|
|
208
210
|
}
|
|
209
211
|
function removeDirectives(node, directiveNames) {
|
|
210
212
|
if (node.directives) {
|
|
211
|
-
return Object.assign(Object.assign({}, node), { directives: node.directives.filter(
|
|
213
|
+
return Object.assign(Object.assign({}, node), { directives: node.directives.filter(d => directiveNames.indexOf(d.name.value) === -1) });
|
|
212
214
|
}
|
|
213
215
|
return node;
|
|
214
216
|
}
|
|
@@ -258,11 +260,7 @@ function getReachableTypes(schema) {
|
|
|
258
260
|
}
|
|
259
261
|
}
|
|
260
262
|
};
|
|
261
|
-
for (const type of [
|
|
262
|
-
schema.getQueryType(),
|
|
263
|
-
schema.getMutationType(),
|
|
264
|
-
schema.getSubscriptionType(),
|
|
265
|
-
]) {
|
|
263
|
+
for (const type of [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]) {
|
|
266
264
|
if (type) {
|
|
267
265
|
collect(type);
|
|
268
266
|
}
|
|
@@ -504,9 +502,7 @@ function directiveLocationRemoved(directive, location) {
|
|
|
504
502
|
function directiveArgumentAdded(directive, arg) {
|
|
505
503
|
return {
|
|
506
504
|
criticality: {
|
|
507
|
-
level: isNonNullType(arg.type)
|
|
508
|
-
? CriticalityLevel.Breaking
|
|
509
|
-
: CriticalityLevel.NonBreaking,
|
|
505
|
+
level: isNonNullType(arg.type) ? CriticalityLevel.Breaking : CriticalityLevel.NonBreaking,
|
|
510
506
|
},
|
|
511
507
|
type: ChangeType.DirectiveArgumentAdded,
|
|
512
508
|
message: `Argument '${arg.name}' was added to directive '${directive.name}'`,
|
|
@@ -692,6 +688,70 @@ function changesInUnion(oldUnion, newUnion, addChange) {
|
|
|
692
688
|
});
|
|
693
689
|
}
|
|
694
690
|
|
|
691
|
+
function compareTwoStrings(str1, str2) {
|
|
692
|
+
if (!str1.length && !str2.length)
|
|
693
|
+
return 1;
|
|
694
|
+
if (!str1.length || !str2.length)
|
|
695
|
+
return 0;
|
|
696
|
+
if (str1.toUpperCase() === str2.toUpperCase())
|
|
697
|
+
return 1;
|
|
698
|
+
if (str1.length === 1 && str2.length === 1)
|
|
699
|
+
return 0;
|
|
700
|
+
const pairs1 = wordLetterPairs(str1);
|
|
701
|
+
const pairs2 = wordLetterPairs(str2);
|
|
702
|
+
const union = pairs1.length + pairs2.length;
|
|
703
|
+
let intersection = 0;
|
|
704
|
+
pairs1.forEach(pair1 => {
|
|
705
|
+
for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
|
|
706
|
+
if (pair1 !== pair2)
|
|
707
|
+
continue;
|
|
708
|
+
intersection++;
|
|
709
|
+
pairs2.splice(i, 1);
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
return (intersection * 2) / union;
|
|
714
|
+
}
|
|
715
|
+
function findBestMatch(mainString, targetStrings) {
|
|
716
|
+
if (!areArgsValid(mainString, targetStrings))
|
|
717
|
+
throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
|
|
718
|
+
const ratings = targetStrings.map(target => ({
|
|
719
|
+
target,
|
|
720
|
+
rating: compareTwoStrings(mainString, target.value),
|
|
721
|
+
}));
|
|
722
|
+
const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
|
|
723
|
+
return { ratings, bestMatch };
|
|
724
|
+
}
|
|
725
|
+
function flattenDeep(arr) {
|
|
726
|
+
return Array.isArray(arr) ? arr.reduce((a, b) => a.concat(flattenDeep(b)), []) : [arr];
|
|
727
|
+
}
|
|
728
|
+
function areArgsValid(mainString, targetStrings) {
|
|
729
|
+
if (typeof mainString !== 'string')
|
|
730
|
+
return false;
|
|
731
|
+
if (!Array.isArray(targetStrings))
|
|
732
|
+
return false;
|
|
733
|
+
if (!targetStrings.length)
|
|
734
|
+
return false;
|
|
735
|
+
if (targetStrings.find(s => typeof s.value !== 'string'))
|
|
736
|
+
return false;
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
function letterPairs(str) {
|
|
740
|
+
const pairs = [];
|
|
741
|
+
for (let i = 0, max = str.length - 1; i < max; i++)
|
|
742
|
+
pairs[i] = str.substring(i, i + 2);
|
|
743
|
+
return pairs;
|
|
744
|
+
}
|
|
745
|
+
function wordLetterPairs(str) {
|
|
746
|
+
const pairs = str.toUpperCase().split(' ').map(letterPairs);
|
|
747
|
+
return flattenDeep(pairs);
|
|
748
|
+
}
|
|
749
|
+
function safeString(obj) {
|
|
750
|
+
return inspect(obj)
|
|
751
|
+
.replace(/\[Object\: null prototype\] /g, '')
|
|
752
|
+
.replace(/(^')|('$)/g, '');
|
|
753
|
+
}
|
|
754
|
+
|
|
695
755
|
function inputFieldRemoved(input, field) {
|
|
696
756
|
return {
|
|
697
757
|
criticality: {
|
|
@@ -699,7 +759,7 @@ function inputFieldRemoved(input, field) {
|
|
|
699
759
|
reason: 'Removing an input field will cause existing queries that use this input field to error.',
|
|
700
760
|
},
|
|
701
761
|
type: ChangeType.InputFieldRemoved,
|
|
702
|
-
message: `Input field '${field.name}' was removed from input object type '${input.name}'`,
|
|
762
|
+
message: `Input field '${field.name}' ${isDeprecated(field) ? '(deprecated) ' : ''}was removed from input object type '${input.name}'`,
|
|
703
763
|
path: [input.name, field.name].join('.'),
|
|
704
764
|
};
|
|
705
765
|
}
|
|
@@ -752,10 +812,10 @@ function inputFieldDefaultValueChanged(input, oldField, newField) {
|
|
|
752
812
|
return {
|
|
753
813
|
criticality: {
|
|
754
814
|
level: CriticalityLevel.Dangerous,
|
|
755
|
-
reason: 'Changing the default value for an argument may change the runtime
|
|
815
|
+
reason: 'Changing the default value for an argument may change the runtime behavior of a field if it was never provided.',
|
|
756
816
|
},
|
|
757
817
|
type: ChangeType.InputFieldDefaultValueChanged,
|
|
758
|
-
message: `Input field '${input.name}.${oldField.name}' default value changed from '${oldField.defaultValue}' to '${newField.defaultValue}'`,
|
|
818
|
+
message: `Input field '${input.name}.${oldField.name}' default value changed from '${safeString(oldField.defaultValue)}' to '${safeString(newField.defaultValue)}'`,
|
|
759
819
|
path: [input.name, oldField.name].join('.'),
|
|
760
820
|
};
|
|
761
821
|
}
|
|
@@ -804,14 +864,12 @@ function changesInInputField(input, oldField, newField, addChange) {
|
|
|
804
864
|
}
|
|
805
865
|
}
|
|
806
866
|
if (isNotEqual(oldField.defaultValue, newField.defaultValue)) {
|
|
807
|
-
if (Array.isArray(oldField.defaultValue) &&
|
|
808
|
-
Array.isArray(newField.defaultValue)) {
|
|
867
|
+
if (Array.isArray(oldField.defaultValue) && Array.isArray(newField.defaultValue)) {
|
|
809
868
|
if (diffArrays(oldField.defaultValue, newField.defaultValue).length > 0) {
|
|
810
869
|
addChange(inputFieldDefaultValueChanged(input, oldField, newField));
|
|
811
870
|
}
|
|
812
871
|
}
|
|
813
|
-
else if (JSON.stringify(oldField.defaultValue) !==
|
|
814
|
-
JSON.stringify(newField.defaultValue)) {
|
|
872
|
+
else if (JSON.stringify(oldField.defaultValue) !== JSON.stringify(newField.defaultValue)) {
|
|
815
873
|
addChange(inputFieldDefaultValueChanged(input, oldField, newField));
|
|
816
874
|
}
|
|
817
875
|
}
|
|
@@ -961,8 +1019,10 @@ function fieldTypeChanged(type, oldField, newField) {
|
|
|
961
1019
|
};
|
|
962
1020
|
}
|
|
963
1021
|
function fieldArgumentAdded(type, field, arg) {
|
|
1022
|
+
const isBreaking = isNonNullType(arg.type) && typeof arg.defaultValue === 'undefined';
|
|
1023
|
+
const defaultValueMsg = typeof arg.defaultValue !== 'undefined' ? ' (with default value) ' : ' ';
|
|
964
1024
|
return {
|
|
965
|
-
criticality:
|
|
1025
|
+
criticality: isBreaking
|
|
966
1026
|
? {
|
|
967
1027
|
level: CriticalityLevel.Breaking,
|
|
968
1028
|
reason: `Adding a required argument to an existing field is a breaking change because it will cause existing uses of this field to error.`,
|
|
@@ -972,7 +1032,7 @@ function fieldArgumentAdded(type, field, arg) {
|
|
|
972
1032
|
reason: `Adding a new argument to an existing field may involve a change in resolve function logic that potentially may cause some side effects.`,
|
|
973
1033
|
},
|
|
974
1034
|
type: ChangeType.FieldArgumentAdded,
|
|
975
|
-
message: `Argument '${arg.name}: ${arg.type}'
|
|
1035
|
+
message: `Argument '${arg.name}: ${arg.type}'${defaultValueMsg}added to field '${type.name}.${field.name}'`,
|
|
976
1036
|
path: [type.name, field.name, arg.name].join('.'),
|
|
977
1037
|
};
|
|
978
1038
|
}
|
|
@@ -988,70 +1048,6 @@ function fieldArgumentRemoved(type, field, arg) {
|
|
|
988
1048
|
};
|
|
989
1049
|
}
|
|
990
1050
|
|
|
991
|
-
function compareTwoStrings(str1, str2) {
|
|
992
|
-
if (!str1.length && !str2.length)
|
|
993
|
-
return 1;
|
|
994
|
-
if (!str1.length || !str2.length)
|
|
995
|
-
return 0;
|
|
996
|
-
if (str1.toUpperCase() === str2.toUpperCase())
|
|
997
|
-
return 1;
|
|
998
|
-
if (str1.length === 1 && str2.length === 1)
|
|
999
|
-
return 0;
|
|
1000
|
-
const pairs1 = wordLetterPairs(str1);
|
|
1001
|
-
const pairs2 = wordLetterPairs(str2);
|
|
1002
|
-
const union = pairs1.length + pairs2.length;
|
|
1003
|
-
let intersection = 0;
|
|
1004
|
-
pairs1.forEach((pair1) => {
|
|
1005
|
-
for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
|
|
1006
|
-
if (pair1 !== pair2)
|
|
1007
|
-
continue;
|
|
1008
|
-
intersection++;
|
|
1009
|
-
pairs2.splice(i, 1);
|
|
1010
|
-
break;
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
return (intersection * 2) / union;
|
|
1014
|
-
}
|
|
1015
|
-
function findBestMatch(mainString, targetStrings) {
|
|
1016
|
-
if (!areArgsValid(mainString, targetStrings))
|
|
1017
|
-
throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
|
|
1018
|
-
const ratings = targetStrings.map((target) => ({
|
|
1019
|
-
target,
|
|
1020
|
-
rating: compareTwoStrings(mainString, target.value),
|
|
1021
|
-
}));
|
|
1022
|
-
const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
|
|
1023
|
-
return { ratings, bestMatch };
|
|
1024
|
-
}
|
|
1025
|
-
function flattenDeep(arr) {
|
|
1026
|
-
return Array.isArray(arr)
|
|
1027
|
-
? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
|
|
1028
|
-
: [arr];
|
|
1029
|
-
}
|
|
1030
|
-
function areArgsValid(mainString, targetStrings) {
|
|
1031
|
-
if (typeof mainString !== 'string')
|
|
1032
|
-
return false;
|
|
1033
|
-
if (!Array.isArray(targetStrings))
|
|
1034
|
-
return false;
|
|
1035
|
-
if (!targetStrings.length)
|
|
1036
|
-
return false;
|
|
1037
|
-
if (targetStrings.find((s) => typeof s.value !== 'string'))
|
|
1038
|
-
return false;
|
|
1039
|
-
return true;
|
|
1040
|
-
}
|
|
1041
|
-
function letterPairs(str) {
|
|
1042
|
-
const pairs = [];
|
|
1043
|
-
for (let i = 0, max = str.length - 1; i < max; i++)
|
|
1044
|
-
pairs[i] = str.substring(i, i + 2);
|
|
1045
|
-
return pairs;
|
|
1046
|
-
}
|
|
1047
|
-
function wordLetterPairs(str) {
|
|
1048
|
-
const pairs = str.toUpperCase().split(' ').map(letterPairs);
|
|
1049
|
-
return flattenDeep(pairs);
|
|
1050
|
-
}
|
|
1051
|
-
function safeString(obj) {
|
|
1052
|
-
return inspect(obj).replace(/\[Object\: null prototype\] /g, '');
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
1051
|
function fieldArgumentDescriptionChanged(type, field, oldArg, newArg) {
|
|
1056
1052
|
return {
|
|
1057
1053
|
criticality: {
|
|
@@ -1097,15 +1093,13 @@ function changesInArgument(type, field, oldArg, newArg, addChange) {
|
|
|
1097
1093
|
addChange(fieldArgumentDescriptionChanged(type, field, oldArg, newArg));
|
|
1098
1094
|
}
|
|
1099
1095
|
if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) {
|
|
1100
|
-
if (Array.isArray(oldArg.defaultValue) &&
|
|
1101
|
-
Array.isArray(newArg.defaultValue)) {
|
|
1096
|
+
if (Array.isArray(oldArg.defaultValue) && Array.isArray(newArg.defaultValue)) {
|
|
1102
1097
|
const diff = diffArrays(oldArg.defaultValue, newArg.defaultValue);
|
|
1103
1098
|
if (diff.length > 0) {
|
|
1104
1099
|
addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
|
|
1105
1100
|
}
|
|
1106
1101
|
}
|
|
1107
|
-
else if (JSON.stringify(oldArg.defaultValue) !==
|
|
1108
|
-
JSON.stringify(newArg.defaultValue)) {
|
|
1102
|
+
else if (JSON.stringify(oldArg.defaultValue) !== JSON.stringify(newArg.defaultValue)) {
|
|
1109
1103
|
addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
|
|
1110
1104
|
}
|
|
1111
1105
|
}
|
|
@@ -1210,9 +1204,9 @@ function changesInDirective(oldDirective, newDirective, addChange) {
|
|
|
1210
1204
|
removed: diffArrays(oldDirective.locations, newDirective.locations),
|
|
1211
1205
|
};
|
|
1212
1206
|
// locations added
|
|
1213
|
-
locations.added.forEach(
|
|
1207
|
+
locations.added.forEach(location => addChange(directiveLocationAdded(newDirective, location)));
|
|
1214
1208
|
// locations removed
|
|
1215
|
-
locations.removed.forEach(
|
|
1209
|
+
locations.removed.forEach(location => addChange(directiveLocationRemoved(oldDirective, location)));
|
|
1216
1210
|
compareLists(oldDirective.args, newDirective.args, {
|
|
1217
1211
|
onAdded(arg) {
|
|
1218
1212
|
addChange(directiveArgumentAdded(newDirective, arg));
|
|
@@ -1243,7 +1237,7 @@ function diffSchema(oldSchema, newSchema) {
|
|
|
1243
1237
|
changes.push(change);
|
|
1244
1238
|
}
|
|
1245
1239
|
changesInSchema(oldSchema, newSchema, addChange);
|
|
1246
|
-
compareLists(Object.values(oldSchema.getTypeMap()).filter(
|
|
1240
|
+
compareLists(Object.values(oldSchema.getTypeMap()).filter(t => !isPrimitive(t)), Object.values(newSchema.getTypeMap()).filter(t => !isPrimitive(t)), {
|
|
1247
1241
|
onAdded(type) {
|
|
1248
1242
|
addChange(typeAdded(type));
|
|
1249
1243
|
},
|
|
@@ -1328,7 +1322,7 @@ function changesInType(oldType, newType, addChange) {
|
|
|
1328
1322
|
}
|
|
1329
1323
|
|
|
1330
1324
|
const dangerousBreaking = ({ changes }) => {
|
|
1331
|
-
return changes.map(
|
|
1325
|
+
return changes.map(change => {
|
|
1332
1326
|
if (change.criticality.level === CriticalityLevel.Dangerous) {
|
|
1333
1327
|
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Breaking }) });
|
|
1334
1328
|
}
|
|
@@ -1340,8 +1334,8 @@ function parsePath(path) {
|
|
|
1340
1334
|
return path.split('.');
|
|
1341
1335
|
}
|
|
1342
1336
|
|
|
1343
|
-
const suppressRemovalOfDeprecatedField = ({ changes, oldSchema
|
|
1344
|
-
return changes.map(
|
|
1337
|
+
const suppressRemovalOfDeprecatedField = ({ changes, oldSchema }) => {
|
|
1338
|
+
return changes.map(change => {
|
|
1345
1339
|
if (change.type === ChangeType.FieldRemoved &&
|
|
1346
1340
|
change.criticality.level === CriticalityLevel.Breaking &&
|
|
1347
1341
|
change.path) {
|
|
@@ -1366,6 +1360,18 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
|
|
|
1366
1360
|
}
|
|
1367
1361
|
}
|
|
1368
1362
|
}
|
|
1363
|
+
if (change.type === ChangeType.InputFieldRemoved &&
|
|
1364
|
+
change.criticality.level === CriticalityLevel.Breaking &&
|
|
1365
|
+
change.path) {
|
|
1366
|
+
const [inputName, inputItem] = parsePath(change.path);
|
|
1367
|
+
const type = oldSchema.getType(inputName);
|
|
1368
|
+
if (isInputObjectType(type)) {
|
|
1369
|
+
const item = type.getFields()[inputItem];
|
|
1370
|
+
if (item && isDeprecated(item)) {
|
|
1371
|
+
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }) });
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1369
1375
|
return change;
|
|
1370
1376
|
});
|
|
1371
1377
|
};
|
|
@@ -1384,15 +1390,15 @@ const descriptionChangeTypes = [
|
|
|
1384
1390
|
ChangeType.TypeDescriptionChanged,
|
|
1385
1391
|
];
|
|
1386
1392
|
const ignoreDescriptionChanges = ({ changes }) => {
|
|
1387
|
-
return changes.filter(
|
|
1393
|
+
return changes.filter(change => descriptionChangeTypes.indexOf(change.type) === -1);
|
|
1388
1394
|
};
|
|
1389
1395
|
|
|
1390
|
-
const considerUsage = ({ changes, config
|
|
1396
|
+
const considerUsage = ({ changes, config }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1391
1397
|
if (!config) {
|
|
1392
1398
|
throw new Error(`considerUsage rule is missing config`);
|
|
1393
1399
|
}
|
|
1394
1400
|
const collectedBreakingField = [];
|
|
1395
|
-
changes.forEach(
|
|
1401
|
+
changes.forEach(change => {
|
|
1396
1402
|
if (change.criticality.level === CriticalityLevel.Breaking && change.path) {
|
|
1397
1403
|
const [typeName, fieldName, argumentName] = parsePath(change.path);
|
|
1398
1404
|
collectedBreakingField.push({
|
|
@@ -1409,11 +1415,11 @@ const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0
|
|
|
1409
1415
|
const suppressedPaths = collectedBreakingField
|
|
1410
1416
|
.filter((_, i) => usageList[i] === true)
|
|
1411
1417
|
.map(({ type, field, argument }) => [type, field, argument].filter(Boolean).join('.'));
|
|
1412
|
-
return changes.map(
|
|
1418
|
+
return changes.map(change => {
|
|
1413
1419
|
// Turns those "safe to break" changes into "dangerous"
|
|
1414
1420
|
if (change.criticality.level === CriticalityLevel.Breaking &&
|
|
1415
1421
|
change.path &&
|
|
1416
|
-
suppressedPaths.some(
|
|
1422
|
+
suppressedPaths.some(p => change.path.startsWith(p))) {
|
|
1417
1423
|
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }), message: `${change.message} (non-breaking based on usage)` });
|
|
1418
1424
|
}
|
|
1419
1425
|
return change;
|
|
@@ -1422,7 +1428,7 @@ const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0
|
|
|
1422
1428
|
|
|
1423
1429
|
const safeUnreachable = ({ changes, oldSchema }) => {
|
|
1424
1430
|
const reachable = getReachableTypes(oldSchema);
|
|
1425
|
-
return changes.map(
|
|
1431
|
+
return changes.map(change => {
|
|
1426
1432
|
if (change.criticality.level === CriticalityLevel.Breaking && change.path) {
|
|
1427
1433
|
const [typeName] = parsePath(change.path);
|
|
1428
1434
|
if (!reachable.has(typeName)) {
|
|
@@ -1529,7 +1535,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1529
1535
|
return 1 + maxInnerDepth;
|
|
1530
1536
|
}
|
|
1531
1537
|
case Kind.SELECTION_SET: {
|
|
1532
|
-
return Math.max(...node.selections.map(
|
|
1538
|
+
return Math.max(...node.selections.map(selection => {
|
|
1533
1539
|
return calculateDepth({
|
|
1534
1540
|
node: selection,
|
|
1535
1541
|
currentDepth: currentDepth,
|
|
@@ -1539,7 +1545,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1539
1545
|
}));
|
|
1540
1546
|
}
|
|
1541
1547
|
case Kind.DOCUMENT: {
|
|
1542
|
-
return Math.max(...node.definitions.map(
|
|
1548
|
+
return Math.max(...node.definitions.map(def => {
|
|
1543
1549
|
return calculateDepth({
|
|
1544
1550
|
node: def,
|
|
1545
1551
|
currentDepth: currentDepth,
|
|
@@ -1551,7 +1557,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1551
1557
|
case Kind.OPERATION_DEFINITION:
|
|
1552
1558
|
case Kind.INLINE_FRAGMENT:
|
|
1553
1559
|
case Kind.FRAGMENT_DEFINITION: {
|
|
1554
|
-
return Math.max(...node.selectionSet.selections.map(
|
|
1560
|
+
return Math.max(...node.selectionSet.selections.map(selection => {
|
|
1555
1561
|
return calculateDepth({
|
|
1556
1562
|
node: selection,
|
|
1557
1563
|
currentDepth,
|
|
@@ -1572,13 +1578,26 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1572
1578
|
}
|
|
1573
1579
|
}
|
|
1574
1580
|
}
|
|
1581
|
+
function countDepth(node, parentDepth, getFragmentReference) {
|
|
1582
|
+
let depth = parentDepth;
|
|
1583
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1584
|
+
for (let child of node.selectionSet.selections) {
|
|
1585
|
+
depth = Math.max(depth, countDepth(child, parentDepth + 1, getFragmentReference));
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (node.kind == Kind.FRAGMENT_SPREAD) {
|
|
1589
|
+
const fragment = getFragmentReference(node.name.value);
|
|
1590
|
+
if (fragment) {
|
|
1591
|
+
depth = Math.max(depth, countDepth(fragment, parentDepth + 1, getFragmentReference));
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return depth;
|
|
1595
|
+
}
|
|
1575
1596
|
|
|
1576
1597
|
function transformDocumentWithApollo(doc, { keepClientFields }) {
|
|
1577
1598
|
return visit(doc, {
|
|
1578
1599
|
Field(node) {
|
|
1579
|
-
return keepClientFields
|
|
1580
|
-
? removeDirectives(node, ['client'])
|
|
1581
|
-
: removeFieldIfDirectives(node, ['client']);
|
|
1600
|
+
return keepClientFields ? removeDirectives(node, ['client']) : removeFieldIfDirectives(node, ['client']);
|
|
1582
1601
|
},
|
|
1583
1602
|
});
|
|
1584
1603
|
}
|
|
@@ -1588,6 +1607,116 @@ function transformSchemaWithApollo(schema) {
|
|
|
1588
1607
|
`));
|
|
1589
1608
|
}
|
|
1590
1609
|
|
|
1610
|
+
function validateAliasCount({ source, doc, maxAliasCount, fragmentGraph, }) {
|
|
1611
|
+
const getFragmentByFragmentName = (fragmentName) => fragmentGraph.getNodeData(fragmentName);
|
|
1612
|
+
for (const definition of doc.definitions) {
|
|
1613
|
+
if (definition.kind !== Kind.OPERATION_DEFINITION) {
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
const aliasCount = countAliases(definition, getFragmentByFragmentName);
|
|
1617
|
+
if (aliasCount > maxAliasCount) {
|
|
1618
|
+
return new GraphQLError(`Too many aliases (${aliasCount}). Maximum allowed is ${maxAliasCount}`, [definition], source, definition.loc && definition.loc.start ? [definition.loc.start] : undefined);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function countAliases(node, getFragmentByName) {
|
|
1623
|
+
let aliases = 0;
|
|
1624
|
+
if ('alias' in node && node.alias) {
|
|
1625
|
+
++aliases;
|
|
1626
|
+
}
|
|
1627
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1628
|
+
for (let child of node.selectionSet.selections) {
|
|
1629
|
+
aliases += countAliases(child, getFragmentByName);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
else if (node.kind === Kind.FRAGMENT_SPREAD) {
|
|
1633
|
+
const fragmentNode = getFragmentByName(node.name.value);
|
|
1634
|
+
if (fragmentNode) {
|
|
1635
|
+
aliases += countAliases(fragmentNode, getFragmentByName);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return aliases;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
function validateDirectiveCount({ source, doc, maxDirectiveCount, fragmentGraph, }) {
|
|
1642
|
+
const getFragmentByFragmentName = (fragmentName) => fragmentGraph.getNodeData(fragmentName);
|
|
1643
|
+
for (const definition of doc.definitions) {
|
|
1644
|
+
if (definition.kind !== Kind.OPERATION_DEFINITION) {
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
const directiveCount = countDirectives(definition, getFragmentByFragmentName);
|
|
1648
|
+
if (directiveCount > maxDirectiveCount) {
|
|
1649
|
+
return new GraphQLError(`Too many directives (${directiveCount}). Maximum allowed is ${maxDirectiveCount}`, [definition], source, definition.loc && definition.loc.start ? [definition.loc.start] : undefined);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
function countDirectives(node, getFragmentByName) {
|
|
1654
|
+
let directives = 0;
|
|
1655
|
+
if (node.directives) {
|
|
1656
|
+
directives += node.directives.length;
|
|
1657
|
+
}
|
|
1658
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1659
|
+
for (let child of node.selectionSet.selections) {
|
|
1660
|
+
directives += countDirectives(child, getFragmentByName);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
if (node.kind == Kind.FRAGMENT_SPREAD) {
|
|
1664
|
+
const fragment = getFragmentByName(node.name.value);
|
|
1665
|
+
if (fragment) {
|
|
1666
|
+
directives += countDirectives(fragment, getFragmentByName);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return directives;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
class ParserWithLexer extends Parser {
|
|
1673
|
+
constructor(source, options) {
|
|
1674
|
+
super(source, options);
|
|
1675
|
+
this.__tokenCount = 0;
|
|
1676
|
+
const lexer = this._lexer;
|
|
1677
|
+
this._lexer = new Proxy(lexer, {
|
|
1678
|
+
get: (target, prop, receiver) => {
|
|
1679
|
+
if (prop === 'advance') {
|
|
1680
|
+
return () => {
|
|
1681
|
+
const token = target.advance();
|
|
1682
|
+
if (token.kind !== TokenKind.EOF) {
|
|
1683
|
+
this.__tokenCount++;
|
|
1684
|
+
}
|
|
1685
|
+
return token;
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
return Reflect.get(target, prop, receiver);
|
|
1689
|
+
},
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
get tokenCount() {
|
|
1693
|
+
return this.__tokenCount;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
function calculateTokenCount(args) {
|
|
1697
|
+
const parser = new ParserWithLexer(args.source);
|
|
1698
|
+
const document = parser.parseDocument();
|
|
1699
|
+
let { tokenCount } = parser;
|
|
1700
|
+
visit(document, {
|
|
1701
|
+
FragmentSpread(node) {
|
|
1702
|
+
const fragmentSource = args.getReferencedFragmentSource(node.name.value);
|
|
1703
|
+
if (fragmentSource) {
|
|
1704
|
+
tokenCount += calculateTokenCount({
|
|
1705
|
+
source: fragmentSource,
|
|
1706
|
+
getReferencedFragmentSource: args.getReferencedFragmentSource,
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
});
|
|
1711
|
+
return tokenCount;
|
|
1712
|
+
}
|
|
1713
|
+
function validateTokenCount(args) {
|
|
1714
|
+
const tokenCount = calculateTokenCount(args);
|
|
1715
|
+
if (tokenCount > args.maxTokenCount) {
|
|
1716
|
+
return new GraphQLError(`Query exceeds maximum token count of ${args.maxTokenCount} (actual: ${tokenCount})`, args.document, args.source, args.document.loc && args.document.loc.start ? [args.document.loc.start] : undefined);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1591
1720
|
function validate(schema, sources, options) {
|
|
1592
1721
|
const config = Object.assign({ strictDeprecated: true, strictFragments: true, keepClientFields: false, apollo: false }, options);
|
|
1593
1722
|
const invalidDocuments = [];
|
|
@@ -1597,43 +1726,41 @@ function validate(schema, sources, options) {
|
|
|
1597
1726
|
const fragments = [];
|
|
1598
1727
|
const fragmentNames = [];
|
|
1599
1728
|
const graph = new DepGraph({ circular: true });
|
|
1600
|
-
documents.forEach(
|
|
1601
|
-
doc.fragments.forEach(
|
|
1729
|
+
documents.forEach(doc => {
|
|
1730
|
+
doc.fragments.forEach(fragment => {
|
|
1602
1731
|
fragmentNames.push(fragment.node.name.value);
|
|
1603
1732
|
fragments.push(fragment);
|
|
1604
1733
|
graph.addNode(fragment.node.name.value, fragment.node);
|
|
1605
1734
|
});
|
|
1606
1735
|
});
|
|
1607
|
-
fragments.forEach(
|
|
1736
|
+
fragments.forEach(fragment => {
|
|
1608
1737
|
const depends = extractFragments(print(fragment.node));
|
|
1609
1738
|
if (depends) {
|
|
1610
|
-
depends.forEach(
|
|
1739
|
+
depends.forEach(name => {
|
|
1611
1740
|
graph.addDependency(fragment.node.name.value, name);
|
|
1612
1741
|
});
|
|
1613
1742
|
}
|
|
1614
1743
|
});
|
|
1615
1744
|
documents
|
|
1616
1745
|
// since we include fragments, validate only operations
|
|
1617
|
-
.filter(
|
|
1618
|
-
.forEach(
|
|
1746
|
+
.filter(doc => doc.hasOperations)
|
|
1747
|
+
.forEach(doc => {
|
|
1619
1748
|
const docWithOperations = {
|
|
1620
1749
|
kind: Kind.DOCUMENT,
|
|
1621
|
-
definitions: doc.operations.map(
|
|
1750
|
+
definitions: doc.operations.map(d => d.node),
|
|
1622
1751
|
};
|
|
1623
1752
|
const extractedFragments = (extractFragments(print(docWithOperations)) || [])
|
|
1624
1753
|
// resolve all nested fragments
|
|
1625
|
-
.map(
|
|
1754
|
+
.map(fragmentName => resolveFragment(graph.getNodeData(fragmentName), graph))
|
|
1626
1755
|
// flatten arrays
|
|
1627
1756
|
.reduce((list, current) => list.concat(current), [])
|
|
1628
1757
|
// remove duplicates
|
|
1629
|
-
.filter((def, i, all) => all.findIndex(
|
|
1758
|
+
.filter((def, i, all) => all.findIndex(item => item.name.value === def.name.value) === i);
|
|
1630
1759
|
const merged = {
|
|
1631
1760
|
kind: Kind.DOCUMENT,
|
|
1632
1761
|
definitions: [...docWithOperations.definitions, ...extractedFragments],
|
|
1633
1762
|
};
|
|
1634
|
-
let transformedSchema = config.apollo
|
|
1635
|
-
? transformSchemaWithApollo(schema)
|
|
1636
|
-
: schema;
|
|
1763
|
+
let transformedSchema = config.apollo ? transformSchemaWithApollo(schema) : schema;
|
|
1637
1764
|
const transformedDoc = config.apollo
|
|
1638
1765
|
? transformDocumentWithApollo(merged, {
|
|
1639
1766
|
keepClientFields: config.keepClientFields,
|
|
@@ -1651,12 +1778,41 @@ function validate(schema, sources, options) {
|
|
|
1651
1778
|
errors.push(depthError);
|
|
1652
1779
|
}
|
|
1653
1780
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1781
|
+
if (config.maxAliasCount) {
|
|
1782
|
+
const aliasError = validateAliasCount({
|
|
1783
|
+
source: doc.source,
|
|
1784
|
+
doc: transformedDoc,
|
|
1785
|
+
maxAliasCount: config.maxAliasCount,
|
|
1786
|
+
fragmentGraph: graph,
|
|
1787
|
+
});
|
|
1788
|
+
if (aliasError) {
|
|
1789
|
+
errors.push(aliasError);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
if (config.maxDirectiveCount) {
|
|
1793
|
+
const directiveError = validateDirectiveCount({
|
|
1794
|
+
source: doc.source,
|
|
1795
|
+
doc: transformedDoc,
|
|
1796
|
+
maxDirectiveCount: config.maxDirectiveCount,
|
|
1797
|
+
fragmentGraph: graph,
|
|
1798
|
+
});
|
|
1799
|
+
if (directiveError) {
|
|
1800
|
+
errors.push(directiveError);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
if (config.maxTokenCount) {
|
|
1804
|
+
const tokenCountError = validateTokenCount({
|
|
1805
|
+
source: doc.source,
|
|
1806
|
+
document: transformedDoc,
|
|
1807
|
+
maxTokenCount: config.maxTokenCount,
|
|
1808
|
+
getReferencedFragmentSource: fragmentName => print(graph.getNodeData(fragmentName)),
|
|
1809
|
+
});
|
|
1810
|
+
if (tokenCountError) {
|
|
1811
|
+
errors.push(tokenCountError);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
const deprecated = config.strictDeprecated ? findDeprecatedUsages(transformedSchema, transformedDoc) : [];
|
|
1815
|
+
const duplicatedFragments = config.strictFragments ? findDuplicatedFragments(fragmentNames) : [];
|
|
1660
1816
|
if (sumLengths(errors, duplicatedFragments, deprecated) > 0) {
|
|
1661
1817
|
invalidDocuments.push({
|
|
1662
1818
|
source: doc.source,
|
|
@@ -1670,7 +1826,7 @@ function validate(schema, sources, options) {
|
|
|
1670
1826
|
function findDuplicatedFragments(fragmentNames) {
|
|
1671
1827
|
return fragmentNames
|
|
1672
1828
|
.filter((name, i, all) => all.indexOf(name) !== i)
|
|
1673
|
-
.map(
|
|
1829
|
+
.map(name => new GraphQLError(`Name of '${name}' fragment is not unique`));
|
|
1674
1830
|
}
|
|
1675
1831
|
//
|
|
1676
1832
|
// PostInfo -> AuthorInfo
|
|
@@ -1679,13 +1835,10 @@ function findDuplicatedFragments(fragmentNames) {
|
|
|
1679
1835
|
function resolveFragment(fragment, graph) {
|
|
1680
1836
|
return graph
|
|
1681
1837
|
.dependenciesOf(fragment.name.value)
|
|
1682
|
-
.reduce((list, current) => [
|
|
1683
|
-
...list,
|
|
1684
|
-
...resolveFragment(graph.getNodeData(current), graph),
|
|
1685
|
-
], [fragment]);
|
|
1838
|
+
.reduce((list, current) => [...list, ...resolveFragment(graph.getNodeData(current), graph)], [fragment]);
|
|
1686
1839
|
}
|
|
1687
1840
|
function extractFragments(document) {
|
|
1688
|
-
return (document.match(/[\.]{3}[a-z0-9\_]+\b/gi) || []).map(
|
|
1841
|
+
return (document.match(/[\.]{3}[a-z0-9\_]+\b/gi) || []).map(name => name.replace('...', ''));
|
|
1689
1842
|
}
|
|
1690
1843
|
function sumLengths(...arrays) {
|
|
1691
1844
|
return arrays.reduce((sum, { length }) => sum + length, 0);
|
|
@@ -1694,20 +1847,19 @@ function sumLengths(...arrays) {
|
|
|
1694
1847
|
function similar(schema, typeName, threshold = 0.4) {
|
|
1695
1848
|
const typeMap = schema.getTypeMap();
|
|
1696
1849
|
const targets = Object.keys(schema.getTypeMap())
|
|
1697
|
-
.filter(
|
|
1698
|
-
.map(
|
|
1850
|
+
.filter(name => !isPrimitive(name) && !isForIntrospection(name))
|
|
1851
|
+
.map(name => ({
|
|
1699
1852
|
typeId: name,
|
|
1700
1853
|
value: stripType(typeMap[name]),
|
|
1701
1854
|
}));
|
|
1702
1855
|
const results = {};
|
|
1703
|
-
if (typeof typeName !== 'undefined' &&
|
|
1704
|
-
!targets.some((t) => t.typeId === typeName)) {
|
|
1856
|
+
if (typeof typeName !== 'undefined' && !targets.some(t => t.typeId === typeName)) {
|
|
1705
1857
|
throw new Error(`Type '${typeName}' doesn't exist`);
|
|
1706
1858
|
}
|
|
1707
|
-
(typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(
|
|
1859
|
+
(typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(source => {
|
|
1708
1860
|
const sourceType = schema.getType(source.typeId);
|
|
1709
|
-
const matchWith = targets.filter(
|
|
1710
|
-
|
|
1861
|
+
const matchWith = targets.filter(target => schema.getType(target.typeId).astNode.kind === sourceType.astNode.kind &&
|
|
1862
|
+
target.typeId !== source.typeId);
|
|
1711
1863
|
if (matchWith.length > 0) {
|
|
1712
1864
|
const found = similarTo(sourceType, matchWith, threshold);
|
|
1713
1865
|
if (found) {
|
|
@@ -1718,7 +1870,7 @@ function similar(schema, typeName, threshold = 0.4) {
|
|
|
1718
1870
|
return results;
|
|
1719
1871
|
}
|
|
1720
1872
|
function similarTo(type, targets, threshold) {
|
|
1721
|
-
const types = targets.filter(
|
|
1873
|
+
const types = targets.filter(target => target.typeId !== type.name);
|
|
1722
1874
|
const result = findBestMatch(stripType(type), types);
|
|
1723
1875
|
if (result.bestMatch.rating < threshold) {
|
|
1724
1876
|
return;
|
|
@@ -1726,7 +1878,7 @@ function similarTo(type, targets, threshold) {
|
|
|
1726
1878
|
return {
|
|
1727
1879
|
bestMatch: result.bestMatch,
|
|
1728
1880
|
ratings: result.ratings
|
|
1729
|
-
.filter(
|
|
1881
|
+
.filter(r => r.rating >= threshold && r.target !== result.bestMatch.target)
|
|
1730
1882
|
.sort((a, b) => a.rating - b.rating)
|
|
1731
1883
|
.reverse(),
|
|
1732
1884
|
};
|
|
@@ -1738,7 +1890,7 @@ function stripType(type) {
|
|
|
1738
1890
|
.replace(/\}$/g, '')
|
|
1739
1891
|
.trim()
|
|
1740
1892
|
.split('\n')
|
|
1741
|
-
.map(
|
|
1893
|
+
.map(s => s.trim())
|
|
1742
1894
|
.sort((a, b) => a.localeCompare(b))
|
|
1743
1895
|
.join(' ');
|
|
1744
1896
|
}
|
|
@@ -1750,7 +1902,7 @@ function coverage(schema, sources) {
|
|
|
1750
1902
|
};
|
|
1751
1903
|
const typeMap = schema.getTypeMap();
|
|
1752
1904
|
const typeInfo = new TypeInfo(schema);
|
|
1753
|
-
const visitor =
|
|
1905
|
+
const visitor = source => ({
|
|
1754
1906
|
Field(node) {
|
|
1755
1907
|
const fieldDef = typeInfo.getFieldDef();
|
|
1756
1908
|
const parent = typeInfo.getParentType();
|
|
@@ -1768,20 +1920,14 @@ function coverage(schema, sources) {
|
|
|
1768
1920
|
typeCoverage.hits++;
|
|
1769
1921
|
fieldCoverage.hits++;
|
|
1770
1922
|
if (node.loc) {
|
|
1771
|
-
fieldCoverage.locations[sourceName] = [
|
|
1772
|
-
node.loc,
|
|
1773
|
-
...(locations || []),
|
|
1774
|
-
];
|
|
1923
|
+
fieldCoverage.locations[sourceName] = [node.loc, ...(locations || [])];
|
|
1775
1924
|
}
|
|
1776
1925
|
if (node.arguments) {
|
|
1777
1926
|
for (const argNode of node.arguments) {
|
|
1778
1927
|
const argCoverage = fieldCoverage.children[argNode.name.value];
|
|
1779
1928
|
argCoverage.hits++;
|
|
1780
1929
|
if (argNode.loc) {
|
|
1781
|
-
argCoverage.locations[sourceName] = [
|
|
1782
|
-
argNode.loc,
|
|
1783
|
-
...(argCoverage.locations[sourceName] || []),
|
|
1784
|
-
];
|
|
1930
|
+
argCoverage.locations[sourceName] = [argNode.loc, ...(argCoverage.locations[sourceName] || [])];
|
|
1785
1931
|
}
|
|
1786
1932
|
}
|
|
1787
1933
|
}
|
|
@@ -1819,14 +1965,31 @@ function coverage(schema, sources) {
|
|
|
1819
1965
|
const documents = coverage.sources.map(readDocument);
|
|
1820
1966
|
documents.forEach((doc, i) => {
|
|
1821
1967
|
const source = coverage.sources[i];
|
|
1822
|
-
doc.operations.forEach(
|
|
1968
|
+
doc.operations.forEach(op => {
|
|
1823
1969
|
visit(op.node, visitWithTypeInfo(typeInfo, visitor(source)));
|
|
1824
1970
|
});
|
|
1825
|
-
doc.fragments.forEach(
|
|
1971
|
+
doc.fragments.forEach(fr => {
|
|
1826
1972
|
visit(fr.node, visitWithTypeInfo(typeInfo, visitor(source)));
|
|
1827
1973
|
});
|
|
1828
1974
|
});
|
|
1829
1975
|
return coverage;
|
|
1830
1976
|
}
|
|
1831
1977
|
|
|
1832
|
-
|
|
1978
|
+
function calculateOperationComplexity(node, config, getFragmentByName, depth = 0) {
|
|
1979
|
+
let cost = config.scalarCost;
|
|
1980
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1981
|
+
cost = config.objectCost;
|
|
1982
|
+
for (let child of node.selectionSet.selections) {
|
|
1983
|
+
cost += config.depthCostFactor * calculateOperationComplexity(child, config, getFragmentByName, depth + 1);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
if (node.kind == Kind.FRAGMENT_SPREAD) {
|
|
1987
|
+
const fragment = getFragmentByName(node.name.value);
|
|
1988
|
+
if (fragment) {
|
|
1989
|
+
cost += config.depthCostFactor * calculateOperationComplexity(fragment, config, getFragmentByName, depth + 1);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
return cost;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
export { ChangeType, CriticalityLevel, DiffRule, calculateOperationComplexity, calculateTokenCount, countAliases, countDepth, countDirectives, coverage, diff, getTypePrefix, similar, validate };
|