next.js/packages/next/src/lib/try-to-parse-path.ts
try-to-parse-path.ts86 lines2.5 KB
import type { Token } from 'next/dist/compiled/path-to-regexp'
import { parse, tokensToRegexp } from 'next/dist/compiled/path-to-regexp'
import isError from './is-error'
import { normalizeTokensForRegexp } from './route-pattern-normalizer'

interface ParseResult {
  error?: any
  parsedPath: string
  regexStr?: string
  route: string
  tokens?: Token[]
}

/**
 * If there is an error show our error link but still show original error or
 * a formatted one if we can
 */
function reportError({ route, parsedPath }: ParseResult, err: any) {
  let errMatches
  if (isError(err) && (errMatches = err.message.match(/at (\d{0,})/))) {
    const position = parseInt(errMatches[1], 10)
    console.error(
      `\nError parsing \`${route}\` ` +
        `https://nextjs.org/docs/messages/invalid-route-source\n` +
        `Reason: ${err.message}\n\n` +
        `  ${parsedPath}\n` +
        `  ${new Array(position).fill(' ').join('')}^\n`
    )
  } else {
    console.error(
      `\nError parsing ${route} https://nextjs.org/docs/messages/invalid-route-source`,
      err
    )
  }
}

/**
 * Safe wrapper around tokensToRegexp that handles path-to-regexp 6.3.0+ validation errors.
 */
function safeTokensToRegexp(tokens: Token[]): RegExp {
  try {
    return tokensToRegexp(tokens)
  } catch (error) {
    if (isError(error)) {
      // Try to normalize tokens with repeating modifiers but no prefix/suffix
      const normalizedTokens = normalizeTokensForRegexp(tokens)
      return tokensToRegexp(normalizedTokens)
    }
    throw error
  }
}

/**
 * Attempts to parse a given route with `path-to-regexp` and returns an object
 * with the result. Whenever an error happens on parse, it will print an error
 * attempting to find the error position and showing a link to the docs. When
 * `handleUrl` is set to `true` it will also attempt to parse the route
 * and use the resulting pathname to parse with `path-to-regexp`.
 */
export function tryToParsePath(
  route: string,
  options?: {
    handleUrl?: boolean
  }
): ParseResult {
  const result: ParseResult = { route, parsedPath: route }
  try {
    if (options?.handleUrl) {
      const parsed = new URL(route, 'http://n')
      result.parsedPath = `${parsed.pathname}${parsed.hash || ''}`
    }

    result.tokens = parse(result.parsedPath)

    // Use safe wrapper instead of proactive detection
    if (result.tokens) {
      result.regexStr = safeTokensToRegexp(result.tokens).source
    }
  } catch (err) {
    reportError(result, err)
    result.error = err
  }

  return result
}
Quest for Codev2.0.0
/
SIGN IN