{"version":3,"file":"segment-parser.mjs","sources":["../../../../src/lib/routing/core/segment-parser.ts"],"sourcesContent":["/**\n * @file Route Segment Parsing Utilities\n * @description Framework-agnostic utilities for parsing route segments from file paths\n * and directory structures. Used for file-system-based routing conventions.\n *\n * @module @/lib/routing/core/segment-parser\n *\n * @example\n * ```typescript\n * import {\n *   parseRouteSegment,\n *   parseDirectoryPath,\n *   segmentsToUrlPath,\n * } from '@/lib/routing/core/segment-parser';\n *\n * // Parse a filename into a route segment\n * parseRouteSegment('[id].tsx'); // { type: 'dynamic', name: ':id', paramName: 'id', ... }\n *\n * // Parse a directory path into segments\n * parseDirectoryPath('users/[id]', 'posts.tsx');\n * // [{ type: 'static', name: 'users' }, { type: 'dynamic', name: ':id' }, { type: 'static', name: 'posts' }]\n *\n * // Convert segments to URL path\n * segmentsToUrlPath(segments); // '/users/:id/posts'\n * ```\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Route segment types for file-system convention\n *\n * - static: `about.tsx` -> `/about`\n * - dynamic: `[id].tsx` -> `/:id`\n * - catchAll: `[[...slug]].tsx` -> `/*`\n * - optional: `[[id]].tsx` -> `/:id?`\n * - group: `(auth)/login.tsx` -> `/login` (group ignored in path)\n * - layout: `_layout.tsx` -> layout wrapper\n * - index: `index.tsx` -> `/`\n */\nexport type RouteSegmentType =\n  | 'static'\n  | 'dynamic'\n  | 'catchAll'\n  | 'optional'\n  | 'group'\n  | 'layout'\n  | 'index';\n\n/**\n * Parsed route segment from filename\n */\nexport interface ParsedRouteSegment {\n  /** Type of segment */\n  readonly type: RouteSegmentType;\n  /** Segment name for URL path */\n  readonly name: string;\n  /** Parameter name for dynamic segments */\n  readonly paramName?: string;\n  /** Whether this segment is optional */\n  readonly isOptional: boolean;\n  /** Original filename before parsing */\n  readonly originalFilename: string;\n}\n\n/**\n * Configuration for segment parsing\n */\nexport interface SegmentParserConfig {\n  /** Extensions to strip from filenames (default: ['.tsx', '.ts', '.jsx', '.js']) */\n  extensions?: readonly string[];\n  /** Prefix for layout files (default: '_') */\n  layoutPrefix?: string;\n  /** Name for index files (default: 'index') */\n  indexName?: string;\n  /** Dynamic segment brackets (default: ['[', ']']) */\n  dynamicBrackets?: readonly [string, string];\n  /** Optional segment brackets (default: ['[[', ']]']) */\n  optionalBrackets?: readonly [string, string];\n  /** Catch-all prefix (default: '...') */\n  catchAllPrefix?: string;\n  /** Group segment brackets (default: ['(', ')']) */\n  groupBrackets?: readonly [string, string];\n}\n\n/**\n * Default segment parser configuration\n */\nexport const DEFAULT_SEGMENT_PARSER_CONFIG: Required<SegmentParserConfig> = {\n  extensions: ['.tsx', '.ts', '.jsx', '.js'],\n  layoutPrefix: '_',\n  indexName: 'index',\n  dynamicBrackets: ['[', ']'],\n  optionalBrackets: ['[[', ']]'],\n  catchAllPrefix: '...',\n  groupBrackets: ['(', ')'],\n};\n\n// =============================================================================\n// Segment Parsing\n// =============================================================================\n\n/**\n * Parse a filename or directory name into a route segment\n *\n * Conventions:\n * - `_layout.tsx` -> layout wrapper\n * - `index.tsx` -> index route (/)\n * - `[id].tsx` -> dynamic segment (/:id)\n * - `[[id]].tsx` -> optional param (/:id?)\n * - `[[...slug]].tsx` -> catch-all (*)\n * - `(group)/` -> route group (ignored in URL)\n * - `about.tsx` -> static segment (/about)\n *\n * @param filename - The filename or directory name to parse\n * @param config - Optional parsing configuration\n * @returns Parsed route segment\n */\nexport function parseRouteSegment(\n  filename: string,\n  config: SegmentParserConfig = {}\n): ParsedRouteSegment {\n  const cfg = { ...DEFAULT_SEGMENT_PARSER_CONFIG, ...config };\n\n  // Remove file extension if present\n  let nameWithoutExt = filename;\n  for (const ext of cfg.extensions) {\n    if (filename.endsWith(ext)) {\n      nameWithoutExt = filename.slice(0, -ext.length);\n      break;\n    }\n  }\n\n  // Layout files: _layout.tsx\n  if (nameWithoutExt.startsWith(cfg.layoutPrefix)) {\n    return {\n      type: 'layout',\n      name: nameWithoutExt.slice(cfg.layoutPrefix.length),\n      isOptional: false,\n      originalFilename: filename,\n    };\n  }\n\n  // Index files: index.tsx\n  if (nameWithoutExt === cfg.indexName) {\n    return {\n      type: 'index',\n      name: '',\n      isOptional: false,\n      originalFilename: filename,\n    };\n  }\n\n  const [optOpen, optClose] = cfg.optionalBrackets;\n  const [dynOpen, dynClose] = cfg.dynamicBrackets;\n  const [grpOpen, grpClose] = cfg.groupBrackets;\n\n  // Catch-all routes: [[...slug]].tsx\n  const catchAllPattern = new RegExp(`^\\\\${optOpen}\\\\${cfg.catchAllPrefix}([\\\\w-]+)\\\\${optClose}$`);\n  const catchAllMatch = nameWithoutExt.match(catchAllPattern);\n  const catchAllParam = catchAllMatch?.[1];\n  if (catchAllParam !== undefined && catchAllParam !== '') {\n    return {\n      type: 'catchAll',\n      name: '*',\n      paramName: catchAllParam,\n      isOptional: false,\n      originalFilename: filename,\n    };\n  }\n\n  // Optional dynamic routes: [[id]].tsx\n  const optionalPattern = new RegExp(`^\\\\${optOpen}([\\\\w-]+)\\\\${optClose}$`);\n  const optionalMatch = nameWithoutExt.match(optionalPattern);\n  const optionalParam = optionalMatch?.[1];\n  if (optionalParam !== undefined && optionalParam !== '') {\n    return {\n      type: 'optional',\n      name: `:${optionalParam}?`,\n      paramName: optionalParam,\n      isOptional: true,\n      originalFilename: filename,\n    };\n  }\n\n  // Dynamic routes: [id].tsx\n  const dynamicPattern = new RegExp(`^\\\\${dynOpen}([\\\\w-]+)\\\\${dynClose}$`);\n  const dynamicMatch = nameWithoutExt.match(dynamicPattern);\n  const dynamicParam = dynamicMatch?.[1];\n  if (dynamicParam !== undefined && dynamicParam !== '') {\n    return {\n      type: 'dynamic',\n      name: `:${dynamicParam}`,\n      paramName: dynamicParam,\n      isOptional: false,\n      originalFilename: filename,\n    };\n  }\n\n  // Route groups: (auth)/\n  const groupPattern = new RegExp(`^\\\\${grpOpen}([\\\\w-]+)\\\\${grpClose}$`);\n  const groupMatch = nameWithoutExt.match(groupPattern);\n  if (groupMatch) {\n    return {\n      type: 'group',\n      name: '', // Groups don't appear in URL\n      isOptional: false,\n      originalFilename: filename,\n    };\n  }\n\n  // Static routes: about.tsx\n  return {\n    type: 'static',\n    name: nameWithoutExt,\n    isOptional: false,\n    originalFilename: filename,\n  };\n}\n\n/**\n * Parse a full directory path into route segments\n *\n * @param dirPath - Directory path (relative to routes root)\n * @param filename - The filename within the directory\n * @param config - Optional parsing configuration\n * @returns Array of parsed route segments\n *\n * @example\n * ```typescript\n * parseDirectoryPath('users/[id]', 'posts.tsx');\n * // Returns segments for: /users/:id/posts\n * ```\n */\nexport function parseDirectoryPath(\n  dirPath: string,\n  filename: string,\n  config: SegmentParserConfig = {}\n): readonly ParsedRouteSegment[] {\n  const segments: ParsedRouteSegment[] = [];\n\n  // Parse directory segments\n  if (dirPath && dirPath !== '.') {\n    const dirParts = dirPath.split(/[/\\\\]/).filter(Boolean);\n    for (const part of dirParts) {\n      segments.push(parseRouteSegment(part, config));\n    }\n  }\n\n  // Parse filename segment\n  segments.push(parseRouteSegment(filename, config));\n\n  return segments;\n}\n\n/**\n * Convert parsed segments to URL path\n *\n * @param segments - Array of parsed route segments\n * @returns URL path string\n *\n * @example\n * ```typescript\n * const segments = parseDirectoryPath('users/[id]', 'posts.tsx');\n * segmentsToUrlPath(segments); // '/users/:id/posts'\n * ```\n */\nexport function segmentsToUrlPath(segments: readonly ParsedRouteSegment[]): string {\n  const pathParts = segments\n    .filter((s) => s.type !== 'layout' && s.type !== 'group')\n    .map((s) => s.name)\n    .filter(Boolean);\n\n  return `/${pathParts.join('/')}`;\n}\n\n// =============================================================================\n// Segment Utilities\n// =============================================================================\n\n/**\n * Check if a segment is dynamic (has a parameter)\n *\n * @param segment - The parsed segment to check\n * @returns True if the segment is dynamic\n */\nexport function isDynamicSegment(segment: ParsedRouteSegment): boolean {\n  return segment.type === 'dynamic' || segment.type === 'optional' || segment.type === 'catchAll';\n}\n\n/**\n * Check if a segment contributes to the URL path\n *\n * @param segment - The parsed segment to check\n * @returns True if the segment appears in the URL\n */\nexport function isUrlSegment(segment: ParsedRouteSegment): boolean {\n  return segment.type !== 'layout' && segment.type !== 'group';\n}\n\n/**\n * Extract all parameter names from segments\n *\n * @param segments - Array of parsed route segments\n * @returns Array of parameter names\n */\nexport function extractSegmentParams(segments: readonly ParsedRouteSegment[]): string[] {\n  return segments.filter((s) => s.paramName !== undefined).map((s) => s.paramName as string);\n}\n\n/**\n * Generate a route ID from segments\n *\n * @param segments - Array of parsed route segments\n * @param urlPath - Optional URL path (will be calculated if not provided)\n * @returns Route ID string\n *\n * @example\n * ```typescript\n * generateRouteId(segments, '/users/:id'); // 'USERS_BY_ID'\n * ```\n */\nexport function generateRouteId(segments: readonly ParsedRouteSegment[], urlPath?: string): string {\n  const path = urlPath ?? segmentsToUrlPath(segments);\n\n  if (path === '/') return 'INDEX';\n\n  return path\n    .replace(/^\\//, '')\n    .replace(/\\//g, '_')\n    .replace(/:/g, 'BY_')\n    .replace(/\\?/g, '_OPT')\n    .replace(/\\*/g, 'CATCH_ALL')\n    .toUpperCase();\n}\n\n/**\n * Generate a human-readable display name from segments\n *\n * @param segments - Array of parsed route segments\n * @returns Display name string\n *\n * @example\n * ```typescript\n * generateDisplayName(segments); // 'User Detail'\n * ```\n */\nexport function generateDisplayName(segments: readonly ParsedRouteSegment[]): string {\n  const lastSegment = segments[segments.length - 1];\n\n  if (!lastSegment) {\n    return 'Home';\n  }\n\n  if (lastSegment.type === 'index') {\n    const parentSegment = segments[segments.length - 2];\n    const parentName = parentSegment?.name;\n    if (parentName !== undefined && parentName !== '') {\n      return capitalize(parentName);\n    }\n    return 'Home';\n  }\n\n  if (lastSegment.type === 'dynamic') {\n    return `${capitalize(lastSegment.paramName ?? 'Detail')} Detail`;\n  }\n\n  if (lastSegment.type === 'catchAll') {\n    return 'Catch All';\n  }\n\n  return capitalize(lastSegment.name);\n}\n\n/**\n * Capitalize a string and convert hyphens to spaces\n */\nfunction capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, ' ');\n}\n\n// =============================================================================\n// Path Depth Calculation\n// =============================================================================\n\n/**\n * Calculate the depth of segments (excluding groups)\n *\n * @param segments - Array of parsed route segments\n * @returns Depth count\n */\nexport function calculateSegmentDepth(segments: readonly ParsedRouteSegment[]): number {\n  return segments.filter((s) => s.type !== 'group').length;\n}\n\n/**\n * Check if segments contain a layout\n *\n * @param segments - Array of parsed route segments\n * @returns True if any segment is a layout\n */\nexport function hasLayoutSegment(segments: readonly ParsedRouteSegment[]): boolean {\n  return segments.some((s) => s.type === 'layout');\n}\n\n/**\n * Check if segments represent an index route\n *\n * @param segments - Array of parsed route segments\n * @returns True if the last segment is an index\n */\nexport function isIndexRoute(segments: readonly ParsedRouteSegment[]): boolean {\n  const lastSegment = segments[segments.length - 1];\n  return lastSegment?.type === 'index';\n}\n"],"names":["DEFAULT_SEGMENT_PARSER_CONFIG","parseRouteSegment","filename","config","cfg","nameWithoutExt","ext","optOpen","optClose","dynOpen","dynClose","grpOpen","grpClose","catchAllPattern","catchAllParam","optionalPattern","optionalParam","dynamicPattern","dynamicParam","groupPattern","parseDirectoryPath","dirPath","segments","dirParts","part","segmentsToUrlPath","s","isDynamicSegment","segment","isUrlSegment","extractSegmentParams","generateRouteId","urlPath","path","generateDisplayName","lastSegment","parentName","capitalize","str","calculateSegmentDepth","hasLayoutSegment","isIndexRoute"],"mappings":"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"}