Repository: facebook/docusaurus
File: packages/docusaurus-utils-common/src/applyTrailingSlash.ts (TypeScript)

Code before:
  // Never transform '/' to ''
  // Never remove the baseUrl trailing slash!
  // If baseUrl = /myBase/, we want to emit /myBase/index.html and not
  // /myBase.html! See https://github.com/facebook/docusaurus/issues/5077
  const shouldNotApply = pathname === '/' || pathname === baseUrl;

  const newPathname = shouldNotApply
    ? pathname
    : handleTrailingSlash(pathname, trailingSlash);

  return path.replace(pathname, newPathname);
}

/** Adds a given string prefix to `str`. */
export function addPrefix(str: string, prefix: string): string {
  return str.startsWith(prefix) ? str : `${prefix}${str}`;
}

/** Removes a given string suffix from `str`. */
export function removeSuffix(str: string, suffix: string): string {
  if (suffix === '') {
    // str.slice(0, 0) is ""
    return str;
  }
  return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str;
}

/** Appends a leading slash to `str`, if one doesn't exist. */
export function addLeadingSlash(str: string): string {
  return addPrefix(str, '/');
}

/** Removes the trailing slash from `str`. */
export function removeTrailingSlash(str: string): string {
  return removeSuffix(str, '/');
}


Review comment (suggestion):
I would move these 2 to a new `stringUtils.ts`, they are not really related to trailing slash but a more general concept

Code after:
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import {addPrefix, removeSuffix} from './stringUtils';
import type {DocusaurusConfig} from '@docusaurus/types';

export type ApplyTrailingSlashParams = Pick<
  DocusaurusConfig,
  'trailingSlash' | 'baseUrl'
>;

export function addTrailingSlash(str: string): string {
  return str.endsWith('/') ? str : `${str}/`;
}

// Trailing slash handling depends in some site configuration options
export default function applyTrailingSlash(
  path: string,
  options: ApplyTrailingSlashParams,
): string {
  const {trailingSlash, baseUrl} = options;

  if (path.startsWith('#')) {
    // Never apply trailing slash to an anchor link
    return path;
  }

  function handleTrailingSlash(str: string, trailing: boolean): string {
    return trailing ? addTrailingSlash(str) : removeTrailingSlash(str);
  }

  // undefined = legacy retrocompatible behavior
  if (typeof trailingSlash === 'undefined') {
    return path;
  }

  // The trailing slash should be handled before the ?search#hash !
  const [pathname] = path.split(/[#?]/) as [string, ...string[]];

  // Never transform '/' to ''
  // Never remove the baseUrl trailing slash!
  // If baseUrl = /myBase/, we want to emit /myBase/index.html and not
  // /myBase.html! See https://github.com/facebook/docusaurus/issues/5077
  const shouldNotApply = pathname === '/' || pathname === baseUrl;

  const newPathname = shouldNotApply
    ? pathname
    : handleTrailingSlash(pathname, trailingSlash);

  return path.replace(pathname, newPathname);
}

/** Appends a leading slash to `str`, if one doesn't exist. */
export function addLeadingSlash(str: string): string {
  return addPrefix(str, '/');
}

/** Removes the trailing slash from `str`. */
export function removeTrailingSlash(str: string): string {
  return removeSuffix(str, '/');
}