* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
import assert from 'node:assert/strict'
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import test, { after } from 'node:test'
import {
installRepositoryLinkRewrite,
rewriteRepositoryHref,
rewriteRepositoryLinks,
} from '../rewrite-repository-links.mjs'
const repoRoot = mkdtempSync(join(tmpdir(), 'asc-devkit-links-'))
const zhSource = join(repoRoot, 'docs', 'zh', 'guide', 'page.md')
const enSource = join(repoRoot, 'docs', 'en', 'page.md')
function createFile(path, content = '') {
mkdirSync(resolve(path, '..'), { recursive: true })
writeFileSync(path, content, 'utf-8')
}
createFile(zhSource)
createFile(enSource)
createFile(join(repoRoot, 'docs', 'zh', 'guide', 'other.md'))
createFile(join(repoRoot, 'docs', 'en', 'other.md'))
createFile(join(repoRoot, 'examples', 'sample', 'README.md'))
mkdirSync(join(repoRoot, 'examples', 'sample dir'), { recursive: true })
createFile(join(repoRoot, 'include', 'api.h'))
mkdirSync(join(repoRoot, 'impl', 'detail'), { recursive: true })
createFile(join(repoRoot, 'tests', 'test.cpp'))
createFile(join(repoRoot, 'tools', 'script.py'))
createFile(join(repoRoot, '.devcontainer', 'README.md'))
createFile(join(repoRoot, 'build.sh'))
createFile(join(repoRoot, 'CONTRIBUTING_en.md'))
createFile(join(repoRoot, 'examples', 'image.png'))
after(() => rmSync(repoRoot, { recursive: true, force: true }))
test('rewrites repository directories as GitCode tree links', () => {
const html = [
'<a href="../../../examples/sample">sample</a>',
'<a href="../../../impl/detail">impl</a>',
].join('')
assert.equal(
rewriteRepositoryLinks(html, zhSource, repoRoot),
[
'<a href="https://gitcode.com/cann/asc-devkit/tree/master/examples/sample">sample</a>',
'<a href="https://gitcode.com/cann/asc-devkit/tree/master/impl/detail">impl</a>',
].join('')
)
})
test('rewrites repository files as GitCode blob links and preserves suffixes', () => {
const html = [
'<a href="../../../include/api.h?plain=1#usage">header</a>',
'<a href="../../../tests/test.cpp">test</a>',
'<a href="../../../tools/script.py">tool</a>',
].join('')
assert.equal(
rewriteRepositoryLinks(html, zhSource, repoRoot),
[
'<a href="https://gitcode.com/cann/asc-devkit/blob/master/include/api.h?plain=1#usage">header</a>',
'<a href="https://gitcode.com/cann/asc-devkit/blob/master/tests/test.cpp">test</a>',
'<a href="https://gitcode.com/cann/asc-devkit/blob/master/tools/script.py">tool</a>',
].join('')
)
})
test('rewrites repository Markdown files before VitePress changes them to HTML links', () => {
assert.equal(
rewriteRepositoryHref('../../../CONTRIBUTING_en.md', zhSource, repoRoot),
'https://gitcode.com/cann/asc-devkit/blob/master/CONTRIBUTING_en.md'
)
})
test('supports root files, hidden directories, and encoded path segments', () => {
assert.equal(
rewriteRepositoryLinks("<a href='../../build.sh'>build</a>", join(repoRoot, 'docs', 'zh', 'page.md'), repoRoot),
"<a href='https://gitcode.com/cann/asc-devkit/blob/master/build.sh'>build</a>"
)
assert.equal(
rewriteRepositoryLinks('<a href="../../.devcontainer/README.md">container</a>', enSource, repoRoot),
'<a href="https://gitcode.com/cann/asc-devkit/blob/master/.devcontainer/README.md">container</a>'
)
assert.equal(
rewriteRepositoryLinks("<a href='../../../examples/sample dir'>sample</a>", zhSource, repoRoot),
"<a href='https://gitcode.com/cann/asc-devkit/tree/master/examples/sample%20dir'>sample</a>"
)
assert.equal(
rewriteRepositoryLinks("<a href='../../../examples/sample%20dir'>encoded sample</a>", zhSource, repoRoot),
"<a href='https://gitcode.com/cann/asc-devkit/tree/master/examples/sample%20dir'>encoded sample</a>"
)
})
test('does not rewrite links inside code or pre blocks', () => {
const html = [
'<a href="../../../include/api.h">link</a>',
'<pre><code>// see <a href="../../../include/api.h">api.h</a> here</code></pre>',
'<code>href="../../../include/api.h"</code>',
].join('')
const result = rewriteRepositoryLinks(html, zhSource, repoRoot)
assert.ok(result.includes('https://gitcode.com/cann/asc-devkit/blob/master/include/api.h'))
assert.equal(
(result.match(/href="\.\.\/\.\.\/\.\.\/include\/api\.h"/g) || []).length,
2,
)
})
test('does not rewrite documentation, image, external, missing, or src links', () => {
const html = [
'<a href="./other.md">Chinese documentation</a>',
'<a href="../other.md">English documentation</a>',
'<a href="../../../examples/image.png">linked image</a>',
'<a href="https://gitcode.com/cann/asc-devkit/tree/master/examples/sample">external</a>',
'<a href="../../../missing/file.h">missing</a>',
'<a href="../../../../../outside.txt">outside</a>',
'<img src="../../../include/api.h">',
].join('')
assert.equal(rewriteRepositoryLinks(html, zhSource, repoRoot), html)
})
test('rewrites Markdown link tokens before the VitePress link renderer appends HTML', () => {
const md = {
renderer: {
rules: {
link_open(tokens, idx) {
const href = tokens[idx].attrs[tokens[idx].attrIndex('href')][1]
return `<a href="${href.startsWith('https://') ? href : `${href}.html`}">`
},
},
},
}
const token = {
attrs: [['href', '../../../include/api.h']],
attrIndex(name) {
return this.attrs.findIndex(([attribute]) => attribute === name)
},
}
installRepositoryLinkRewrite(md, () => zhSource, repoRoot)
assert.equal(
md.renderer.rules.link_open([token], 0, {}, {}, {}),
'<a href="https://gitcode.com/cann/asc-devkit/blob/master/include/api.h">'
)
})