aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/check-headings.js177
1 files changed, 177 insertions, 0 deletions
diff --git a/scripts/check-headings.js b/scripts/check-headings.js
new file mode 100644
index 0000000..355b5a2
--- /dev/null
+++ b/scripts/check-headings.js
@@ -0,0 +1,177 @@
+/**
+ * Checks all general question MDX files under ippan-situmon to find
+ * `####` sub-headings that are NOT in the QuestionSummary component.
+ */
+const fs = require("fs");
+const path = require("path");
+
+const BASE = path.resolve(__dirname, "..", "src/content/docs/ippan-situmon");
+
+function findMdxFiles(dir) {
+ const results = [];
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ results.push(...findMdxFiles(fullPath));
+ } else if (
+ entry.name.endsWith(".mdx") &&
+ entry.name !== "index.mdx" &&
+ entry.name !== "_partial.mdx"
+ ) {
+ results.push(fullPath);
+ }
+ }
+ return results;
+}
+
+// Parse anchor values from a QuestionSummary qa array
+function parseAnchors(content) {
+ const anchorRegex = /anchor:\s*"([^"]+)"/g;
+ const anchors = [];
+ let match;
+ while ((match = anchorRegex.exec(content)) !== null) {
+ anchors.push(match[1]);
+ }
+ return anchors;
+}
+
+// Parse `####` headings from MDX content
+function parseH4Headings(content) {
+ const headingRegex = /^####\s+(.+?)(?:\s*\{[^}]*\})?\s*$/gm;
+ const headings = [];
+ let match;
+ while ((match = headingRegex.exec(content)) !== null) {
+ const raw = match[1].trim();
+ headings.push(raw);
+ }
+ return headings;
+}
+
+// Generate Starlight-style anchor ID from heading text
+function toAnchorId(text) {
+ return text
+ .toLowerCase()
+ .replace(/<[^>]+>/g, "") // remove HTML tags
+ .replace(
+ /[^\w\s\-\u3040-\u309f\u30a0-\u30ff\u4e00-\u9fffぁ-んァ-ン一-龯ー・〜?!。、()「」【】]/g,
+ "",
+ )
+ .replace(/\s+/g, "-")
+ .replace(/-+/g, "-")
+ .replace(/^-|-$/g, "");
+}
+
+// Check if a heading matches any anchor
+function headingMatchesAnchor(heading, anchors) {
+ const headingId = toAnchorId(heading);
+
+ for (const anchor of anchors) {
+ // Exact match
+ if (anchor === heading) return true;
+
+ // Anchor ID match
+ if (anchor === headingId) return true;
+
+ // Anchor prefixed with "-"
+ if (anchor === `-${heading}`) return true;
+ if (anchor === `-${headingId}`) return true;
+
+ // Anchor that starts with "-" - check if headingId matches after "-"
+ if (anchor.startsWith("-")) {
+ const afterDash = anchor.substring(1);
+ if (afterDash === headingId || afterDash === heading) return true;
+ }
+
+ // Headings sometimes have HTML in them (like <sub>)
+ // Strip HTML from heading and compare to anchor
+ const headingPlain = heading.replace(/<[^>]+>/g, "");
+ const headingPlainId = toAnchorId(headingPlain);
+
+ if (anchor === headingPlain) return true;
+ if (anchor === headingPlainId) return true;
+ if (anchor === `-${headingPlain}`) return true;
+ if (anchor === `-${headingPlainId}`) return true;
+ if (anchor.startsWith("-") && anchor.substring(1) === headingPlainId)
+ return true;
+ }
+ return false;
+}
+
+function main() {
+ const files = findMdxFiles(BASE).sort();
+ const results = [];
+
+ for (const file of files) {
+ const content = fs.readFileSync(file, "utf-8");
+ const h4Headings = parseH4Headings(content);
+ const anchors = parseAnchors(content);
+
+ if (h4Headings.length === 0) continue;
+
+ const relPath = path.relative(process.cwd(), file);
+ const missing = [];
+
+ for (const heading of h4Headings) {
+ if (!headingMatchesAnchor(heading, anchors)) {
+ missing.push(heading);
+ }
+ }
+
+ results.push({
+ file: relPath,
+ totalH4: h4Headings.length,
+ totalAnchors: anchors.length,
+ missing,
+ allPresent: missing.length === 0,
+ });
+ }
+
+ // Output results
+ console.log("=".repeat(80));
+ console.log("ANALYSIS: #### headings NOT in QuestionSummary");
+ console.log("=".repeat(80));
+ console.log();
+
+ let filesWithMissing = 0;
+ let totalMissing = 0;
+
+ for (const r of results) {
+ if (r.missing.length === 0) continue;
+
+ filesWithMissing++;
+ totalMissing += r.missing.length;
+
+ if (r.totalAnchors === 0) {
+ console.log(`❌ ${r.file}`);
+ console.log(
+ ` Has ${r.totalH4} '####' headings but NO QuestionSummary component.`,
+ );
+ console.log();
+ continue;
+ }
+
+ console.log(
+ `⚠️ ${r.file} [${r.totalAnchors} anchors, ${r.missing.length}/${r.totalH4} headings missing]`,
+ );
+ for (const m of r.missing) {
+ console.log(` #### ${m}`);
+ }
+ console.log();
+ }
+
+ console.log("=".repeat(80));
+ console.log(`SUMMARY:`);
+ console.log(` Files with missing headings: ${filesWithMissing}`);
+ console.log(
+ ` Files entirely missing QuestionSummary: ${results.filter((r) => r.totalAnchors === 0 && r.totalH4 > 0).length}`,
+ );
+ console.log(
+ ` Files fully in sync: ${results.filter((r) => r.allPresent && r.totalAnchors > 0).length}`,
+ );
+ console.log(` Total missing headings: ${totalMissing}`);
+ console.log(` Total files analyzed: ${results.length}`);
+ console.log("=".repeat(80));
+}
+
+main();