Skip to content

Commit

Permalink
Merge pull request #42 from UmbrellaDocs/fix-section-links-check
Browse files Browse the repository at this point in the history
Fix section links checking in the same file
  • Loading branch information
gaurav-nelson committed May 9, 2024
2 parents 9524b58 + e9d6212 commit 3625c91
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 60 deletions.
3 changes: 2 additions & 1 deletion index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,15 @@ 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");
expect(results[3].status).toBe("alive");
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 () => {
Expand Down
102 changes: 45 additions & 57 deletions lib/check-file-links.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
export { checkFileExistence };
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
6 changes: 5 additions & 1 deletion test/fixtures/relative/relative1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 3625c91

Please sign in to comment.