[IMP] tools: update debug script, add tests

closes #525
closes #526
This commit is contained in:
Géry Debongnie
2019-12-02 17:13:12 +01:00
committed by aab-odoo
parent ed4ab51c17
commit 85f26a0286
8 changed files with 157 additions and 40 deletions
+183
View File
@@ -0,0 +1,183 @@
/**
* Doc Link Checker
*
* We define here a test to make sure that there are no dead link in the Owl
* documentation.
*/
import * as fs from "fs";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
interface MarkDownLink {
name: string;
link: string;
}
interface MarkDownSection {
name: string;
slug: string;
}
interface FileData {
name: string;
path: string[];
fullName: string;
links: MarkDownLink[];
sections: MarkDownSection[];
}
const LINK_REGEXP = /\[([^\[]+)\]\(([^\)]+)\)/g;
const HEADING_REGEXP = /\n(#+\s*)(.*)/g;
export function addMardownData(fileData): void {
const sep = fileData.path.length > 0 ? "/" : "";
const fullName = fileData.path.join("/") + sep + fileData.name;
const content = fs.readFileSync(fullName, { encoding: "utf8" });
let m;
// get links info
do {
m = LINK_REGEXP.exec(content);
if (m) {
fileData.links.push({ name: m[0], link: m[2] });
}
} while (m);
// get sections info
do {
m = HEADING_REGEXP.exec(content);
if (m) {
fileData.sections.push({ name: m[0], slug: slugify(m[2]) });
}
} while (m);
}
/**
* Returns a list of FileData corresponding to all files that need to be
* validated.
*/
function getFiles(path: string[] = []): FileData[] {
if (path.length === 0) {
const baseFiles: FileData[] = [
{ name: "README.md", path: [], links: [], sections: [], fullName: "README.md" },
{ name: "roadmap.md", path: [], links: [], sections: [], fullName: "roadmap.md" }
];
const rest = getFiles(["doc"]);
const result = baseFiles.concat(rest);
result.forEach(addMardownData);
return result;
}
const files = fs.readdirSync(path.join("/"), { withFileTypes: true }).map(f => {
if (f.isDirectory()) {
return getFiles(path.concat(f.name));
}
const fullName = path.join("/") + (path.length > 0 ? "/" : "") + f.name;
return [
{
name: f.name,
path,
links: [],
sections: [],
fullName
}
];
});
return Array.prototype.concat(...files);
}
const LOCAL_FILES = ["LICENSE", "tools/debug.js"];
export function isLinkValid(link: MarkDownLink, current: FileData, files: FileData[]): boolean {
if (link.link.startsWith("http")) {
// no check on external links
return true;
}
// Step 1: extract path, name, hash
// path = ['doc', 'architecture]
// name = 'rendering.md'
// hash = 'blabla' (or '' if no hash)
const parts = link.link.split("#");
const hash = parts[1] || "";
let name;
let path;
if (parts[0]) {
let temp = parts[0].split("/");
name = temp[temp.length - 1];
temp.splice(-1);
path = current.path.slice();
for (let elem of temp) {
if (elem === "..") {
path.splice(-1);
} else if (elem !== ".") {
path.push(elem);
}
}
} else {
// there are no file name, so this is a relative link to the current file
name = current.name;
path = current.path;
}
// Step 2: build normalized link file name
const linkFullName = path.join("/") + (path.length > 0 ? "/" : "") + name;
// Step 3: check link name against white list of local files
if (LOCAL_FILES.includes(linkFullName)) {
return true;
}
// Step 4: check if there is a matching file
let target: FileData | undefined = files.find(f => f.fullName === linkFullName);
if (!target) {
return false;
}
// Step 5: if necessary, check if there is a corresponding link inside the target
// link name
if (hash) {
if (!target.sections.find(s => s.slug === hash)) {
return false;
}
}
return true;
}
// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
function slugify(str) {
const a = "àáäâãåăæçèéëêǵḧìíïîḿńǹñòóöôœøṕŕßśșțùúüûǘẃẍÿź·_,:;";
const b = "aaaaaaaaceeeeghiiiimnnnooooooprssstuuuuuwxyz-----";
const p = new RegExp(a.split("").join("|"), "g");
return str
.toString()
.toLowerCase()
.replace(/\//g, "") // remove /
.replace(/\s+/g, "-") // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, "-and-") // Replace & with and
.replace(/[^\w\-]+/g, "") // Remove all non-word characters
.replace(/\-\-+/g, "-") // Replace multiple - with single -
.replace(/^-+/, "") // Trim - from start of text
.replace(/-+$/, ""); // Trim - from end of text
}
//--------------------------------------------------------------------------
// Test
//--------------------------------------------------------------------------
test("All markdown links work", () => {
let linkNumber = 0;
let invalidLinkNumber = 0;
const data = getFiles();
for (let file of data) {
for (let link of file.links) {
linkNumber++;
if (!isLinkValid(link, file, data)) {
console.warn(`Invalid Link: "${link.name}" in "${file.name}"`);
invalidLinkNumber++;
}
}
}
expect(invalidLinkNumber).toBe(0);
expect(linkNumber).toBeGreaterThan(10);
});