From 858aa445542be3e06ad2dc18fee52c93c744c27e Mon Sep 17 00:00:00 2001 From: mi-hol <22799428+mi-hol@users.noreply.github.com> Date: Sun, 17 Sep 2023 19:33:58 +0200 Subject: [PATCH] Validate links to md files (#153) --- package.json | 2 +- scripts/linkValidator.js | 101 +++++++++++++++++++++------- scripts/tests/linkValidator.test.md | 49 ++++++++++++++ 3 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 scripts/tests/linkValidator.test.md diff --git a/package.json b/package.json index 2a73802..fee0f96 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint:fix": "biome format --write . && biome check --apply .", "lint:fix:unsafe": "biome check . --apply-unsafe .", "lint:ts": "tsc", - "lint:links": "node scripts/linkValidator.js", + "lint:links": "node scripts/linkValidator.js 'docs/**/*.md?(x)'", "lint:md": "npx markdownlint-cli docs", "lint:biome": "biome check .", "swizzle": "docusaurus swizzle", diff --git a/scripts/linkValidator.js b/scripts/linkValidator.js index 6958b54..5fb56ba 100644 --- a/scripts/linkValidator.js +++ b/scripts/linkValidator.js @@ -1,27 +1,71 @@ +/* + * usage: + * note: Windows path separator used below! (to avoid error with parsing of this comment block) + * " node .\scripts\linkValidator.js 'docs\**\*.md?(x)' " + * + * for testing: + * "node linkValidator.js .\linkValidator.test.md" + */ + const fs = require('fs'); const glob = require('glob'); const red = (text) => `\x1b[31m${text}\x1b[0m`; -/** - * Check whether links are not staring with "wiki.fome.tech" - * @param {Array} files - List of file names to check - */ -const validateAbsoluteUrls = (files) => { - const wikiUrl = 'wiki.fome.tech'; - const regexPattern = new RegExp(`\\]\\((https|http):\\/\\/${wikiUrl}`, 'i'); +/** @type {Array<{ errType: string, fileName: string, lineContent: string, lineNo: number }>} */ +const errors = []; +const wikiUrl = 'wiki.fome.tech'; - /** @type {Array<{ fileName: string, lineContent: string, lineNo: number }>} */ - const errors = []; +const validateDocRules = (files) => { + /** + * Check whether links are adhering to custom rules + * @param {Array} files - List of file names to check + */ - console.log('Validating absolute URLs...'); + let fileMatchIndicator = ''; + if (files.length === 0) { + console.log('UsageError: no files qualify!'); + process.exit(1); + } + if (files.length > 1) { + fileMatchIndicator = `${files.length} files`; + } else { + fileMatchIndicator = files[0]; + } + console.log(`Validating rules for URL links in: ${fileMatchIndicator}`); + + // * Check whether links are not staring with "https://wiki.fome.tech" + // Note: dynamic javascript string interpolation is used here (see https://www.crstin.com/js-regex-interpolation/) + const regexPatternDynAbsLink = new RegExp(`\\]\\((https|http):\\/\\/${wikiUrl}`, 'i'); + // hint: test static regex via https://regex101.com + // * Check whether links are not using a "numbered prefix" like "(/01-blah)" + const regexPatternStatNumPrefix = new RegExp(/\(.*\/\d\d\-.*\)/, 'i'); + // * Check whether links are not markdown links, meaning ending with .md or .mdx like "(/01-blah.md)" + const regexPatternStatMdLink = new RegExp(/\(.*\.(md|mdx)\)/, 'i'); files.forEach((fileName) => { const lines = fs.readFileSync(fileName, 'utf8').split('\n'); lines.forEach((line) => { - if (regexPattern.test(line)) { + if (regexPatternDynAbsLink.test(line)) { errors.push({ + errType: 'AbsLink', + fileName, + lineContent: line, + lineNo: lines.indexOf(line) + 1, + }); + } + if (regexPatternStatMdLink.test(line)) { + errors.push({ + errType: 'MdLink', + fileName, + lineContent: line, + lineNo: lines.indexOf(line) + 1, + }); + } + if (regexPatternStatNumPrefix.test(line)) { + errors.push({ + errType: 'NumPrefix', fileName, lineContent: line, lineNo: lines.indexOf(line) + 1, @@ -29,29 +73,36 @@ const validateAbsoluteUrls = (files) => { } }); }); - - if (errors.length > 0) { - console.log('❌ Failed\n'); - console.log(red(`Absolute URLs to "${wikiUrl}" found in the following files:\n`)); - errors.forEach((error) => { - console.log(`[${error.fileName}:${error.lineNo}] ${error.lineContent.trim()}`); - }); - - process.exit(1); - } }; /** * Load all md and mdx files from / docs and process them */ const main = () => { - const files = glob.sync('docs/**/*.md?(x)'); + // ToDo: add try + catch? + const args = process.argv.slice(2); + const files = glob.sync(args[0]); // process - validateAbsoluteUrls(files); - // validateRelativeUrls(files); + validateDocRules(files); - console.log('✅ Ok'); + if (errors.length === 0) { + console.log('✅ Ok'); + } else { + console.log('❌ Failed\n'); + console.log(red(`Number of Errors found: ${errors.length}`)); + errors.forEach((error) => { + console.log( + `[${error.fileName}]` + + `[Line:${error.lineNo}]` + + `[errType:${error.errType}]` + + `"${error.lineContent.trim()}"`, + ); + }); + + process.exit(2); + } }; +// main main(); diff --git a/scripts/tests/linkValidator.test.md b/scripts/tests/linkValidator.test.md new file mode 100644 index 0000000..8baf2dd --- /dev/null +++ b/scripts/tests/linkValidator.test.md @@ -0,0 +1,49 @@ +# Tests for linkValidator.js + +## 1. test case prevented by markdownlint MD034/no-bare-urls + +[T] (https://wikiXfome.tech/01-test.md) + +## 2. test case prevented by markdownlint MD034/no-bare-urls + +blah (https://wiki.fome.tech/01-test.md) + +## 3. test case expected: MdLink + AbsLink + NumPrefix + +[T](https://wiki.fome.tech/01-test.md) + +## 4. test case expected: false positive AbsLink + +[T](https://wikiXfome.tech/test) # note: false positive, but extreme unlikely to hit in real world + +## 5. test case expected: MdLink + NumPrefix + +(/01-test.md) + +## 6. test case expected: MdLink + +(test.md) + +## 7. test case expected: MdLink + +(test.md) blah + +## 8. test case expected: MdLink + +blah (test.md) blah + +## 9. test case expected: MdLink + +(../test.mdx) + +## 10. test case expected: no error + +(T)(test.jpg) + +## 11. test case expected: no error + +(T)(../../test) + +## 12. test case expected: NumPrefix + +(/01-test)