@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.
Files changed (56) hide show
  1. package/README.md +5 -3
  2. package/{dist/ast → ast}/document.d.ts +0 -0
  3. package/{dist/coverage → coverage}/index.d.ts +0 -0
  4. package/{dist/coverage → coverage}/output/json.d.ts +0 -0
  5. package/{dist/diff → diff}/argument.d.ts +0 -0
  6. package/{dist/diff → diff}/changes/argument.d.ts +0 -0
  7. package/{dist/diff → diff}/changes/change.d.ts +0 -0
  8. package/{dist/diff → diff}/changes/directive.d.ts +0 -0
  9. package/{dist/diff → diff}/changes/enum.d.ts +0 -0
  10. package/{dist/diff → diff}/changes/field.d.ts +0 -0
  11. package/{dist/diff → diff}/changes/input.d.ts +0 -0
  12. package/{dist/diff → diff}/changes/object.d.ts +0 -0
  13. package/{dist/diff → diff}/changes/schema.d.ts +0 -0
  14. package/{dist/diff → diff}/changes/type.d.ts +0 -0
  15. package/{dist/diff → diff}/changes/union.d.ts +0 -0
  16. package/{dist/diff → diff}/directive.d.ts +0 -0
  17. package/{dist/diff → diff}/enum.d.ts +0 -0
  18. package/{dist/diff → diff}/field.d.ts +0 -0
  19. package/{dist/diff → diff}/index.d.ts +0 -0
  20. package/{dist/diff → diff}/input.d.ts +0 -0
  21. package/{dist/diff → diff}/interface.d.ts +0 -0
  22. package/{dist/diff → diff}/object.d.ts +0 -0
  23. package/{dist/diff → diff}/onComplete/types.d.ts +0 -0
  24. package/{dist/diff → diff}/rules/config.d.ts +0 -0
  25. package/{dist/diff → diff}/rules/consider-usage.d.ts +0 -0
  26. package/{dist/diff → diff}/rules/dangerous-breaking.d.ts +0 -0
  27. package/{dist/diff → diff}/rules/ignore-description-changes.d.ts +0 -0
  28. package/{dist/diff → diff}/rules/index.d.ts +0 -0
  29. package/{dist/diff → diff}/rules/safe-unreachable.d.ts +0 -0
  30. package/{dist/diff → diff}/rules/suppress-removal-of-deprecated-field.d.ts +0 -0
  31. package/{dist/diff → diff}/rules/types.d.ts +0 -0
  32. package/{dist/diff → diff}/schema.d.ts +0 -0
  33. package/{dist/diff → diff}/union.d.ts +0 -0
  34. package/index.d.ts +12 -0
  35. package/{dist/index.js → index.js} +325 -157
  36. package/{dist/index.mjs → index.mjs} +322 -159
  37. package/package.json +29 -35
  38. package/{dist/similar → similar}/index.d.ts +0 -0
  39. package/{dist/utils → utils}/apollo.d.ts +0 -0
  40. package/{dist/utils → utils}/compare.d.ts +0 -0
  41. package/{dist/utils → utils}/graphql.d.ts +0 -0
  42. package/utils/isDeprecated.d.ts +2 -0
  43. package/{dist/utils → utils}/path.d.ts +0 -0
  44. package/{dist/utils → utils}/string.d.ts +0 -0
  45. package/validate/alias-count.d.ts +10 -0
  46. package/validate/complexity.d.ts +16 -0
  47. package/validate/directive-count.d.ts +10 -0
  48. package/{dist/validate → validate}/index.d.ts +17 -2
  49. package/{dist/validate → validate}/query-depth.d.ts +2 -1
  50. package/validate/token-count.d.ts +12 -0
  51. package/dist/LICENSE +0 -21
  52. package/dist/README.md +0 -55
  53. package/dist/index.d.ts +0 -7
  54. package/dist/package.json +0 -46
  55. package/dist/utils/isDeprecated.d.ts +0 -2
  56. 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((c) => !b.some((d) => isEqual(d, c)));
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((directive) => directive.name.value === 'deprecated')) {
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 (['String', 'Int', 'Float', 'Boolean', 'ID'].indexOf(typeof type === 'string' ? type : type.name) !== -1);
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}`, [node]));
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((d) => directiveNames.indexOf(d.name.value) !== -1)) {
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((d) => directiveNames.indexOf(d.name.value) === -1) });
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 behaviour of a field if it was never provided.',
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: isNonNullType(arg.type)
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}' added to field '${type.name}.${field.name}'`,
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((location) => addChange(directiveLocationAdded(newDirective, location)));
1207
+ locations.added.forEach(location => addChange(directiveLocationAdded(newDirective, location)));
1214
1208
  // locations removed
1215
- locations.removed.forEach((location) => addChange(directiveLocationRemoved(oldDirective, location)));
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((t) => !isPrimitive(t)), Object.values(newSchema.getTypeMap()).filter((t) => !isPrimitive(t)), {
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((change) => {
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((change) => {
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((change) => descriptionChangeTypes.indexOf(change.type) === -1);
1393
+ return changes.filter(change => descriptionChangeTypes.indexOf(change.type) === -1);
1388
1394
  };
1389
1395
 
1390
- const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0, function* () {
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((change) => {
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((change) => {
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((p) => change.path.startsWith(p))) {
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((change) => {
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((selection) => {
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((def) => {
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((selection) => {
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((doc) => {
1601
- doc.fragments.forEach((fragment) => {
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((fragment) => {
1736
+ fragments.forEach(fragment => {
1608
1737
  const depends = extractFragments(print(fragment.node));
1609
1738
  if (depends) {
1610
- depends.forEach((name) => {
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((doc) => doc.hasOperations)
1618
- .forEach((doc) => {
1746
+ .filter(doc => doc.hasOperations)
1747
+ .forEach(doc => {
1619
1748
  const docWithOperations = {
1620
1749
  kind: Kind.DOCUMENT,
1621
- definitions: doc.operations.map((d) => d.node),
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((fragmentName) => resolveFragment(graph.getNodeData(fragmentName), graph))
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((item) => item.name.value === def.name.value) === i);
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
- const deprecated = config.strictDeprecated
1655
- ? findDeprecatedUsages(transformedSchema, transformedDoc)
1656
- : [];
1657
- const duplicatedFragments = config.strictFragments
1658
- ? findDuplicatedFragments(fragmentNames)
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((name) => new GraphQLError(`Name of '${name}' fragment is not unique`));
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((name) => name.replace('...', ''));
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((name) => !isPrimitive(name) && !isForIntrospection(name))
1698
- .map((name) => ({
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((source) => {
1859
+ (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(source => {
1708
1860
  const sourceType = schema.getType(source.typeId);
1709
- const matchWith = targets.filter((target) => schema.getType(target.typeId).astNode.kind ===
1710
- sourceType.astNode.kind && target.typeId !== source.typeId);
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((target) => target.typeId !== type.name);
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((r) => r.rating >= threshold && r.target !== result.bestMatch.target)
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((s) => s.trim())
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 = (source) => ({
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((op) => {
1968
+ doc.operations.forEach(op => {
1823
1969
  visit(op.node, visitWithTypeInfo(typeInfo, visitor(source)));
1824
1970
  });
1825
- doc.fragments.forEach((fr) => {
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
- export { ChangeType, CriticalityLevel, DiffRule, coverage, diff, getTypePrefix, similar, validate };
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 };