mirror of
https://github.com/YunoHost/doc.git
synced 2024-09-03 20:06:26 +02:00
129 lines
4.8 KiB
JavaScript
129 lines
4.8 KiB
JavaScript
|
const transliteration = require('transliteration');
|
||
|
const fs = require('fs');
|
||
|
const url = require('url');
|
||
|
const md = require('markdown-it')({ html: true });
|
||
|
const helpers = require('markdownlint/helpers/helpers');
|
||
|
const flat = require('../lib/flat');
|
||
|
|
||
|
const validateAnchor = (link, href, tokens, onError) => {
|
||
|
let anchorFound = false;
|
||
|
tokens.filter((token) => token.type === 'heading_open').forEach((heading) => {
|
||
|
if (!heading.line) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const headingAnchor = transliteration.slugify(heading.line.toLowerCase(), {
|
||
|
replace: {
|
||
|
ä: 'ae',
|
||
|
ö: 'oe',
|
||
|
ü: 'ue',
|
||
|
},
|
||
|
});
|
||
|
|
||
|
if (`#${headingAnchor}` === href) {
|
||
|
anchorFound = true;
|
||
|
}
|
||
|
});
|
||
|
if (!anchorFound) {
|
||
|
onError({
|
||
|
lineNumber: link.lineNumber,
|
||
|
detail: `Did not find matching heading for anchor '${href}'`,
|
||
|
context: link.line,
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Annotate tokens with line/lineNumber, duplication from markdownlint because function
|
||
|
// is not exposed
|
||
|
const annotateTokens = (tokens, lines) => {
|
||
|
let tbodyMap = null;
|
||
|
return tokens.map((token) => {
|
||
|
// Handle missing maps for table body
|
||
|
if (token.type === 'tbody_open') {
|
||
|
tbodyMap = token.map.slice();
|
||
|
} else if ((token.type === 'tr_close') && tbodyMap) {
|
||
|
tbodyMap[0] += 1;
|
||
|
} else if (token.type === 'tbody_close') {
|
||
|
tbodyMap = null;
|
||
|
}
|
||
|
const mappedToken = token;
|
||
|
if (tbodyMap && !token.map) {
|
||
|
mappedToken.map = tbodyMap.slice();
|
||
|
}
|
||
|
// Update token metadata
|
||
|
if (token.map) {
|
||
|
mappedToken.line = lines[token.map[0]];
|
||
|
mappedToken.lineNumber = token.map[0] + 1;
|
||
|
// Trim bottom of token to exclude whitespace lines
|
||
|
while (token.map[1] && !(lines[token.map[1] - 1].trim())) {
|
||
|
mappedToken.map[1] -= 1;
|
||
|
}
|
||
|
// Annotate children with lineNumber
|
||
|
let childLineNumber = token.lineNumber;
|
||
|
(token.children || []).map((child) => {
|
||
|
const mappedChild = child;
|
||
|
mappedChild.lineNumber = childLineNumber;
|
||
|
mappedChild.line = lines[childLineNumber - 1];
|
||
|
if ((child.type === 'softbreak') || (child.type === 'hardbreak')) {
|
||
|
childLineNumber += 1;
|
||
|
}
|
||
|
return mappedChild;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return mappedToken;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
const parseMarkdownContent = (fileContent) => {
|
||
|
// Remove UTF-8 byte order marker (if present)
|
||
|
let content = fileContent.replace(/^\ufeff/, '');
|
||
|
// Ignore the content of HTML comments
|
||
|
content = helpers.clearHtmlCommentText(content);
|
||
|
// Parse content into tokens and lines
|
||
|
const tokens = md.parse(content, {});
|
||
|
const lines = content.split(helpers.newLineRe);
|
||
|
return annotateTokens(tokens, lines);
|
||
|
};
|
||
|
|
||
|
module.exports = {
|
||
|
names: ['valid-internal-links'],
|
||
|
description: 'Rule that reports if a file has an internal link to an invalid file',
|
||
|
tags: ['test'],
|
||
|
function: function rule(params, onError) {
|
||
|
flat(params.tokens).filter((token) => token.type === 'link_open').forEach((link) => {
|
||
|
link.attrs.forEach((attr) => {
|
||
|
if (attr[0] === 'href') {
|
||
|
const href = attr[1];
|
||
|
if (href.match(/^#/)) {
|
||
|
validateAnchor(link, href, params.tokens, onError);
|
||
|
} else if (href && !href.match(/^(mailto:|https?:)/)) {
|
||
|
const parsedHref = url.parse(href);
|
||
|
let path;
|
||
|
if (parsedHref.pathname.match(/^\//)) {
|
||
|
path = `pages${parsedHref.pathname}`;
|
||
|
} else {
|
||
|
path = `${params.name.split('/').slice(0, -1).join('/')}/${parsedHref.pathname}`;
|
||
|
}
|
||
|
if (!fs.existsSync(path) || !fs.lstatSync(path).isFile()) {
|
||
|
onError({
|
||
|
lineNumber: link.lineNumber,
|
||
|
detail: `Relative link '${href}' does not link to a valid file.`,
|
||
|
context: link.line,
|
||
|
});
|
||
|
} else if (parsedHref.hash) {
|
||
|
// console.log(md.parse(fs.readFileSync(path).toString()));
|
||
|
validateAnchor(
|
||
|
link,
|
||
|
parsedHref.hash,
|
||
|
parseMarkdownContent(fs.readFileSync(path).toString()),
|
||
|
onError,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
};
|