mirror of
https://github.com/OrcaSlicer/OrcaSlicer_WIKI.git
synced 2026-05-17 08:35:46 +03:00
Improve Tab link validation and error reporting (#46)
Refactored link reference collection to handle append_option_line calls more robustly and improved argument parsing. Enhanced line number calculation for error messages and updated failure formatting to include GitHub links. Failures are now sorted by line and only messages are exported.
This commit is contained in:
160
.github/workflows/validate_tab_links.yml
vendored
160
.github/workflows/validate_tab_links.yml
vendored
@@ -39,6 +39,7 @@ jobs:
|
||||
return;
|
||||
}
|
||||
const source = await response.text();
|
||||
const lineOffsets = buildLineOffsets(source);
|
||||
|
||||
const references = collectReferences(source);
|
||||
if (!references.length) {
|
||||
@@ -113,7 +114,8 @@ jobs:
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
const block = failures.join('\n');
|
||||
failures.sort((a, b) => a.line - b.line);
|
||||
const block = failures.map(failure => failure.message).join('\n');
|
||||
core.exportVariable('ERROR_BLOCK', block);
|
||||
return;
|
||||
}
|
||||
@@ -132,31 +134,113 @@ jobs:
|
||||
refs.push({
|
||||
option: match[1],
|
||||
target: match[2].trim(),
|
||||
line: lineFromIndex(text, match.index),
|
||||
line: lineFromIndex(match.index),
|
||||
});
|
||||
}
|
||||
|
||||
const labelPathPattern = /label_path\s*=\s*"([^"]+)"/g;
|
||||
const labelPathPattern = /\bline\s*\.\s*label_path\s*=\s*"([^"]+)"/g;
|
||||
while ((match = labelPathPattern.exec(text)) !== null) {
|
||||
refs.push({
|
||||
option: 'label_path',
|
||||
target: match[1].trim(),
|
||||
line: lineFromIndex(text, match.index),
|
||||
line: lineFromIndex(match.index),
|
||||
});
|
||||
}
|
||||
|
||||
const appendOptionLinePattern = /append_option_line\s*\(\s*[^,]+,\s*[^,]+,\s*"([^"]+)"/g;
|
||||
while ((match = appendOptionLinePattern.exec(text)) !== null) {
|
||||
refs.push({
|
||||
option: 'append_option_line',
|
||||
target: match[1].trim(),
|
||||
line: lineFromIndex(text, match.index),
|
||||
});
|
||||
for (const reference of collectAppendOptionLineReferences(text)) {
|
||||
refs.push(reference);
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
function collectAppendOptionLineReferences(text) {
|
||||
const refs = [];
|
||||
const pattern = /append_option_line\s*\(/g;
|
||||
let match;
|
||||
while ((match = pattern.exec(text)) !== null) {
|
||||
const callStart = match.index;
|
||||
const argsResult = parseCallArguments(text, pattern.lastIndex);
|
||||
if (!argsResult.args) {
|
||||
pattern.lastIndex = argsResult.endIndex;
|
||||
continue;
|
||||
}
|
||||
pattern.lastIndex = argsResult.endIndex;
|
||||
if (argsResult.args.length < 3) {
|
||||
continue;
|
||||
}
|
||||
const targetLiteral = extractStringLiteral(argsResult.args[2]);
|
||||
if (!targetLiteral) {
|
||||
continue;
|
||||
}
|
||||
refs.push({
|
||||
option: 'append_option_line',
|
||||
target: targetLiteral.trim(),
|
||||
line: lineFromIndex(callStart),
|
||||
});
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
function parseCallArguments(text, startIndex) {
|
||||
const args = [];
|
||||
let current = '';
|
||||
let depth = 1;
|
||||
let inString = false;
|
||||
let stringChar = '';
|
||||
let escaped = false;
|
||||
let i = startIndex;
|
||||
for (; i < text.length; i += 1) {
|
||||
const ch = text[i];
|
||||
if (inString) {
|
||||
current += ch;
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (ch === '\\') {
|
||||
escaped = true;
|
||||
} else if (ch === stringChar) {
|
||||
inString = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ch === '"' || ch === '\'') {
|
||||
inString = true;
|
||||
stringChar = ch;
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
if (ch === '(') {
|
||||
depth += 1;
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
if (ch === ')') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
args.push(current.trim());
|
||||
return { args, endIndex: i + 1 };
|
||||
}
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
if (ch === ',' && depth === 1) {
|
||||
args.push(current.trim());
|
||||
current = '';
|
||||
continue;
|
||||
}
|
||||
current += ch;
|
||||
}
|
||||
return { args: null, endIndex: text.length };
|
||||
}
|
||||
|
||||
function extractStringLiteral(argumentText) {
|
||||
const trimmed = argumentText.trim();
|
||||
if (!trimmed.startsWith('"') || !trimmed.endsWith('"')) {
|
||||
return '';
|
||||
}
|
||||
return trimmed.slice(1, -1);
|
||||
}
|
||||
|
||||
function ensureMarkdownIndex() {
|
||||
if (markdownIndexReady) {
|
||||
return;
|
||||
@@ -273,37 +357,63 @@ jobs:
|
||||
return slugify(decoded);
|
||||
}
|
||||
|
||||
function lineFromIndex(text, index) {
|
||||
let line = 1;
|
||||
for (let i = 0; i < index; i += 1) {
|
||||
function buildLineOffsets(text) {
|
||||
const offsets = [0];
|
||||
for (let i = 0; i < text.length; i += 1) {
|
||||
if (text.charCodeAt(i) === 10) {
|
||||
line += 1;
|
||||
offsets.push(i + 1);
|
||||
}
|
||||
}
|
||||
return line;
|
||||
return offsets;
|
||||
}
|
||||
|
||||
function lineFromIndex(position) {
|
||||
let low = 0;
|
||||
let high = lineOffsets.length - 1;
|
||||
while (low <= high) {
|
||||
const mid = (low + high) >> 1;
|
||||
if (lineOffsets[mid] <= position) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
return high + 1;
|
||||
}
|
||||
|
||||
function formatFailure(reference, reason, details) {
|
||||
const link = `https://github.com/OrcaSlicer/OrcaSlicer/blob/main/src/slic3r/GUI/Tab.cpp#L${reference.line}`;
|
||||
const lineInfo = `[Tab.cpp line ${reference.line}](${link})`;
|
||||
const failure = { line: reference.line, message: '' };
|
||||
switch (reason) {
|
||||
case 'hashCount':
|
||||
return `Tab.cpp line ${reference.line}: link "${details}" cannot contain more than one '#'.`;
|
||||
failure.message = `${lineInfo}: link "${details}" cannot contain more than one '#'.`;
|
||||
break;
|
||||
case 'missingDocName':
|
||||
return `Tab.cpp line ${reference.line}: link "${details}" must include a document name.`;
|
||||
failure.message = `${lineInfo}: link "${details}" must include a document name.`;
|
||||
break;
|
||||
case 'missingAnchor':
|
||||
return `Tab.cpp line ${reference.line}: link "${details}" must include a heading name after '#'.`;
|
||||
failure.message = `${lineInfo}: link "${details}" must include a heading name after '#'.`;
|
||||
break;
|
||||
case 'pathNotAllowed':
|
||||
return `Tab.cpp line ${reference.line}: link "${details}" must omit any directory segments.`;
|
||||
failure.message = `${lineInfo}: link "${details}" must omit any directory segments.`;
|
||||
break;
|
||||
case 'extensionNotAllowed':
|
||||
return `Tab.cpp line ${reference.line}: link "${details}" must omit the .md suffix.`;
|
||||
failure.message = `${lineInfo}: link "${details}" must omit the .md suffix.`;
|
||||
break;
|
||||
case 'missingDocument':
|
||||
return `Tab.cpp line ${reference.line}: document ${details} does not exist in the wiki.`;
|
||||
failure.message = `${lineInfo}: document ${details} does not exist in the wiki.`;
|
||||
break;
|
||||
case 'ambiguousDocument':
|
||||
return `Tab.cpp line ${reference.line}: document reference is ambiguous (${details}).`;
|
||||
failure.message = `${lineInfo}: document reference is ambiguous (${details}).`;
|
||||
break;
|
||||
case 'missingCrossDocAnchor':
|
||||
return `Tab.cpp line ${reference.line}: heading ${details} was not found.`;
|
||||
failure.message = `${lineInfo}: heading ${details} was not found.`;
|
||||
break;
|
||||
default:
|
||||
return `Tab.cpp line ${reference.line}: invalid link ${details}.`;
|
||||
failure.message = `${lineInfo}: invalid link ${details}.`;
|
||||
}
|
||||
return failure;
|
||||
}
|
||||
|
||||
- name: Show invalid Tab links
|
||||
|
||||
Reference in New Issue
Block a user