diff --git a/index.test.js b/index.test.js index da8140e..111e19d 100644 --- a/index.test.js +++ b/index.test.js @@ -70,7 +70,7 @@ test("linkspector should check relative links in Markdown file", async () => { } expect(hasErrorLinks).toBe(true); - expect(results.length).toBe(7); + expect(results.length).toBe(8); expect(results[0].status).toBe("alive"); expect(results[1].status).toBe("alive"); expect(results[2].status).toBe("alive"); @@ -78,6 +78,7 @@ test("linkspector should check relative links in Markdown file", async () => { expect(results[4].status).toBe("alive"); expect(results[5].status).toBe("alive"); expect(results[6].status).toBe("error"); + expect(results[7].status).toBe("error"); }); test("linkspector should check top-level relative links in Markdown file", async () => { diff --git a/lib/check-file-links.js b/lib/check-file-links.js index 36e21ea..939068d 100644 --- a/lib/check-file-links.js +++ b/lib/check-file-links.js @@ -5,77 +5,65 @@ import remarkParse from "remark-parse"; import remarkGfm from "remark-gfm"; import { visit } from "unist-util-visit"; +/** + * Checks if a file and a section within the file exist. + * + * @param {Object} link - The link object. + * @param {string} file - The current file path. + * @returns {Object} An object containing the status code, status message, and error message (if any). + */ function checkFileExistence(link, file) { - let statusCode, status, errorMessage; + // Initialize status code, status message, and error message + let statusCode = "200"; + let status = "alive"; + let errorMessage = ""; try { - let fileDir = path.dirname(file); - let urlWithoutSection = link.url; - let sectionId = null; + // Split the URL into the file part and the section part + const [urlWithoutSection = "", sectionId = null] = link.url.split("#"); - if (link.url.includes("#")) { - [urlWithoutSection, sectionId] = link.url.split("#"); - } - - let filePath; + // Determine the file path + const filePath = urlWithoutSection.startsWith("/") + ? path.join(process.cwd(), urlWithoutSection) + : urlWithoutSection === "" + ? file + : path.resolve(path.dirname(file), urlWithoutSection); - if (urlWithoutSection.startsWith("/")) { - filePath = path.join(process.cwd(), urlWithoutSection); - } else if (urlWithoutSection.startsWith("#") || urlWithoutSection === "") { - sectionId = urlWithoutSection.slice(1); - filePath = file; - } else { - filePath = path.resolve(fileDir, urlWithoutSection); - } + // Check if the file exists + if (!fs.existsSync(filePath)) { + statusCode = "404"; + status = "error"; + errorMessage = `Cannot find: ${link.url}`; + } else if (sectionId) { + // If the file exists and there's a section part in the URL, check if the section exists + const mdContent = fs.readFileSync(filePath, "utf8"); + const tree = unified().use(remarkParse).use(remarkGfm).parse(mdContent); - if (fs.existsSync(filePath)) { - statusCode = "200"; - status = "alive"; - if (sectionId) { - let mdContent = fs.readFileSync(filePath, "utf8"); - const tree = unified().use(remarkParse).use(remarkGfm).parse(mdContent); + // Collect all heading IDs in the file + const headingNodes = new Set(); + visit(tree, "heading", (node) => { + const headingId = node.children[0].type === "html" + ? node.children[0].value.match(/name="(.+?)"/)?.[1] + : node.children[0].value.includes("{#") + ? node.children[0].value.match(/{#(.+?)}/)?.[1] + : node.children[0].value.toLowerCase().replace(/ /g, "-").replace(/\./g, ""); - let headingNodes = new Set(); - visit(tree, "heading", (node) => { - let headingId; - if (node.children[0].type === "html") { - let match = node.children[0].value.match(/name="(.+?)"/); - if (match) { - headingId = match[1]; - } - } else { - let headingText = node.children[0].value; - if (headingText.includes("{#")) { - let match = headingText.match(/{#(.+?)}/); - if (match) { - headingId = match[1]; - } - } else { - headingId = headingText - .toLowerCase() - .replace(/ /g, "-") - .replace(/\./g, ""); - } - } - headingNodes.add(headingId); - }); + headingNodes.add(headingId); + }); - if (!headingNodes.has(sectionId)) { - statusCode = "404"; - status = "error"; - errorMessage = `Cannot find section: ${sectionId} in file: ${link.url}.`; - } + // Check if the section exists + if (!headingNodes.has(sectionId)) { + statusCode = "404"; + status = "error"; + errorMessage = `Cannot find section: #${sectionId} in file: ${filePath}.`; } - } else { - statusCode = "404"; - status = "error"; - errorMessage = `Cannot find: ${link.url}.`; } } catch (err) { console.error(`Error in checking if file ${link.url} exist! ${err}`); } + // Return the status code, status message, and error message return { statusCode, status, errorMessage }; } -export { checkFileExistence }; \ No newline at end of file +export { checkFileExistence }; diff --git a/package.json b/package.json index e5910aa..5fe155e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@umbrelladocs/linkspector", - "version": "0.3.5", + "version": "0.3.6", "description": "Uncover broken links in your content.", "type": "module", "main": "linkspector.js", diff --git a/test/fixtures/relative/relative1.md b/test/fixtures/relative/relative1.md index a319ca0..fae8c24 100644 --- a/test/fixtures/relative/relative1.md +++ b/test/fixtures/relative/relative1.md @@ -16,8 +16,12 @@ This is a paragraph in the first file in a third level heading. [Link to Relative 2 Heading Level Three](relative2.md#custom-id-level-three) -### Relative 1 Heading Level Four +#### Relative 1 Heading Level Four This is a paragraph in the first file in a fourth level heading. [Link to Relative 3 Broken link](relative3.md#relative-3-heading-level-one) + +##### Relative 1 Heading Level Five + +[Link to broken section](#broken-section)