@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.mjs CHANGED
@@ -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;
@@ -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}'`,
@@ -705,7 +701,7 @@ function compareTwoStrings(str1, str2) {
705
701
  const pairs2 = wordLetterPairs(str2);
706
702
  const union = pairs1.length + pairs2.length;
707
703
  let intersection = 0;
708
- pairs1.forEach((pair1) => {
704
+ pairs1.forEach(pair1 => {
709
705
  for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
710
706
  if (pair1 !== pair2)
711
707
  continue;
@@ -719,7 +715,7 @@ function compareTwoStrings(str1, str2) {
719
715
  function findBestMatch(mainString, targetStrings) {
720
716
  if (!areArgsValid(mainString, targetStrings))
721
717
  throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
722
- const ratings = targetStrings.map((target) => ({
718
+ const ratings = targetStrings.map(target => ({
723
719
  target,
724
720
  rating: compareTwoStrings(mainString, target.value),
725
721
  }));
@@ -727,9 +723,7 @@ function findBestMatch(mainString, targetStrings) {
727
723
  return { ratings, bestMatch };
728
724
  }
729
725
  function flattenDeep(arr) {
730
- return Array.isArray(arr)
731
- ? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
732
- : [arr];
726
+ return Array.isArray(arr) ? arr.reduce((a, b) => a.concat(flattenDeep(b)), []) : [arr];
733
727
  }
734
728
  function areArgsValid(mainString, targetStrings) {
735
729
  if (typeof mainString !== 'string')
@@ -738,7 +732,7 @@ function areArgsValid(mainString, targetStrings) {
738
732
  return false;
739
733
  if (!targetStrings.length)
740
734
  return false;
741
- if (targetStrings.find((s) => typeof s.value !== 'string'))
735
+ if (targetStrings.find(s => typeof s.value !== 'string'))
742
736
  return false;
743
737
  return true;
744
738
  }
@@ -753,7 +747,9 @@ function wordLetterPairs(str) {
753
747
  return flattenDeep(pairs);
754
748
  }
755
749
  function safeString(obj) {
756
- return inspect(obj).replace(/\[Object\: null prototype\] /g, '').replace(/(^')|('$)/g, '');
750
+ return inspect(obj)
751
+ .replace(/\[Object\: null prototype\] /g, '')
752
+ .replace(/(^')|('$)/g, '');
757
753
  }
758
754
 
759
755
  function inputFieldRemoved(input, field) {
@@ -868,14 +864,12 @@ function changesInInputField(input, oldField, newField, addChange) {
868
864
  }
869
865
  }
870
866
  if (isNotEqual(oldField.defaultValue, newField.defaultValue)) {
871
- if (Array.isArray(oldField.defaultValue) &&
872
- Array.isArray(newField.defaultValue)) {
867
+ if (Array.isArray(oldField.defaultValue) && Array.isArray(newField.defaultValue)) {
873
868
  if (diffArrays(oldField.defaultValue, newField.defaultValue).length > 0) {
874
869
  addChange(inputFieldDefaultValueChanged(input, oldField, newField));
875
870
  }
876
871
  }
877
- else if (JSON.stringify(oldField.defaultValue) !==
878
- JSON.stringify(newField.defaultValue)) {
872
+ else if (JSON.stringify(oldField.defaultValue) !== JSON.stringify(newField.defaultValue)) {
879
873
  addChange(inputFieldDefaultValueChanged(input, oldField, newField));
880
874
  }
881
875
  }
@@ -1099,15 +1093,13 @@ function changesInArgument(type, field, oldArg, newArg, addChange) {
1099
1093
  addChange(fieldArgumentDescriptionChanged(type, field, oldArg, newArg));
1100
1094
  }
1101
1095
  if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) {
1102
- if (Array.isArray(oldArg.defaultValue) &&
1103
- Array.isArray(newArg.defaultValue)) {
1096
+ if (Array.isArray(oldArg.defaultValue) && Array.isArray(newArg.defaultValue)) {
1104
1097
  const diff = diffArrays(oldArg.defaultValue, newArg.defaultValue);
1105
1098
  if (diff.length > 0) {
1106
1099
  addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
1107
1100
  }
1108
1101
  }
1109
- else if (JSON.stringify(oldArg.defaultValue) !==
1110
- JSON.stringify(newArg.defaultValue)) {
1102
+ else if (JSON.stringify(oldArg.defaultValue) !== JSON.stringify(newArg.defaultValue)) {
1111
1103
  addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
1112
1104
  }
1113
1105
  }
@@ -1212,9 +1204,9 @@ function changesInDirective(oldDirective, newDirective, addChange) {
1212
1204
  removed: diffArrays(oldDirective.locations, newDirective.locations),
1213
1205
  };
1214
1206
  // locations added
1215
- locations.added.forEach((location) => addChange(directiveLocationAdded(newDirective, location)));
1207
+ locations.added.forEach(location => addChange(directiveLocationAdded(newDirective, location)));
1216
1208
  // locations removed
1217
- locations.removed.forEach((location) => addChange(directiveLocationRemoved(oldDirective, location)));
1209
+ locations.removed.forEach(location => addChange(directiveLocationRemoved(oldDirective, location)));
1218
1210
  compareLists(oldDirective.args, newDirective.args, {
1219
1211
  onAdded(arg) {
1220
1212
  addChange(directiveArgumentAdded(newDirective, arg));
@@ -1245,7 +1237,7 @@ function diffSchema(oldSchema, newSchema) {
1245
1237
  changes.push(change);
1246
1238
  }
1247
1239
  changesInSchema(oldSchema, newSchema, addChange);
1248
- 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)), {
1249
1241
  onAdded(type) {
1250
1242
  addChange(typeAdded(type));
1251
1243
  },
@@ -1330,7 +1322,7 @@ function changesInType(oldType, newType, addChange) {
1330
1322
  }
1331
1323
 
1332
1324
  const dangerousBreaking = ({ changes }) => {
1333
- return changes.map((change) => {
1325
+ return changes.map(change => {
1334
1326
  if (change.criticality.level === CriticalityLevel.Dangerous) {
1335
1327
  return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Breaking }) });
1336
1328
  }
@@ -1342,8 +1334,8 @@ function parsePath(path) {
1342
1334
  return path.split('.');
1343
1335
  }
1344
1336
 
1345
- const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
1346
- return changes.map((change) => {
1337
+ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema }) => {
1338
+ return changes.map(change => {
1347
1339
  if (change.type === ChangeType.FieldRemoved &&
1348
1340
  change.criticality.level === CriticalityLevel.Breaking &&
1349
1341
  change.path) {
@@ -1398,15 +1390,15 @@ const descriptionChangeTypes = [
1398
1390
  ChangeType.TypeDescriptionChanged,
1399
1391
  ];
1400
1392
  const ignoreDescriptionChanges = ({ changes }) => {
1401
- return changes.filter((change) => descriptionChangeTypes.indexOf(change.type) === -1);
1393
+ return changes.filter(change => descriptionChangeTypes.indexOf(change.type) === -1);
1402
1394
  };
1403
1395
 
1404
- 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* () {
1405
1397
  if (!config) {
1406
1398
  throw new Error(`considerUsage rule is missing config`);
1407
1399
  }
1408
1400
  const collectedBreakingField = [];
1409
- changes.forEach((change) => {
1401
+ changes.forEach(change => {
1410
1402
  if (change.criticality.level === CriticalityLevel.Breaking && change.path) {
1411
1403
  const [typeName, fieldName, argumentName] = parsePath(change.path);
1412
1404
  collectedBreakingField.push({
@@ -1423,11 +1415,11 @@ const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0
1423
1415
  const suppressedPaths = collectedBreakingField
1424
1416
  .filter((_, i) => usageList[i] === true)
1425
1417
  .map(({ type, field, argument }) => [type, field, argument].filter(Boolean).join('.'));
1426
- return changes.map((change) => {
1418
+ return changes.map(change => {
1427
1419
  // Turns those "safe to break" changes into "dangerous"
1428
1420
  if (change.criticality.level === CriticalityLevel.Breaking &&
1429
1421
  change.path &&
1430
- suppressedPaths.some((p) => change.path.startsWith(p))) {
1422
+ suppressedPaths.some(p => change.path.startsWith(p))) {
1431
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)` });
1432
1424
  }
1433
1425
  return change;
@@ -1436,7 +1428,7 @@ const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0
1436
1428
 
1437
1429
  const safeUnreachable = ({ changes, oldSchema }) => {
1438
1430
  const reachable = getReachableTypes(oldSchema);
1439
- return changes.map((change) => {
1431
+ return changes.map(change => {
1440
1432
  if (change.criticality.level === CriticalityLevel.Breaking && change.path) {
1441
1433
  const [typeName] = parsePath(change.path);
1442
1434
  if (!reachable.has(typeName)) {
@@ -1543,7 +1535,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1543
1535
  return 1 + maxInnerDepth;
1544
1536
  }
1545
1537
  case Kind.SELECTION_SET: {
1546
- return Math.max(...node.selections.map((selection) => {
1538
+ return Math.max(...node.selections.map(selection => {
1547
1539
  return calculateDepth({
1548
1540
  node: selection,
1549
1541
  currentDepth: currentDepth,
@@ -1553,7 +1545,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1553
1545
  }));
1554
1546
  }
1555
1547
  case Kind.DOCUMENT: {
1556
- return Math.max(...node.definitions.map((def) => {
1548
+ return Math.max(...node.definitions.map(def => {
1557
1549
  return calculateDepth({
1558
1550
  node: def,
1559
1551
  currentDepth: currentDepth,
@@ -1565,7 +1557,7 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1565
1557
  case Kind.OPERATION_DEFINITION:
1566
1558
  case Kind.INLINE_FRAGMENT:
1567
1559
  case Kind.FRAGMENT_DEFINITION: {
1568
- return Math.max(...node.selectionSet.selections.map((selection) => {
1560
+ return Math.max(...node.selectionSet.selections.map(selection => {
1569
1561
  return calculateDepth({
1570
1562
  node: selection,
1571
1563
  currentDepth,
@@ -1586,13 +1578,26 @@ function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1586
1578
  }
1587
1579
  }
1588
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
+ }
1589
1596
 
1590
1597
  function transformDocumentWithApollo(doc, { keepClientFields }) {
1591
1598
  return visit(doc, {
1592
1599
  Field(node) {
1593
- return keepClientFields
1594
- ? removeDirectives(node, ['client'])
1595
- : removeFieldIfDirectives(node, ['client']);
1600
+ return keepClientFields ? removeDirectives(node, ['client']) : removeFieldIfDirectives(node, ['client']);
1596
1601
  },
1597
1602
  });
1598
1603
  }
@@ -1602,6 +1607,116 @@ function transformSchemaWithApollo(schema) {
1602
1607
  `));
1603
1608
  }
1604
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
+
1605
1720
  function validate(schema, sources, options) {
1606
1721
  const config = Object.assign({ strictDeprecated: true, strictFragments: true, keepClientFields: false, apollo: false }, options);
1607
1722
  const invalidDocuments = [];
@@ -1611,43 +1726,41 @@ function validate(schema, sources, options) {
1611
1726
  const fragments = [];
1612
1727
  const fragmentNames = [];
1613
1728
  const graph = new DepGraph({ circular: true });
1614
- documents.forEach((doc) => {
1615
- doc.fragments.forEach((fragment) => {
1729
+ documents.forEach(doc => {
1730
+ doc.fragments.forEach(fragment => {
1616
1731
  fragmentNames.push(fragment.node.name.value);
1617
1732
  fragments.push(fragment);
1618
1733
  graph.addNode(fragment.node.name.value, fragment.node);
1619
1734
  });
1620
1735
  });
1621
- fragments.forEach((fragment) => {
1736
+ fragments.forEach(fragment => {
1622
1737
  const depends = extractFragments(print(fragment.node));
1623
1738
  if (depends) {
1624
- depends.forEach((name) => {
1739
+ depends.forEach(name => {
1625
1740
  graph.addDependency(fragment.node.name.value, name);
1626
1741
  });
1627
1742
  }
1628
1743
  });
1629
1744
  documents
1630
1745
  // since we include fragments, validate only operations
1631
- .filter((doc) => doc.hasOperations)
1632
- .forEach((doc) => {
1746
+ .filter(doc => doc.hasOperations)
1747
+ .forEach(doc => {
1633
1748
  const docWithOperations = {
1634
1749
  kind: Kind.DOCUMENT,
1635
- definitions: doc.operations.map((d) => d.node),
1750
+ definitions: doc.operations.map(d => d.node),
1636
1751
  };
1637
1752
  const extractedFragments = (extractFragments(print(docWithOperations)) || [])
1638
1753
  // resolve all nested fragments
1639
- .map((fragmentName) => resolveFragment(graph.getNodeData(fragmentName), graph))
1754
+ .map(fragmentName => resolveFragment(graph.getNodeData(fragmentName), graph))
1640
1755
  // flatten arrays
1641
1756
  .reduce((list, current) => list.concat(current), [])
1642
1757
  // remove duplicates
1643
- .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);
1644
1759
  const merged = {
1645
1760
  kind: Kind.DOCUMENT,
1646
1761
  definitions: [...docWithOperations.definitions, ...extractedFragments],
1647
1762
  };
1648
- let transformedSchema = config.apollo
1649
- ? transformSchemaWithApollo(schema)
1650
- : schema;
1763
+ let transformedSchema = config.apollo ? transformSchemaWithApollo(schema) : schema;
1651
1764
  const transformedDoc = config.apollo
1652
1765
  ? transformDocumentWithApollo(merged, {
1653
1766
  keepClientFields: config.keepClientFields,
@@ -1665,12 +1778,41 @@ function validate(schema, sources, options) {
1665
1778
  errors.push(depthError);
1666
1779
  }
1667
1780
  }
1668
- const deprecated = config.strictDeprecated
1669
- ? findDeprecatedUsages(transformedSchema, transformedDoc)
1670
- : [];
1671
- const duplicatedFragments = config.strictFragments
1672
- ? findDuplicatedFragments(fragmentNames)
1673
- : [];
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) : [];
1674
1816
  if (sumLengths(errors, duplicatedFragments, deprecated) > 0) {
1675
1817
  invalidDocuments.push({
1676
1818
  source: doc.source,
@@ -1684,7 +1826,7 @@ function validate(schema, sources, options) {
1684
1826
  function findDuplicatedFragments(fragmentNames) {
1685
1827
  return fragmentNames
1686
1828
  .filter((name, i, all) => all.indexOf(name) !== i)
1687
- .map((name) => new GraphQLError(`Name of '${name}' fragment is not unique`));
1829
+ .map(name => new GraphQLError(`Name of '${name}' fragment is not unique`));
1688
1830
  }
1689
1831
  //
1690
1832
  // PostInfo -> AuthorInfo
@@ -1693,13 +1835,10 @@ function findDuplicatedFragments(fragmentNames) {
1693
1835
  function resolveFragment(fragment, graph) {
1694
1836
  return graph
1695
1837
  .dependenciesOf(fragment.name.value)
1696
- .reduce((list, current) => [
1697
- ...list,
1698
- ...resolveFragment(graph.getNodeData(current), graph),
1699
- ], [fragment]);
1838
+ .reduce((list, current) => [...list, ...resolveFragment(graph.getNodeData(current), graph)], [fragment]);
1700
1839
  }
1701
1840
  function extractFragments(document) {
1702
- 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('...', ''));
1703
1842
  }
1704
1843
  function sumLengths(...arrays) {
1705
1844
  return arrays.reduce((sum, { length }) => sum + length, 0);
@@ -1708,20 +1847,19 @@ function sumLengths(...arrays) {
1708
1847
  function similar(schema, typeName, threshold = 0.4) {
1709
1848
  const typeMap = schema.getTypeMap();
1710
1849
  const targets = Object.keys(schema.getTypeMap())
1711
- .filter((name) => !isPrimitive(name) && !isForIntrospection(name))
1712
- .map((name) => ({
1850
+ .filter(name => !isPrimitive(name) && !isForIntrospection(name))
1851
+ .map(name => ({
1713
1852
  typeId: name,
1714
1853
  value: stripType(typeMap[name]),
1715
1854
  }));
1716
1855
  const results = {};
1717
- if (typeof typeName !== 'undefined' &&
1718
- !targets.some((t) => t.typeId === typeName)) {
1856
+ if (typeof typeName !== 'undefined' && !targets.some(t => t.typeId === typeName)) {
1719
1857
  throw new Error(`Type '${typeName}' doesn't exist`);
1720
1858
  }
1721
- (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach((source) => {
1859
+ (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(source => {
1722
1860
  const sourceType = schema.getType(source.typeId);
1723
- const matchWith = targets.filter((target) => schema.getType(target.typeId).astNode.kind ===
1724
- 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);
1725
1863
  if (matchWith.length > 0) {
1726
1864
  const found = similarTo(sourceType, matchWith, threshold);
1727
1865
  if (found) {
@@ -1732,7 +1870,7 @@ function similar(schema, typeName, threshold = 0.4) {
1732
1870
  return results;
1733
1871
  }
1734
1872
  function similarTo(type, targets, threshold) {
1735
- const types = targets.filter((target) => target.typeId !== type.name);
1873
+ const types = targets.filter(target => target.typeId !== type.name);
1736
1874
  const result = findBestMatch(stripType(type), types);
1737
1875
  if (result.bestMatch.rating < threshold) {
1738
1876
  return;
@@ -1740,7 +1878,7 @@ function similarTo(type, targets, threshold) {
1740
1878
  return {
1741
1879
  bestMatch: result.bestMatch,
1742
1880
  ratings: result.ratings
1743
- .filter((r) => r.rating >= threshold && r.target !== result.bestMatch.target)
1881
+ .filter(r => r.rating >= threshold && r.target !== result.bestMatch.target)
1744
1882
  .sort((a, b) => a.rating - b.rating)
1745
1883
  .reverse(),
1746
1884
  };
@@ -1752,7 +1890,7 @@ function stripType(type) {
1752
1890
  .replace(/\}$/g, '')
1753
1891
  .trim()
1754
1892
  .split('\n')
1755
- .map((s) => s.trim())
1893
+ .map(s => s.trim())
1756
1894
  .sort((a, b) => a.localeCompare(b))
1757
1895
  .join(' ');
1758
1896
  }
@@ -1764,7 +1902,7 @@ function coverage(schema, sources) {
1764
1902
  };
1765
1903
  const typeMap = schema.getTypeMap();
1766
1904
  const typeInfo = new TypeInfo(schema);
1767
- const visitor = (source) => ({
1905
+ const visitor = source => ({
1768
1906
  Field(node) {
1769
1907
  const fieldDef = typeInfo.getFieldDef();
1770
1908
  const parent = typeInfo.getParentType();
@@ -1782,20 +1920,14 @@ function coverage(schema, sources) {
1782
1920
  typeCoverage.hits++;
1783
1921
  fieldCoverage.hits++;
1784
1922
  if (node.loc) {
1785
- fieldCoverage.locations[sourceName] = [
1786
- node.loc,
1787
- ...(locations || []),
1788
- ];
1923
+ fieldCoverage.locations[sourceName] = [node.loc, ...(locations || [])];
1789
1924
  }
1790
1925
  if (node.arguments) {
1791
1926
  for (const argNode of node.arguments) {
1792
1927
  const argCoverage = fieldCoverage.children[argNode.name.value];
1793
1928
  argCoverage.hits++;
1794
1929
  if (argNode.loc) {
1795
- argCoverage.locations[sourceName] = [
1796
- argNode.loc,
1797
- ...(argCoverage.locations[sourceName] || []),
1798
- ];
1930
+ argCoverage.locations[sourceName] = [argNode.loc, ...(argCoverage.locations[sourceName] || [])];
1799
1931
  }
1800
1932
  }
1801
1933
  }
@@ -1833,14 +1965,31 @@ function coverage(schema, sources) {
1833
1965
  const documents = coverage.sources.map(readDocument);
1834
1966
  documents.forEach((doc, i) => {
1835
1967
  const source = coverage.sources[i];
1836
- doc.operations.forEach((op) => {
1968
+ doc.operations.forEach(op => {
1837
1969
  visit(op.node, visitWithTypeInfo(typeInfo, visitor(source)));
1838
1970
  });
1839
- doc.fragments.forEach((fr) => {
1971
+ doc.fragments.forEach(fr => {
1840
1972
  visit(fr.node, visitWithTypeInfo(typeInfo, visitor(source)));
1841
1973
  });
1842
1974
  });
1843
1975
  return coverage;
1844
1976
  }
1845
1977
 
1846
- 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 };