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
}