From 821247142eaa2f146e09580b0f479f28a2a2e627 Mon Sep 17 00:00:00 2001
From: ozaki <29860391+OzakIOne@users.noreply.github.com>
Date: Fri, 29 Mar 2024 14:45:30 +0100
Subject: [PATCH] refactor(utils): remove duplicated function (#9972)

Co-authored-by: sebastien <lorber.sebastien@gmail.com>
---
 .../src/__tests__/collectRedirects.test.ts    |  2 +-
 .../src/collectRedirects.ts                   |  7 ++-
 .../src/extensionRedirects.ts                 |  2 +-
 .../src/index.ts                              |  2 +-
 .../package.json                              |  1 +
 .../src/sidebars/generator.ts                 |  2 +-
 .../src/slug.ts                               |  8 +--
 .../src/__tests__/applyTrailingSlash.test.ts  | 30 ++++++++++
 .../src/__tests__/stringUtils.test.ts         | 55 +++++++++++++++++++
 .../src/applyTrailingSlash.ts                 | 22 +++++---
 packages/docusaurus-utils-common/src/index.ts |  4 ++
 .../src/stringUtils.ts                        | 30 ++++++++++
 .../docusaurus-utils-validation/package.json  |  1 +
 .../src/validationSchemas.ts                  |  8 +--
 packages/docusaurus-utils/package.json        |  1 +
 .../src/__tests__/jsUtils.test.ts             | 29 +---------
 .../src/__tests__/urlUtils.test.ts            | 30 ----------
 packages/docusaurus-utils/src/globUtils.ts    |  3 +-
 packages/docusaurus-utils/src/index.ts        | 10 +---
 packages/docusaurus-utils/src/jsUtils.ts      | 24 --------
 packages/docusaurus-utils/src/urlUtils.ts     | 17 ------
 packages/docusaurus/package.json              |  2 +-
 packages/docusaurus/src/server/brokenLinks.ts |  3 +-
 .../docusaurus/src/server/configValidation.ts |  8 ++-
 24 files changed, 160 insertions(+), 141 deletions(-)
 create mode 100644 packages/docusaurus-utils-common/src/__tests__/stringUtils.test.ts
 create mode 100644 packages/docusaurus-utils-common/src/stringUtils.ts

diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts
index e303f7289b92..11b0d3455061 100644
--- a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts
+++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts
@@ -5,7 +5,7 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import {removeTrailingSlash} from '@docusaurus/utils';
+import {removeTrailingSlash} from '@docusaurus/utils-common';
 import {normalizePluginOptions} from '@docusaurus/utils-validation';
 import collectRedirects from '../collectRedirects';
 import {validateOptions} from '../options';
diff --git a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts
index 92a29a6ed307..1e3ac44e4405 100644
--- a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts
+++ b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts
@@ -7,8 +7,11 @@
 
 import _ from 'lodash';
 import logger from '@docusaurus/logger';
-import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
-import {applyTrailingSlash} from '@docusaurus/utils-common';
+import {
+  applyTrailingSlash,
+  addTrailingSlash,
+  removeTrailingSlash,
+} from '@docusaurus/utils-common';
 import {
   createFromExtensionsRedirects,
   createToExtensionsRedirects,
diff --git a/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts b/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts
index 86fa4aeb48de..3fc48cdfad49 100644
--- a/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts
+++ b/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts
@@ -9,7 +9,7 @@ import {
   addTrailingSlash,
   removeSuffix,
   removeTrailingSlash,
-} from '@docusaurus/utils';
+} from '@docusaurus/utils-common';
 import type {RedirectItem} from './types';
 
 const ExtensionAdditionalMessage =
diff --git a/packages/docusaurus-plugin-client-redirects/src/index.ts b/packages/docusaurus-plugin-client-redirects/src/index.ts
index 5b64560bac04..e7aa97961f37 100644
--- a/packages/docusaurus-plugin-client-redirects/src/index.ts
+++ b/packages/docusaurus-plugin-client-redirects/src/index.ts
@@ -5,7 +5,7 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
+import {addLeadingSlash, removePrefix} from '@docusaurus/utils-common';
 import collectRedirects from './collectRedirects';
 import writeRedirectFiles, {
   toRedirectFiles,
diff --git a/packages/docusaurus-plugin-content-docs/package.json b/packages/docusaurus-plugin-content-docs/package.json
index d7099a125e60..e8349e4fb455 100644
--- a/packages/docusaurus-plugin-content-docs/package.json
+++ b/packages/docusaurus-plugin-content-docs/package.json
@@ -41,6 +41,7 @@
     "@docusaurus/module-type-aliases": "3.0.0",
     "@docusaurus/types": "3.0.0",
     "@docusaurus/utils": "3.0.0",
+    "@docusaurus/utils-common": "3.0.0",
     "@docusaurus/utils-validation": "3.0.0",
     "@types/react-router-config": "^5.0.7",
     "combine-promises": "^1.1.0",
diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts
index 5a6b325449b1..0edf909bb9c8 100644
--- a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts
+++ b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts
@@ -8,7 +8,7 @@
 import path from 'path';
 import _ from 'lodash';
 import logger from '@docusaurus/logger';
-import {addTrailingSlash} from '@docusaurus/utils';
+import {addTrailingSlash} from '@docusaurus/utils-common';
 import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
 import type {
   SidebarItemDoc,
diff --git a/packages/docusaurus-plugin-content-docs/src/slug.ts b/packages/docusaurus-plugin-content-docs/src/slug.ts
index 130c7a63b1f5..65887029e8bd 100644
--- a/packages/docusaurus-plugin-content-docs/src/slug.ts
+++ b/packages/docusaurus-plugin-content-docs/src/slug.ts
@@ -5,12 +5,8 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import {
-  addLeadingSlash,
-  addTrailingSlash,
-  isValidPathname,
-  resolvePathname,
-} from '@docusaurus/utils';
+import {isValidPathname, resolvePathname} from '@docusaurus/utils';
+import {addLeadingSlash, addTrailingSlash} from '@docusaurus/utils-common';
 import {
   DefaultNumberPrefixParser,
   stripPathNumberPrefixes,
diff --git a/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts b/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts
index 4096b8273fca..c86013f7fd37 100644
--- a/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts
+++ b/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts
@@ -6,7 +6,10 @@
  */
 
 import applyTrailingSlash, {
+  addTrailingSlash,
   type ApplyTrailingSlashParams,
+  addLeadingSlash,
+  removeTrailingSlash,
 } from '../applyTrailingSlash';
 
 function params(
@@ -176,3 +179,30 @@ describe('applyTrailingSlash', () => {
     ).toBe('https://xyz.com/abc/?search#anchor');
   });
 });
+
+describe('addTrailingSlash', () => {
+  it('is no-op for path with trailing slash', () => {
+    expect(addTrailingSlash('/abcd/')).toBe('/abcd/');
+  });
+  it('adds / for path without trailing slash', () => {
+    expect(addTrailingSlash('/abcd')).toBe('/abcd/');
+  });
+});
+
+describe('addLeadingSlash', () => {
+  it('is no-op for path with leading slash', () => {
+    expect(addLeadingSlash('/abc')).toBe('/abc');
+  });
+  it('adds / for path without leading slash', () => {
+    expect(addLeadingSlash('abc')).toBe('/abc');
+  });
+});
+
+describe('removeTrailingSlash', () => {
+  it('is no-op for path without trailing slash', () => {
+    expect(removeTrailingSlash('/abcd')).toBe('/abcd');
+  });
+  it('removes / for path with trailing slash', () => {
+    expect(removeTrailingSlash('/abcd/')).toBe('/abcd');
+  });
+});
diff --git a/packages/docusaurus-utils-common/src/__tests__/stringUtils.test.ts b/packages/docusaurus-utils-common/src/__tests__/stringUtils.test.ts
new file mode 100644
index 000000000000..9d01520300bf
--- /dev/null
+++ b/packages/docusaurus-utils-common/src/__tests__/stringUtils.test.ts
@@ -0,0 +1,55 @@
+/**
+ * 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, addSuffix, removePrefix, removeSuffix} from '../stringUtils';
+
+describe('removePrefix', () => {
+  it("is no-op when prefix doesn't exist", () => {
+    expect(removePrefix('abcdef', 'ijk')).toBe('abcdef');
+    expect(removePrefix('abcdef', 'def')).toBe('abcdef');
+    expect(removePrefix('abcdef', '')).toBe('abcdef');
+  });
+  it('removes prefix', () => {
+    expect(removePrefix('prefix', 'pre')).toBe('fix');
+  });
+});
+
+describe('removeSuffix', () => {
+  it("is no-op when suffix doesn't exist", () => {
+    expect(removeSuffix('abcdef', 'ijk')).toBe('abcdef');
+    expect(removeSuffix('abcdef', 'abc')).toBe('abcdef');
+    expect(removeSuffix('abcdef', '')).toBe('abcdef');
+  });
+  it('removes suffix', () => {
+    expect(removeSuffix('abcdef', 'ef')).toBe('abcd');
+  });
+  it('removes empty suffix', () => {
+    expect(removeSuffix('abcdef', '')).toBe('abcdef');
+  });
+});
+
+describe('addPrefix', () => {
+  it('is no-op when prefix already exists', () => {
+    expect(addPrefix('abcdef', 'abc')).toBe('abcdef');
+    expect(addPrefix('abc', '')).toBe('abc');
+    expect(addPrefix('', '')).toBe('');
+  });
+  it('adds prefix', () => {
+    expect(addPrefix('def', 'abc')).toBe('abcdef');
+  });
+});
+
+describe('addSuffix', () => {
+  it('is no-op when suffix already exists', () => {
+    expect(addSuffix('abcdef', 'def')).toBe('abcdef');
+    expect(addSuffix('abc', '')).toBe('abc');
+    expect(addSuffix('', '')).toBe('');
+  });
+  it('adds suffix', () => {
+    expect(addSuffix('abc', 'def')).toBe('abcdef');
+  });
+});
diff --git a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts
index d8faa439cc5b..f83e5ce9c076 100644
--- a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts
+++ b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts
@@ -5,6 +5,7 @@
  * 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<
@@ -12,6 +13,10 @@ export type ApplyTrailingSlashParams = Pick<
   '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,
@@ -24,13 +29,6 @@ export default function applyTrailingSlash(
     return path;
   }
 
-  // TODO deduplicate: also present in @docusaurus/utils
-  function addTrailingSlash(str: string): string {
-    return str.endsWith('/') ? str : `${str}/`;
-  }
-  function removeTrailingSlash(str: string): string {
-    return str.endsWith('/') ? str.slice(0, -1) : str;
-  }
   function handleTrailingSlash(str: string, trailing: boolean): string {
     return trailing ? addTrailingSlash(str) : removeTrailingSlash(str);
   }
@@ -55,3 +53,13 @@ export default function applyTrailingSlash(
 
   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, '/');
+}
diff --git a/packages/docusaurus-utils-common/src/index.ts b/packages/docusaurus-utils-common/src/index.ts
index 17b69e0727f0..0c4aae428147 100644
--- a/packages/docusaurus-utils-common/src/index.ts
+++ b/packages/docusaurus-utils-common/src/index.ts
@@ -11,6 +11,10 @@ export const blogPostContainerID = '__blog-post-container';
 
 export {
   default as applyTrailingSlash,
+  addTrailingSlash,
+  addLeadingSlash,
+  removeTrailingSlash,
   type ApplyTrailingSlashParams,
 } from './applyTrailingSlash';
+export {addPrefix, removeSuffix, addSuffix, removePrefix} from './stringUtils';
 export {getErrorCausalChain} from './errorUtils';
diff --git a/packages/docusaurus-utils-common/src/stringUtils.ts b/packages/docusaurus-utils-common/src/stringUtils.ts
new file mode 100644
index 000000000000..29dea13c3210
--- /dev/null
+++ b/packages/docusaurus-utils-common/src/stringUtils.ts
@@ -0,0 +1,30 @@
+/**
+ * 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.
+ */
+
+/** 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;
+}
+
+/** Adds a given string suffix to `str`. */
+export function addSuffix(str: string, suffix: string): string {
+  return str.endsWith(suffix) ? str : `${str}${suffix}`;
+}
+
+/** Removes a given string prefix from `str`. */
+export function removePrefix(str: string, prefix: string): string {
+  return str.startsWith(prefix) ? str.slice(prefix.length) : str;
+}
diff --git a/packages/docusaurus-utils-validation/package.json b/packages/docusaurus-utils-validation/package.json
index f37c918de7b5..7a90a9aa891a 100644
--- a/packages/docusaurus-utils-validation/package.json
+++ b/packages/docusaurus-utils-validation/package.json
@@ -20,6 +20,7 @@
   "dependencies": {
     "@docusaurus/logger": "3.0.0",
     "@docusaurus/utils": "3.0.0",
+    "@docusaurus/utils-common": "3.0.0",
     "joi": "^17.9.2",
     "js-yaml": "^4.1.0",
     "tslib": "^2.6.0"
diff --git a/packages/docusaurus-utils-validation/src/validationSchemas.ts b/packages/docusaurus-utils-validation/src/validationSchemas.ts
index 2e624458f5bc..69b4ff5c9996 100644
--- a/packages/docusaurus-utils-validation/src/validationSchemas.ts
+++ b/packages/docusaurus-utils-validation/src/validationSchemas.ts
@@ -5,12 +5,8 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import {
-  isValidPathname,
-  DEFAULT_PLUGIN_ID,
-  type Tag,
-  addLeadingSlash,
-} from '@docusaurus/utils';
+import {isValidPathname, DEFAULT_PLUGIN_ID, type Tag} from '@docusaurus/utils';
+import {addLeadingSlash} from '@docusaurus/utils-common';
 import Joi from './Joi';
 import {JoiFrontMatter} from './JoiFrontMatter';
 
diff --git a/packages/docusaurus-utils/package.json b/packages/docusaurus-utils/package.json
index b3fa5dee583d..9f7adc9cbe3b 100644
--- a/packages/docusaurus-utils/package.json
+++ b/packages/docusaurus-utils/package.json
@@ -19,6 +19,7 @@
   "license": "MIT",
   "dependencies": {
     "@docusaurus/logger": "3.0.0",
+    "@docusaurus/utils-common": "3.0.0",
     "@svgr/webpack": "^6.5.1",
     "escape-string-regexp": "^4.0.0",
     "file-loader": "^6.2.0",
diff --git a/packages/docusaurus-utils/src/__tests__/jsUtils.test.ts b/packages/docusaurus-utils/src/__tests__/jsUtils.test.ts
index 9ba72646964c..fb3750e0eae7 100644
--- a/packages/docusaurus-utils/src/__tests__/jsUtils.test.ts
+++ b/packages/docusaurus-utils/src/__tests__/jsUtils.test.ts
@@ -7,34 +7,7 @@
 
 import {jest} from '@jest/globals';
 import _ from 'lodash';
-import {
-  removeSuffix,
-  removePrefix,
-  mapAsyncSequential,
-  findAsyncSequential,
-} from '../jsUtils';
-
-describe('removeSuffix', () => {
-  it("is no-op when suffix doesn't exist", () => {
-    expect(removeSuffix('abcdef', 'ijk')).toBe('abcdef');
-    expect(removeSuffix('abcdef', 'abc')).toBe('abcdef');
-    expect(removeSuffix('abcdef', '')).toBe('abcdef');
-  });
-  it('removes suffix', () => {
-    expect(removeSuffix('abcdef', 'ef')).toBe('abcd');
-  });
-});
-
-describe('removePrefix', () => {
-  it("is no-op when prefix doesn't exist", () => {
-    expect(removePrefix('abcdef', 'ijk')).toBe('abcdef');
-    expect(removePrefix('abcdef', 'def')).toBe('abcdef');
-    expect(removePrefix('abcdef', '')).toBe('abcdef');
-  });
-  it('removes prefix', () => {
-    expect(removePrefix('prefix', 'pre')).toBe('fix');
-  });
-});
+import {mapAsyncSequential, findAsyncSequential} from '../jsUtils';
 
 describe('mapAsyncSequential', () => {
   function sleep(timeout: number): Promise<void> {
diff --git a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts
index 7ed1cbbf9cea..1f63138d9780 100644
--- a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts
+++ b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts
@@ -10,9 +10,6 @@ import {
   getEditUrl,
   fileToPath,
   isValidPathname,
-  addTrailingSlash,
-  addLeadingSlash,
-  removeTrailingSlash,
   resolvePathname,
   encodePath,
   buildSshUrl,
@@ -207,33 +204,6 @@ describe('isValidPathname', () => {
   });
 });
 
-describe('addTrailingSlash', () => {
-  it('is no-op for path with trailing slash', () => {
-    expect(addTrailingSlash('/abcd/')).toBe('/abcd/');
-  });
-  it('adds / for path without trailing slash', () => {
-    expect(addTrailingSlash('/abcd')).toBe('/abcd/');
-  });
-});
-
-describe('addLeadingSlash', () => {
-  it('is no-op for path with leading slash', () => {
-    expect(addLeadingSlash('/abc')).toBe('/abc');
-  });
-  it('adds / for path without leading slash', () => {
-    expect(addLeadingSlash('abc')).toBe('/abc');
-  });
-});
-
-describe('removeTrailingSlash', () => {
-  it('is no-op for path without trailing slash', () => {
-    expect(removeTrailingSlash('/abcd')).toBe('/abcd');
-  });
-  it('removes / for path with trailing slash', () => {
-    expect(removeTrailingSlash('/abcd/')).toBe('/abcd');
-  });
-});
-
 describe('parseURLPath', () => {
   it('parse and resolve pathname', () => {
     expect(parseURLPath('')).toEqual({
diff --git a/packages/docusaurus-utils/src/globUtils.ts b/packages/docusaurus-utils/src/globUtils.ts
index 777eb84ccc50..72b65d75d7f9 100644
--- a/packages/docusaurus-utils/src/globUtils.ts
+++ b/packages/docusaurus-utils/src/globUtils.ts
@@ -9,8 +9,7 @@
 
 import path from 'path';
 import Micromatch from 'micromatch'; // Note: Micromatch is used by Globby
-import {addSuffix} from './jsUtils';
-
+import {addSuffix} from '@docusaurus/utils-common';
 /** A re-export of the globby instance. */
 export {default as Globby} from 'globby';
 
diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts
index 2a47cfb6140a..405da5258dd4 100644
--- a/packages/docusaurus-utils/src/index.ts
+++ b/packages/docusaurus-utils/src/index.ts
@@ -35,12 +35,7 @@ export {
   getPluginI18nPath,
   localizePath,
 } from './i18nUtils';
-export {
-  removeSuffix,
-  removePrefix,
-  mapAsyncSequential,
-  findAsyncSequential,
-} from './jsUtils';
+export {mapAsyncSequential, findAsyncSequential} from './jsUtils';
 export {
   normalizeUrl,
   getEditUrl,
@@ -50,9 +45,6 @@ export {
   resolvePathname,
   parseURLPath,
   serializeURLPath,
-  addLeadingSlash,
-  addTrailingSlash,
-  removeTrailingSlash,
   hasSSHProtocol,
   buildHttpsUrl,
   buildSshUrl,
diff --git a/packages/docusaurus-utils/src/jsUtils.ts b/packages/docusaurus-utils/src/jsUtils.ts
index 2540d4166d69..240798b7ef82 100644
--- a/packages/docusaurus-utils/src/jsUtils.ts
+++ b/packages/docusaurus-utils/src/jsUtils.ts
@@ -5,30 +5,6 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-/** Adds a given string prefix to `str`. */
-export function addPrefix(str: string, prefix: string): string {
-  return str.startsWith(prefix) ? str : `${prefix}${str}`;
-}
-
-/** Adds a given string suffix to `str`. */
-export function addSuffix(str: string, suffix: string): string {
-  return str.endsWith(suffix) ? str : `${str}${suffix}`;
-}
-
-/** 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;
-}
-
-/** Removes a given string prefix from `str`. */
-export function removePrefix(str: string, prefix: string): string {
-  return str.startsWith(prefix) ? str.slice(prefix.length) : str;
-}
-
 /**
  * `Array#map` for async operations where order matters.
  * @param array The array to traverse.
diff --git a/packages/docusaurus-utils/src/urlUtils.ts b/packages/docusaurus-utils/src/urlUtils.ts
index 8a7af4aa4b6d..ddfc04272c36 100644
--- a/packages/docusaurus-utils/src/urlUtils.ts
+++ b/packages/docusaurus-utils/src/urlUtils.ts
@@ -6,7 +6,6 @@
  */
 
 import resolvePathnameUnsafe from 'resolve-pathname';
-import {addPrefix, addSuffix, removeSuffix} from './jsUtils';
 
 /**
  * Much like `path.join`, but much better. Takes an array of URL segments, and
@@ -232,22 +231,6 @@ export function resolvePathname(to: string, from?: string): string {
   return resolvePathnameUnsafe(to, from);
 }
 
-/** Appends a leading slash to `str`, if one doesn't exist. */
-export function addLeadingSlash(str: string): string {
-  return addPrefix(str, '/');
-}
-
-// TODO deduplicate: also present in @docusaurus/utils-common
-/** Appends a trailing slash to `str`, if one doesn't exist. */
-export function addTrailingSlash(str: string): string {
-  return addSuffix(str, '/');
-}
-
-/** Removes the trailing slash from `str`. */
-export function removeTrailingSlash(str: string): string {
-  return removeSuffix(str, '/');
-}
-
 /** Constructs an SSH URL that can be used to push to GitHub. */
 export function buildSshUrl(
   githubHost: string,
diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json
index 104ebb888fbc..6d47141a33c2 100644
--- a/packages/docusaurus/package.json
+++ b/packages/docusaurus/package.json
@@ -69,8 +69,8 @@
     "del": "^6.1.1",
     "detect-port": "^1.5.1",
     "escape-html": "^1.0.3",
-    "eval": "^0.1.8",
     "eta": "^2.2.0",
+    "eval": "^0.1.8",
     "file-loader": "^6.2.0",
     "fs-extra": "^11.1.1",
     "html-minifier-terser": "^7.2.0",
diff --git a/packages/docusaurus/src/server/brokenLinks.ts b/packages/docusaurus/src/server/brokenLinks.ts
index e3ad16926030..9e940f7b8c6b 100644
--- a/packages/docusaurus/src/server/brokenLinks.ts
+++ b/packages/docusaurus/src/server/brokenLinks.ts
@@ -9,13 +9,12 @@ import _ from 'lodash';
 import logger from '@docusaurus/logger';
 import {matchRoutes as reactRouterMatchRoutes} from 'react-router-config';
 import {
-  addTrailingSlash,
   parseURLPath,
-  removeTrailingSlash,
   serializeURLPath,
   flattenRoutes,
   type URLPath,
 } from '@docusaurus/utils';
+import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils-common';
 import type {RouteConfig, ReportingSeverity} from '@docusaurus/types';
 
 function matchRoutes(routeConfig: RouteConfig[], pathname: string) {
diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts
index eafabc3cedad..cf2d16aa59f5 100644
--- a/packages/docusaurus/src/server/configValidation.ts
+++ b/packages/docusaurus/src/server/configValidation.ts
@@ -9,11 +9,13 @@ import {
   DEFAULT_PARSE_FRONT_MATTER,
   DEFAULT_STATIC_DIR_NAME,
   DEFAULT_I18N_DIR_NAME,
-  addLeadingSlash,
-  addTrailingSlash,
-  removeTrailingSlash,
 } from '@docusaurus/utils';
 import {Joi, printWarning} from '@docusaurus/utils-validation';
+import {
+  addTrailingSlash,
+  addLeadingSlash,
+  removeTrailingSlash,
+} from '@docusaurus/utils-common';
 import type {
   DocusaurusConfig,
   I18nConfig,