@graphql-inspector/core 0.0.0-canary.e88122e → 0.0.0-canary.fec2df4

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,68 @@ 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
+
1609
1676
  function validate(schema, sources, options) {
1610
1677
  const config = Object.assign({ strictDeprecated: true, strictFragments: true, keepClientFields: false, apollo: false }, options);
1611
1678
  const invalidDocuments = [];
@@ -1615,43 +1682,41 @@ function validate(schema, sources, options) {
1615
1682
  const fragments = [];
1616
1683
  const fragmentNames = [];
1617
1684
  const graph = new dependencyGraph.DepGraph({ circular: true });
1618
- documents.forEach((doc) => {
1619
- doc.fragments.forEach((fragment) => {
1685
+ documents.forEach(doc => {
1686
+ doc.fragments.forEach(fragment => {
1620
1687
  fragmentNames.push(fragment.node.name.value);
1621
1688
  fragments.push(fragment);
1622
1689
  graph.addNode(fragment.node.name.value, fragment.node);
1623
1690
  });
1624
1691
  });
1625
- fragments.forEach((fragment) => {
1692
+ fragments.forEach(fragment => {
1626
1693
  const depends = extractFragments(graphql.print(fragment.node));
1627
1694
  if (depends) {
1628
- depends.forEach((name) => {
1695
+ depends.forEach(name => {
1629
1696
  graph.addDependency(fragment.node.name.value, name);
1630
1697
  });
1631
1698
  }
1632
1699
  });
1633
1700
  documents
1634
1701
  // since we include fragments, validate only operations
1635
- .filter((doc) => doc.hasOperations)
1636
- .forEach((doc) => {
1702
+ .filter(doc => doc.hasOperations)
1703
+ .forEach(doc => {
1637
1704
  const docWithOperations = {
1638
1705
  kind: graphql.Kind.DOCUMENT,
1639
- definitions: doc.operations.map((d) => d.node),
1706
+ definitions: doc.operations.map(d => d.node),
1640
1707
  };
1641
1708
  const extractedFragments = (extractFragments(graphql.print(docWithOperations)) || [])
1642
1709
  // resolve all nested fragments
1643
- .map((fragmentName) => resolveFragment(graph.getNodeData(fragmentName), graph))
1710
+ .map(fragmentName => resolveFragment(graph.getNodeData(fragmentName), graph))
1644
1711
  // flatten arrays
1645
1712
  .reduce((list, current) => list.concat(current), [])
1646
1713
  // remove duplicates
1647
- .filter((def, i, all) => all.findIndex((item) => item.name.value === def.name.value) === i);
1714
+ .filter((def, i, all) => all.findIndex(item => item.name.value === def.name.value) === i);
1648
1715
  const merged = {
1649
1716
  kind: graphql.Kind.DOCUMENT,
1650
1717
  definitions: [...docWithOperations.definitions, ...extractedFragments],
1651
1718
  };
1652
- let transformedSchema = config.apollo
1653
- ? transformSchemaWithApollo(schema)
1654
- : schema;
1719
+ let transformedSchema = config.apollo ? transformSchemaWithApollo(schema) : schema;
1655
1720
  const transformedDoc = config.apollo
1656
1721
  ? transformDocumentWithApollo(merged, {
1657
1722
  keepClientFields: config.keepClientFields,
@@ -1669,12 +1734,30 @@ function validate(schema, sources, options) {
1669
1734
  errors.push(depthError);
1670
1735
  }
1671
1736
  }
1672
- const deprecated = config.strictDeprecated
1673
- ? findDeprecatedUsages(transformedSchema, transformedDoc)
1674
- : [];
1675
- const duplicatedFragments = config.strictFragments
1676
- ? findDuplicatedFragments(fragmentNames)
1677
- : [];
1737
+ if (config.maxAliasCount) {
1738
+ const aliasError = validateAliasCount({
1739
+ source: doc.source,
1740
+ doc: transformedDoc,
1741
+ maxAliasCount: config.maxAliasCount,
1742
+ fragmentGraph: graph,
1743
+ });
1744
+ if (aliasError) {
1745
+ errors.push(aliasError);
1746
+ }
1747
+ }
1748
+ if (config.maxDirectiveCount) {
1749
+ const directiveError = validateDirectiveCount({
1750
+ source: doc.source,
1751
+ doc: transformedDoc,
1752
+ maxDirectiveCount: config.maxDirectiveCount,
1753
+ fragmentGraph: graph,
1754
+ });
1755
+ if (directiveError) {
1756
+ errors.push(directiveError);
1757
+ }
1758
+ }
1759
+ const deprecated = config.strictDeprecated ? findDeprecatedUsages(transformedSchema, transformedDoc) : [];
1760
+ const duplicatedFragments = config.strictFragments ? findDuplicatedFragments(fragmentNames) : [];
1678
1761
  if (sumLengths(errors, duplicatedFragments, deprecated) > 0) {
1679
1762
  invalidDocuments.push({
1680
1763
  source: doc.source,
@@ -1688,7 +1771,7 @@ function validate(schema, sources, options) {
1688
1771
  function findDuplicatedFragments(fragmentNames) {
1689
1772
  return fragmentNames
1690
1773
  .filter((name, i, all) => all.indexOf(name) !== i)
1691
- .map((name) => new graphql.GraphQLError(`Name of '${name}' fragment is not unique`));
1774
+ .map(name => new graphql.GraphQLError(`Name of '${name}' fragment is not unique`));
1692
1775
  }
1693
1776
  //
1694
1777
  // PostInfo -> AuthorInfo
@@ -1697,13 +1780,10 @@ function findDuplicatedFragments(fragmentNames) {
1697
1780
  function resolveFragment(fragment, graph) {
1698
1781
  return graph
1699
1782
  .dependenciesOf(fragment.name.value)
1700
- .reduce((list, current) => [
1701
- ...list,
1702
- ...resolveFragment(graph.getNodeData(current), graph),
1703
- ], [fragment]);
1783
+ .reduce((list, current) => [...list, ...resolveFragment(graph.getNodeData(current), graph)], [fragment]);
1704
1784
  }
1705
1785
  function extractFragments(document) {
1706
- return (document.match(/[\.]{3}[a-z0-9\_]+\b/gi) || []).map((name) => name.replace('...', ''));
1786
+ return (document.match(/[\.]{3}[a-z0-9\_]+\b/gi) || []).map(name => name.replace('...', ''));
1707
1787
  }
1708
1788
  function sumLengths(...arrays) {
1709
1789
  return arrays.reduce((sum, { length }) => sum + length, 0);
@@ -1712,20 +1792,19 @@ function sumLengths(...arrays) {
1712
1792
  function similar(schema, typeName, threshold = 0.4) {
1713
1793
  const typeMap = schema.getTypeMap();
1714
1794
  const targets = Object.keys(schema.getTypeMap())
1715
- .filter((name) => !isPrimitive(name) && !isForIntrospection(name))
1716
- .map((name) => ({
1795
+ .filter(name => !isPrimitive(name) && !isForIntrospection(name))
1796
+ .map(name => ({
1717
1797
  typeId: name,
1718
1798
  value: stripType(typeMap[name]),
1719
1799
  }));
1720
1800
  const results = {};
1721
- if (typeof typeName !== 'undefined' &&
1722
- !targets.some((t) => t.typeId === typeName)) {
1801
+ if (typeof typeName !== 'undefined' && !targets.some(t => t.typeId === typeName)) {
1723
1802
  throw new Error(`Type '${typeName}' doesn't exist`);
1724
1803
  }
1725
- (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach((source) => {
1804
+ (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach(source => {
1726
1805
  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);
1806
+ const matchWith = targets.filter(target => schema.getType(target.typeId).astNode.kind === sourceType.astNode.kind &&
1807
+ target.typeId !== source.typeId);
1729
1808
  if (matchWith.length > 0) {
1730
1809
  const found = similarTo(sourceType, matchWith, threshold);
1731
1810
  if (found) {
@@ -1736,7 +1815,7 @@ function similar(schema, typeName, threshold = 0.4) {
1736
1815
  return results;
1737
1816
  }
1738
1817
  function similarTo(type, targets, threshold) {
1739
- const types = targets.filter((target) => target.typeId !== type.name);
1818
+ const types = targets.filter(target => target.typeId !== type.name);
1740
1819
  const result = findBestMatch(stripType(type), types);
1741
1820
  if (result.bestMatch.rating < threshold) {
1742
1821
  return;
@@ -1744,7 +1823,7 @@ function similarTo(type, targets, threshold) {
1744
1823
  return {
1745
1824
  bestMatch: result.bestMatch,
1746
1825
  ratings: result.ratings
1747
- .filter((r) => r.rating >= threshold && r.target !== result.bestMatch.target)
1826
+ .filter(r => r.rating >= threshold && r.target !== result.bestMatch.target)
1748
1827
  .sort((a, b) => a.rating - b.rating)
1749
1828
  .reverse(),
1750
1829
  };
@@ -1756,7 +1835,7 @@ function stripType(type) {
1756
1835
  .replace(/\}$/g, '')
1757
1836
  .trim()
1758
1837
  .split('\n')
1759
- .map((s) => s.trim())
1838
+ .map(s => s.trim())
1760
1839
  .sort((a, b) => a.localeCompare(b))
1761
1840
  .join(' ');
1762
1841
  }
@@ -1768,7 +1847,7 @@ function coverage(schema, sources) {
1768
1847
  };
1769
1848
  const typeMap = schema.getTypeMap();
1770
1849
  const typeInfo = new graphql.TypeInfo(schema);
1771
- const visitor = (source) => ({
1850
+ const visitor = source => ({
1772
1851
  Field(node) {
1773
1852
  const fieldDef = typeInfo.getFieldDef();
1774
1853
  const parent = typeInfo.getParentType();
@@ -1786,20 +1865,14 @@ function coverage(schema, sources) {
1786
1865
  typeCoverage.hits++;
1787
1866
  fieldCoverage.hits++;
1788
1867
  if (node.loc) {
1789
- fieldCoverage.locations[sourceName] = [
1790
- node.loc,
1791
- ...(locations || []),
1792
- ];
1868
+ fieldCoverage.locations[sourceName] = [node.loc, ...(locations || [])];
1793
1869
  }
1794
1870
  if (node.arguments) {
1795
1871
  for (const argNode of node.arguments) {
1796
1872
  const argCoverage = fieldCoverage.children[argNode.name.value];
1797
1873
  argCoverage.hits++;
1798
1874
  if (argNode.loc) {
1799
- argCoverage.locations[sourceName] = [
1800
- argNode.loc,
1801
- ...(argCoverage.locations[sourceName] || []),
1802
- ];
1875
+ argCoverage.locations[sourceName] = [argNode.loc, ...(argCoverage.locations[sourceName] || [])];
1803
1876
  }
1804
1877
  }
1805
1878
  }
@@ -1837,17 +1910,81 @@ function coverage(schema, sources) {
1837
1910
  const documents = coverage.sources.map(readDocument);
1838
1911
  documents.forEach((doc, i) => {
1839
1912
  const source = coverage.sources[i];
1840
- doc.operations.forEach((op) => {
1913
+ doc.operations.forEach(op => {
1841
1914
  graphql.visit(op.node, graphql.visitWithTypeInfo(typeInfo, visitor(source)));
1842
1915
  });
1843
- doc.fragments.forEach((fr) => {
1916
+ doc.fragments.forEach(fr => {
1844
1917
  graphql.visit(fr.node, graphql.visitWithTypeInfo(typeInfo, visitor(source)));
1845
1918
  });
1846
1919
  });
1847
1920
  return coverage;
1848
1921
  }
1849
1922
 
1923
+ function calculateOperationComplexity(node, config, getFragmentByName, depth = 0) {
1924
+ let cost = config.scalarCost;
1925
+ if ('selectionSet' in node && node.selectionSet) {
1926
+ cost = config.objectCost;
1927
+ for (let child of node.selectionSet.selections) {
1928
+ cost += config.depthCostFactor * calculateOperationComplexity(child, config, getFragmentByName, depth + 1);
1929
+ }
1930
+ }
1931
+ if (node.kind == graphql.Kind.FRAGMENT_SPREAD) {
1932
+ const fragment = getFragmentByName(node.name.value);
1933
+ if (fragment) {
1934
+ cost += config.depthCostFactor * calculateOperationComplexity(fragment, config, getFragmentByName, depth + 1);
1935
+ }
1936
+ }
1937
+ return cost;
1938
+ }
1939
+
1940
+ class ParserWithLexer extends parser.Parser {
1941
+ constructor(source, options) {
1942
+ super(source, options);
1943
+ this.__tokenCount = 0;
1944
+ const lexer = this._lexer;
1945
+ this._lexer = new Proxy(lexer, {
1946
+ get: (target, prop, receiver) => {
1947
+ if (prop === 'advance') {
1948
+ return () => {
1949
+ const token = target.advance();
1950
+ if (token.kind !== graphql.TokenKind.EOF) {
1951
+ this.__tokenCount++;
1952
+ }
1953
+ return token;
1954
+ };
1955
+ }
1956
+ return Reflect.get(target, prop, receiver);
1957
+ },
1958
+ });
1959
+ }
1960
+ get tokenCount() {
1961
+ return this.__tokenCount;
1962
+ }
1963
+ }
1964
+ function calculateTokenCount(args) {
1965
+ const parser = new ParserWithLexer(args.source);
1966
+ const document = parser.parseDocument();
1967
+ let { tokenCount } = parser;
1968
+ graphql.visit(document, {
1969
+ FragmentSpread(node) {
1970
+ const fragmentSource = args.getReferencedFragmentSource(node.name.value);
1971
+ if (fragmentSource) {
1972
+ tokenCount += calculateTokenCount({
1973
+ source: fragmentSource,
1974
+ getReferencedFragmentSource: args.getReferencedFragmentSource,
1975
+ });
1976
+ }
1977
+ },
1978
+ });
1979
+ return tokenCount;
1980
+ }
1981
+
1850
1982
  exports.DiffRule = DiffRule;
1983
+ exports.calculateOperationComplexity = calculateOperationComplexity;
1984
+ exports.calculateTokenCount = calculateTokenCount;
1985
+ exports.countAliases = countAliases;
1986
+ exports.countDepth = countDepth;
1987
+ exports.countDirectives = countDirectives;
1851
1988
  exports.coverage = coverage;
1852
1989
  exports.diff = diff;
1853
1990
  exports.getTypePrefix = getTypePrefix;