@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
|
@@ -8,6 +8,7 @@ const tslib = require('tslib');
|
|
|
8
8
|
const graphql = require('graphql');
|
|
9
9
|
const inspect = _interopDefault(require('object-inspect'));
|
|
10
10
|
const dependencyGraph = require('dependency-graph');
|
|
11
|
+
const parser = require('graphql/language/parser');
|
|
11
12
|
|
|
12
13
|
function keyMap(list, keyFn) {
|
|
13
14
|
return list.reduce((map, item) => {
|
|
@@ -49,7 +50,7 @@ function isVoid(a) {
|
|
|
49
50
|
return typeof a === 'undefined' || a === null;
|
|
50
51
|
}
|
|
51
52
|
function diffArrays(a, b) {
|
|
52
|
-
return a.filter(
|
|
53
|
+
return a.filter(c => !b.some(d => isEqual(d, c)));
|
|
53
54
|
}
|
|
54
55
|
function compareLists(oldList, newList, callbacks) {
|
|
55
56
|
const oldMap = keyMap(oldList, ({ name }) => name);
|
|
@@ -100,7 +101,7 @@ function isDeprecated(fieldOrEnumValue) {
|
|
|
100
101
|
if (fieldOrEnumValue.deprecationReason != null) {
|
|
101
102
|
return true;
|
|
102
103
|
}
|
|
103
|
-
if ((_b = (_a = fieldOrEnumValue.astNode) === null || _a === void 0 ? void 0 : _a.directives) === null || _b === void 0 ? void 0 : _b.some(
|
|
104
|
+
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')) {
|
|
104
105
|
return true;
|
|
105
106
|
}
|
|
106
107
|
return false;
|
|
@@ -115,8 +116,7 @@ function safeChangeForField(oldType, newType) {
|
|
|
115
116
|
return safeChangeForField(ofType, newType.ofType);
|
|
116
117
|
}
|
|
117
118
|
if (graphql.isListType(oldType)) {
|
|
118
|
-
return ((graphql.isListType(newType) &&
|
|
119
|
-
safeChangeForField(oldType.ofType, newType.ofType)) ||
|
|
119
|
+
return ((graphql.isListType(newType) && safeChangeForField(oldType.ofType, newType.ofType)) ||
|
|
120
120
|
(graphql.isNonNullType(newType) && safeChangeForField(oldType, newType.ofType)));
|
|
121
121
|
}
|
|
122
122
|
return false;
|
|
@@ -129,7 +129,7 @@ function safeChangeForInputValue(oldType, newType) {
|
|
|
129
129
|
return safeChangeForInputValue(oldType.ofType, newType.ofType);
|
|
130
130
|
}
|
|
131
131
|
if (graphql.isNonNullType(oldType)) {
|
|
132
|
-
const ofType = graphql.isNonNullType(newType) ? newType : newType;
|
|
132
|
+
const ofType = graphql.isNonNullType(newType) ? newType.ofType : newType;
|
|
133
133
|
return safeChangeForInputValue(oldType.ofType, ofType);
|
|
134
134
|
}
|
|
135
135
|
return false;
|
|
@@ -151,7 +151,7 @@ function getTypePrefix(type) {
|
|
|
151
151
|
return kindsMap[kind.toString()];
|
|
152
152
|
}
|
|
153
153
|
function isPrimitive(type) {
|
|
154
|
-
return
|
|
154
|
+
return ['String', 'Int', 'Float', 'Boolean', 'ID'].indexOf(typeof type === 'string' ? type : type.name) !== -1;
|
|
155
155
|
}
|
|
156
156
|
function isForIntrospection(type) {
|
|
157
157
|
return ([
|
|
@@ -176,7 +176,9 @@ function findDeprecatedUsages(schema, ast) {
|
|
|
176
176
|
if (reason) {
|
|
177
177
|
const fieldDef = typeInfo.getFieldDef();
|
|
178
178
|
if (fieldDef) {
|
|
179
|
-
errors.push(new graphql.GraphQLError(`The argument '${argument === null || argument === void 0 ? void 0 : argument.name}' of '${fieldDef.name}' is deprecated. ${reason}`, [
|
|
179
|
+
errors.push(new graphql.GraphQLError(`The argument '${argument === null || argument === void 0 ? void 0 : argument.name}' of '${fieldDef.name}' is deprecated. ${reason}`, [
|
|
180
|
+
node,
|
|
181
|
+
]));
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
}
|
|
@@ -206,7 +208,7 @@ function findDeprecatedUsages(schema, ast) {
|
|
|
206
208
|
}
|
|
207
209
|
function removeFieldIfDirectives(node, directiveNames) {
|
|
208
210
|
if (node.directives) {
|
|
209
|
-
if (node.directives.some(
|
|
211
|
+
if (node.directives.some(d => directiveNames.indexOf(d.name.value) !== -1)) {
|
|
210
212
|
return null;
|
|
211
213
|
}
|
|
212
214
|
}
|
|
@@ -214,7 +216,7 @@ function removeFieldIfDirectives(node, directiveNames) {
|
|
|
214
216
|
}
|
|
215
217
|
function removeDirectives(node, directiveNames) {
|
|
216
218
|
if (node.directives) {
|
|
217
|
-
return Object.assign(Object.assign({}, node), { directives: node.directives.filter(
|
|
219
|
+
return Object.assign(Object.assign({}, node), { directives: node.directives.filter(d => directiveNames.indexOf(d.name.value) === -1) });
|
|
218
220
|
}
|
|
219
221
|
return node;
|
|
220
222
|
}
|
|
@@ -264,11 +266,7 @@ function getReachableTypes(schema) {
|
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
};
|
|
267
|
-
for (const type of [
|
|
268
|
-
schema.getQueryType(),
|
|
269
|
-
schema.getMutationType(),
|
|
270
|
-
schema.getSubscriptionType(),
|
|
271
|
-
]) {
|
|
269
|
+
for (const type of [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]) {
|
|
272
270
|
if (type) {
|
|
273
271
|
collect(type);
|
|
274
272
|
}
|
|
@@ -508,9 +506,7 @@ function directiveLocationRemoved(directive, location) {
|
|
|
508
506
|
function directiveArgumentAdded(directive, arg) {
|
|
509
507
|
return {
|
|
510
508
|
criticality: {
|
|
511
|
-
level: graphql.isNonNullType(arg.type)
|
|
512
|
-
? exports.CriticalityLevel.Breaking
|
|
513
|
-
: exports.CriticalityLevel.NonBreaking,
|
|
509
|
+
level: graphql.isNonNullType(arg.type) ? exports.CriticalityLevel.Breaking : exports.CriticalityLevel.NonBreaking,
|
|
514
510
|
},
|
|
515
511
|
type: exports.ChangeType.DirectiveArgumentAdded,
|
|
516
512
|
message: `Argument '${arg.name}' was added to directive '${directive.name}'`,
|
|
@@ -696,6 +692,70 @@ function changesInUnion(oldUnion, newUnion, addChange) {
|
|
|
696
692
|
});
|
|
697
693
|
}
|
|
698
694
|
|
|
695
|
+
function compareTwoStrings(str1, str2) {
|
|
696
|
+
if (!str1.length && !str2.length)
|
|
697
|
+
return 1;
|
|
698
|
+
if (!str1.length || !str2.length)
|
|
699
|
+
return 0;
|
|
700
|
+
if (str1.toUpperCase() === str2.toUpperCase())
|
|
701
|
+
return 1;
|
|
702
|
+
if (str1.length === 1 && str2.length === 1)
|
|
703
|
+
return 0;
|
|
704
|
+
const pairs1 = wordLetterPairs(str1);
|
|
705
|
+
const pairs2 = wordLetterPairs(str2);
|
|
706
|
+
const union = pairs1.length + pairs2.length;
|
|
707
|
+
let intersection = 0;
|
|
708
|
+
pairs1.forEach(pair1 => {
|
|
709
|
+
for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
|
|
710
|
+
if (pair1 !== pair2)
|
|
711
|
+
continue;
|
|
712
|
+
intersection++;
|
|
713
|
+
pairs2.splice(i, 1);
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
return (intersection * 2) / union;
|
|
718
|
+
}
|
|
719
|
+
function findBestMatch(mainString, targetStrings) {
|
|
720
|
+
if (!areArgsValid(mainString, targetStrings))
|
|
721
|
+
throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
|
|
722
|
+
const ratings = targetStrings.map(target => ({
|
|
723
|
+
target,
|
|
724
|
+
rating: compareTwoStrings(mainString, target.value),
|
|
725
|
+
}));
|
|
726
|
+
const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
|
|
727
|
+
return { ratings, bestMatch };
|
|
728
|
+
}
|
|
729
|
+
function flattenDeep(arr) {
|
|
730
|
+
return Array.isArray(arr) ? arr.reduce((a, b) => a.concat(flattenDeep(b)), []) : [arr];
|
|
731
|
+
}
|
|
732
|
+
function areArgsValid(mainString, targetStrings) {
|
|
733
|
+
if (typeof mainString !== 'string')
|
|
734
|
+
return false;
|
|
735
|
+
if (!Array.isArray(targetStrings))
|
|
736
|
+
return false;
|
|
737
|
+
if (!targetStrings.length)
|
|
738
|
+
return false;
|
|
739
|
+
if (targetStrings.find(s => typeof s.value !== 'string'))
|
|
740
|
+
return false;
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
function letterPairs(str) {
|
|
744
|
+
const pairs = [];
|
|
745
|
+
for (let i = 0, max = str.length - 1; i < max; i++)
|
|
746
|
+
pairs[i] = str.substring(i, i + 2);
|
|
747
|
+
return pairs;
|
|
748
|
+
}
|
|
749
|
+
function wordLetterPairs(str) {
|
|
750
|
+
const pairs = str.toUpperCase().split(' ').map(letterPairs);
|
|
751
|
+
return flattenDeep(pairs);
|
|
752
|
+
}
|
|
753
|
+
function safeString(obj) {
|
|
754
|
+
return inspect(obj)
|
|
755
|
+
.replace(/\[Object\: null prototype\] /g, '')
|
|
756
|
+
.replace(/(^')|('$)/g, '');
|
|
757
|
+
}
|
|
758
|
+
|
|
699
759
|
function inputFieldRemoved(input, field) {
|
|
700
760
|
return {
|
|
701
761
|
criticality: {
|
|
@@ -703,7 +763,7 @@ function inputFieldRemoved(input, field) {
|
|
|
703
763
|
reason: 'Removing an input field will cause existing queries that use this input field to error.',
|
|
704
764
|
},
|
|
705
765
|
type: exports.ChangeType.InputFieldRemoved,
|
|
706
|
-
message: `Input field '${field.name}' was removed from input object type '${input.name}'`,
|
|
766
|
+
message: `Input field '${field.name}' ${isDeprecated(field) ? '(deprecated) ' : ''}was removed from input object type '${input.name}'`,
|
|
707
767
|
path: [input.name, field.name].join('.'),
|
|
708
768
|
};
|
|
709
769
|
}
|
|
@@ -756,10 +816,10 @@ function inputFieldDefaultValueChanged(input, oldField, newField) {
|
|
|
756
816
|
return {
|
|
757
817
|
criticality: {
|
|
758
818
|
level: exports.CriticalityLevel.Dangerous,
|
|
759
|
-
reason: 'Changing the default value for an argument may change the runtime
|
|
819
|
+
reason: 'Changing the default value for an argument may change the runtime behavior of a field if it was never provided.',
|
|
760
820
|
},
|
|
761
821
|
type: exports.ChangeType.InputFieldDefaultValueChanged,
|
|
762
|
-
message: `Input field '${input.name}.${oldField.name}' default value changed from '${oldField.defaultValue}' to '${newField.defaultValue}'`,
|
|
822
|
+
message: `Input field '${input.name}.${oldField.name}' default value changed from '${safeString(oldField.defaultValue)}' to '${safeString(newField.defaultValue)}'`,
|
|
763
823
|
path: [input.name, oldField.name].join('.'),
|
|
764
824
|
};
|
|
765
825
|
}
|
|
@@ -808,14 +868,12 @@ function changesInInputField(input, oldField, newField, addChange) {
|
|
|
808
868
|
}
|
|
809
869
|
}
|
|
810
870
|
if (isNotEqual(oldField.defaultValue, newField.defaultValue)) {
|
|
811
|
-
if (Array.isArray(oldField.defaultValue) &&
|
|
812
|
-
Array.isArray(newField.defaultValue)) {
|
|
871
|
+
if (Array.isArray(oldField.defaultValue) && Array.isArray(newField.defaultValue)) {
|
|
813
872
|
if (diffArrays(oldField.defaultValue, newField.defaultValue).length > 0) {
|
|
814
873
|
addChange(inputFieldDefaultValueChanged(input, oldField, newField));
|
|
815
874
|
}
|
|
816
875
|
}
|
|
817
|
-
else if (JSON.stringify(oldField.defaultValue) !==
|
|
818
|
-
JSON.stringify(newField.defaultValue)) {
|
|
876
|
+
else if (JSON.stringify(oldField.defaultValue) !== JSON.stringify(newField.defaultValue)) {
|
|
819
877
|
addChange(inputFieldDefaultValueChanged(input, oldField, newField));
|
|
820
878
|
}
|
|
821
879
|
}
|
|
@@ -965,8 +1023,10 @@ function fieldTypeChanged(type, oldField, newField) {
|
|
|
965
1023
|
};
|
|
966
1024
|
}
|
|
967
1025
|
function fieldArgumentAdded(type, field, arg) {
|
|
1026
|
+
const isBreaking = graphql.isNonNullType(arg.type) && typeof arg.defaultValue === 'undefined';
|
|
1027
|
+
const defaultValueMsg = typeof arg.defaultValue !== 'undefined' ? ' (with default value) ' : ' ';
|
|
968
1028
|
return {
|
|
969
|
-
criticality:
|
|
1029
|
+
criticality: isBreaking
|
|
970
1030
|
? {
|
|
971
1031
|
level: exports.CriticalityLevel.Breaking,
|
|
972
1032
|
reason: `Adding a required argument to an existing field is a breaking change because it will cause existing uses of this field to error.`,
|
|
@@ -976,7 +1036,7 @@ function fieldArgumentAdded(type, field, arg) {
|
|
|
976
1036
|
reason: `Adding a new argument to an existing field may involve a change in resolve function logic that potentially may cause some side effects.`,
|
|
977
1037
|
},
|
|
978
1038
|
type: exports.ChangeType.FieldArgumentAdded,
|
|
979
|
-
message: `Argument '${arg.name}: ${arg.type}'
|
|
1039
|
+
message: `Argument '${arg.name}: ${arg.type}'${defaultValueMsg}added to field '${type.name}.${field.name}'`,
|
|
980
1040
|
path: [type.name, field.name, arg.name].join('.'),
|
|
981
1041
|
};
|
|
982
1042
|
}
|
|
@@ -992,70 +1052,6 @@ function fieldArgumentRemoved(type, field, arg) {
|
|
|
992
1052
|
};
|
|
993
1053
|
}
|
|
994
1054
|
|
|
995
|
-
function compareTwoStrings(str1, str2) {
|
|
996
|
-
if (!str1.length && !str2.length)
|
|
997
|
-
return 1;
|
|
998
|
-
if (!str1.length || !str2.length)
|
|
999
|
-
return 0;
|
|
1000
|
-
if (str1.toUpperCase() === str2.toUpperCase())
|
|
1001
|
-
return 1;
|
|
1002
|
-
if (str1.length === 1 && str2.length === 1)
|
|
1003
|
-
return 0;
|
|
1004
|
-
const pairs1 = wordLetterPairs(str1);
|
|
1005
|
-
const pairs2 = wordLetterPairs(str2);
|
|
1006
|
-
const union = pairs1.length + pairs2.length;
|
|
1007
|
-
let intersection = 0;
|
|
1008
|
-
pairs1.forEach((pair1) => {
|
|
1009
|
-
for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
|
|
1010
|
-
if (pair1 !== pair2)
|
|
1011
|
-
continue;
|
|
1012
|
-
intersection++;
|
|
1013
|
-
pairs2.splice(i, 1);
|
|
1014
|
-
break;
|
|
1015
|
-
}
|
|
1016
|
-
});
|
|
1017
|
-
return (intersection * 2) / union;
|
|
1018
|
-
}
|
|
1019
|
-
function findBestMatch(mainString, targetStrings) {
|
|
1020
|
-
if (!areArgsValid(mainString, targetStrings))
|
|
1021
|
-
throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
|
|
1022
|
-
const ratings = targetStrings.map((target) => ({
|
|
1023
|
-
target,
|
|
1024
|
-
rating: compareTwoStrings(mainString, target.value),
|
|
1025
|
-
}));
|
|
1026
|
-
const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
|
|
1027
|
-
return { ratings, bestMatch };
|
|
1028
|
-
}
|
|
1029
|
-
function flattenDeep(arr) {
|
|
1030
|
-
return Array.isArray(arr)
|
|
1031
|
-
? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
|
|
1032
|
-
: [arr];
|
|
1033
|
-
}
|
|
1034
|
-
function areArgsValid(mainString, targetStrings) {
|
|
1035
|
-
if (typeof mainString !== 'string')
|
|
1036
|
-
return false;
|
|
1037
|
-
if (!Array.isArray(targetStrings))
|
|
1038
|
-
return false;
|
|
1039
|
-
if (!targetStrings.length)
|
|
1040
|
-
return false;
|
|
1041
|
-
if (targetStrings.find((s) => typeof s.value !== 'string'))
|
|
1042
|
-
return false;
|
|
1043
|
-
return true;
|
|
1044
|
-
}
|
|
1045
|
-
function letterPairs(str) {
|
|
1046
|
-
const pairs = [];
|
|
1047
|
-
for (let i = 0, max = str.length - 1; i < max; i++)
|
|
1048
|
-
pairs[i] = str.substring(i, i + 2);
|
|
1049
|
-
return pairs;
|
|
1050
|
-
}
|
|
1051
|
-
function wordLetterPairs(str) {
|
|
1052
|
-
const pairs = str.toUpperCase().split(' ').map(letterPairs);
|
|
1053
|
-
return flattenDeep(pairs);
|
|
1054
|
-
}
|
|
1055
|
-
function safeString(obj) {
|
|
1056
|
-
return inspect(obj).replace(/\[Object\: null prototype\] /g, '');
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
1055
|
function fieldArgumentDescriptionChanged(type, field, oldArg, newArg) {
|
|
1060
1056
|
return {
|
|
1061
1057
|
criticality: {
|
|
@@ -1101,15 +1097,13 @@ function changesInArgument(type, field, oldArg, newArg, addChange) {
|
|
|
1101
1097
|
addChange(fieldArgumentDescriptionChanged(type, field, oldArg, newArg));
|
|
1102
1098
|
}
|
|
1103
1099
|
if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) {
|
|
1104
|
-
if (Array.isArray(oldArg.defaultValue) &&
|
|
1105
|
-
Array.isArray(newArg.defaultValue)) {
|
|
1100
|
+
if (Array.isArray(oldArg.defaultValue) && Array.isArray(newArg.defaultValue)) {
|
|
1106
1101
|
const diff = diffArrays(oldArg.defaultValue, newArg.defaultValue);
|
|
1107
1102
|
if (diff.length > 0) {
|
|
1108
1103
|
addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
|
|
1109
1104
|
}
|
|
1110
1105
|
}
|
|
1111
|
-
else if (JSON.stringify(oldArg.defaultValue) !==
|
|
1112
|
-
JSON.stringify(newArg.defaultValue)) {
|
|
1106
|
+
else if (JSON.stringify(oldArg.defaultValue) !== JSON.stringify(newArg.defaultValue)) {
|
|
1113
1107
|
addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
|
|
1114
1108
|
}
|
|
1115
1109
|
}
|
|
@@ -1214,9 +1208,9 @@ function changesInDirective(oldDirective, newDirective, addChange) {
|
|
|
1214
1208
|
removed: diffArrays(oldDirective.locations, newDirective.locations),
|
|
1215
1209
|
};
|
|
1216
1210
|
// locations added
|
|
1217
|
-
locations.added.forEach(
|
|
1211
|
+
locations.added.forEach(location => addChange(directiveLocationAdded(newDirective, location)));
|
|
1218
1212
|
// locations removed
|
|
1219
|
-
locations.removed.forEach(
|
|
1213
|
+
locations.removed.forEach(location => addChange(directiveLocationRemoved(oldDirective, location)));
|
|
1220
1214
|
compareLists(oldDirective.args, newDirective.args, {
|
|
1221
1215
|
onAdded(arg) {
|
|
1222
1216
|
addChange(directiveArgumentAdded(newDirective, arg));
|
|
@@ -1247,7 +1241,7 @@ function diffSchema(oldSchema, newSchema) {
|
|
|
1247
1241
|
changes.push(change);
|
|
1248
1242
|
}
|
|
1249
1243
|
changesInSchema(oldSchema, newSchema, addChange);
|
|
1250
|
-
compareLists(Object.values(oldSchema.getTypeMap()).filter(
|
|
1244
|
+
compareLists(Object.values(oldSchema.getTypeMap()).filter(t => !isPrimitive(t)), Object.values(newSchema.getTypeMap()).filter(t => !isPrimitive(t)), {
|
|
1251
1245
|
onAdded(type) {
|
|
1252
1246
|
addChange(typeAdded(type));
|
|
1253
1247
|
},
|
|
@@ -1332,7 +1326,7 @@ function changesInType(oldType, newType, addChange) {
|
|
|
1332
1326
|
}
|
|
1333
1327
|
|
|
1334
1328
|
const dangerousBreaking = ({ changes }) => {
|
|
1335
|
-
return changes.map(
|
|
1329
|
+
return changes.map(change => {
|
|
1336
1330
|
if (change.criticality.level === exports.CriticalityLevel.Dangerous) {
|
|
1337
1331
|
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Breaking }) });
|
|
1338
1332
|
}
|
|
@@ -1344,8 +1338,8 @@ function parsePath(path) {
|
|
|
1344
1338
|
return path.split('.');
|
|
1345
1339
|
}
|
|
1346
1340
|
|
|
1347
|
-
const suppressRemovalOfDeprecatedField = ({ changes, oldSchema
|
|
1348
|
-
return changes.map(
|
|
1341
|
+
const suppressRemovalOfDeprecatedField = ({ changes, oldSchema }) => {
|
|
1342
|
+
return changes.map(change => {
|
|
1349
1343
|
if (change.type === exports.ChangeType.FieldRemoved &&
|
|
1350
1344
|
change.criticality.level === exports.CriticalityLevel.Breaking &&
|
|
1351
1345
|
change.path) {
|
|
@@ -1370,6 +1364,18 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
|
|
|
1370
1364
|
}
|
|
1371
1365
|
}
|
|
1372
1366
|
}
|
|
1367
|
+
if (change.type === exports.ChangeType.InputFieldRemoved &&
|
|
1368
|
+
change.criticality.level === exports.CriticalityLevel.Breaking &&
|
|
1369
|
+
change.path) {
|
|
1370
|
+
const [inputName, inputItem] = parsePath(change.path);
|
|
1371
|
+
const type = oldSchema.getType(inputName);
|
|
1372
|
+
if (graphql.isInputObjectType(type)) {
|
|
1373
|
+
const item = type.getFields()[inputItem];
|
|
1374
|
+
if (item && isDeprecated(item)) {
|
|
1375
|
+
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Dangerous }) });
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1373
1379
|
return change;
|
|
1374
1380
|
});
|
|
1375
1381
|
};
|
|
@@ -1388,15 +1394,15 @@ const descriptionChangeTypes = [
|
|
|
1388
1394
|
exports.ChangeType.TypeDescriptionChanged,
|
|
1389
1395
|
];
|
|
1390
1396
|
const ignoreDescriptionChanges = ({ changes }) => {
|
|
1391
|
-
return changes.filter(
|
|
1397
|
+
return changes.filter(change => descriptionChangeTypes.indexOf(change.type) === -1);
|
|
1392
1398
|
};
|
|
1393
1399
|
|
|
1394
|
-
const considerUsage = ({ changes, config
|
|
1400
|
+
const considerUsage = ({ changes, config }) => tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
1395
1401
|
if (!config) {
|
|
1396
1402
|
throw new Error(`considerUsage rule is missing config`);
|
|
1397
1403
|
}
|
|
1398
1404
|
const collectedBreakingField = [];
|
|
1399
|
-
changes.forEach(
|
|
1405
|
+
changes.forEach(change => {
|
|
1400
1406
|
if (change.criticality.level === exports.CriticalityLevel.Breaking && change.path) {
|
|
1401
1407
|
const [typeName, fieldName, argumentName] = parsePath(change.path);
|
|
1402
1408
|
collectedBreakingField.push({
|
|
@@ -1413,11 +1419,11 @@ const considerUsage = ({ changes, config, }) => tslib.__awaiter(void 0, void 0,
|
|
|
1413
1419
|
const suppressedPaths = collectedBreakingField
|
|
1414
1420
|
.filter((_, i) => usageList[i] === true)
|
|
1415
1421
|
.map(({ type, field, argument }) => [type, field, argument].filter(Boolean).join('.'));
|
|
1416
|
-
return changes.map(
|
|
1422
|
+
return changes.map(change => {
|
|
1417
1423
|
// Turns those "safe to break" changes into "dangerous"
|
|
1418
1424
|
if (change.criticality.level === exports.CriticalityLevel.Breaking &&
|
|
1419
1425
|
change.path &&
|
|
1420
|
-
suppressedPaths.some(
|
|
1426
|
+
suppressedPaths.some(p => change.path.startsWith(p))) {
|
|
1421
1427
|
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Dangerous }), message: `${change.message} (non-breaking based on usage)` });
|
|
1422
1428
|
}
|
|
1423
1429
|
return change;
|
|
@@ -1426,7 +1432,7 @@ const considerUsage = ({ changes, config, }) => tslib.__awaiter(void 0, void 0,
|
|
|
1426
1432
|
|
|
1427
1433
|
const safeUnreachable = ({ changes, oldSchema }) => {
|
|
1428
1434
|
const reachable = getReachableTypes(oldSchema);
|
|
1429
|
-
return changes.map(
|
|
1435
|
+
return changes.map(change => {
|
|
1430
1436
|
if (change.criticality.level === exports.CriticalityLevel.Breaking && change.path) {
|
|
1431
1437
|
const [typeName] = parsePath(change.path);
|
|
1432
1438
|
if (!reachable.has(typeName)) {
|
|
@@ -1533,7 +1539,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1533
1539
|
return 1 + maxInnerDepth;
|
|
1534
1540
|
}
|
|
1535
1541
|
case graphql.Kind.SELECTION_SET: {
|
|
1536
|
-
return Math.max(...node.selections.map(
|
|
1542
|
+
return Math.max(...node.selections.map(selection => {
|
|
1537
1543
|
return calculateDepth({
|
|
1538
1544
|
node: selection,
|
|
1539
1545
|
currentDepth: currentDepth,
|
|
@@ -1543,7 +1549,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1543
1549
|
}));
|
|
1544
1550
|
}
|
|
1545
1551
|
case graphql.Kind.DOCUMENT: {
|
|
1546
|
-
return Math.max(...node.definitions.map(
|
|
1552
|
+
return Math.max(...node.definitions.map(def => {
|
|
1547
1553
|
return calculateDepth({
|
|
1548
1554
|
node: def,
|
|
1549
1555
|
currentDepth: currentDepth,
|
|
@@ -1555,7 +1561,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1555
1561
|
case graphql.Kind.OPERATION_DEFINITION:
|
|
1556
1562
|
case graphql.Kind.INLINE_FRAGMENT:
|
|
1557
1563
|
case graphql.Kind.FRAGMENT_DEFINITION: {
|
|
1558
|
-
return Math.max(...node.selectionSet.selections.map(
|
|
1564
|
+
return Math.max(...node.selectionSet.selections.map(selection => {
|
|
1559
1565
|
return calculateDepth({
|
|
1560
1566
|
node: selection,
|
|
1561
1567
|
currentDepth,
|
|
@@ -1576,13 +1582,26 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
|
|
|
1576
1582
|
}
|
|
1577
1583
|
}
|
|
1578
1584
|
}
|
|
1585
|
+
function countDepth(node, parentDepth, getFragmentReference) {
|
|
1586
|
+
let depth = parentDepth;
|
|
1587
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1588
|
+
for (let child of node.selectionSet.selections) {
|
|
1589
|
+
depth = Math.max(depth, countDepth(child, parentDepth + 1, getFragmentReference));
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (node.kind == graphql.Kind.FRAGMENT_SPREAD) {
|
|
1593
|
+
const fragment = getFragmentReference(node.name.value);
|
|
1594
|
+
if (fragment) {
|
|
1595
|
+
depth = Math.max(depth, countDepth(fragment, parentDepth + 1, getFragmentReference));
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return depth;
|
|
1599
|
+
}
|
|
1579
1600
|
|
|
1580
1601
|
function transformDocumentWithApollo(doc, { keepClientFields }) {
|
|
1581
1602
|
return graphql.visit(doc, {
|
|
1582
1603
|
Field(node) {
|
|
1583
|
-
return keepClientFields
|
|
1584
|
-
? removeDirectives(node, ['client'])
|
|
1585
|
-
: removeFieldIfDirectives(node, ['client']);
|
|
1604
|
+
return keepClientFields ? removeDirectives(node, ['client']) : removeFieldIfDirectives(node, ['client']);
|
|
1586
1605
|
},
|
|
1587
1606
|
});
|
|
1588
1607
|
}
|
|
@@ -1592,6 +1611,116 @@ function transformSchemaWithApollo(schema) {
|
|
|
1592
1611
|
`));
|
|
1593
1612
|
}
|
|
1594
1613
|
|
|
1614
|
+
function validateAliasCount({ source, doc, maxAliasCount, fragmentGraph, }) {
|
|
1615
|
+
const getFragmentByFragmentName = (fragmentName) => fragmentGraph.getNodeData(fragmentName);
|
|
1616
|
+
for (const definition of doc.definitions) {
|
|
1617
|
+
if (definition.kind !== graphql.Kind.OPERATION_DEFINITION) {
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
const aliasCount = countAliases(definition, getFragmentByFragmentName);
|
|
1621
|
+
if (aliasCount > maxAliasCount) {
|
|
1622
|
+
return new graphql.GraphQLError(`Too many aliases (${aliasCount}). Maximum allowed is ${maxAliasCount}`, [definition], source, definition.loc && definition.loc.start ? [definition.loc.start] : undefined);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function countAliases(node, getFragmentByName) {
|
|
1627
|
+
let aliases = 0;
|
|
1628
|
+
if ('alias' in node && node.alias) {
|
|
1629
|
+
++aliases;
|
|
1630
|
+
}
|
|
1631
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1632
|
+
for (let child of node.selectionSet.selections) {
|
|
1633
|
+
aliases += countAliases(child, getFragmentByName);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
else if (node.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
|
1637
|
+
const fragmentNode = getFragmentByName(node.name.value);
|
|
1638
|
+
if (fragmentNode) {
|
|
1639
|
+
aliases += countAliases(fragmentNode, getFragmentByName);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
return aliases;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function validateDirectiveCount({ source, doc, maxDirectiveCount, fragmentGraph, }) {
|
|
1646
|
+
const getFragmentByFragmentName = (fragmentName) => fragmentGraph.getNodeData(fragmentName);
|
|
1647
|
+
for (const definition of doc.definitions) {
|
|
1648
|
+
if (definition.kind !== graphql.Kind.OPERATION_DEFINITION) {
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
const directiveCount = countDirectives(definition, getFragmentByFragmentName);
|
|
1652
|
+
if (directiveCount > maxDirectiveCount) {
|
|
1653
|
+
return new graphql.GraphQLError(`Too many directives (${directiveCount}). Maximum allowed is ${maxDirectiveCount}`, [definition], source, definition.loc && definition.loc.start ? [definition.loc.start] : undefined);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function countDirectives(node, getFragmentByName) {
|
|
1658
|
+
let directives = 0;
|
|
1659
|
+
if (node.directives) {
|
|
1660
|
+
directives += node.directives.length;
|
|
1661
|
+
}
|
|
1662
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1663
|
+
for (let child of node.selectionSet.selections) {
|
|
1664
|
+
directives += countDirectives(child, getFragmentByName);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if (node.kind == graphql.Kind.FRAGMENT_SPREAD) {
|
|
1668
|
+
const fragment = getFragmentByName(node.name.value);
|
|
1669
|
+
if (fragment) {
|
|
1670
|
+
directives += countDirectives(fragment, getFragmentByName);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
return directives;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
class ParserWithLexer extends parser.Parser {
|
|
1677
|
+
constructor(source, options) {
|
|
1678
|
+
super(source, options);
|
|
1679
|
+
this.__tokenCount = 0;
|
|
1680
|
+
const lexer = this._lexer;
|
|
1681
|
+
this._lexer = new Proxy(lexer, {
|
|
1682
|
+
get: (target, prop, receiver) => {
|
|
1683
|
+
if (prop === 'advance') {
|
|
1684
|
+
return () => {
|
|
1685
|
+
const token = target.advance();
|
|
1686
|
+
if (token.kind !== graphql.TokenKind.EOF) {
|
|
1687
|
+
this.__tokenCount++;
|
|
1688
|
+
}
|
|
1689
|
+
return token;
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
return Reflect.get(target, prop, receiver);
|
|
1693
|
+
},
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
get tokenCount() {
|
|
1697
|
+
return this.__tokenCount;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
function calculateTokenCount(args) {
|
|
1701
|
+
const parser = new ParserWithLexer(args.source);
|
|
1702
|
+
const document = parser.parseDocument();
|
|
1703
|
+
let { tokenCount } = parser;
|
|
1704
|
+
graphql.visit(document, {
|
|
1705
|
+
FragmentSpread(node) {
|
|
1706
|
+
const fragmentSource = args.getReferencedFragmentSource(node.name.value);
|
|
1707
|
+
if (fragmentSource) {
|
|
1708
|
+
tokenCount += calculateTokenCount({
|
|
1709
|
+
source: fragmentSource,
|
|
1710
|
+
getReferencedFragmentSource: args.getReferencedFragmentSource,
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
},
|
|
1714
|
+
});
|
|
1715
|
+
return tokenCount;
|
|
1716
|
+
}
|
|
1717
|
+
function validateTokenCount(args) {
|
|
1718
|
+
const tokenCount = calculateTokenCount(args);
|
|
1719
|
+
if (tokenCount > args.maxTokenCount) {
|
|
1720
|
+
return new graphql.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);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1595
1724
|
function validate(schema, sources, options) {
|
|
1596
1725
|
const config = Object.assign({ strictDeprecated: true, strictFragments: true, keepClientFields: false, apollo: false }, options);
|
|
1597
1726
|
const invalidDocuments = [];
|
|
@@ -1601,43 +1730,41 @@ function validate(schema, sources, options) {
|
|
|
1601
1730
|
const fragments = [];
|
|
1602
1731
|
const fragmentNames = [];
|
|
1603
1732
|
const graph = new dependencyGraph.DepGraph({ circular: true });
|
|
1604
|
-
documents.forEach(
|
|
1605
|
-
doc.fragments.forEach(
|
|
1733
|
+
documents.forEach(doc => {
|
|
1734
|
+
doc.fragments.forEach(fragment => {
|
|
1606
1735
|
fragmentNames.push(fragment.node.name.value);
|
|
1607
1736
|
fragments.push(fragment);
|
|
1608
1737
|
graph.addNode(fragment.node.name.value, fragment.node);
|
|
1609
1738
|
});
|
|
1610
1739
|
});
|
|
1611
|
-
fragments.forEach(
|
|
1740
|
+
fragments.forEach(fragment => {
|
|
1612
1741
|
const depends = extractFragments(graphql.print(fragment.node));
|
|
1613
1742
|
if (depends) {
|
|
1614
|
-
depends.forEach(
|
|
1743
|
+
depends.forEach(name => {
|
|
1615
1744
|
graph.addDependency(fragment.node.name.value, name);
|
|
1616
1745
|
});
|
|
1617
1746
|
}
|
|
1618
1747
|
});
|
|
1619
1748
|
documents
|
|
1620
1749
|
// since we include fragments, validate only operations
|
|
1621
|
-
.filter(
|
|
1622
|
-
.forEach(
|
|
1750
|
+
.filter(doc => doc.hasOperations)
|
|
1751
|
+
.forEach(doc => {
|
|
1623
1752
|
const docWithOperations = {
|
|
1624
1753
|
kind: graphql.Kind.DOCUMENT,
|
|
1625
|
-
definitions: doc.operations.map(
|
|
1754
|
+
definitions: doc.operations.map(d => d.node),
|
|
1626
1755
|
};
|
|
1627
1756
|
const extractedFragments = (extractFragments(graphql.print(docWithOperations)) || [])
|
|
1628
1757
|
// resolve all nested fragments
|
|
1629
|
-
.map(
|
|
1758
|
+
.map(fragmentName => resolveFragment(graph.getNodeData(fragmentName), graph))
|
|
1630
1759
|
// flatten arrays
|
|
1631
1760
|
.reduce((list, current) => list.concat(current), [])
|
|
1632
1761
|
// remove duplicates
|
|
1633
|
-
.filter((def, i, all) => all.findIndex(
|
|
1762
|
+
.filter((def, i, all) => all.findIndex(item => item.name.value === def.name.value) === i);
|
|
1634
1763
|
const merged = {
|
|
1635
1764
|
kind: graphql.Kind.DOCUMENT,
|
|
1636
1765
|
definitions: [...docWithOperations.definitions, ...extractedFragments],
|
|
1637
1766
|
};
|
|
1638
|
-
let transformedSchema = config.apollo
|
|
1639
|
-
? transformSchemaWithApollo(schema)
|
|
1640
|
-
: schema;
|
|
1767
|
+
let transformedSchema = config.apollo ? transformSchemaWithApollo(schema) : schema;
|
|
1641
1768
|
const transformedDoc = config.apollo
|
|
1642
1769
|
? transformDocumentWithApollo(merged, {
|
|
1643
1770
|
keepClientFields: config.keepClientFields,
|
|
@@ -1655,12 +1782,41 @@ function validate(schema, sources, options) {
|
|
|
1655
1782
|
errors.push(depthError);
|
|
1656
1783
|
}
|
|
1657
1784
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1785
|
+
if (config.maxAliasCount) {
|
|
1786
|
+
const aliasError = validateAliasCount({
|
|
1787
|
+
source: doc.source,
|
|
1788
|
+
doc: transformedDoc,
|
|
1789
|
+
maxAliasCount: config.maxAliasCount,
|
|
1790
|
+
fragmentGraph: graph,
|
|
1791
|
+
});
|
|
1792
|
+
if (aliasError) {
|
|
1793
|
+
errors.push(aliasError);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
if (config.maxDirectiveCount) {
|
|
1797
|
+
const directiveError = validateDirectiveCount({
|
|
1798
|
+
source: doc.source,
|
|
1799
|
+
doc: transformedDoc,
|
|
1800
|
+
maxDirectiveCount: config.maxDirectiveCount,
|
|
1801
|
+
fragmentGraph: graph,
|
|
1802
|
+
});
|
|
1803
|
+
if (directiveError) {
|
|
1804
|
+
errors.push(directiveError);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
if (config.maxTokenCount) {
|
|
1808
|
+
const tokenCountError = validateTokenCount({
|
|
1809
|
+
source: doc.source,
|
|
1810
|
+
document: transformedDoc,
|
|
1811
|
+
maxTokenCount: config.maxTokenCount,
|
|
1812
|
+
getReferencedFragmentSource: fragmentName => graphql.print(graph.getNodeData(fragmentName)),
|
|
1813
|
+
});
|
|
1814
|
+
if (tokenCountError) {
|
|
1815
|
+
errors.push(tokenCountError);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
const deprecated = config.strictDeprecated ? findDeprecatedUsages(transformedSchema, transformedDoc) : [];
|
|
1819
|
+
const duplicatedFragments = config.strictFragments ? findDuplicatedFragments(fragmentNames) : [];
|
|
1664
1820
|
if (sumLengths(errors, duplicatedFragments, deprecated) > 0) {
|
|
1665
1821
|
invalidDocuments.push({
|
|
1666
1822
|
source: doc.source,
|
|
@@ -1674,7 +1830,7 @@ function validate(schema, sources, options) {
|
|
|
1674
1830
|
function findDuplicatedFragments(fragmentNames) {
|
|
1675
1831
|
return fragmentNames
|
|
1676
1832
|
.filter((name, i, all) => all.indexOf(name) !== i)
|
|
1677
|
-
.map(
|
|
1833
|
+
.map(name => new graphql.GraphQLError(`Name of '${name}' fragment is not unique`));
|
|
1678
1834
|
}
|
|
1679
1835
|
//
|
|
1680
1836
|
// PostInfo -> AuthorInfo
|
|
@@ -1683,13 +1839,10 @@ function findDuplicatedFragments(fragmentNames) {
|
|
|
1683
1839
|
function resolveFragment(fragment, graph) {
|
|
1684
1840
|
return graph
|
|
1685
1841
|
.dependenciesOf(fragment.name.value)
|
|
1686
|
-
.reduce((list, current) => [
|
|
1687
|
-
...list,
|
|
1688
|
-
...resolveFragment(graph.getNodeData(current), graph),
|
|
1689
|
-
], [fragment]);
|
|
1842
|
+
.reduce((list, current) => [...list, ...resolveFragment(graph.getNodeData(current), graph)], [fragment]);
|
|
1690
1843
|
}
|
|
1691
1844
|
function extractFragments(document) {
|
|
1692
|
-
return (document.match(/[\.]{3}[a-z0-9\_]+\b/gi) || []).map(
|
|
1845
|
+
return (document.match(/[\.]{3}[a-z0-9\_]+\b/gi) || []).map(name => name.replace('...', ''));
|
|
1693
1846
|
}
|
|
1694
1847
|
function sumLengths(...arrays) {
|
|
1695
1848
|
return arrays.reduce((sum, { length }) => sum + length, 0);
|
|
@@ -1698,20 +1851,19 @@ function sumLengths(...arrays) {
|
|
|
1698
1851
|
function similar(schema, typeName, threshold = 0.4) {
|
|
1699
1852
|
const typeMap = schema.getTypeMap();
|
|
1700
1853
|
const targets = Object.keys(schema.getTypeMap())
|
|
1701
|
-
.filter(
|
|
1702
|
-
.map(
|
|
1854
|
+
.filter(name => !isPrimitive(name) && !isForIntrospection(name))
|
|
1855
|
+
.map(name => ({
|
|
1703
1856
|
typeId: name,
|
|
1704
1857
|
value: stripType(typeMap[name]),
|
|
1705
1858
|
}));
|
|
1706
1859
|
const results = {};
|
|
1707
|
-
if (typeof typeName !== 'undefined' &&
|
|
1708
|
-
!targets.some((t) => t.typeId === typeName)) {
|
|
1860
|
+
if (typeof typeName !== 'undefined' && !targets.some(t => t.typeId === typeName)) {
|
|
1709
1861
|
throw new Error(`Type '${typeName}' doesn't exist`);
|
|
1710
1862
|
}
|
|
1711
|
-
(typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(
|
|
1863
|
+
(typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(source => {
|
|
1712
1864
|
const sourceType = schema.getType(source.typeId);
|
|
1713
|
-
const matchWith = targets.filter(
|
|
1714
|
-
|
|
1865
|
+
const matchWith = targets.filter(target => schema.getType(target.typeId).astNode.kind === sourceType.astNode.kind &&
|
|
1866
|
+
target.typeId !== source.typeId);
|
|
1715
1867
|
if (matchWith.length > 0) {
|
|
1716
1868
|
const found = similarTo(sourceType, matchWith, threshold);
|
|
1717
1869
|
if (found) {
|
|
@@ -1722,7 +1874,7 @@ function similar(schema, typeName, threshold = 0.4) {
|
|
|
1722
1874
|
return results;
|
|
1723
1875
|
}
|
|
1724
1876
|
function similarTo(type, targets, threshold) {
|
|
1725
|
-
const types = targets.filter(
|
|
1877
|
+
const types = targets.filter(target => target.typeId !== type.name);
|
|
1726
1878
|
const result = findBestMatch(stripType(type), types);
|
|
1727
1879
|
if (result.bestMatch.rating < threshold) {
|
|
1728
1880
|
return;
|
|
@@ -1730,7 +1882,7 @@ function similarTo(type, targets, threshold) {
|
|
|
1730
1882
|
return {
|
|
1731
1883
|
bestMatch: result.bestMatch,
|
|
1732
1884
|
ratings: result.ratings
|
|
1733
|
-
.filter(
|
|
1885
|
+
.filter(r => r.rating >= threshold && r.target !== result.bestMatch.target)
|
|
1734
1886
|
.sort((a, b) => a.rating - b.rating)
|
|
1735
1887
|
.reverse(),
|
|
1736
1888
|
};
|
|
@@ -1742,7 +1894,7 @@ function stripType(type) {
|
|
|
1742
1894
|
.replace(/\}$/g, '')
|
|
1743
1895
|
.trim()
|
|
1744
1896
|
.split('\n')
|
|
1745
|
-
.map(
|
|
1897
|
+
.map(s => s.trim())
|
|
1746
1898
|
.sort((a, b) => a.localeCompare(b))
|
|
1747
1899
|
.join(' ');
|
|
1748
1900
|
}
|
|
@@ -1754,7 +1906,7 @@ function coverage(schema, sources) {
|
|
|
1754
1906
|
};
|
|
1755
1907
|
const typeMap = schema.getTypeMap();
|
|
1756
1908
|
const typeInfo = new graphql.TypeInfo(schema);
|
|
1757
|
-
const visitor =
|
|
1909
|
+
const visitor = source => ({
|
|
1758
1910
|
Field(node) {
|
|
1759
1911
|
const fieldDef = typeInfo.getFieldDef();
|
|
1760
1912
|
const parent = typeInfo.getParentType();
|
|
@@ -1772,20 +1924,14 @@ function coverage(schema, sources) {
|
|
|
1772
1924
|
typeCoverage.hits++;
|
|
1773
1925
|
fieldCoverage.hits++;
|
|
1774
1926
|
if (node.loc) {
|
|
1775
|
-
fieldCoverage.locations[sourceName] = [
|
|
1776
|
-
node.loc,
|
|
1777
|
-
...(locations || []),
|
|
1778
|
-
];
|
|
1927
|
+
fieldCoverage.locations[sourceName] = [node.loc, ...(locations || [])];
|
|
1779
1928
|
}
|
|
1780
1929
|
if (node.arguments) {
|
|
1781
1930
|
for (const argNode of node.arguments) {
|
|
1782
1931
|
const argCoverage = fieldCoverage.children[argNode.name.value];
|
|
1783
1932
|
argCoverage.hits++;
|
|
1784
1933
|
if (argNode.loc) {
|
|
1785
|
-
argCoverage.locations[sourceName] = [
|
|
1786
|
-
argNode.loc,
|
|
1787
|
-
...(argCoverage.locations[sourceName] || []),
|
|
1788
|
-
];
|
|
1934
|
+
argCoverage.locations[sourceName] = [argNode.loc, ...(argCoverage.locations[sourceName] || [])];
|
|
1789
1935
|
}
|
|
1790
1936
|
}
|
|
1791
1937
|
}
|
|
@@ -1823,17 +1969,39 @@ function coverage(schema, sources) {
|
|
|
1823
1969
|
const documents = coverage.sources.map(readDocument);
|
|
1824
1970
|
documents.forEach((doc, i) => {
|
|
1825
1971
|
const source = coverage.sources[i];
|
|
1826
|
-
doc.operations.forEach(
|
|
1972
|
+
doc.operations.forEach(op => {
|
|
1827
1973
|
graphql.visit(op.node, graphql.visitWithTypeInfo(typeInfo, visitor(source)));
|
|
1828
1974
|
});
|
|
1829
|
-
doc.fragments.forEach(
|
|
1975
|
+
doc.fragments.forEach(fr => {
|
|
1830
1976
|
graphql.visit(fr.node, graphql.visitWithTypeInfo(typeInfo, visitor(source)));
|
|
1831
1977
|
});
|
|
1832
1978
|
});
|
|
1833
1979
|
return coverage;
|
|
1834
1980
|
}
|
|
1835
1981
|
|
|
1982
|
+
function calculateOperationComplexity(node, config, getFragmentByName, depth = 0) {
|
|
1983
|
+
let cost = config.scalarCost;
|
|
1984
|
+
if ('selectionSet' in node && node.selectionSet) {
|
|
1985
|
+
cost = config.objectCost;
|
|
1986
|
+
for (let child of node.selectionSet.selections) {
|
|
1987
|
+
cost += config.depthCostFactor * calculateOperationComplexity(child, config, getFragmentByName, depth + 1);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
if (node.kind == graphql.Kind.FRAGMENT_SPREAD) {
|
|
1991
|
+
const fragment = getFragmentByName(node.name.value);
|
|
1992
|
+
if (fragment) {
|
|
1993
|
+
cost += config.depthCostFactor * calculateOperationComplexity(fragment, config, getFragmentByName, depth + 1);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return cost;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1836
1999
|
exports.DiffRule = DiffRule;
|
|
2000
|
+
exports.calculateOperationComplexity = calculateOperationComplexity;
|
|
2001
|
+
exports.calculateTokenCount = calculateTokenCount;
|
|
2002
|
+
exports.countAliases = countAliases;
|
|
2003
|
+
exports.countDepth = countDepth;
|
|
2004
|
+
exports.countDirectives = countDirectives;
|
|
1837
2005
|
exports.coverage = coverage;
|
|
1838
2006
|
exports.diff = diff;
|
|
1839
2007
|
exports.getTypePrefix = getTypePrefix;
|