Updated Wiki content

OrcaSlicerBot
2026-04-08 18:27:37 +00:00
parent 9c0c184154
commit 0a64cb3064
2 changed files with 81 additions and 11 deletions

@@ -104,6 +104,11 @@ jobs:
failures.push(formatFailure(reference, 'sameDocEmptyAnchor', reference.target)); failures.push(formatFailure(reference, 'sameDocEmptyAnchor', reference.target));
continue; continue;
} }
const decodedAnchor = decodeLinkComponent(classification.anchorRaw);
if (!isKebabCase(decodedAnchor)) {
failures.push(formatFailure(reference, 'anchorNotKebabCase', `#${classification.anchorRaw}`));
continue;
}
const anchors = getAnchors(reference.filePath); const anchors = getAnchors(reference.filePath);
if (!anchors.has(classification.anchorSlug)) { if (!anchors.has(classification.anchorSlug)) {
failures.push(formatFailure(reference, 'missingSameDocAnchor', `#${classification.anchorRaw}`)); failures.push(formatFailure(reference, 'missingSameDocAnchor', `#${classification.anchorRaw}`));
@@ -132,6 +137,12 @@ jobs:
continue; continue;
} }
const decodedAnchor = decodeLinkComponent(classification.anchorRaw);
if (!isKebabCase(decodedAnchor)) {
failures.push(formatFailure(reference, 'anchorNotKebabCase', `${docResult.linkPath}#${classification.anchorRaw}`));
continue;
}
const anchors = getAnchors(docResult.relativePath); const anchors = getAnchors(docResult.relativePath);
if (!anchors.has(classification.anchorSlug)) { if (!anchors.has(classification.anchorSlug)) {
failures.push(formatFailure(reference, 'missingCrossDocAnchor', `${docResult.linkPath}#${classification.anchorRaw}`)); failures.push(formatFailure(reference, 'missingCrossDocAnchor', `${docResult.linkPath}#${classification.anchorRaw}`));
@@ -238,6 +249,12 @@ jobs:
return result; return result;
} }
if (!isSnakeCase(sanitized)) {
result.error = 'docNotSnakeCase';
result.detail = rawPath;
return result;
}
ensureMarkdownIndex(); ensureMarkdownIndex();
const matches = findMarkdownDocuments(sanitized); const matches = findMarkdownDocuments(sanitized);
if (!matches.length) { if (!matches.length) {
@@ -311,6 +328,24 @@ jobs:
return markdownNameIndex.get(baseName) || []; return markdownNameIndex.get(baseName) || [];
} }
function isSnakeCase(value) {
return /^[a-z0-9]+(?:_[a-z0-9]+)*$/.test(value);
}
function isKebabCase(value) {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
}
function decodeLinkComponent(value) {
let decoded = value.trim();
try {
decoded = decodeURIComponent(decoded);
} catch (_) {
// Ignore decode failure.
}
return decoded;
}
function getAnchors(relativePath) { function getAnchors(relativePath) {
if (headingCache.has(relativePath)) { if (headingCache.has(relativePath)) {
return headingCache.get(relativePath); return headingCache.get(relativePath);
@@ -417,6 +452,8 @@ jobs:
return `${reference.filePath} line ${reference.line}: document links must not include directories; use just the filename (got "${details}").`; return `${reference.filePath} line ${reference.line}: document links must not include directories; use just the filename (got "${details}").`;
case 'extensionNotAllowed': case 'extensionNotAllowed':
return `${reference.filePath} line ${reference.line}: link target "${details}" must omit the .md suffix.`; return `${reference.filePath} line ${reference.line}: link target "${details}" must omit the .md suffix.`;
case 'docNotSnakeCase':
return `${reference.filePath} line ${reference.line}: document name "${details}" must be snake_case.`;
case 'missingDocName': case 'missingDocName':
return `${reference.filePath} line ${reference.line}: document link "${details}" must include a file name (without .md).`; return `${reference.filePath} line ${reference.line}: document link "${details}" must include a file name (without .md).`;
case 'missingDocument': case 'missingDocument':
@@ -425,6 +462,8 @@ jobs:
return `${reference.filePath} line ${reference.line}: document link matches multiple files (${details}).`; return `${reference.filePath} line ${reference.line}: document link matches multiple files (${details}).`;
case 'crossDocEmptyAnchor': case 'crossDocEmptyAnchor':
return `${reference.filePath} line ${reference.line}: link to ${details} must include a heading name after '#'.`; return `${reference.filePath} line ${reference.line}: link to ${details} must include a heading name after '#'.`;
case 'anchorNotKebabCase':
return `${reference.filePath} line ${reference.line}: heading reference ${details} must be kebab-case.`;
case 'missingCrossDocAnchor': case 'missingCrossDocAnchor':
return `${reference.filePath} line ${reference.line}: heading ${details} was not found.`; return `${reference.filePath} line ${reference.line}: heading ${details} was not found.`;
case 'invalidHashCount': case 'invalidHashCount':

@@ -76,13 +76,19 @@ jobs:
continue; continue;
} }
const matches = findMarkdownDocuments(docName); const decodedDocName = decodeLinkComponent(docName);
if (!isSnakeCase(decodedDocName)) {
failures.push(formatFailure(reference, 'docNotSnakeCase', docName));
continue;
}
const matches = findMarkdownDocuments(decodedDocName);
if (!matches.length) { if (!matches.length) {
failures.push(formatFailure(reference, 'missingDocument', `${docName}.md`)); failures.push(formatFailure(reference, 'missingDocument', `${decodedDocName}.md`));
continue; continue;
} }
if (matches.length > 1) { if (matches.length > 1) {
failures.push(formatFailure(reference, 'ambiguousDocument', `${docName} -> ${matches.slice(0, 5).join(', ')}`)); failures.push(formatFailure(reference, 'ambiguousDocument', `${decodedDocName} -> ${matches.slice(0, 5).join(', ')}`));
continue; continue;
} }
@@ -102,6 +108,12 @@ jobs:
continue; continue;
} }
const decodedAnchor = decodeLinkComponent(anchorRaw);
if (!isKebabCase(decodedAnchor)) {
failures.push(formatFailure(reference, 'anchorNotKebabCase', `${decodedDocName}#${anchorRaw}`));
continue;
}
const anchors = getAnchors(relativePath); const anchors = getAnchors(relativePath);
const anchorSlug = normalizeAnchor(anchorRaw); const anchorSlug = normalizeAnchor(anchorRaw);
if (!anchorSlug) { if (!anchorSlug) {
@@ -109,7 +121,7 @@ jobs:
continue; continue;
} }
if (!anchors.has(anchorSlug)) { if (!anchors.has(anchorSlug)) {
failures.push(formatFailure(reference, 'missingCrossDocAnchor', `${docName}#${anchorRaw}`)); failures.push(formatFailure(reference, 'missingCrossDocAnchor', `${decodedDocName}#${anchorRaw}`));
} }
} }
@@ -281,6 +293,24 @@ jobs:
return markdownNameIndex.get(baseName) || []; return markdownNameIndex.get(baseName) || [];
} }
function isSnakeCase(value) {
return /^[a-z0-9]+(?:_[a-z0-9]+)*$/.test(value);
}
function isKebabCase(value) {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
}
function decodeLinkComponent(value) {
let decoded = value.trim();
try {
decoded = decodeURIComponent(decoded);
} catch (_) {
// ignore decode failures
}
return decoded;
}
function getAnchors(relativePath) { function getAnchors(relativePath) {
if (headingCache.has(relativePath)) { if (headingCache.has(relativePath)) {
return headingCache.get(relativePath); return headingCache.get(relativePath);
@@ -348,13 +378,8 @@ jobs:
if (!raw) { if (!raw) {
return ''; return '';
} }
let decoded = raw.trim(); const decoded = decodeLinkComponent(raw);
try { return slugify(decoded);
decoded = decodeURIComponent(decoded);
} catch (_) {
// ignore decode failures
}
return slugify(decoded, { preserveCase: true });
} }
function buildLineOffsets(text) { function buildLineOffsets(text) {
@@ -401,12 +426,18 @@ jobs:
case 'extensionNotAllowed': case 'extensionNotAllowed':
failure.message = `${lineInfo}: link "${details}" must omit the .md suffix.`; failure.message = `${lineInfo}: link "${details}" must omit the .md suffix.`;
break; break;
case 'docNotSnakeCase':
failure.message = `${lineInfo}: document name "${details}" must be snake_case.`;
break;
case 'missingDocument': case 'missingDocument':
failure.message = `${lineInfo}: document ${details} does not exist in the wiki.`; failure.message = `${lineInfo}: document ${details} does not exist in the wiki.`;
break; break;
case 'ambiguousDocument': case 'ambiguousDocument':
failure.message = `${lineInfo}: document reference is ambiguous (${details}).`; failure.message = `${lineInfo}: document reference is ambiguous (${details}).`;
break; break;
case 'anchorNotKebabCase':
failure.message = `${lineInfo}: heading reference ${details} must be kebab-case.`;
break;
case 'missingCrossDocAnchor': case 'missingCrossDocAnchor':
failure.message = `${lineInfo}: heading ${details} was not found.`; failure.message = `${lineInfo}: heading ${details} was not found.`;
break; break;