fix(validate-sidebar): tighten language detection and drift guard, add docstrings
* fix(validate-sidebar): replace subdirectory heuristic with locale pattern matching
detectLanguageDirs() previously classified any top-level docs/ directory
containing subdirectories as a translation language. This was too broad —
if an English section ever gained nested subfolders it would be silently
excluded from validation.
Replaced with a BCP 47 locale-code regex (/^[a-z]{2}(?:-[a-zA-Z]{2})?$/)
that matches known patterns (cs, fr, vi-vn, zh-cn) and won't falsely
classify content sections like explanation/ or reference/.
* fix(validate-sidebar): guard drift check against undefined order values
extractSidebarOrder() returns { hasSidebar: false } when no sidebar block
exists, leaving order as undefined rather than null. The drift check only
guarded against null, allowing undefined values to emit noisy warnings
like "Order drift: ... order undefined".
Changed the guard to typeof === 'number' which correctly excludes both
undefined and null without relying on a specific sentinel value.
* chore(validate-sidebar): add JSDoc docstrings to all functions
Adds @param and @returns annotations to extractSidebarOrder,
detectLanguageDirs, getEnglishSections, checkDirectory,
checkTranslationDrift, and relativePath.
This commit is contained in:
parent
98bf8a3a9f
commit
7ef3a66dca
|
|
@ -23,6 +23,13 @@ const DOCS_ROOT = path.resolve(__dirname, '../docs');
|
|||
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---/;
|
||||
const SIDEBAR_ORDER_REGEX = /sidebar:\s*\r?\n(?:(?:[ \t]+.*\r?\n)|(?:\r?\n))*?[ \t]+order:\s*(\d+)/;
|
||||
const HAS_SIDEBAR_REGEX = /^sidebar:/m;
|
||||
const LOCALE_PATTERN = /^[a-z]{2}(?:-[a-zA-Z]{2})?$/;
|
||||
|
||||
/**
|
||||
* Extract sidebar.order from YAML frontmatter.
|
||||
* @param {string} content - Full file contents of a markdown file.
|
||||
* @returns {{ hasSidebar: boolean, order?: number|null }} Whether a sidebar block exists and its order value.
|
||||
*/
|
||||
function extractSidebarOrder(content) {
|
||||
const match = content.match(FRONTMATTER_REGEX);
|
||||
if (!match) return { hasSidebar: false };
|
||||
|
|
@ -41,29 +48,33 @@ function extractSidebarOrder(content) {
|
|||
return { hasSidebar: true, order: parseInt(orderMatch[1], 10) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect translation language directories under docs/ by matching locale-code names.
|
||||
* @returns {string[]} Directory names matching locale patterns (e.g. "cs", "zh-cn").
|
||||
*/
|
||||
function detectLanguageDirs() {
|
||||
const dirs = [];
|
||||
const entries = fs.readdirSync(DOCS_ROOT, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || entry.name.startsWith('_')) continue;
|
||||
|
||||
const subPath = path.join(DOCS_ROOT, entry.name);
|
||||
const subEntries = fs.readdirSync(subPath, { withFileTypes: true });
|
||||
const hasSubdirs = subEntries.some((e) => e.isDirectory() && !e.name.startsWith('_'));
|
||||
|
||||
if (hasSubdirs) {
|
||||
dirs.push(entry.name);
|
||||
}
|
||||
}
|
||||
return dirs;
|
||||
return entries
|
||||
.filter((e) => e.isDirectory() && !e.name.startsWith('_'))
|
||||
.map((e) => e.name)
|
||||
.filter((name) => LOCALE_PATTERN.test(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* List all top-level content directories under docs/ (excludes _-prefixed dirs).
|
||||
* @returns {string[]} Directory names for English content sections.
|
||||
*/
|
||||
function getEnglishSections() {
|
||||
const entries = fs.readdirSync(DOCS_ROOT, { withFileTypes: true });
|
||||
return entries.filter((e) => e.isDirectory() && !e.name.startsWith('_')).map((e) => e.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sidebar.order values for all markdown files in a directory.
|
||||
* @param {string} dirPath - Absolute path to the directory to scan.
|
||||
* @param {Array<{level:string,type:string,file?:string,order?:number,directory?:string,missing?:number,message:string}>} issues - Array to push errors into.
|
||||
* @returns {Map<number,string[]>} Map of order values to the files that hold them.
|
||||
*/
|
||||
function checkDirectory(dirPath, issues) {
|
||||
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
const mdFiles = entries.filter((e) => e.isFile() && (e.name.endsWith('.md') || e.name.endsWith('.mdx')));
|
||||
|
|
@ -132,6 +143,13 @@ function checkDirectory(dirPath, issues) {
|
|||
return orderMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare translated sidebar orders against English counterparts and warn on drift.
|
||||
* @param {string} lang - Language directory name (e.g. "cs").
|
||||
* @param {string[]} langSections - Section subdirectories within the language folder.
|
||||
* @param {Map<string,Map<number,string[]>>} englishOrderMaps - English order maps keyed by section name.
|
||||
* @param {Array<{level:string,type:string,file:string,englishFile:string,langOrder:number,englishOrder:number,message:string}>} warnings - Array to push drift warnings into.
|
||||
*/
|
||||
function checkTranslationDrift(lang, langSections, englishOrderMaps, warnings) {
|
||||
for (const section of langSections) {
|
||||
const sectionDir = path.join(DOCS_ROOT, lang, section);
|
||||
|
|
@ -155,7 +173,7 @@ function checkTranslationDrift(lang, langSections, englishOrderMaps, warnings) {
|
|||
const langResult = extractSidebarOrder(langContent);
|
||||
const engResult = extractSidebarOrder(engContent);
|
||||
|
||||
if (langResult.order !== null && engResult.order !== null && langResult.order !== engResult.order) {
|
||||
if (typeof langResult.order === 'number' && typeof engResult.order === 'number' && langResult.order !== engResult.order) {
|
||||
warnings.push({
|
||||
level: 'warning',
|
||||
type: 'order-drift',
|
||||
|
|
@ -170,6 +188,11 @@ function checkTranslationDrift(lang, langSections, englishOrderMaps, warnings) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an absolute path to one relative to DOCS_ROOT.
|
||||
* @param {string} filePath - Absolute file path.
|
||||
* @returns {string} Relative path from docs root.
|
||||
*/
|
||||
function relativePath(filePath) {
|
||||
return path.relative(DOCS_ROOT, filePath);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue