Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/generators/legacy-html/utils/__tests__/slugger.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { createLegacySlugger } from '../slugger.mjs';

describe('createLegacySlugger', () => {
it('prefixes with api stem and uses underscores', () => {
const getLegacySlug = createLegacySlugger();
assert.strictEqual(getLegacySlug('File System', 'fs'), 'fs_file_system');
});

it('replaces special characters with underscores', () => {
const getLegacySlug = createLegacySlugger();
assert.strictEqual(
getLegacySlug('fs.readFile(path)', 'fs'),
'fs_fs_readfile_path'
);
});

it('strips leading and trailing underscores', () => {
const getLegacySlug = createLegacySlugger();
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello');
});

it('prefixes with underscore when result starts with non-alpha', () => {
const getLegacySlug = createLegacySlugger();
assert.strictEqual(getLegacySlug('123 test', '0num'), '_0num_123_test');
});

it('deduplicates with a counter for identical titles', () => {
const getLegacySlug = createLegacySlugger();
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello');
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_1');
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_2');
assert.strictEqual(getLegacySlug('World', 'fs'), 'fs_world');
});
});
16 changes: 14 additions & 2 deletions src/generators/legacy-html/utils/buildContent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { u as createTree } from 'unist-builder';
import { SKIP, visit } from 'unist-util-visit';

import buildExtraContent from './buildExtraContent.mjs';
import { createLegacySlugger } from './slugger.mjs';
import getConfig from '../../../utils/configuration/index.mjs';
import {
GITHUB_BLOB_URL,
Expand All @@ -20,12 +21,14 @@ import { QUERIES, UNIST } from '../../../utils/queries/index.mjs';
* @param {import('unist').Parent} parent The parent node of the current node
* @returns {import('hast').Element} The HTML AST tree of the heading content
*/
const buildHeading = ({ data, children, depth }, index, parent) => {
const buildHeading = ({ data, children, depth }, index, parent, legacySlug) => {
// Creates the heading element with the heading text and the link to the heading
const headingElement = createElement(`h${depth + 1}`, [
// The inner Heading markdown content is still using Remark nodes, and they need
// to be converted into Rehype nodes
...children,
// Legacy anchor alias to preserve old external links
createElement('span', createElement(`a#${legacySlug}`)),
// Creates the element that references the link to the heading
// (The `#` anchor on the right of each Heading section)
createElement(
Expand Down Expand Up @@ -220,6 +223,8 @@ const buildMetadataElement = (node, remark) => {
* @param {import('unified').Processor} remark The Remark instance to be used to process
*/
export default (headNodes, metadataEntries, remark) => {
const getLegacySlug = createLegacySlugger();

// Creates the root node for the content
const parsedNodes = createTree(
'root',
Expand All @@ -229,7 +234,14 @@ export default (headNodes, metadataEntries, remark) => {
const content = structuredClone(entry.content);

// Parses the Heading nodes into Heading elements
visit(content, UNIST.isHeading, buildHeading);
visit(content, UNIST.isHeading, (node, index, parent) =>
buildHeading(
node,
index,
parent,
getLegacySlug(node.data.text, entry.api)
)
);

// Parses the Blockquotes into Stability elements
// This is treated differently as we want to preserve the position of a Stability Index
Expand Down
23 changes: 23 additions & 0 deletions src/generators/legacy-html/utils/slugger.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

/**
* Creates a stateful slugger for legacy anchor links.
*
* Generates underscore-separated slugs in the form `{apiStem}_{text}`,
* appending `_{n}` for duplicates to preserve historical anchor compatibility.
*
* @returns {(text: string, apiStem: string) => string}
*/
export const createLegacySlugger =
(counters = {}) =>
(text, apiStem) => {
const id = `${apiStem}_${text}`
.toLowerCase()
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '')
.replace(/[^a-z0-9]+/g, '_')
.replace(/^\d/, '_$&');

counters[id] ??= -1;
const count = ++counters[id];
return count > 0 ? `${id}_${count}` : id;
};
Loading