@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
@@ -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((c) => !b.some((d) => isEqual(d, c)));
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((directive) => directive.name.value === 'deprecated')) {
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 (['String', 'Int', 'Float', 'Boolean', 'ID'].indexOf(typeof type === 'string' ? type : type.name) !== -1);
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}`, [node]));
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((d) => directiveNames.indexOf(d.name.value) !== -1)) {
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((d) => directiveNames.indexOf(d.name.value) === -1) });
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 behaviour of a field if it was never provided.',
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: graphql.isNonNullType(arg.type)
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}' added to field '${type.name}.${field.name}'`,
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((location) => addChange(directiveLocationAdded(newDirective, location)));
1211
+ locations.added.forEach(location => addChange(directiveLocationAdded(newDirective, location)));
1218
1212
  // locations removed
1219
- locations.removed.forEach((location) => addChange(directiveLocationRemoved(oldDirective, location)));
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((t) => !isPrimitive(t)), Object.values(newSchema.getTypeMap()).filter((t) => !isPrimitive(t)), {
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((change) => {
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((change) => {
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((change) => descriptionChangeTypes.indexOf(change.type) === -1);
1397
+ return changes.filter(change => descriptionChangeTypes.indexOf(change.type) === -1);
1392
1398
  };
1393
1399
 
1394
- const considerUsage = ({ changes, config, }) => tslib.__awaiter(void 0, void 0, void 0, function* () {
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((change) => {
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((change) => {
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((p) => change.path.startsWith(p))) {
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((change) => {
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((selection) => {
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((def) => {
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((selection) => {
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((doc) => {
1605
- doc.fragments.forEach((fragment) => {
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((fragment) => {
1740
+ fragments.forEach(fragment => {
1612
1741
  const depends = extractFragments(graphql.print(fragment.node));
1613
1742
  if (depends) {
1614
- depends.forEach((name) => {
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((doc) => doc.hasOperations)
1622
- .forEach((doc) => {
1750
+ .filter(doc => doc.hasOperations)
1751
+ .forEach(doc => {
1623
1752
  const docWithOperations = {
1624
1753
  kind: graphql.Kind.DOCUMENT,
1625
- definitions: doc.operations.map((d) => d.node),
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((fragmentName) => resolveFragment(graph.getNodeData(fragmentName), graph))
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((item) => item.name.value === def.name.value) === i);
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
- const deprecated = config.strictDeprecated
1659
- ? findDeprecatedUsages(transformedSchema, transformedDoc)
1660
- : [];
1661
- const duplicatedFragments = config.strictFragments
1662
- ? findDuplicatedFragments(fragmentNames)
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((name) => new graphql.GraphQLError(`Name of '${name}' fragment is not unique`));
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((name) => name.replace('...', ''));
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((name) => !isPrimitive(name) && !isForIntrospection(name))
1702
- .map((name) => ({
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((source) => {
1863
+ (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(source => {
1712
1864
  const sourceType = schema.getType(source.typeId);
1713
- const matchWith = targets.filter((target) => schema.getType(target.typeId).astNode.kind ===
1714
- sourceType.astNode.kind && target.typeId !== source.typeId);
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((target) => target.typeId !== type.name);
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((r) => r.rating >= threshold && r.target !== result.bestMatch.target)
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((s) => s.trim())
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 = (source) => ({
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((op) => {
1972
+ doc.operations.forEach(op => {
1827
1973
  graphql.visit(op.node, graphql.visitWithTypeInfo(typeInfo, visitor(source)));
1828
1974
  });
1829
- doc.fragments.forEach((fr) => {
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;