Repository: honojs/hono
File: src/utils/url.ts (TypeScript)

Code before:
  const result = getPath(request)

  // if strict routing is false => `/hello/hey/` and `/hello/hey` are treated the same
  return result.length > 1 && result.at(-1) === '/' ? result.slice(0, -1) : result
}

export const mergePath = (...paths: string[]): string => {
  let p: string = ''
  let endsWithSlash = false

  for (let path of paths) {
    // calculate endsWithSlash at the start of each iteration
    endsWithSlash = p.at(-1) === '/'

    /* ['/hey/','/say'] => ['/hey', '/say'] */
    if (endsWithSlash) {
      p = p.slice(0, -1)
    }

    /* ['/hey','say'] => ['/hey', '/say'] */
    if (path[0] !== '/') {
      path = `/${path}`
    }

    /* ['/hey/', '/'] => `/hey/` */
    if (path === '/' && endsWithSlash) {
      p = `${p}/`
    } else if (path !== '/') {
      p = `${p}${path}`
    }

    /* ['/', '/'] => `/` */
    if (path === '/' && p === '') {
      p = '/'
    }
  }

  return p
}

export const checkOptionalParameter = (path: string): string[] | null => {
  /*
   If path is `/api/animals/:type?` it will return:
   [`/api/animals`, `/api/animals/:type`]
   in other cases it will return null
  */

  if (!path.match(/\:.+\?$/)) {
    return null
  }


Review comment (suggestion):
@usualoma 

This is like my rambling. But I want to know your opinion.

I wondered which is fast `base?.at(-1)` and `base?.[base.length - 1`. I've tried to bench with `mitata` but the almost same, I think it's hard to compare the execution time is too short. But I benchmarked within my Chrome console with this code:

```js
const base = "abcdef";
const iterations = 1_000_000;

console.time("at method");
for (let i = 0; i < iterations; i++) {
  const char = base?.at(-1);
}
console.timeEnd("at method");

console.time("index method");
for (let i = 0; i < iterations; i++) {
  const char = base?.[base.length - 1];
}
console.timeEnd("index method");
```

Result:

![CleanShot 2025-02-11 at 15 48 40@2x](https://github.com/user-attachments/assets/f45843bf-863e-4a6e-8872-95f5ad4769dd)

Wow `base?.[base.length - 1]` is fast! However, I think there is a slight difference. So `base?.at(-1)` is okay since it is 10 characters short (without minifying).

Code after:
  const result = getPath(request)

  // if strict routing is false => `/hello/hey/` and `/hello/hey` are treated the same
  return result.length > 1 && result.at(-1) === '/' ? result.slice(0, -1) : result
}

/**
 * Merge paths.
 * @param {string[]} ...paths - The paths to merge.
 * @returns {string} The merged path.
 * @example
 * mergePath('/api', '/users') // '/api/users'
 * mergePath('/api/', '/users') // '/api/users'
 * mergePath('/api', '/') // '/api'
 * mergePath('/api/', '/') // '/api/'
 */
export const mergePath: (...paths: string[]) => string = (
  base: string | undefined,
  sub: string | undefined,
  ...rest: string[]
): string => {
  if (rest.length) {
    sub = mergePath(sub as string, ...rest)
  }
  return `${base?.[0] === '/' ? '' : '/'}${base}${
    sub === '/' ? '' : `${base?.at(-1) === '/' ? '' : '/'}${sub?.[0] === '/' ? sub.slice(1) : sub}`
  }`
}

export const checkOptionalParameter = (path: string): string[] | null => {
  /*
   If path is `/api/animals/:type?` it will return:
   [`/api/animals`, `/api/animals/:type`]
   in other cases it will return null
  */

  if (!path.match(/\:.+\?$/)) {
    return null
  }

  const segments = path.split('/')
  const results: string[] = []
  let basePath = ''

  segments.forEach((segment) => {
    if (segment !== '' && !/\:/.test(segment)) {
      basePath += '/' + segment
    } else if (/\:/.test(segment)) {
      if (/\?/.test(segment)) {
        if (results.length === 0 && basePath === '') {
          results.push('/')