From 68296f97acbe792549c1e0f0043527727d2eb901 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Fri, 14 Jun 2024 17:02:41 +0200
Subject: [PATCH 01/11] Add patterns index validation

---
 package.json                           |   1 +
 scripts/commands/checkPatternsIndex.ts | 183 +++++++++++++++++++++++++
 2 files changed, 184 insertions(+)
 create mode 100644 scripts/commands/checkPatternsIndex.ts

diff --git a/package.json b/package.json
index c0083f9d93f..a3dcededb9f 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "check:internal-links": "node -r esbuild-register scripts/commands/checkInternalLinks.ts",
     "check:external-links": "node -r esbuild-register scripts/commands/checkExternalLinks.ts",
     "check:pages-render": "node -r esbuild-register scripts/commands/checkPagesRender.ts",
+    "check:patterns-index": "node -r esbuild-register scripts/commands/checkPatternsIndex.ts",
     "check:orphan-pages": "node -r esbuild-register scripts/commands/checkOrphanPages.ts",
     "check:qiskit-bot": "node -r esbuild-register scripts/commands/checkQiskitBotFiles.ts",
     "check:stale-images": "node -r esbuild-register scripts/commands/checkStaleImages.ts",
diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
new file mode 100644
index 00000000000..bb9b904fbaa
--- /dev/null
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -0,0 +1,183 @@
+// This code is a Qiskit project.
+//
+// (C) Copyright IBM 2024.
+//
+// This code is licensed under the Apache License, Version 2.0. You may
+// obtain a copy of this license in the LICENSE file in the root directory
+// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+//
+// Any modifications or derivative works of this code must retain this
+// copyright notice, and modified files need to carry a notice indicating
+// that they have been altered from the originals.
+
+import { readFile } from "fs/promises";
+
+const IGNORE_URL = ["/guides/qiskit-serverless"];
+
+const INDEX_PAGES = [
+  "docs/guides/map-problem-to-circuits.mdx",
+  "docs/guides/optimize-for-hardware.mdx",
+  "docs/guides/execute-on-hardware.mdx",
+  "docs/guides/postprocess-results.mdx",
+];
+
+const TOC_PATH = `docs/guides/_toc.json`;
+
+async function getIndexPages(indexPage: string): Promise<string[]> {
+  const rawIndex = await readFile(indexPage, "utf-8");
+  const result: string[] = [];
+  for (const line of rawIndex.split("\n")) {
+    // The index represents an unordered list of entries starting with `*`.
+    if (!line.trimStart().startsWith("* ")) {
+      continue;
+    }
+
+    const module = extractPageSlug(line);
+    if (module) {
+      result.push(module);
+    }
+  }
+
+  return result;
+}
+
+function extractPageSlug(text: string): string | undefined {
+  const re = /\((.*)\)/gm;
+  // Ex: '* [Circuit library](./circuit-library)'.
+  const match = re.exec(text);
+  if (!match) {
+    return;
+  }
+  const pageSlug = match[1];
+  if (pageSlug.startsWith("http") || pageSlug.startsWith("/")) {
+    return pageSlug;
+  }
+  return `/guides/${pageSlug.split("/").pop()}`;
+}
+
+function getTocSectionPageNames(sectionNode: any): string[] {
+  let results = [];
+  if (sectionNode.url) {
+    results.push(sectionNode.url);
+  }
+
+  if (sectionNode.children) {
+    for (const child of sectionNode.children) {
+      results = [...results, ...getTocSectionPageNames(child)];
+    }
+  }
+
+  return results;
+}
+
+async function getToolsTocEntriesToCheck(): Promise<string[]> {
+  const toc = JSON.parse(await readFile(TOC_PATH, "utf-8"));
+  const toolsNode = toc.children.find((child: any) => child.title == "Tools");
+  const toolsPages = getTocSectionPageNames(toolsNode);
+  return toolsPages.filter((page) => !IGNORE_URL.includes(page));
+}
+
+async function getPagesAndDuplicatesErrors(
+  src: string,
+  entries: string[],
+): Promise<[string[], string[]]> {
+  const toolsPages: string[] = [];
+  const errors = [];
+  for (const entry of entries) {
+    if (toolsPages.includes(entry)) {
+      errors.push(`❌ ${src}: The entry ${entry} is duplicated`);
+    } else {
+      toolsPages.push(entry);
+    }
+  }
+
+  return [toolsPages, errors];
+}
+
+function printErrors(
+  duplicatesErrors: string[],
+  extraIndexEntriesErrors: string[],
+  extraToolsEntriesErrors: string[],
+): void {
+  if (duplicatesErrors.length > 0) {
+    duplicatesErrors.forEach((error) => console.error(error));
+    console.error(`\nRemove all duplicated entries on the indices.`);
+    console.error("--------\n");
+  }
+
+  if (extraIndexEntriesErrors.length > 0) {
+    extraIndexEntriesErrors.forEach((error) => console.error(error));
+    console.error(`\nMake sure all pages have an entry in the Tools menu.`);
+    console.error("--------\n");
+  }
+
+  if (extraToolsEntriesErrors.length > 0) {
+    extraToolsEntriesErrors.forEach((error) => console.error(error));
+    console.error(
+      "\nAdd the entries in one of the following index pages, or add the URL to the `IGNORE_URL` list at the beginning of `/scripts/commands/checkPatternsIndex.tsx` if it's not used in Workflow:",
+    );
+    INDEX_PAGES.forEach((index) => console.error(`\t➡️  ${index}`));
+  }
+
+  if (
+    duplicatesErrors.length > 0 ||
+    extraIndexEntriesErrors.length > 0 ||
+    extraToolsEntriesErrors.length > 0
+  ) {
+    process.exit(1);
+  }
+}
+
+async function main() {
+  const duplicatesErrors: string[] = [];
+  const extraIndexEntriesErrors: string[] = [];
+  const extraToolsEntriesErrors: string[] = [];
+
+  const toolsEntries = await getToolsTocEntriesToCheck();
+  let [toolsPages, toolsErrors] = await getPagesAndDuplicatesErrors(
+    TOC_PATH,
+    toolsEntries,
+  );
+  duplicatesErrors.push(...toolsErrors);
+
+  for (const indexPage of INDEX_PAGES) {
+    const indexEntries = await getIndexPages(indexPage);
+    let [indexPages, indexErrors] = await getPagesAndDuplicatesErrors(
+      indexPage,
+      indexEntries,
+    );
+    duplicatesErrors.push(...indexErrors);
+
+    const ExtraIndexPages = indexPages.filter(
+      (page) => !toolsPages.includes(page),
+    );
+    if (ExtraIndexPages.length > 0) {
+      ExtraIndexPages.forEach((page) =>
+        extraIndexEntriesErrors.push(
+          `❌ ${indexPage}: The entry ${page} doesn't appear in the \`Tools\` menu.`,
+        ),
+      );
+    }
+
+    toolsPages = toolsPages.filter((page) => !indexPages.includes(page));
+  }
+
+  if (toolsPages.length > 0) {
+    toolsPages.forEach((page) =>
+      extraToolsEntriesErrors.push(
+        `❌ The entry ${page} is not present on any index page`,
+      ),
+    );
+  }
+
+  printErrors(
+    duplicatesErrors,
+    extraIndexEntriesErrors,
+    extraToolsEntriesErrors,
+  );
+  console.log("\n✅ No missing or duplicated pages were found\n");
+}
+
+main().then(() => process.exit());
+
+// TODO: Allow that the guides/ folder don't exist

From b0e6b834d4b77c99859c0687954820d64cbfb3b9 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Fri, 14 Jun 2024 18:13:05 +0200
Subject: [PATCH 02/11] rename functions and add comments

---
 scripts/commands/checkPatternsIndex.ts | 27 +++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index bb9b904fbaa..13fa7352796 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -23,7 +23,7 @@ const INDEX_PAGES = [
 
 const TOC_PATH = `docs/guides/_toc.json`;
 
-async function getIndexPages(indexPage: string): Promise<string[]> {
+async function getIndexEntries(indexPage: string): Promise<string[]> {
   const rawIndex = await readFile(indexPage, "utf-8");
   const result: string[] = [];
   for (const line of rawIndex.split("\n")) {
@@ -77,7 +77,7 @@ async function getToolsTocEntriesToCheck(): Promise<string[]> {
   return toolsPages.filter((page) => !IGNORE_URL.includes(page));
 }
 
-async function getPagesAndDuplicatesErrors(
+async function deduplicateEntriesAndGetErrors(
   src: string,
   entries: string[],
 ): Promise<[string[], string[]]> {
@@ -133,23 +133,23 @@ async function main() {
   const extraIndexEntriesErrors: string[] = [];
   const extraToolsEntriesErrors: string[] = [];
 
-  const toolsEntries = await getToolsTocEntriesToCheck();
-  let [toolsPages, toolsErrors] = await getPagesAndDuplicatesErrors(
+  const allToolsEntries = await getToolsTocEntriesToCheck();
+  let [toolsEntries, toolsErrors] = await deduplicateEntriesAndGetErrors(
     TOC_PATH,
-    toolsEntries,
+    allToolsEntries,
   );
   duplicatesErrors.push(...toolsErrors);
 
   for (const indexPage of INDEX_PAGES) {
-    const indexEntries = await getIndexPages(indexPage);
-    let [indexPages, indexErrors] = await getPagesAndDuplicatesErrors(
+    const allIndexEntries = await getIndexEntries(indexPage);
+    let [indexEntries, indexErrors] = await deduplicateEntriesAndGetErrors(
       indexPage,
-      indexEntries,
+      allIndexEntries,
     );
     duplicatesErrors.push(...indexErrors);
 
-    const ExtraIndexPages = indexPages.filter(
-      (page) => !toolsPages.includes(page),
+    const ExtraIndexPages = indexEntries.filter(
+      (page) => !toolsEntries.includes(page),
     );
     if (ExtraIndexPages.length > 0) {
       ExtraIndexPages.forEach((page) =>
@@ -159,11 +159,12 @@ async function main() {
       );
     }
 
-    toolsPages = toolsPages.filter((page) => !indexPages.includes(page));
+    // Remove index entries from the tools entries list
+    toolsEntries = toolsEntries.filter((page) => !indexEntries.includes(page));
   }
 
-  if (toolsPages.length > 0) {
-    toolsPages.forEach((page) =>
+  if (toolsEntries.length > 0) {
+    toolsEntries.forEach((page) =>
       extraToolsEntriesErrors.push(
         `❌ The entry ${page} is not present on any index page`,
       ),

From 511922c30a3e7b9f3dc7b8d51782d5821b6e4455 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Fri, 14 Jun 2024 18:27:07 +0200
Subject: [PATCH 03/11] skip check if reorg is not done

---
 .github/workflows/main.yml             |  2 ++
 package.json                           |  2 +-
 scripts/commands/checkPatternsIndex.ts | 14 ++++++++++++--
 3 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 734b31ec92f..6ff86b63a2c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -32,6 +32,8 @@ jobs:
         run: npm run check:spelling
       - name: Check Qiskit bot config
         run: npm run check:qiskit-bot
+      - name: Check Patterns index
+        run: npm run check:patterns-index
       - name: Internal link checker
         run: npm run check:internal-links
       - name: Check orphaned pages
diff --git a/package.json b/package.json
index a3dcededb9f..302c2e9175c 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
   "author": "Qiskit Development Team",
   "license": "Apache-2.0",
   "scripts": {
-    "check": "npm run check:qiskit-bot && npm run check:metadata && npm run check:spelling && npm run check:internal-links && npm run check:orphan-pages && npm run check:fmt",
+    "check": "npm run check:qiskit-bot && npm run check:patterns-index && npm run check:metadata && npm run check:spelling && npm run check:internal-links && npm run check:orphan-pages && npm run check:fmt",
     "check:metadata": "node -r esbuild-register scripts/commands/checkMetadata.ts",
     "check:spelling": "cspell --relative --no-progress docs/**/*.md* docs/api/**/*.md* --config cspell/cSpell.json",
     "check:fmt": "prettier --check .",
diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index 13fa7352796..f22045a717f 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -11,6 +11,7 @@
 // that they have been altered from the originals.
 
 import { readFile } from "fs/promises";
+import { pathExists } from "../lib/fs";
 
 const IGNORE_URL = ["/guides/qiskit-serverless"];
 
@@ -94,7 +95,7 @@ async function deduplicateEntriesAndGetErrors(
   return [toolsPages, errors];
 }
 
-function printErrors(
+function maybePrintErrorsAndFail(
   duplicatesErrors: string[],
   extraIndexEntriesErrors: string[],
   extraToolsEntriesErrors: string[],
@@ -129,6 +130,15 @@ function printErrors(
 }
 
 async function main() {
+  // Todo: Remove this conditional once the migration is done. This is used only to avoid
+  // the script crashing if the file's structure doesn't exist.
+  if (!(await pathExists(TOC_PATH))) {
+    console.log(
+      `🚧 Check skipped because the migration hasn't been completed.\n`,
+    );
+    process.exit(0);
+  }
+
   const duplicatesErrors: string[] = [];
   const extraIndexEntriesErrors: string[] = [];
   const extraToolsEntriesErrors: string[] = [];
@@ -171,7 +181,7 @@ async function main() {
     );
   }
 
-  printErrors(
+  maybePrintErrorsAndFail(
     duplicatesErrors,
     extraIndexEntriesErrors,
     extraToolsEntriesErrors,

From 409887345eeb18e59ea06ee3a1245145c8745f93 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Fri, 14 Jun 2024 19:47:57 +0200
Subject: [PATCH 04/11] refactor script

---
 scripts/commands/checkPatternsIndex.ts | 104 +++++++++++++++----------
 1 file changed, 62 insertions(+), 42 deletions(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index f22045a717f..580575d2cd3 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -33,9 +33,9 @@ async function getIndexEntries(indexPage: string): Promise<string[]> {
       continue;
     }
 
-    const module = extractPageSlug(line);
-    if (module) {
-      result.push(module);
+    const slug = extractPageSlug(line);
+    if (slug) {
+      result.push(slug);
     }
   }
 
@@ -47,7 +47,8 @@ function extractPageSlug(text: string): string | undefined {
   // Ex: '* [Circuit library](./circuit-library)'.
   const match = re.exec(text);
   if (!match) {
-    return;
+    // Nested sections don't have any link
+    return undefined;
   }
   const pageSlug = match[1];
   if (pageSlug.startsWith("http") || pageSlug.startsWith("/")) {
@@ -78,21 +79,54 @@ async function getToolsTocEntriesToCheck(): Promise<string[]> {
   return toolsPages.filter((page) => !IGNORE_URL.includes(page));
 }
 
-async function deduplicateEntriesAndGetErrors(
+async function getDeduplicateEntriesAndAddErrors(
   src: string,
-  entries: string[],
-): Promise<[string[], string[]]> {
-  const toolsPages: string[] = [];
-  const errors = [];
+  errors: string[],
+): Promise<string[]> {
+  const entries =
+    src == TOC_PATH
+      ? await getToolsTocEntriesToCheck()
+      : await getIndexEntries(src);
+  const deduplicatedPages: string[] = [];
+
   for (const entry of entries) {
-    if (toolsPages.includes(entry)) {
+    if (deduplicatedPages.includes(entry)) {
       errors.push(`❌ ${src}: The entry ${entry} is duplicated`);
     } else {
-      toolsPages.push(entry);
+      deduplicatedPages.push(entry);
     }
   }
 
-  return [toolsPages, errors];
+  return deduplicatedPages;
+}
+
+function addExtraIndexPagesErrors(
+  indexPage: string,
+  indexEntries: string[],
+  toolsEntries: string[],
+  errors: string[],
+): void {
+  const ExtraIndexPages = indexEntries.filter(
+    (page) => !toolsEntries.includes(page),
+  );
+  if (ExtraIndexPages.length > 0) {
+    ExtraIndexPages.forEach((page) =>
+      errors.push(
+        `❌ ${indexPage}: The entry ${page} doesn't appear in the \`Tools\` menu.`,
+      ),
+    );
+  }
+}
+
+function addExtraToolsEntriesErrors(
+  toolsEntries: string[],
+  errors: string[],
+): void {
+  if (toolsEntries.length > 0) {
+    toolsEntries.forEach((page) =>
+      errors.push(`❌ The entry ${page} is not present on any index page`),
+    );
+  }
 }
 
 function maybePrintErrorsAndFail(
@@ -100,16 +134,20 @@ function maybePrintErrorsAndFail(
   extraIndexEntriesErrors: string[],
   extraToolsEntriesErrors: string[],
 ): void {
+  let allGood = true;
+
   if (duplicatesErrors.length > 0) {
     duplicatesErrors.forEach((error) => console.error(error));
     console.error(`\nRemove all duplicated entries on the indices.`);
     console.error("--------\n");
+    allGood = false;
   }
 
   if (extraIndexEntriesErrors.length > 0) {
     extraIndexEntriesErrors.forEach((error) => console.error(error));
     console.error(`\nMake sure all pages have an entry in the Tools menu.`);
     console.error("--------\n");
+    allGood = false;
   }
 
   if (extraToolsEntriesErrors.length > 0) {
@@ -118,13 +156,10 @@ function maybePrintErrorsAndFail(
       "\nAdd the entries in one of the following index pages, or add the URL to the `IGNORE_URL` list at the beginning of `/scripts/commands/checkPatternsIndex.tsx` if it's not used in Workflow:",
     );
     INDEX_PAGES.forEach((index) => console.error(`\t➡️  ${index}`));
+    allGood = false;
   }
 
-  if (
-    duplicatesErrors.length > 0 ||
-    extraIndexEntriesErrors.length > 0 ||
-    extraToolsEntriesErrors.length > 0
-  ) {
+  if (!allGood) {
     process.exit(1);
   }
 }
@@ -143,43 +178,28 @@ async function main() {
   const extraIndexEntriesErrors: string[] = [];
   const extraToolsEntriesErrors: string[] = [];
 
-  const allToolsEntries = await getToolsTocEntriesToCheck();
-  let [toolsEntries, toolsErrors] = await deduplicateEntriesAndGetErrors(
+  let toolsEntries = await getDeduplicateEntriesAndAddErrors(
     TOC_PATH,
-    allToolsEntries,
+    duplicatesErrors,
   );
-  duplicatesErrors.push(...toolsErrors);
 
   for (const indexPage of INDEX_PAGES) {
-    const allIndexEntries = await getIndexEntries(indexPage);
-    let [indexEntries, indexErrors] = await deduplicateEntriesAndGetErrors(
+    const indexEntries = await getDeduplicateEntriesAndAddErrors(
       indexPage,
-      allIndexEntries,
+      duplicatesErrors,
     );
-    duplicatesErrors.push(...indexErrors);
-
-    const ExtraIndexPages = indexEntries.filter(
-      (page) => !toolsEntries.includes(page),
+    addExtraIndexPagesErrors(
+      indexPage,
+      indexEntries,
+      toolsEntries,
+      extraIndexEntriesErrors,
     );
-    if (ExtraIndexPages.length > 0) {
-      ExtraIndexPages.forEach((page) =>
-        extraIndexEntriesErrors.push(
-          `❌ ${indexPage}: The entry ${page} doesn't appear in the \`Tools\` menu.`,
-        ),
-      );
-    }
 
     // Remove index entries from the tools entries list
     toolsEntries = toolsEntries.filter((page) => !indexEntries.includes(page));
   }
 
-  if (toolsEntries.length > 0) {
-    toolsEntries.forEach((page) =>
-      extraToolsEntriesErrors.push(
-        `❌ The entry ${page} is not present on any index page`,
-      ),
-    );
-  }
+  addExtraToolsEntriesErrors(toolsEntries, extraToolsEntriesErrors);
 
   maybePrintErrorsAndFail(
     duplicatesErrors,

From 51fa929a2dd8dbcae7607a8c0d9051275ff3a7a0 Mon Sep 17 00:00:00 2001
From: Arnau Casau <47946624+arnaucasau@users.noreply.github.com>
Date: Mon, 17 Jun 2024 12:11:32 +0200
Subject: [PATCH 05/11] Remove todo

---
 scripts/commands/checkPatternsIndex.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index 580575d2cd3..7e608c6d110 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -210,5 +210,3 @@ async function main() {
 }
 
 main().then(() => process.exit());
-
-// TODO: Allow that the guides/ folder don't exist

From 18bce22f6d7d3aa7c7b62b03cabdab8a09311448 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Tue, 18 Jun 2024 15:28:53 +0200
Subject: [PATCH 06/11] incorporate feedback

---
 scripts/commands/checkPatternsIndex.ts | 120 ++++++++++++-------------
 1 file changed, 59 insertions(+), 61 deletions(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index 7e608c6d110..e4af7406137 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -12,8 +12,9 @@
 
 import { readFile } from "fs/promises";
 import { pathExists } from "../lib/fs";
+import type { TocEntry } from "../lib/api/generateToc";
 
-const IGNORE_URL = ["/guides/qiskit-serverless"];
+const IGNORED_URLS = ["/guides/qiskit-serverless"];
 
 const INDEX_PAGES = [
   "docs/guides/map-problem-to-circuits.mdx",
@@ -22,13 +23,15 @@ const INDEX_PAGES = [
   "docs/guides/postprocess-results.mdx",
 ];
 
-const TOC_PATH = `docs/guides/_toc.json`;
+const TOC_PATH = "docs/guides/_toc.json";
 
-async function getIndexEntries(indexPage: string): Promise<string[]> {
-  const rawIndex = await readFile(indexPage, "utf-8");
+async function getIndexEntries(indexPath: string): Promise<string[]> {
+  const rawIndex = await readFile(indexPath, "utf-8");
   const result: string[] = [];
+  // The index page has several unordered lists starting with *, each with links to other pages.
+  // We want to get every slug from those lists. Note that we don't care which list the slugs show
+  // up in - we only care if the slug shows up at all anywhere.
   for (const line of rawIndex.split("\n")) {
-    // The index represents an unordered list of entries starting with `*`.
     if (!line.trimStart().startsWith("* ")) {
       continue;
     }
@@ -54,10 +57,11 @@ function extractPageSlug(text: string): string | undefined {
   if (pageSlug.startsWith("http") || pageSlug.startsWith("/")) {
     return pageSlug;
   }
-  return `/guides/${pageSlug.split("/").pop()}`;
+  const page = pageSlug.split("/").pop();
+  return `/guides/${page}`;
 }
 
-function getTocSectionPageNames(sectionNode: any): string[] {
+function getTocSectionPageNames(sectionNode: TocEntry): string[] {
   let results = [];
   if (sectionNode.url) {
     results.push(sectionNode.url);
@@ -74,59 +78,50 @@ function getTocSectionPageNames(sectionNode: any): string[] {
 
 async function getToolsTocEntriesToCheck(): Promise<string[]> {
   const toc = JSON.parse(await readFile(TOC_PATH, "utf-8"));
-  const toolsNode = toc.children.find((child: any) => child.title == "Tools");
+  const toolsNode = toc.children.find(
+    (child: TocEntry) => child.title == "Tools",
+  );
   const toolsPages = getTocSectionPageNames(toolsNode);
-  return toolsPages.filter((page) => !IGNORE_URL.includes(page));
+  return toolsPages.filter((page) => !IGNORED_URLS.includes(page));
 }
 
-async function getDeduplicateEntriesAndAddErrors(
+async function deduplicateEntries(
   src: string,
-  errors: string[],
-): Promise<string[]> {
-  const entries =
-    src == TOC_PATH
-      ? await getToolsTocEntriesToCheck()
-      : await getIndexEntries(src);
-  const deduplicatedPages: string[] = [];
+  entries: string[],
+): Promise<[Set<string>, string[]]> {
+  const deduplicatedPages: Set<string> = new Set();
+  const errors: string[] = [];
 
   for (const entry of entries) {
-    if (deduplicatedPages.includes(entry)) {
+    if (deduplicatedPages.has(entry)) {
       errors.push(`❌ ${src}: The entry ${entry} is duplicated`);
     } else {
-      deduplicatedPages.push(entry);
+      deduplicatedPages.add(entry);
     }
   }
 
-  return deduplicatedPages;
+  return [deduplicatedPages, errors];
 }
 
-function addExtraIndexPagesErrors(
+function getExtraIndexPagesErrors(
   indexPage: string,
-  indexEntries: string[],
-  toolsEntries: string[],
-  errors: string[],
-): void {
-  const ExtraIndexPages = indexEntries.filter(
-    (page) => !toolsEntries.includes(page),
+  indexEntries: Set<string>,
+  toolsEntries: Set<string>,
+): string[] {
+  const extraIndexPages = [...indexEntries].filter(
+    (page) => !toolsEntries.has(page),
+  );
+
+  return extraIndexPages.map(
+    (page) =>
+      `❌ ${indexPage}: The entry ${page} doesn't appear in the \`Tools\` menu.`,
   );
-  if (ExtraIndexPages.length > 0) {
-    ExtraIndexPages.forEach((page) =>
-      errors.push(
-        `❌ ${indexPage}: The entry ${page} doesn't appear in the \`Tools\` menu.`,
-      ),
-    );
-  }
 }
 
-function addExtraToolsEntriesErrors(
-  toolsEntries: string[],
-  errors: string[],
-): void {
-  if (toolsEntries.length > 0) {
-    toolsEntries.forEach((page) =>
-      errors.push(`❌ The entry ${page} is not present on any index page`),
-    );
-  }
+function getExtraToolsEntriesErrors(toolsEntries: Set<string>): string[] {
+  return [...toolsEntries].map(
+    (page) => `❌ The entry ${page} is not present on any index page`,
+  );
 }
 
 function maybePrintErrorsAndFail(
@@ -138,7 +133,9 @@ function maybePrintErrorsAndFail(
 
   if (duplicatesErrors.length > 0) {
     duplicatesErrors.forEach((error) => console.error(error));
-    console.error(`\nRemove all duplicated entries on the indices.`);
+    console.error(
+      `\nRemove all duplicated entries on the indices and Tools menu, which is set in docs/guides/_toc.json.`,
+    );
     console.error("--------\n");
     allGood = false;
   }
@@ -153,7 +150,7 @@ function maybePrintErrorsAndFail(
   if (extraToolsEntriesErrors.length > 0) {
     extraToolsEntriesErrors.forEach((error) => console.error(error));
     console.error(
-      "\nAdd the entries in one of the following index pages, or add the URL to the `IGNORE_URL` list at the beginning of `/scripts/commands/checkPatternsIndex.tsx` if it's not used in Workflow:",
+      "\nAdd the entries in one of the following index pages, or add the URL to the `IGNORED_URLS` list at the beginning of `/scripts/commands/checkPatternsIndex.tsx` if it's not used in Workflow:",
     );
     INDEX_PAGES.forEach((index) => console.error(`\t➡️  ${index}`));
     allGood = false;
@@ -174,32 +171,33 @@ async function main() {
     process.exit(0);
   }
 
-  const duplicatesErrors: string[] = [];
-  const extraIndexEntriesErrors: string[] = [];
-  const extraToolsEntriesErrors: string[] = [];
-
-  let toolsEntries = await getDeduplicateEntriesAndAddErrors(
+  const toolsAllEntries = await getToolsTocEntriesToCheck();
+  let [toolsEntries, duplicatesErrors] = await deduplicateEntries(
     TOC_PATH,
-    duplicatesErrors,
+    toolsAllEntries,
   );
 
+  let extraIndexEntriesErrors: string[] = [];
   for (const indexPage of INDEX_PAGES) {
-    const indexEntries = await getDeduplicateEntriesAndAddErrors(
+    const indexAllEntries = await getIndexEntries(indexPage);
+    let [indexEntries, indexDuplicatedErrors] = await deduplicateEntries(
       indexPage,
-      duplicatesErrors,
-    );
-    addExtraIndexPagesErrors(
-      indexPage,
-      indexEntries,
-      toolsEntries,
-      extraIndexEntriesErrors,
+      indexAllEntries,
     );
+    duplicatesErrors = [...duplicatesErrors, ...indexDuplicatedErrors];
+
+    extraIndexEntriesErrors = [
+      ...extraIndexEntriesErrors,
+      ...getExtraIndexPagesErrors(indexPage, indexEntries, toolsEntries),
+    ];
 
     // Remove index entries from the tools entries list
-    toolsEntries = toolsEntries.filter((page) => !indexEntries.includes(page));
+    toolsEntries = new Set(
+      [...toolsEntries].filter((page) => !indexEntries.has(page)),
+    );
   }
 
-  addExtraToolsEntriesErrors(toolsEntries, extraToolsEntriesErrors);
+  const extraToolsEntriesErrors = getExtraToolsEntriesErrors(toolsEntries);
 
   maybePrintErrorsAndFail(
     duplicatesErrors,

From 9d7a7b0341788e8b8af83a66b9f104c2dd30f696 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Tue, 18 Jun 2024 15:31:30 +0200
Subject: [PATCH 07/11] comment

---
 scripts/commands/checkPatternsIndex.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index e4af7406137..c5fdd520a71 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -134,7 +134,7 @@ function maybePrintErrorsAndFail(
   if (duplicatesErrors.length > 0) {
     duplicatesErrors.forEach((error) => console.error(error));
     console.error(
-      `\nRemove all duplicated entries on the indices and Tools menu, which is set in docs/guides/_toc.json.`,
+      `\nRemove all duplicated entries on the indices and in the Tools menu, which is set in docs/guides/_toc.json.`,
     );
     console.error("--------\n");
     allGood = false;

From a56ce22f36495b7abfd4f5526efdd20d460891ca Mon Sep 17 00:00:00 2001
From: Arnau Casau <47946624+arnaucasau@users.noreply.github.com>
Date: Tue, 18 Jun 2024 15:32:01 +0200
Subject: [PATCH 08/11] Update scripts/commands/checkPatternsIndex.ts

Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
---
 scripts/commands/checkPatternsIndex.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index c5fdd520a71..50feb924829 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -142,7 +142,7 @@ function maybePrintErrorsAndFail(
 
   if (extraIndexEntriesErrors.length > 0) {
     extraIndexEntriesErrors.forEach((error) => console.error(error));
-    console.error(`\nMake sure all pages have an entry in the Tools menu.`);
+    console.error(`\nMake sure all pages have an entry in the Tools menu, which is set in docs/guides/_toc.json.`);
     console.error("--------\n");
     allGood = false;
   }

From 2a41ee71fe234c08e55b1c43e0309690964840d8 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Tue, 18 Jun 2024 15:37:50 +0200
Subject: [PATCH 09/11] lint

---
 scripts/commands/checkPatternsIndex.ts | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index 50feb924829..0c4db3c5c4f 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -142,7 +142,9 @@ function maybePrintErrorsAndFail(
 
   if (extraIndexEntriesErrors.length > 0) {
     extraIndexEntriesErrors.forEach((error) => console.error(error));
-    console.error(`\nMake sure all pages have an entry in the Tools menu, which is set in docs/guides/_toc.json.`);
+    console.error(
+      `\nMake sure all pages have an entry in the Tools menu, which is set in docs/guides/_toc.json.`,
+    );
     console.error("--------\n");
     allGood = false;
   }

From a6f0e183e332245d67ebd10c9c113b27ff384034 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Tue, 18 Jun 2024 21:05:53 +0200
Subject: [PATCH 10/11] feedback

Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
---
 scripts/commands/checkPatternsIndex.ts | 33 +++++++++++++-------------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index 0c4db3c5c4f..faedc7e1b58 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -69,7 +69,7 @@ function getTocSectionPageNames(sectionNode: TocEntry): string[] {
 
   if (sectionNode.children) {
     for (const child of sectionNode.children) {
-      results = [...results, ...getTocSectionPageNames(child)];
+      results.push(...getTocSectionPageNames(child));
     }
   }
 
@@ -86,7 +86,7 @@ async function getToolsTocEntriesToCheck(): Promise<string[]> {
 }
 
 async function deduplicateEntries(
-  src: string,
+  filePath: string,
   entries: string[],
 ): Promise<[Set<string>, string[]]> {
   const deduplicatedPages: Set<string> = new Set();
@@ -94,7 +94,7 @@ async function deduplicateEntries(
 
   for (const entry of entries) {
     if (deduplicatedPages.has(entry)) {
-      errors.push(`❌ ${src}: The entry ${entry} is duplicated`);
+      errors.push(`❌ ${filePath}: The entry ${entry} is duplicated`);
     } else {
       deduplicatedPages.add(entry);
     }
@@ -108,18 +108,18 @@ function getExtraIndexPagesErrors(
   indexEntries: Set<string>,
   toolsEntries: Set<string>,
 ): string[] {
-  const extraIndexPages = [...indexEntries].filter(
-    (page) => !toolsEntries.has(page),
-  );
-
-  return extraIndexPages.map(
-    (page) =>
-      `❌ ${indexPage}: The entry ${page} doesn't appear in the \`Tools\` menu.`,
-  );
+  return [...indexEntries]
+    .filter((page) => !toolsEntries.has(page))
+    .map(
+      (page) =>
+        `❌ ${indexPage}: The entry ${page} doesn't appear in the \`Tools\` menu.`,
+    );
 }
 
-function getExtraToolsEntriesErrors(toolsEntries: Set<string>): string[] {
-  return [...toolsEntries].map(
+function getExtraToolsEntriesErrors(
+  remainingToolsEntries: Set<string>,
+): string[] {
+  return [...remainingToolsEntries].map(
     (page) => `❌ The entry ${page} is not present on any index page`,
   );
 }
@@ -186,12 +186,11 @@ async function main() {
       indexPage,
       indexAllEntries,
     );
-    duplicatesErrors = [...duplicatesErrors, ...indexDuplicatedErrors];
+    duplicatesErrors.push(...indexDuplicatedErrors);
 
-    extraIndexEntriesErrors = [
-      ...extraIndexEntriesErrors,
+    extraIndexEntriesErrors.push(
       ...getExtraIndexPagesErrors(indexPage, indexEntries, toolsEntries),
-    ];
+    );
 
     // Remove index entries from the tools entries list
     toolsEntries = new Set(

From c3ff441a988ac60cfa710b661e5cb84a646a9a33 Mon Sep 17 00:00:00 2001
From: Arnau Casau <arnaucasau@gmail.com>
Date: Tue, 18 Jun 2024 21:22:58 +0200
Subject: [PATCH 11/11] use delete in the toolsEntries Set

---
 scripts/commands/checkPatternsIndex.ts | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/scripts/commands/checkPatternsIndex.ts b/scripts/commands/checkPatternsIndex.ts
index faedc7e1b58..4675040d289 100644
--- a/scripts/commands/checkPatternsIndex.ts
+++ b/scripts/commands/checkPatternsIndex.ts
@@ -192,10 +192,11 @@ async function main() {
       ...getExtraIndexPagesErrors(indexPage, indexEntries, toolsEntries),
     );
 
-    // Remove index entries from the tools entries list
-    toolsEntries = new Set(
-      [...toolsEntries].filter((page) => !indexEntries.has(page)),
-    );
+    toolsEntries.forEach((page) => {
+      if (indexEntries.has(page)) {
+        toolsEntries.delete(page);
+      }
+    });
   }
 
   const extraToolsEntriesErrors = getExtraToolsEntriesErrors(toolsEntries);