Skip to content

Commit 9c9c7f9

Browse files
committed
feat(ast): strip /index suffix from path when using index files
Index files (e.g. index.md) should resolve to their parent directory path rather than /index. For example: - /index.md -> path: / - /api/index.md -> path: /api - /api/fs.md -> path: /api/fs (unchanged)
1 parent b2330d9 commit 9c9c7f9

2 files changed

Lines changed: 77 additions & 2 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
3+
import assert from 'node:assert/strict';
4+
import { describe, it, mock } from 'node:test';
5+
6+
// Mock fs/promises so processChunk doesn't touch the real filesystem
7+
mock.module('node:fs/promises', {
8+
namedExports: {
9+
readFile: async () => '# Hello',
10+
},
11+
});
12+
13+
// Mock remark to avoid parsing overhead
14+
mock.module('../../../utils/remark.mjs', {
15+
namedExports: {
16+
getRemark: () => ({ parse: () => ({ type: 'root', children: [] }) }),
17+
},
18+
});
19+
20+
// Mock queries to avoid regex replacements interfering
21+
mock.module('../../../utils/queries/index.mjs', {
22+
namedExports: {
23+
QUERIES: {
24+
standardYamlFrontmatter: /(?!x)x/, // never matches
25+
stabilityIndexPrefix: /(?!x)x/, // never matches
26+
},
27+
},
28+
});
29+
30+
const { processChunk } = await import('../generate.mjs');
31+
32+
describe('processChunk path computation', () => {
33+
it('strips /index suffix from a top-level index file', async () => {
34+
const results = await processChunk([['doc/api/index.md', 'doc/api']], [0]);
35+
assert.strictEqual(results[0].path, '/');
36+
});
37+
38+
it('strips /index suffix from a nested index file', async () => {
39+
const results = await processChunk(
40+
[['doc/api/sub/index.md', 'doc/api']],
41+
[0]
42+
);
43+
assert.strictEqual(results[0].path, '/sub');
44+
});
45+
46+
it('keeps path unchanged for non-index files', async () => {
47+
const results = await processChunk([['doc/api/fs.md', 'doc/api']], [0]);
48+
assert.strictEqual(results[0].path, '/fs');
49+
});
50+
51+
it('keeps path unchanged for files whose name contains index but is not index', async () => {
52+
const results = await processChunk(
53+
[['doc/api/indexes.md', 'doc/api']],
54+
[0]
55+
);
56+
assert.strictEqual(results[0].path, '/indexes');
57+
});
58+
59+
it('processes multiple files correctly', async () => {
60+
const input = [
61+
['doc/api/index.md', 'doc/api'],
62+
['doc/api/fs.md', 'doc/api'],
63+
['doc/api/sub/index.md', 'doc/api'],
64+
];
65+
const results = await processChunk(input, [0, 1, 2]);
66+
assert.strictEqual(results[0].path, '/');
67+
assert.strictEqual(results[1].path, '/fs');
68+
assert.strictEqual(results[2].path, '/sub');
69+
});
70+
});

src/generators/ast/generate.mjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,16 @@ export async function processChunk(inputSlice, itemIndices) {
3535
match => `[${match}](${STABILITY_INDEX_URL})`
3636
);
3737

38-
const relativePath = sep + withExt(relative(parent, path));
38+
const strippedPath = withExt(relative(parent, path));
39+
// Treat index files as the directory root (e.g. /index → /, /api/index → /api)
40+
const relativePath =
41+
strippedPath === 'index'
42+
? sep
43+
: sep + strippedPath.replace(/(\/|^)index$/, '');
3944

4045
results.push({
4146
tree: remark().parse(value),
42-
// The path is the relative path minus the extension
47+
// The path is the relative path minus the extension (and /index suffix)
4348
path: relativePath,
4449
});
4550
}

0 commit comments

Comments
 (0)