Skip to content

Commit f4292a9

Browse files
refactor(legacy-html): adopt functional closure for legacy slugger
Co-authored-by: Aviv Keller <me@aviv.sh>
1 parent 8e5017f commit f4292a9

File tree

3 files changed

+36
-66
lines changed

3 files changed

+36
-66
lines changed

src/generators/legacy-html/utils/__tests__/slugger.test.mjs

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,33 @@ import { createLegacySlugger } from '../slugger.mjs';
77

88
describe('createLegacySlugger', () => {
99
it('prefixes with api stem and uses underscores', () => {
10-
const slugger = createLegacySlugger();
11-
assert.strictEqual(
12-
slugger.getLegacySlug('File System', 'fs'),
13-
'fs_file_system'
14-
);
10+
const getLegacySlug = createLegacySlugger();
11+
assert.strictEqual(getLegacySlug('File System', 'fs'), 'fs_file_system');
1512
});
1613

1714
it('replaces special characters with underscores', () => {
18-
const slugger = createLegacySlugger();
15+
const getLegacySlug = createLegacySlugger();
1916
assert.strictEqual(
20-
slugger.getLegacySlug('fs.readFile(path)', 'fs'),
17+
getLegacySlug('fs.readFile(path)', 'fs'),
2118
'fs_fs_readfile_path'
2219
);
2320
});
2421

2522
it('strips leading and trailing underscores', () => {
26-
const slugger = createLegacySlugger();
27-
assert.strictEqual(slugger.getLegacySlug('Hello', 'fs'), 'fs_hello');
23+
const getLegacySlug = createLegacySlugger();
24+
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello');
2825
});
2926

3027
it('prefixes with underscore when result starts with non-alpha', () => {
31-
const slugger = createLegacySlugger();
32-
assert.strictEqual(
33-
slugger.getLegacySlug('123 test', '0num'),
34-
'_0num_123_test'
35-
);
28+
const getLegacySlug = createLegacySlugger();
29+
assert.strictEqual(getLegacySlug('123 test', '0num'), '_0num_123_test');
3630
});
3731

3832
it('deduplicates with a counter for identical titles', () => {
39-
const slugger = createLegacySlugger();
40-
assert.strictEqual(slugger.getLegacySlug('Hello', 'fs'), 'fs_hello');
41-
assert.strictEqual(slugger.getLegacySlug('Hello', 'fs'), 'fs_hello_1');
42-
assert.strictEqual(slugger.getLegacySlug('Hello', 'fs'), 'fs_hello_2');
43-
assert.strictEqual(slugger.getLegacySlug('World', 'fs'), 'fs_world');
33+
const getLegacySlug = createLegacySlugger();
34+
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello');
35+
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_1');
36+
assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_2');
37+
assert.strictEqual(getLegacySlug('World', 'fs'), 'fs_world');
4438
});
4539
});

src/generators/legacy-html/utils/buildContent.mjs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ const buildMetadataElement = (node, remark) => {
223223
* @param {import('unified').Processor} remark The Remark instance to be used to process
224224
*/
225225
export default (headNodes, metadataEntries, remark) => {
226-
const legacySlugger = createLegacySlugger();
226+
const getLegacySlug = createLegacySlugger();
227227

228228
// Creates the root node for the content
229229
const parsedNodes = createTree(
@@ -234,13 +234,14 @@ export default (headNodes, metadataEntries, remark) => {
234234
const content = structuredClone(entry.content);
235235

236236
// Parses the Heading nodes into Heading elements
237-
visit(content, UNIST.isHeading, (node, index, parent) => {
238-
const legacySlug = legacySlugger.getLegacySlug(
239-
node.data.text,
240-
entry.api
241-
);
242-
buildHeading(node, index, parent, legacySlug);
243-
});
237+
visit(content, UNIST.isHeading, (node, index, parent) =>
238+
buildHeading(
239+
node,
240+
index,
241+
parent,
242+
getLegacySlug(node.data.text, entry.api)
243+
)
244+
);
244245

245246
// Parses the Blockquotes into Stability elements
246247
// This is treated differently as we want to preserve the position of a Stability Index
Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,23 @@
11
'use strict';
22

3-
const notAlphaNumerics = /[^a-z0-9]+/g;
4-
const edgeUnderscores = /^_+|_+$/g;
5-
const notAlphaStart = /^[^a-z]/;
6-
73
/**
8-
* Deduplicates legacy slugs by appending an incremented counter.
9-
* Adapted from maintainer suggestion to preserve `id` on first occurrence.
4+
* Creates a stateful slugger for legacy anchor links.
5+
*
6+
* Generates underscore-separated slugs in the form `{apiStem}_{text}`,
7+
* appending `_{n}` for duplicates to preserve historical anchor compatibility.
108
*
11-
* @param {Record<string, number>} counters
12-
* @returns {(id: string) => string}
9+
* @returns {(text: string, apiStem: string) => string}
1310
*/
14-
export const legacyDeduplicator =
15-
(counters = { __proto__: null }) =>
16-
id => {
11+
export const createLegacySlugger =
12+
(counters = {}) =>
13+
(text, apiStem) => {
14+
const id = `${apiStem}_${text}`
15+
.toLowerCase()
16+
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '')
17+
.replace(/[^a-z0-9]+/g, '_')
18+
.replace(/^\d/, '_$&');
19+
1720
counters[id] ??= -1;
1821
const count = ++counters[id];
1922
return count > 0 ? `${id}_${count}` : id;
2023
};
21-
22-
/**
23-
* Creates a stateful slugger for legacy anchor links.
24-
*
25-
* @returns {{ getLegacySlug: (text: string, apiStem: string) => string }}
26-
*/
27-
export const createLegacySlugger = () => {
28-
const deduplicate = legacyDeduplicator();
29-
30-
return {
31-
/**
32-
* Generates a legacy-style slug to preserve old anchor links.
33-
*
34-
* @param {string} text The heading text
35-
* @param {string} apiStem The API file identifier (e.g. 'fs', 'http')
36-
* @returns {string} The legacy slug
37-
*/
38-
getLegacySlug: (text, apiStem) => {
39-
const id = `${apiStem}_${text}`
40-
.toLowerCase()
41-
.replace(notAlphaNumerics, '_')
42-
.replace(edgeUnderscores, '')
43-
.replace(notAlphaStart, '_$&');
44-
45-
return deduplicate(id);
46-
},
47-
};
48-
};

0 commit comments

Comments
 (0)