{"version":3,"sources":["../src/routing/specificity.ts"],"sourcesContent":["export type RouteSegmentSpecificity = \"static\" | \"dynamic\" | \"catch-all\" | \"optional-catch-all\";\n\nexport class AmbiguousRouteError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = \"AmbiguousRouteError\";\n  }\n}\n\nexport class NonTerminalCatchAllRouteError extends TypeError {\n  constructor(message: string) {\n    super(message);\n    this.name = \"NonTerminalCatchAllRouteError\";\n  }\n}\n\nexport class DuplicateRouteParameterError extends AmbiguousRouteError {\n  constructor(message: string) {\n    super(message);\n    this.name = \"DuplicateRouteParameterError\";\n  }\n}\n\nexport class ReservedRouteParameterError extends AmbiguousRouteError {\n  constructor(message: string) {\n    super(message);\n    this.name = \"ReservedRouteParameterError\";\n  }\n}\n\nexport class BrowserUnstableRouteError extends TypeError {\n  constructor(message: string) {\n    super(message);\n    this.name = \"BrowserUnstableRouteError\";\n  }\n}\n\nconst SEGMENT_RANK: Record<RouteSegmentSpecificity, number> = {\n  static: 4,\n  dynamic: 3,\n  \"catch-all\": 1,\n  \"optional-catch-all\": 0,\n};\n\n// Ending a route is more specific than consuming the same path through a\n// catch-all, while a following static or dynamic segment remains more specific.\nconst ROUTE_END_RANK = 2;\n\n/** Sort route patterns from the most specific segment sequence to the least specific. */\nexport function compareRouteSpecificity(\n  left: readonly RouteSegmentSpecificity[],\n  right: readonly RouteSegmentSpecificity[],\n): number {\n  const length = Math.max(left.length, right.length);\n\n  for (let index = 0; index < length; index++) {\n    const leftRank = index < left.length ? SEGMENT_RANK[left[index]!] : ROUTE_END_RANK;\n    const rightRank = index < right.length ? SEGMENT_RANK[right[index]!] : ROUTE_END_RANK;\n    if (leftRank !== rightRank) return rightRank - leftRank;\n  }\n\n  return 0;\n}\n\nexport type RoutePatternSyntax = \"page\" | \"router\" | \"api\";\n\nconst ROUTER_PARAMETER_NAME = \"[A-Za-z0-9_$-]+\";\nconst PAGE_PARAMETER_PATTERN = /^(?:\\[\\[\\.\\.\\.(.+)\\]\\]|\\[\\.\\.\\.(.+)\\]|\\[(.+)\\])$/;\nconst ROUTER_PARAMETER_PATTERN =\n  /^(?:\\[\\[\\.\\.\\.([A-Za-z0-9_$-]+)\\]\\]|\\[\\.\\.\\.([A-Za-z0-9_$-]+)\\]|\\[([A-Za-z0-9_$-]+)\\]|:([A-Za-z0-9_$-]+)|\\*([A-Za-z0-9_$-]+)\\??)$/;\nconst RESERVED_PARAMETER_NAMES = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nexport function assertBrowserStableRoutePath(pattern: string): void {\n  if (pattern.includes(\"\\\\\") || hasControlCharacter(pattern)) {\n    throw new BrowserUnstableRouteError(\n      `Route path \"${pattern}\" cannot contain backslashes or control characters.`,\n    );\n  }\n\n  for (const segment of pattern.split(\"/\").filter(Boolean)) {\n    if (\n      (segment.startsWith(\"(\") && segment.endsWith(\")\")) ||\n      (segment.startsWith(\"[\") && segment.endsWith(\"]\"))\n    ) {\n      continue;\n    }\n\n    let decoded = segment;\n    try {\n      decoded = decodeURIComponent(segment);\n    } catch {\n      // Malformed escapes stay literal in browser pathnames.\n    }\n    if (\n      decoded === \".\" ||\n      decoded === \"..\" ||\n      decoded.includes(\"/\") ||\n      decoded.includes(\"\\\\\") ||\n      hasControlCharacter(decoded)\n    ) {\n      throw new BrowserUnstableRouteError(\n        `Route path \"${pattern}\" contains browser-unstable segment \"${segment}\".`,\n      );\n    }\n  }\n}\n\nfunction hasControlCharacter(value: string): boolean {\n  return Array.from(value).some((character) => {\n    const code = character.charCodeAt(0);\n    return code <= 31 || (code >= 127 && code <= 159);\n  });\n}\n\nexport function assertUniqueRouteParameters(\n  pattern: string,\n  syntax: RoutePatternSyntax = \"page\",\n): void {\n  const parameterPattern = syntax === \"router\" ? ROUTER_PARAMETER_PATTERN : PAGE_PARAMETER_PATTERN;\n  const names = new Set<string>();\n\n  for (const segment of splitRoutePattern(pattern, syntax)) {\n    const match = parameterPattern.exec(segment);\n    const name = match?.slice(1).find(Boolean);\n    if (!name) continue;\n    if (RESERVED_PARAMETER_NAMES.has(name)) {\n      throw new ReservedRouteParameterError(\n        `Route parameter \"${name}\" in route \"${pattern}\" is reserved. Use a different parameter name.`,\n      );\n    }\n    if (names.has(name)) {\n      throw new DuplicateRouteParameterError(\n        `Duplicate route parameter \"${name}\" in route \"${pattern}\". Each dynamic segment must use a unique name.`,\n      );\n    }\n    names.add(name);\n  }\n}\n\nfunction splitRoutePattern(pattern: string, syntax: RoutePatternSyntax): string[] {\n  return pattern\n    .replace(/\\\\/g, \"/\")\n    .split(\"/\")\n    .filter(Boolean)\n    .filter((segment) =>\n      syntax === \"api\" ? true : !(segment.startsWith(\"(\") && segment.endsWith(\")\")),\n    );\n}\n\nexport function assertTerminalCatchAll(pattern: string, syntax: RoutePatternSyntax = \"page\"): void {\n  const segments = splitRoutePattern(pattern, syntax);\n  const parameterName = syntax === \"router\" ? ROUTER_PARAMETER_NAME : \".+\";\n  const catchAllPattern = new RegExp(\n    syntax === \"router\"\n      ? `^(?:\\\\[\\\\[\\\\.\\\\.\\\\.${parameterName}\\\\]\\\\]|\\\\[\\\\.\\\\.\\\\.${parameterName}\\\\]|\\\\*${parameterName}\\\\??)$`\n      : `^(?:\\\\[\\\\[\\\\.\\\\.\\\\.${parameterName}\\\\]\\\\]|\\\\[\\\\.\\\\.\\\\.${parameterName}\\\\])$`,\n  );\n  const catchAllIndex = segments.findIndex((segment) => catchAllPattern.test(segment));\n  if (catchAllIndex >= 0 && catchAllIndex !== segments.length - 1) {\n    throw new NonTerminalCatchAllRouteError(\n      `Catch-all segment \"${segments[catchAllIndex]}\" must be the final segment in route \"${pattern}\".`,\n    );\n  }\n}\n\n/** Return the URL-matching shape of a route without its parameter names. */\nexport function getRoutePatternShape(pattern: string, syntax: RoutePatternSyntax = \"page\"): string {\n  assertTerminalCatchAll(pattern, syntax);\n  const segments = splitRoutePattern(pattern, syntax).map((segment) => {\n    const specificity = getPatternSegmentSpecificity(segment, syntax);\n    if (specificity !== \"static\") return specificity;\n\n    try {\n      return `static:${decodeURIComponent(segment)}`;\n    } catch {\n      return `static:${segment}`;\n    }\n  });\n\n  return segments.length === 0 ? \"/\" : JSON.stringify(segments);\n}\n\n/** Return the specificity of every URL-consuming segment in a route pattern. */\nexport function getRoutePatternSpecificity(\n  pattern: string,\n  syntax: RoutePatternSyntax = \"page\",\n): RouteSegmentSpecificity[] {\n  assertTerminalCatchAll(pattern, syntax);\n  return splitRoutePattern(pattern, syntax).map((segment) =>\n    getPatternSegmentSpecificity(segment, syntax),\n  );\n}\n\nfunction getPatternSegmentSpecificity(\n  segment: string,\n  syntax: RoutePatternSyntax,\n): RouteSegmentSpecificity {\n  const parameterName = syntax === \"router\" ? ROUTER_PARAMETER_NAME : \".+\";\n  const supportsColonAndStar = syntax === \"router\";\n  if (\n    new RegExp(`^\\\\[\\\\[\\\\.\\\\.\\\\.${parameterName}\\\\]\\\\]$`).test(segment) ||\n    (supportsColonAndStar && new RegExp(`^\\\\*${parameterName}\\\\?$`).test(segment))\n  ) {\n    return \"optional-catch-all\";\n  }\n  if (\n    new RegExp(`^\\\\[\\\\.\\\\.\\\\.${parameterName}\\\\]$`).test(segment) ||\n    (supportsColonAndStar && new RegExp(`^\\\\*${parameterName}$`).test(segment))\n  ) {\n    return \"catch-all\";\n  }\n  if (\n    new RegExp(`^\\\\[${parameterName}\\\\]$`).test(segment) ||\n    (supportsColonAndStar && new RegExp(`^:${parameterName}$`).test(segment))\n  ) {\n    return \"dynamic\";\n  }\n\n  return \"static\";\n}\n"],"mappings":"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","names":[]}