feat!: stricter validation of image links (check if the file is an image)

BREAKING CHANGE: links like ![Image](./not-an-image.txt) will now report
an error
This commit is contained in:
2026-02-05 17:46:14 +01:00
parent 12a22980ce
commit 211c71d75b
6 changed files with 30 additions and 5 deletions

4
package-lock.json generated
View File

@@ -9,7 +9,8 @@
"version": "0.0.0-development", "version": "0.0.0-development",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"markdown-it": "14.1.0" "markdown-it": "14.1.0",
"mime": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/markdown-it": "14.1.2", "@types/markdown-it": "14.1.2",
@@ -3637,7 +3638,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz",
"integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==",
"dev": true,
"funding": [ "funding": [
"https://github.com/sponsors/broofa" "https://github.com/sponsors/broofa"
], ],

View File

@@ -36,7 +36,8 @@
"release": "semantic-release" "release": "semantic-release"
}, },
"dependencies": { "dependencies": {
"markdown-it": "14.1.0" "markdown-it": "14.1.0",
"mime": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/markdown-it": "14.1.2", "@types/markdown-it": "14.1.2",

View File

@@ -1,5 +1,6 @@
import { pathToFileURL } from "node:url" import { fileURLToPath, pathToFileURL } from "node:url"
import fs from "node:fs" import fs from "node:fs"
import mime from "mime"
import { filterTokens } from "./markdownlint-rule-helpers/helpers.js" import { filterTokens } from "./markdownlint-rule-helpers/helpers.js"
import { import {
@@ -69,7 +70,7 @@ const relativeLinksRule = {
url = new URL(hrefSrc, pathToFileURL(params.name)) url = new URL(hrefSrc, pathToFileURL(params.name))
} }
if (url.protocol !== "file:") { if (url.protocol !== "file:" && type !== "image") {
continue continue
} }
@@ -83,6 +84,15 @@ const relativeLinksRule = {
continue continue
} }
const mimeType = mime.getType(fileURLToPath(url))
if (type === "image" && (mimeType == null || !mimeType.startsWith("image/"))) {
onError({
lineNumber,
detail: `${detail} should be an image`,
})
continue
}
if (url.hash.length <= 0) { if (url.hash.length <= 0) {
if (hrefSrc.includes("#")) { if (hrefSrc.includes("#")) {
if (type === "image") { if (type === "image") {

View File

@@ -0,0 +1,5 @@
# Invalid
![Image](../not-an-image.txt)
![Image 2](mailto:not-an-image@pictures.com)

1
test/fixtures/not-an-image.txt vendored Normal file
View File

@@ -0,0 +1 @@
not an image

View File

@@ -147,6 +147,14 @@ test("ensure the rule validates correctly", async (t) => {
}, },
}, },
}, },
{
name: "should be invalid with file with an image link that is not an image",
fixturePath: "test/fixtures/invalid/invalid-image.md",
errors: [
'"../not-an-image.txt" should be an image',
'"mailto:not-an-image@pictures.com" should exist in the file system',
],
},
] ]
for (const { name, fixturePath, errors, config = defaultConfig } of testCases) { for (const { name, fixturePath, errors, config = defaultConfig } of testCases) {