@graphql-inspector/core 3.3.0 → 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/index.js CHANGED
@@ -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;
@@ -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}'`,
@@ -709,7 +705,7 @@ function compareTwoStrings(str1, str2) {
709
705
  const pairs2 = wordLetterPairs(str2);
710
706
  const union = pairs1.length + pairs2.length;
711
707
  let intersection = 0;
712
- pairs1.forEach((pair1) => {
708
+ pairs1.forEach(pair1 => {
713
709
  for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
714
710
  if (pair1 !== pair2)
715
711
  continue;
@@ -723,7 +719,7 @@ function compareTwoStrings(str1, str2) {
723
719
  function findBestMatch(mainString, targetStrings) {
724
720
  if (!areArgsValid(mainString, targetStrings))
725
721
  throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
726
- const ratings = targetStrings.map((target) => ({
722
+ const ratings = targetStrings.map(target => ({
727
723
  target,
728
724
  rating: compareTwoStrings(mainString, target.value),
729
725
  }));
@@ -731,9 +727,7 @@ function findBestMatch(mainString, targetStrings) {
731
727
  return { ratings, bestMatch };
732
728
  }
733
729
  function flattenDeep(arr) {
734
- return Array.isArray(arr)
735
- ? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
736
- : [arr];
730
+ return Array.isArray(arr) ? arr.reduce((a, b) => a.concat(flattenDeep(b)), []) : [arr];
737
731
  }
738
732
  function areArgsValid(mainString, targetStrings) {
739
733
  if (typeof mainString !== 'string')
@@ -742,7 +736,7 @@ function areArgsValid(mainString, targetStrings) {
742
736
  return false;
743
737
  if (!targetStrings.length)
744
738
  return false;
745
- if (targetStrings.find((s) => typeof s.value !== 'string'))
739
+ if (targetStrings.find(s => typeof s.value !== 'string'))
746
740
  return false;
747
741
  return true;
748
742
  }
@@ -757,7 +751,9 @@ function wordLetterPairs(str) {
757
751
  return flattenDeep(pairs);
758
752
  }
759
753
  function safeString(obj) {
760
- return inspect(obj).replace(/\[Object\: null prototype\] /g, '').replace(/(^')|('$)/g, '');
754
+ return inspect(obj)
755
+ .replace(/\[Object\: null prototype\] /g, '')
756
+ .replace(/(^')|('$)/g, '');
761
757
  }
762
758
 
763
759
  function inputFieldRemoved(input, field) {
@@ -872,14 +868,12 @@ function changesInInputField(input, oldField, newField, addChange) {
872
868
  }
873
869
  }
874
870
  if (isNotEqual(oldField.defaultValue, newField.defaultValue)) {
875
- if (Array.isArray(oldField.defaultValue) &&
876
- Array.isArray(newField.defaultValue)) {
871
+ if (Array.isArray(oldField.defaultValue) && Array.isArray(newField.defaultValue)) {
877
872
  if (diffArrays(oldField.defaultValue, newField.defaultValue).length > 0) {
878
873
  addChange(inputFieldDefaultValueChanged(input, oldField, newField));
879
874
  }
880
875
  }
881
- else if (JSON.stringify(oldField.defaultValue) !==
882
- JSON.stringify(newField.defaultValue)) {
876
+ else if (JSON.stringify(oldField.defaultValue) !== JSON.stringify(newField.defaultValue)) {
883
877
  addChange(inputFieldDefaultValueChanged(input, oldField, newField));
884
878
  }
885
879
  }
@@ -1103,15 +1097,13 @@ function changesInArgument(type, field, oldArg, newArg, addChange) {
1103
1097
  addChange(fieldArgumentDescriptionChanged(type, field, oldArg, newArg));
1104
1098
  }
1105
1099
  if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) {
1106
- if (Array.isArray(oldArg.defaultValue) &&
1107
- Array.isArray(newArg.defaultValue)) {
1100
+ if (Array.isArray(oldArg.defaultValue) && Array.isArray(newArg.defaultValue)) {
1108
1101
  const diff = diffArrays(oldArg.defaultValue, newArg.defaultValue);
1109
1102
  if (diff.length > 0) {
1110
1103
  addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
1111
1104
  }
1112
1105
  }
1113
- else if (JSON.stringify(oldArg.defaultValue) !==
1114
- JSON.stringify(newArg.defaultValue)) {
1106
+ else if (JSON.stringify(oldArg.defaultValue) !== JSON.stringify(newArg.defaultValue)) {
1115
1107
  addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
1116
1108
  }
1117
1109
  }
@@ -1216,9 +1208,9 @@ function changesInDirective(oldDirective, newDirective, addChange) {
1216
1208
  removed: diffArrays(oldDirective.locations, newDirective.locations),
1217
1209
  };
1218
1210
  // locations added
1219
- locations.added.forEach((location) => addChange(directiveLocationAdded(newDirective, location)));
1211
+ locations.added.forEach(location => addChange(directiveLocationAdded(newDirective, location)));
1220
1212
  // locations removed
1221
- locations.removed.forEach((location) => addChange(directiveLocationRemoved(oldDirective, location)));
1213
+ locations.removed.forEach(location => addChange(directiveLocationRemoved(oldDirective, location)));
1222
1214
  compareLists(oldDirective.args, newDirective.args, {
1223
1215
  onAdded(arg) {
1224
1216
  addChange(directiveArgumentAdded(newDirective, arg));
@@ -1249,7 +1241,7 @@ function diffSchema(oldSchema, newSchema) {
1249
1241
  changes.push(change);
1250
1242
  }
1251
1243
  changesInSchema(oldSchema, newSchema, addChange);
1252
- 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)), {
1253
1245
  onAdded(type) {
1254
1246
  addChange(typeAdded(type));
1255
1247
  },
@@ -1334,7 +1326,7 @@ function changesInType(oldType, newType, addChange) {
1334
1326
  }
1335
1327
 
1336
1328
  const dangerousBreaking = ({ changes }) => {
1337
- return changes.map((change) => {
1329
+ return changes.map(change => {
1338
1330
  if (change.criticality.level === exports.CriticalityLevel.Dangerous) {
1339
1331
  return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Breaking }) });
1340
1332
  }
@@ -1346,8 +1338,8 @@ function parsePath(path) {
1346
1338
  return path.split('.');
1347
1339
  }
1348
1340
 
1349
- const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
1350
- return changes.map((change) => {
1341
+ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema }) => {
1342
+ return changes.map(change => {
1351
1343
  if (change.type === exports.ChangeType.FieldRemoved &&
1352
1344
  change.criticality.level === exports.CriticalityLevel.Breaking &&
1353
1345
  change.path) {
@@ -1402,15 +1394,15 @@ const descriptionChangeTypes = [
1402
1394
  exports.ChangeType.TypeDescriptionChanged,
1403
1395
  ];
1404
1396
  const ignoreDescriptionChanges = ({ changes }) => {
1405
- return changes.filter((change) => descriptionChangeTypes.indexOf(change.type) === -1);
1397
+ return changes.filter(change => descriptionChangeTypes.indexOf(change.type) === -1);
1406
1398
  };
1407
1399
 
1408
- 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* () {
1409
1401
  if (!config) {
1410
1402
  throw new Error(`considerUsage rule is missing config`);
1411
1403
  }
1412
1404
  const collectedBreakingField = [];
1413
- changes.forEach((change) => {
1405
+ changes.forEach(change => {
1414
1406
  if (change.criticality.level === exports.CriticalityLevel.Breaking && change.path) {
1415
1407
  const [typeName, fieldName, argumentName] = parsePath(change.path);
1416
1408
  collectedBreakingField.push({
@@ -1427,11 +1419,11 @@ const considerUsage = ({ changes, config, }) => tslib.__awaiter(void 0, void 0,
1427
1419
  const suppressedPaths = collectedBreakingField
1428
1420
  .filter((_, i) => usageList[i] === true)
1429
1421
  .map(({ type, field, argument }) => [type, field, argument].filter(Boolean).join('.'));
1430
- return changes.map((change) => {
1422
+ return changes.map(change => {
1431
1423
  // Turns those "safe to break" changes into "dangerous"
1432
1424
  if (change.criticality.level === exports.CriticalityLevel.Breaking &&
1433
1425
  change.path &&
1434
- suppressedPaths.some((p) => change.path.startsWith(p))) {
1426
+ suppressedPaths.some(p => change.path.startsWith(p))) {
1435
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)` });
1436
1428
  }
1437
1429
  return change;
@@ -1440,7 +1432,7 @@ const considerUsage = ({ changes, config, }) => tslib.__awaiter(void 0, void 0,
1440
1432
 
1441
1433
  const safeUnreachable = ({ changes, oldSchema }) => {
1442
1434
  const reachable = getReachableTypes(oldSchema);
1443
- return changes.map((change) => {
1435
+ return changes.map(change => {
1444
1436
  if (change.criticality.level === exports.CriticalityLevel.Breaking && change.path) {
1445
1437
  const [typeName] = parsePath(change.path);
1446
1438
  if (!reachable.has(typeName)) {
@@ -1547,7 +1539,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1547
1539
  return 1 + maxInnerDepth;
1548
1540
  }
1549
1541
  case graphql.Kind.SELECTION_SET: {
1550
- return Math.max(...node.selections.map((selection) => {
1542
+ return Math.max(...node.selections.map(selection => {
1551
1543
  return calculateDepth({
1552
1544
  node: selection,
1553
1545
  currentDepth: currentDepth,
@@ -1557,7 +1549,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1557
1549
  }));
1558
1550
  }
1559
1551
  case graphql.Kind.DOCUMENT: {
1560
- return Math.max(...node.definitions.map((def) => {
1552
+ return Math.max(...node.definitions.map(def => {
1561
1553
  return calculateDepth({
1562
1554
  node: def,
1563
1555
  currentDepth: currentDepth,
@@ -1569,7 +1561,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1569
1561
  case graphql.Kind.OPERATION_DEFINITION:
1570
1562
  case graphql.Kind.INLINE_FRAGMENT:
1571
1563
  case graphql.Kind.FRAGMENT_DEFINITION: {
1572
- return Math.max(...node.selectionSet.selections.map((selection) => {
1564
+ return Math.max(...node.selectionSet.selections.map(selection => {
1573
1565
  return calculateDepth({
1574
1566
  node: selection,
1575
1567
  currentDepth,
@@ -1590,13 +1582,26 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1590
1582
  }
1591
1583
  }
1592
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
+ }
1593
1600
 
1594
1601
  function transformDocumentWithApollo(doc, { keepClientFields }) {
1595
1602
  return graphql.visit(doc, {
1596
1603
  Field(node) {
1597
- return keepClientFields
1598
- ? removeDirectives(node, ['client'])
1599
- : removeFieldIfDirectives(node, ['client']);
1604
+ return keepClientFields ? removeDirectives(node, ['client']) : removeFieldIfDirectives(node, ['client']);
1600
1605
  },
1601
1606
  });
1602
1607
  }
@@ -1606,6 +1611,116 @@ function transformSchemaWithApollo(schema) {
1606
1611
  `));
1607
1612
  }
1608
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
+
1609
1724
  function validate(schema, sources, options) {
1610
1725
  const config = Object.assign({ strictDeprecated: true, strictFragments: true, keepClientFields: false, apollo: false }, options);
1611
1726
  const invalidDocuments = [];
@@ -1615,43 +1730,41 @@ function validate(schema, sources, options) {
1615
1730
  const fragments = [];
1616
1731
  const fragmentNames = [];
1617
1732
  const graph = new dependencyGraph.DepGraph({ circular: true });
1618
- documents.forEach((doc) => {
1619
- doc.fragments.forEach((fragment) => {
1733
+ documents.forEach(doc => {
1734
+ doc.fragments.forEach(fragment => {
1620
1735
  fragmentNames.push(fragment.node.name.value);
1621
1736
  fragments.push(fragment);
1622
1737
  graph.addNode(fragment.node.name.value, fragment.node);
1623
1738
  });
1624
1739
  });
1625
- fragments.forEach((fragment) => {
1740
+ fragments.forEach(fragment => {
1626
1741
  const depends = extractFragments(graphql.print(fragment.node));
1627
1742
  if (depends) {
1628
- depends.forEach((name) => {
1743
+ depends.forEach(name => {
1629
1744
  graph.addDependency(fragment.node.name.value, name);
1630
1745
  });
1631
1746
  }
1632
1747
  });
1633
1748
  documents
1634
1749
  // since we include fragments, validate only operations
1635
- .filter((doc) => doc.hasOperations)
1636
- .forEach((doc) => {
1750
+ .filter(doc => doc.hasOperations)
1751
+ .forEach(doc => {
1637
1752
  const docWithOperations = {
1638
1753
  kind: graphql.Kind.DOCUMENT,
1639
- definitions: doc.operations.map((d) => d.node),
1754
+ definitions: doc.operations.map(d => d.node),
1640
1755
  };
1641
1756
  const extractedFragments = (extractFragments(graphql.print(docWithOperations)) || [])
1642
1757
  // resolve all nested fragments
1643
- .map((fragmentName) => resolveFragment(graph.getNodeData(fragmentName), graph))
1758
+ .map(fragmentName => resolveFragment(graph.getNodeData(fragmentName), graph))
1644
1759
  // flatten arrays
1645
1760
  .reduce((list, current) => list.concat(current), [])
1646
1761
  // remove duplicates
1647
- .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);
1648
1763
  const merged = {
1649
1764
  kind: graphql.Kind.DOCUMENT,
1650
1765
  definitions: [...docWithOperations.definitions, ...extractedFragments],
1651
1766
  };
1652
- let transformedSchema = config.apollo
1653
- ? transformSchemaWithApollo(schema)
1654
- : schema;
1767
+ let transformedSchema = config.apollo ? transformSchemaWithApollo(schema) : schema;
1655
1768
  const transformedDoc = config.apollo
1656
1769
  ? transformDocumentWithApollo(merged, {
1657
1770
  keepClientFields: config.keepClientFields,
@@ -1669,12 +1782,41 @@ function validate(schema, sources, options) {
1669
1782
  errors.push(depthError);
1670
1783
  }
1671
1784
  }
1672
- const deprecated = config.strictDeprecated
1673
- ? findDeprecatedUsages(transformedSchema, transformedDoc)
1674
- : [];
1675
- const duplicatedFragments = config.strictFragments
1676
- ? findDuplicatedFragments(fragmentNames)
1677
- : [];
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) : [];
1678
1820
  if (sumLengths(errors, duplicatedFragments, deprecated) > 0) {
1679
1821
  invalidDocuments.push({
1680
1822
  source: doc.source,
@@ -1688,7 +1830,7 @@ function validate(schema, sources, options) {
1688
1830
  function findDuplicatedFragments(fragmentNames) {
1689
1831
  return fragmentNames
1690
1832
  .filter((name, i, all) => all.indexOf(name) !== i)
1691
- .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`));
1692
1834
  }
1693
1835
  //
1694
1836
  // PostInfo -> AuthorInfo
@@ -1697,13 +1839,10 @@ function findDuplicatedFragments(fragmentNames) {
1697
1839
  function resolveFragment(fragment, graph) {
1698
1840
  return graph
1699
1841
  .dependenciesOf(fragment.name.value)
1700
- .reduce((list, current) => [
1701
- ...list,
1702
- ...resolveFragment(graph.getNodeData(current), graph),
1703
- ], [fragment]);
1842
+ .reduce((list, current) => [...list, ...resolveFragment(graph.getNodeData(current), graph)], [fragment]);
1704
1843
  }
1705
1844
  function extractFragments(document) {
1706
- 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('...', ''));
1707
1846
  }
1708
1847
  function sumLengths(...arrays) {
1709
1848
  return arrays.reduce((sum, { length }) => sum + length, 0);
@@ -1712,20 +1851,19 @@ function sumLengths(...arrays) {
1712
1851
  function similar(schema, typeName, threshold = 0.4) {
1713
1852
  const typeMap = schema.getTypeMap();
1714
1853
  const targets = Object.keys(schema.getTypeMap())
1715
- .filter((name) => !isPrimitive(name) && !isForIntrospection(name))
1716
- .map((name) => ({
1854
+ .filter(name => !isPrimitive(name) && !isForIntrospection(name))
1855
+ .map(name => ({
1717
1856
  typeId: name,
1718
1857
  value: stripType(typeMap[name]),
1719
1858
  }));
1720
1859
  const results = {};
1721
- if (typeof typeName !== 'undefined' &&
1722
- !targets.some((t) => t.typeId === typeName)) {
1860
+ if (typeof typeName !== 'undefined' && !targets.some(t => t.typeId === typeName)) {
1723
1861
  throw new Error(`Type '${typeName}' doesn't exist`);
1724
1862
  }
1725
- (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach((source) => {
1863
+ (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(source => {
1726
1864
  const sourceType = schema.getType(source.typeId);
1727
- const matchWith = targets.filter((target) => schema.getType(target.typeId).astNode.kind ===
1728
- 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);
1729
1867
  if (matchWith.length > 0) {
1730
1868
  const found = similarTo(sourceType, matchWith, threshold);
1731
1869
  if (found) {
@@ -1736,7 +1874,7 @@ function similar(schema, typeName, threshold = 0.4) {
1736
1874
  return results;
1737
1875
  }
1738
1876
  function similarTo(type, targets, threshold) {
1739
- const types = targets.filter((target) => target.typeId !== type.name);
1877
+ const types = targets.filter(target => target.typeId !== type.name);
1740
1878
  const result = findBestMatch(stripType(type), types);
1741
1879
  if (result.bestMatch.rating < threshold) {
1742
1880
  return;
@@ -1744,7 +1882,7 @@ function similarTo(type, targets, threshold) {
1744
1882
  return {
1745
1883
  bestMatch: result.bestMatch,
1746
1884
  ratings: result.ratings
1747
- .filter((r) => r.rating >= threshold && r.target !== result.bestMatch.target)
1885
+ .filter(r => r.rating >= threshold && r.target !== result.bestMatch.target)
1748
1886
  .sort((a, b) => a.rating - b.rating)
1749
1887
  .reverse(),
1750
1888
  };
@@ -1756,7 +1894,7 @@ function stripType(type) {
1756
1894
  .replace(/\}$/g, '')
1757
1895
  .trim()
1758
1896
  .split('\n')
1759
- .map((s) => s.trim())
1897
+ .map(s => s.trim())
1760
1898
  .sort((a, b) => a.localeCompare(b))
1761
1899
  .join(' ');
1762
1900
  }
@@ -1768,7 +1906,7 @@ function coverage(schema, sources) {
1768
1906
  };
1769
1907
  const typeMap = schema.getTypeMap();
1770
1908
  const typeInfo = new graphql.TypeInfo(schema);
1771
- const visitor = (source) => ({
1909
+ const visitor = source => ({
1772
1910
  Field(node) {
1773
1911
  const fieldDef = typeInfo.getFieldDef();
1774
1912
  const parent = typeInfo.getParentType();
@@ -1786,20 +1924,14 @@ function coverage(schema, sources) {
1786
1924
  typeCoverage.hits++;
1787
1925
  fieldCoverage.hits++;
1788
1926
  if (node.loc) {
1789
- fieldCoverage.locations[sourceName] = [
1790
- node.loc,
1791
- ...(locations || []),
1792
- ];
1927
+ fieldCoverage.locations[sourceName] = [node.loc, ...(locations || [])];
1793
1928
  }
1794
1929
  if (node.arguments) {
1795
1930
  for (const argNode of node.arguments) {
1796
1931
  const argCoverage = fieldCoverage.children[argNode.name.value];
1797
1932
  argCoverage.hits++;
1798
1933
  if (argNode.loc) {
1799
- argCoverage.locations[sourceName] = [
1800
- argNode.loc,
1801
- ...(argCoverage.locations[sourceName] || []),
1802
- ];
1934
+ argCoverage.locations[sourceName] = [argNode.loc, ...(argCoverage.locations[sourceName] || [])];
1803
1935
  }
1804
1936
  }
1805
1937
  }
@@ -1837,17 +1969,39 @@ function coverage(schema, sources) {
1837
1969
  const documents = coverage.sources.map(readDocument);
1838
1970
  documents.forEach((doc, i) => {
1839
1971
  const source = coverage.sources[i];
1840
- doc.operations.forEach((op) => {
1972
+ doc.operations.forEach(op => {
1841
1973
  graphql.visit(op.node, graphql.visitWithTypeInfo(typeInfo, visitor(source)));
1842
1974
  });
1843
- doc.fragments.forEach((fr) => {
1975
+ doc.fragments.forEach(fr => {
1844
1976
  graphql.visit(fr.node, graphql.visitWithTypeInfo(typeInfo, visitor(source)));
1845
1977
  });
1846
1978
  });
1847
1979
  return coverage;
1848
1980
  }
1849
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
+
1850
1999
  exports.DiffRule = DiffRule;
2000
+ exports.calculateOperationComplexity = calculateOperationComplexity;
2001
+ exports.calculateTokenCount = calculateTokenCount;
2002
+ exports.countAliases = countAliases;
2003
+ exports.countDepth = countDepth;
2004
+ exports.countDirectives = countDirectives;
1851
2005
  exports.coverage = coverage;
1852
2006
  exports.diff = diff;
1853
2007
  exports.getTypePrefix = getTypePrefix;