From 8a449ad181f98470d91353914da0c07d5202a91c Mon Sep 17 00:00:00 2001 From: Aleksandr Mezin Date: Tue, 27 May 2025 09:47:03 +0300 Subject: [PATCH] feat: add `root_path` config option (#14) Fixes #12 --- .markdownlint-cli2.mjs | 4 +- CONTRIBUTING.md | 2 +- README.md | 26 +++++++++-- src/index.js | 22 ++++++--- .../config-dependent/absolute-paths.md | 3 ++ test/index.test.js | 45 ++++++++++++++++--- 6 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 test/fixtures/config-dependent/absolute-paths.md diff --git a/.markdownlint-cli2.mjs b/.markdownlint-cli2.mjs index 1f8f0ef..704b160 100644 --- a/.markdownlint-cli2.mjs +++ b/.markdownlint-cli2.mjs @@ -4,7 +4,9 @@ const config = { config: { extends: "markdownlint/style/prettier", default: true, - "relative-links": true, + "relative-links": { + root_path: ".", + }, "no-inline-html": false, }, globs: ["**/*.md"], diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8cb0ec4..90b7a7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks a lot for your interest in contributing to **markdownlint-rule-relative-l ## Code of Conduct -**markdownlint-rule-relative-links** adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](./CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated. +**markdownlint-rule-relative-links** adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](/CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated. ## Open Development diff --git a/README.md b/README.md index ce22323..ec351af 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,13 @@ awesome.md:3 relative-links Relative links should be valid ["./invalid.txt" shou - Support images (e.g: `![Image](./image.png)`). - Support links fragments similar to the [built-in `markdownlint` rule - MD051](https://github.com/DavidAnson/markdownlint/blob/main/doc/md051.md) (e.g: `[Link](./awesome.md#heading)`). - Ignore external links and absolute paths as it only checks relative links (e.g: `https://example.com/` or `/absolute/path.png`). +- If necessary, absolute paths can be validated too, with [`root_path` configuration option](#absolute-paths). ### Limitations - Only images and links defined using markdown syntax are validated, html syntax is ignored (e.g: `` or ``). -Contributions are welcome to improve the rule, and to alleviate these limitations. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. +Contributions are welcome to improve the rule, and to alleviate these limitations. See [CONTRIBUTING.md](/CONTRIBUTING.md) for more information. ### Related links @@ -108,6 +109,25 @@ export default config } ``` +### Absolute paths + +GitHub (and, likely, other similar platforms) resolves absolute paths in Markdown links relative to the repository root. + +To validate such links, add `root_path` option to the configuration: + +```js + config: { + default: true, + "relative-links": { + root_path: ".", + }, + }, +``` + +After this change, all absolute paths will be converted to relative paths, and will be resolved relative to the specified directory. + +For example, if you run markdownlint from a subdirectory (if `package.json` is located in a subdirectory), you should set `root_path` to `".."`. + ## Usage ```sh @@ -118,8 +138,8 @@ node --run lint:markdown Anyone can help to improve the project, submit a Feature Request, a bug report or even correct a simple spelling mistake. -The steps to contribute can be found in the [CONTRIBUTING.md](./CONTRIBUTING.md) file. +The steps to contribute can be found in the [CONTRIBUTING.md](/CONTRIBUTING.md) file. ## 📄 License -[MIT](./LICENSE) +[MIT](/LICENSE) diff --git a/src/index.js b/src/index.js index 2c83946..7562da1 100644 --- a/src/index.js +++ b/src/index.js @@ -51,17 +51,25 @@ const relativeLinksRule = { } } - if (hrefSrc == null) { + if (hrefSrc == null || hrefSrc.startsWith("#")) { continue } - const url = new URL(hrefSrc, pathToFileURL(params.name)) - const isRelative = - url.protocol === "file:" && - !hrefSrc.startsWith("/") && - !hrefSrc.startsWith("#") + let url - if (!isRelative) { + if (hrefSrc.startsWith("/")) { + const rootPath = params.config["root_path"] + + if (!rootPath) { + continue + } + + url = new URL(`.${hrefSrc}`, pathToFileURL(`${rootPath}/`)) + } else { + url = new URL(hrefSrc, pathToFileURL(params.name)) + } + + if (url.protocol !== "file:") { continue } diff --git a/test/fixtures/config-dependent/absolute-paths.md b/test/fixtures/config-dependent/absolute-paths.md new file mode 100644 index 0000000..65fad64 --- /dev/null +++ b/test/fixtures/config-dependent/absolute-paths.md @@ -0,0 +1,3 @@ +# Valid + +![Absolute Path](/test/fixtures/image.png) diff --git a/test/index.test.js b/test/index.test.js index 4d03a95..d4a2738 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -5,17 +5,22 @@ import * as markdownlint from "markdownlint/promise" import relativeLinksRule, { markdownIt } from "../src/index.js" +const defaultConfig = { + "relative-links": true, +} + /** * * @param {string} fixtureFile + * @param {Object} config * @returns */ -const validateMarkdownLint = async (fixtureFile) => { +const validateMarkdownLint = async (fixtureFile, config = defaultConfig) => { const lintResults = await markdownlint.lint({ files: [fixtureFile], config: { default: false, - "relative-links": true, + ...config, }, customRules: [relativeLinksRule], markdownItFactory: () => { @@ -146,11 +151,27 @@ test("ensure the rule validates correctly", async (t) => { fixturePath: "test/fixtures/invalid/non-existing-image.md", errors: ['"./image.png" should exist in the file system'], }, + { + name: "should be invalid with incorrect absolute paths", + fixturePath: "test/fixtures/config-dependent/absolute-paths.md", + errors: ['"/test/fixtures/image.png" should exist in the file system'], + config: { + "relative-links": { + root_path: "test", + }, + }, + }, ] - for (const { name, fixturePath, errors } of testCases) { + for (const { + name, + fixturePath, + errors, + config = defaultConfig, + } of testCases) { await t.test(name, async () => { - const lintResults = (await validateMarkdownLint(fixturePath)) ?? [] + const lintResults = + (await validateMarkdownLint(fixturePath, config)) ?? [] const errorsDetails = lintResults.map((result) => { assert.deepEqual(result.ruleNames, relativeLinksRule.names) assert.deepEqual( @@ -219,7 +240,7 @@ test("ensure the rule validates correctly", async (t) => { fixturePath: "test/fixtures/valid/existing-image.md", }, { - name: "should ignore absolute paths", + name: "should ignore absolute paths if root_path is not set", fixturePath: "test/fixtures/valid/ignore-absolute-paths.md", }, { @@ -231,11 +252,21 @@ test("ensure the rule validates correctly", async (t) => { fixturePath: "test/fixtures/valid/ignore-fragment-checking-in-own-file.md", }, + { + name: "should be valid with correct absolute paths if root_path is set", + fixturePath: "test/fixtures/config-dependent/absolute-paths.md", + config: { + "relative-links": { + root_path: ".", + }, + }, + }, ] - for (const { name, fixturePath } of testCases) { + for (const { name, fixturePath, config = defaultConfig } of testCases) { await t.test(name, async () => { - const lintResults = (await validateMarkdownLint(fixturePath)) ?? [] + const lintResults = + (await validateMarkdownLint(fixturePath, config)) ?? [] const errorsDetails = lintResults.map((result) => { return result.errorDetail })