{"version":3,"file":"NoFragmentCyclesRule.js","sourceRoot":"","sources":["../../../src/validation/rules/NoFragmentCyclesRule.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,qCAAoC;AA6C3D,MAAM,UAAU,oBAAoB,CAClC,OAA6B;IAI7B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IAGvC,MAAM,UAAU,GAA8B,EAAE,CAAC;IAGjD,MAAM,qBAAqB,GAA+B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE9E,OAAO;QACL,mBAAmB,EAAE,GAAG,EAAE,CAAC,KAAK;QAChC,kBAAkB,CAAC,IAAI;YACrB,oBAAoB,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;IAKF,SAAS,oBAAoB,CAAC,QAAgC;QAC5D,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO;QACT,CAAC;QAED,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;QACzC,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAE/B,MAAM,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACtE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,qBAAqB,CAAC,YAAY,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QAExD,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACrC,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YACzC,MAAM,UAAU,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;YAErD,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC5B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;gBACvD,IAAI,cAAc,EAAE,CAAC;oBACnB,oBAAoB,CAAC,cAAc,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC/C,MAAM,OAAO,GAAG,SAAS;qBACtB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;qBACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;qBACpC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEd,OAAO,CAAC,WAAW,CACjB,IAAI,YAAY,CACd,2BAA2B,UAAU,iBAAiB;oBACpD,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAC7C,EAAE,KAAK,EAAE,SAAS,EAAE,CACrB,CACF,CAAC;YACJ,CAAC;YACD,UAAU,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;QAED,qBAAqB,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IAClD,CAAC;AACH,CAAC","sourcesContent":["/** @category Validation Rules */\n\nimport type { ObjMap } from '../../jsutils/ObjMap.ts';\n\nimport { GraphQLError } from '../../error/GraphQLError.ts';\n\nimport type {\n  FragmentDefinitionNode,\n  FragmentSpreadNode,\n} from '../../language/ast.ts';\nimport type { ASTVisitor } from '../../language/visitor.ts';\n\nimport type { ASTValidationContext } from '../ValidationContext.ts';\n\n/**\n * No fragment cycles\n *\n * The graph of fragment spreads must not form any cycles including spreading itself.\n * Otherwise an operation could infinitely spread or infinitely execute on cycles in the underlying data.\n *\n * See https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles\n * @param context - The validation context used while checking the document.\n * @returns A visitor that reports validation errors for this rule.\n * @example\n * ```ts\n * import { buildSchema, parse, validate } from 'graphql';\n * import { NoFragmentCyclesRule } from 'graphql/validation';\n *\n * const schema = buildSchema(`\n *   type Query {\n *     name: String\n *   }\n * `);\n *\n * const invalidDocument = parse(`\n *   fragment A on Query { ...B } fragment B on Query { ...A } query { ...A }\n * `);\n * const invalidErrors = validate(schema, invalidDocument, [NoFragmentCyclesRule]);\n *\n * invalidErrors.length; // => 1\n *\n * const validDocument = parse(`\n *   fragment A on Query { name } query { ...A }\n * `);\n * const validErrors = validate(schema, validDocument, [NoFragmentCyclesRule]);\n *\n * validErrors; // => []\n * ```\n */\nexport function NoFragmentCyclesRule(\n  context: ASTValidationContext,\n): ASTVisitor {\n  // Tracks already visited fragments to maintain O(N) and to ensure that cycles\n  // are not redundantly reported.\n  const visitedFrags = new Set<string>();\n\n  // Array of AST nodes used to produce meaningful errors\n  const spreadPath: Array<FragmentSpreadNode> = [];\n\n  // Position in the spread path\n  const spreadPathIndexByName: ObjMap<number | undefined> = Object.create(null);\n\n  return {\n    OperationDefinition: () => false,\n    FragmentDefinition(node) {\n      detectCycleRecursive(node);\n      return false;\n    },\n  };\n\n  // This does a straight-forward DFS to find cycles.\n  // It does not terminate when a cycle was found but continues to explore\n  // the graph to find all possible cycles.\n  function detectCycleRecursive(fragment: FragmentDefinitionNode): void {\n    if (visitedFrags.has(fragment.name.value)) {\n      return;\n    }\n\n    const fragmentName = fragment.name.value;\n    visitedFrags.add(fragmentName);\n\n    const spreadNodes = context.getFragmentSpreads(fragment.selectionSet);\n    if (spreadNodes.length === 0) {\n      return;\n    }\n\n    spreadPathIndexByName[fragmentName] = spreadPath.length;\n\n    for (const spreadNode of spreadNodes) {\n      const spreadName = spreadNode.name.value;\n      const cycleIndex = spreadPathIndexByName[spreadName];\n\n      spreadPath.push(spreadNode);\n      if (cycleIndex === undefined) {\n        const spreadFragment = context.getFragment(spreadName);\n        if (spreadFragment) {\n          detectCycleRecursive(spreadFragment);\n        }\n      } else {\n        const cyclePath = spreadPath.slice(cycleIndex);\n        const viaPath = cyclePath\n          .slice(0, -1)\n          .map((s) => '\"' + s.name.value + '\"')\n          .join(', ');\n\n        context.reportError(\n          new GraphQLError(\n            `Cannot spread fragment \"${spreadName}\" within itself` +\n              (viaPath !== '' ? ` via ${viaPath}.` : '.'),\n            { nodes: cyclePath },\n          ),\n        );\n      }\n      spreadPath.pop();\n    }\n\n    spreadPathIndexByName[fragmentName] = undefined;\n  }\n}\n"]}