Skip to content

Conversation

lumirlumir
Copy link
Member

@lumirlumir lumirlumir commented Aug 5, 2025

Prerequisites checklist

What is the purpose of this pull request?

Hi,

In this PR, I've refactored the no-missing-link-fragments rule, thanks to #469 (review).

Previously, we used a separate traversing logic with the extractText helper function. This was somewhat redundant, since ESLint already traverses the AST for us, resulting in duplicate traversing.

I've updated the code to use an ESQuery selector instead, which simplifies the traversing logic.

I've also made some additional cleanups to make the code more consistent and easier to read.

What changes did you make? (Give an overview)

In this PR, I've refactored the no-missing-link-fragments rule.

Related Issues

Ref: #469 (review)

Is there anything you'd like reviewers to focus on?

I've made sure all existing test cases are passing.

Comment on lines +30 to +32
const customHeadingIdPattern = /\{#(?<id>[^}\s]+)\}\s*$/u;
const htmlIdNamePattern =
/(?<!<)<(?:[^>]+)\s(?:id|name)\s*=\s*["']?([^"'\s>]+)["']?/giu;

/**
* Checks if the fragment is a valid GitHub line reference
* @param {string} fragment The fragment to check
* @returns {boolean} Whether the fragment is a valid GitHub line reference
*/
function isGitHubLineReference(fragment) {
return githubLineReferencePattern.test(fragment);
}

/**
* Extracts the text recursively from a node
* @param {Node} node The node from which to recursively extract text
* @returns {string} The extracted text
*/
function extractText(node) {
if (node.type === "html") {
return "";
}
if ("value" in node) {
return /** @type {string} */ (node.value);
}
if ("children" in node) {
return /** @type {Node[]} */ (node.children).map(extractText).join("");
}
return "";
}
/(?<!<)<(?:[^>]+)\s(?:id|name)\s*=\s*["']?(?<id>[^"'\s>]+)["']?/giu;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve only added named capture groups to improve readability.

@@ -103,8 +76,8 @@ export default {
},

create(context) {
const { allowPattern: allowPatternString, ignoreCase } =
context.options[0];
const [{ allowPattern: allowPatternString, ignoreCase }] =
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In many other existing Markdown rules, we typically use full destructuring syntax.

Comment on lines +94 to +102
heading() {
headingText = "";
},

"heading *:not(html)"({ value }) {
headingText += value ?? "";
},

"heading:exit"() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve replaced the extractText helper function with an ESQuery selector logic.

Comment on lines +104 to +106
const baseId = customIdMatch
? customIdMatch.groups.id
: headingText;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (customIdMatch) {
	baseId = customIdMatch[1];
} else {
	const tempSlugger = new GithubSlugger();
	baseId = tempSlugger.slug(rawHeadingText);
}

Previously, we used GithubSlugger here again, but it wasn't necessary, so I removed the duplicate slugging.

Since the next line of code already calls slug(), this was redundant.

I've reviewed this carefully and confirmed that removing it does not cause any side effects.

Comment on lines +113 to +115
const htmlTextWithoutComments = node.value
.trim()
.replace(htmlCommentPattern, "");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a simple cleanup here.

Comment on lines +154 to +157
if (
allowPattern?.test(decodedFragment) ||
githubLineReferencePattern.test(decodedFragment)
) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a simple cleanup here too.

Comment on lines +157 to +163
dedent`
# foo bar baz
# foo-bar-baz

[Link](#foo-bar-baz)
[Link](#foo-bar-baz-1)
`,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a simple addition of test cases to ensure https://github.com/eslint/markdown/pull/495/files#r2255966805.

@lumirlumir lumirlumir marked this pull request as ready for review August 6, 2025 06:27
@lumirlumir lumirlumir added this to Triage Aug 6, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Aug 6, 2025
Copy link
Contributor

@snitin315 snitin315 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

Leaving it open for 2nd review.

@lumirlumir lumirlumir moved this from Needs Triage to Second Review Needed in Triage Aug 10, 2025
Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks.

@nzakas nzakas merged commit 71e921a into main Aug 11, 2025
23 checks passed
@nzakas nzakas deleted the refactor-simplify-no-missing-link-fragments-with-esquery-selector branch August 11, 2025 14:53
@github-project-automation github-project-automation bot moved this from Second Review Needed to Complete in Triage Aug 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Complete
Development

Successfully merging this pull request may close these issues.

3 participants