{"version":3,"file":"canonical-id-BNdkINTi.mjs","names":["CanonicalIdSchema: z.ZodType<CanonicalId>","z","resolved","normalized","idParts","scopeStack: ScopeFrame[]","frame: ScopeFrame","exportBinding: string | undefined"],"sources":["../src/canonical-id/canonical-id.ts","../src/canonical-id/path-tracker.ts"],"sourcesContent":["import { isAbsolute, relative, resolve } from \"node:path\";\nimport z from \"zod\";\nimport { normalizePath } from \"../utils\";\n\nexport type CanonicalId = string & { readonly __brand: \"CanonicalId\" };\n\nconst canonicalIdSeparator = \"::\" as const;\n\nexport const CanonicalIdSchema: z.ZodType<CanonicalId> = z.string() as unknown as z.ZodType<CanonicalId>;\n\n/**\n * Options for creating a canonical ID.\n */\nexport type CreateCanonicalIdOptions = {\n  /**\n   * Base directory for relative path computation.\n   * When provided, the canonical ID will use a relative path from baseDir.\n   * When undefined, an absolute path is required and used as-is.\n   */\n  readonly baseDir?: string;\n};\n\n/**\n * Create a canonical ID from a file path and AST path.\n *\n * @param filePath - The file path (absolute, or relative if baseDir is provided)\n * @param astPath - The AST path identifying the definition within the file\n * @param options - Optional configuration including baseDir for relative path support\n * @returns A canonical ID in the format \"{path}::{astPath}\"\n */\nexport const createCanonicalId = (filePath: string, astPath: string, options?: CreateCanonicalIdOptions): CanonicalId => {\n  const { baseDir } = options ?? {};\n\n  if (baseDir) {\n    // With baseDir, compute relative path\n    const absolutePath = isAbsolute(filePath) ? filePath : resolve(baseDir, filePath);\n    const resolved = resolve(absolutePath);\n    const relativePath = relative(baseDir, resolved);\n    const normalized = normalizePath(relativePath);\n\n    const idParts = [normalized, astPath];\n    return idParts.join(canonicalIdSeparator) as CanonicalId;\n  }\n\n  // Without baseDir, require absolute path (legacy behavior)\n  if (!isAbsolute(filePath)) {\n    throw new Error(\"[INTERNAL] CANONICAL_ID_REQUIRES_ABSOLUTE_PATH\");\n  }\n\n  const resolved = resolve(filePath);\n  const normalized = normalizePath(resolved);\n\n  // Create a 2-part ID: {absPath}::{astPath}\n  // astPath uniquely identifies the definition's location in the AST (e.g., \"MyComponent.useQuery.def\")\n  const idParts = [normalized, astPath];\n\n  return idParts.join(canonicalIdSeparator) as CanonicalId;\n};\n\n/**\n * Check if a canonical ID uses a relative path.\n * Relative canonical IDs do not start with '/'.\n *\n * @param canonicalId - The canonical ID to check\n * @returns true if the canonical ID uses a relative path\n */\nexport const isRelativeCanonicalId = (canonicalId: CanonicalId | string): boolean => {\n  return !canonicalId.startsWith(\"/\");\n};\n\n/**\n * Parse a canonical ID into its components.\n * @param canonicalId - The canonical ID to parse (e.g., \"/app/src/user.ts::userFragment\")\n * @returns An object with filePath and astPath\n */\nexport const parseCanonicalId = (\n  canonicalId: CanonicalId | string,\n): {\n  filePath: string;\n  astPath: string;\n} => {\n  const idx = canonicalId.indexOf(canonicalIdSeparator);\n  if (idx === -1) {\n    return { filePath: canonicalId, astPath: \"\" };\n  }\n  return {\n    filePath: canonicalId.slice(0, idx),\n    astPath: canonicalId.slice(idx + canonicalIdSeparator.length),\n  };\n};\n\n/**\n * Validation result for canonical ID format.\n */\nexport type CanonicalIdValidationResult = { readonly isValid: true } | { readonly isValid: false; readonly reason: string };\n\n/**\n * Validate a canonical ID format.\n * A valid canonical ID has format: \"{filePath}::{astPath}\"\n * where both filePath and astPath are non-empty.\n *\n * @param canonicalId - The canonical ID to validate\n * @returns Validation result with isValid boolean and optional error reason\n */\nexport const validateCanonicalId = (canonicalId: string): CanonicalIdValidationResult => {\n  const idx = canonicalId.indexOf(canonicalIdSeparator);\n\n  if (idx === -1) {\n    return { isValid: false, reason: \"missing '::' separator\" };\n  }\n\n  const filePath = canonicalId.slice(0, idx);\n  const astPath = canonicalId.slice(idx + canonicalIdSeparator.length);\n\n  if (filePath === \"\") {\n    return { isValid: false, reason: \"empty file path\" };\n  }\n\n  if (astPath === \"\") {\n    return { isValid: false, reason: \"empty AST path\" };\n  }\n\n  return { isValid: true };\n};\n","/**\n * Canonical path tracker for AST traversal.\n *\n * This module provides a stateful helper that tracks scope information during\n * AST traversal to generate canonical IDs. It's designed to integrate with\n * existing plugin visitor patterns (Babel, SWC, TypeScript) without requiring\n * a separate AST traversal.\n *\n * Usage pattern:\n * 1. Plugin creates tracker at file/program entry\n * 2. Plugin calls enterScope/exitScope during its traversal\n * 3. Plugin calls registerDefinition when discovering GQL definitions\n * 4. Tracker provides canonical ID information\n */\n\nimport type { CanonicalId } from \"./canonical-id\";\nimport { createCanonicalId } from \"./canonical-id\";\n\n/**\n * Scope frame for tracking AST path segments\n */\nexport type ScopeFrame = {\n  /** Name segment (e.g., \"MyComponent\", \"useQuery\", \"arrow#1\") */\n  readonly nameSegment: string;\n  /** Kind of scope */\n  readonly kind: \"function\" | \"class\" | \"variable\" | \"property\" | \"method\" | \"expression\";\n  /** Occurrence index for disambiguation */\n  readonly occurrence: number;\n};\n\n/**\n * Opaque handle for scope tracking\n */\nexport type ScopeHandle = {\n  readonly __brand: \"ScopeHandle\";\n  readonly depth: number;\n};\n\n/**\n * Canonical path tracker interface\n */\nexport interface CanonicalPathTracker {\n  /**\n   * Enter a new scope during traversal\n   * @param options Scope information\n   * @returns Handle to use when exiting the scope\n   */\n  enterScope(options: { segment: string; kind: ScopeFrame[\"kind\"]; stableKey?: string }): ScopeHandle;\n\n  /**\n   * Exit a scope during traversal\n   * @param handle Handle returned from enterScope\n   */\n  exitScope(handle: ScopeHandle): void;\n\n  /**\n   * Register a definition discovered during traversal\n   * @returns Definition metadata including astPath and canonical ID information\n   */\n  registerDefinition(): {\n    astPath: string;\n    isTopLevel: boolean;\n    exportBinding?: string;\n  };\n\n  /**\n   * Resolve a canonical ID from an astPath\n   * @param astPath AST path string\n   * @returns Canonical ID\n   */\n  resolveCanonicalId(astPath: string): CanonicalId;\n\n  /**\n   * Register an export binding\n   * @param local Local variable name\n   * @param exported Exported name\n   */\n  registerExportBinding(local: string, exported: string): void;\n\n  /**\n   * Get current scope depth\n   * @returns Current depth (0 = top level)\n   */\n  currentDepth(): number;\n}\n\n/**\n * Build AST path from scope stack (internal helper)\n */\nconst _buildAstPath = (stack: readonly ScopeFrame[]): string => {\n  return stack.map((frame) => frame.nameSegment).join(\".\");\n};\n\n/**\n * Create a canonical path tracker\n *\n * @param options Configuration options\n * @returns Tracker instance\n *\n * @example\n * ```typescript\n * // In a Babel plugin\n * const tracker = createCanonicalTracker({ filePath: state.filename });\n *\n * const visitor = {\n *   FunctionDeclaration: {\n *     enter(path) {\n *       const handle = tracker.enterScope({\n *         segment: path.node.id.name,\n *         kind: 'function'\n *       });\n *     },\n *     exit(path) {\n *       tracker.exitScope(handle);\n *     }\n *   }\n * };\n * ```\n */\nexport const createCanonicalTracker = (options: {\n  filePath: string;\n  /**\n   * Base directory for relative path computation in canonical IDs.\n   * When provided, canonical IDs will use relative paths from baseDir.\n   */\n  baseDir?: string;\n  getExportName?: (localName: string) => string | undefined;\n}): CanonicalPathTracker => {\n  const { filePath, baseDir, getExportName } = options;\n\n  // Scope stack\n  const scopeStack: ScopeFrame[] = [];\n\n  // Occurrence counters for disambiguating duplicate names\n  const occurrenceCounters = new Map<string, number>();\n\n  // Used paths for ensuring uniqueness\n  const usedPaths = new Set<string>();\n\n  // Export bindings map\n  const exportBindings = new Map<string, string>();\n\n  const getNextOccurrence = (key: string): number => {\n    const current = occurrenceCounters.get(key) ?? 0;\n    occurrenceCounters.set(key, current + 1);\n    return current;\n  };\n\n  const ensureUniquePath = (basePath: string): string => {\n    let path = basePath;\n    let suffix = 0;\n    while (usedPaths.has(path)) {\n      suffix++;\n      path = `${basePath}$${suffix}`;\n    }\n    usedPaths.add(path);\n    return path;\n  };\n\n  return {\n    enterScope({ segment, kind, stableKey }): ScopeHandle {\n      const key = stableKey ?? `${kind}:${segment}`;\n      const occurrence = getNextOccurrence(key);\n\n      const frame: ScopeFrame = {\n        nameSegment: segment,\n        kind,\n        occurrence,\n      };\n\n      scopeStack.push(frame);\n\n      return {\n        __brand: \"ScopeHandle\",\n        depth: scopeStack.length - 1,\n      } as ScopeHandle;\n    },\n\n    exitScope(handle: ScopeHandle): void {\n      // Validate handle depth matches current stack\n      if (handle.depth !== scopeStack.length - 1) {\n        throw new Error(`[INTERNAL] Invalid scope exit: expected depth ${scopeStack.length - 1}, got ${handle.depth}`);\n      }\n      scopeStack.pop();\n    },\n\n    registerDefinition(): {\n      astPath: string;\n      isTopLevel: boolean;\n      exportBinding?: string;\n    } {\n      const basePath = _buildAstPath(scopeStack);\n      const astPath = ensureUniquePath(basePath);\n      const isTopLevel = scopeStack.length === 0;\n\n      // Check export binding if provided\n      let exportBinding: string | undefined;\n      if (getExportName && isTopLevel) {\n        // For top-level definitions, try to get export name\n        // This is a simplified version - real logic depends on how the definition is bound\n        exportBinding = undefined;\n      }\n\n      return {\n        astPath,\n        isTopLevel,\n        exportBinding,\n      };\n    },\n\n    resolveCanonicalId(astPath: string): CanonicalId {\n      return createCanonicalId(filePath, astPath, { baseDir });\n    },\n\n    registerExportBinding(local: string, exported: string): void {\n      exportBindings.set(local, exported);\n    },\n\n    currentDepth(): number {\n      return scopeStack.length;\n    },\n  };\n};\n\n/**\n * Helper to create occurrence tracker (for backward compatibility)\n */\nexport const createOccurrenceTracker = (): {\n  getNextOccurrence: (key: string) => number;\n} => {\n  const occurrenceCounters = new Map<string, number>();\n\n  return {\n    getNextOccurrence(key: string): number {\n      const current = occurrenceCounters.get(key) ?? 0;\n      occurrenceCounters.set(key, current + 1);\n      return current;\n    },\n  };\n};\n\n/**\n * Helper to create path tracker (for backward compatibility)\n */\nexport const createPathTracker = (): {\n  ensureUniquePath: (basePath: string) => string;\n} => {\n  const usedPaths = new Set<string>();\n\n  return {\n    ensureUniquePath(basePath: string): string {\n      let path = basePath;\n      let suffix = 0;\n      while (usedPaths.has(path)) {\n        suffix++;\n        path = `${basePath}$${suffix}`;\n      }\n      usedPaths.add(path);\n      return path;\n    },\n  };\n};\n\n/**\n * Build AST path from scope stack (for backward compatibility)\n */\nexport const buildAstPath = (stack: readonly ScopeFrame[]): string => {\n  return stack.map((frame) => frame.nameSegment).join(\".\");\n};\n"],"mappings":"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"}