-
Notifications
You must be signed in to change notification settings - Fork 86
fix: report locations with <CR> linebreaks in no-reference-like-urls
#525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,19 +8,17 @@ | |
| //----------------------------------------------------------------------------- | ||
|
|
||
| import { normalizeIdentifier } from "micromark-util-normalize-identifier"; | ||
| import { findOffsets } from "../util.js"; | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Type Definitions | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * @import { SourceRange } from "@eslint/core" | ||
| * @import { Heading, Paragraph, TableCell } from "mdast"; | ||
| * @import { Image, Link } from "mdast"; | ||
| * @import { MarkdownRuleDefinition } from "../types.js"; | ||
| * @typedef {"referenceLikeUrl"} NoReferenceLikeUrlMessageIds | ||
| * @typedef {[]} NoReferenceLikeUrlOptions | ||
| * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReferenceLikeUrlOptions, MessageIds: NoReferenceLikeUrlMessageIds }>} NoReferenceLikeUrlRuleDefinition | ||
| * @typedef {"referenceLikeUrl"} NoReferenceLikeUrlsMessageIds | ||
| * @typedef {[]} NoReferenceLikeUrlsOptions | ||
| * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReferenceLikeUrlsOptions, MessageIds: NoReferenceLikeUrlsMessageIds }>} NoReferenceLikeUrlsRuleDefinition | ||
| */ | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
|
|
@@ -29,23 +27,13 @@ import { findOffsets } from "../util.js"; | |
|
|
||
| /** Pattern to match both inline links: `[text](url)` and images: ``, with optional title */ | ||
| const linkOrImagePattern = | ||
| /(?<=(?<!\\)(?:\\{2})*)(?<imageBang>!)?\[(?<label>(?:\\.|[^()\\]|\([\s\S]*\))*?)\]\((?<destination>[ \t]*(?:\r\n?|\n)?(?<![ \t])[ \t]*(?:<[^>]*>|[^ \t()]+))(?:[ \t]*(?:\r\n?|\n)?(?<![ \t])[ \t]*(?<title>"[^"]*"|'[^']*'|\([^)]*\)))?[ \t]*(?:\r\n?|\n)?(?<![ \t])[ \t]*\)(?!\()/gu; | ||
|
|
||
| /** | ||
| * Checks if a given index is within any skip range. | ||
| * @param {number} index The index to check | ||
| * @param {Array<SourceRange>} skipRanges The skip ranges | ||
| * @returns {boolean} True if index is in a skip range | ||
| */ | ||
| function isInSkipRange(index, skipRanges) { | ||
| return skipRanges.some(range => range[0] <= index && index < range[1]); | ||
| } | ||
| /(?<imageBang>!)?\[(?<label>(?:\\.|[^()\\]|\([\s\S]*\))*?)\]\((?<destination>[ \t]*(?:\r\n?|\n)?(?<![ \t])[ \t]*(?:<[^>]*>|[^ \t()]+))(?:[ \t]*(?:\r\n?|\n)?(?<![ \t])[ \t]*(?<title>"[^"]*"|'[^']*'|\([^)]*\)))?[ \t]*(?:\r\n?|\n)?(?<![ \t])[ \t]*\)$/u; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [[](](link)
[[])](link)

![[])](image)According to the AST, the code above are valid links and images. But, they produces false negatives like the following:
{
code: dedent`
[[](](mercury)
[mercury]: https://example.com/mercury
`,
output: dedent`
[[](][mercury]
[mercury]: https://example.com/mercury
`,
errors: [
{
messageId: "referenceLikeUrl",
data: { type: "link", prefix: "" },
line: 1,
column: 1,
endLine: 1,
endColumn: 15,
},
],
},
{
code: dedent`
[[])](mercury)
[mercury]: https://example.com/mercury
`,
output: dedent`
[[])][mercury]
[mercury]: https://example.com/mercury
`,
errors: [
{
messageId: "referenceLikeUrl",
data: { type: "link", prefix: "" },
line: 1,
column: 1,
endLine: 1,
endColumn: 15,
},
],
},
{
code: dedent`

[mercury]: https://example.com/mercury
`,
output: dedent`
](mercury)
[mercury]: https://example.com/mercury
`,
output: dedent`
![[])][mercury]
[mercury]: https://example.com/mercury
`,
errors: [
{
messageId: "referenceLikeUrl",
data: { type: "image", prefix: "!" },
line: 1,
column: 1,
endLine: 1,
endColumn: 16,
},
],
},
But, I'm not sure this fix should be included in this PR. As you mentioned above, would it be better to address this bug in a separate PR?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is this a bug that this PR would introduce, or does it already exist in the current version of this rule in the main branch?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that this bug already exists. It also fails on the main branch.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then I think we could fix it in a separate PR as it's unrelated to this change (unless this change would make it more difficult to fix this bug and we'd need to revert it?). This change was meant to be just a refactor to simplify the rule, and the |
||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Rule Definition | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| /** @type {NoReferenceLikeUrlRuleDefinition} */ | ||
| /** @type {NoReferenceLikeUrlsRuleDefinition} */ | ||
| export default { | ||
| meta: { | ||
| type: "problem", | ||
|
|
@@ -67,66 +55,33 @@ export default { | |
|
|
||
| create(context) { | ||
| const { sourceCode } = context; | ||
| /** @type {Array<SourceRange>} */ | ||
| const skipRanges = []; | ||
| /** @type {Set<string>} */ | ||
| const definitionIdentifiers = new Set(); | ||
| /** @type {Array<Heading | Paragraph | TableCell>} */ | ||
| /** @type {Array<Image | Link>} */ | ||
| const relevantNodes = []; | ||
|
|
||
| return { | ||
| definition(node) { | ||
| definitionIdentifiers.add(node.identifier); | ||
| }, | ||
|
|
||
| heading(node) { | ||
| relevantNodes.push(node); | ||
| }, | ||
|
|
||
| "heading :matches(html, inlineCode)"(node) { | ||
| skipRanges.push(sourceCode.getRange(node)); | ||
| }, | ||
|
|
||
| paragraph(node) { | ||
| "image, link"(/** @type {Image | Link} */ node) { | ||
| relevantNodes.push(node); | ||
| }, | ||
|
|
||
| "paragraph :matches(html, inlineCode)"(node) { | ||
| skipRanges.push(sourceCode.getRange(node)); | ||
| }, | ||
|
|
||
| tableCell(node) { | ||
| relevantNodes.push(node); | ||
| }, | ||
|
|
||
| "tableCell :matches(html, inlineCode)"(node) { | ||
| skipRanges.push(sourceCode.getRange(node)); | ||
| }, | ||
|
|
||
| "root:exit"() { | ||
| for (const node of relevantNodes) { | ||
| const text = sourceCode.getText(node); | ||
|
|
||
| let match; | ||
| while ((match = linkOrImagePattern.exec(text)) !== null) { | ||
| const match = linkOrImagePattern.exec(text); | ||
| if (match !== null) { | ||
| const { | ||
| imageBang, | ||
| label, | ||
| destination, | ||
| title: titleRaw, | ||
| } = match.groups; | ||
| const title = titleRaw?.slice(1, -1); | ||
| const matchIndex = match.index; | ||
| const matchLength = match[0].length; | ||
|
|
||
| if ( | ||
| isInSkipRange( | ||
| matchIndex + node.position.start.offset, | ||
| skipRanges, | ||
| ) | ||
| ) { | ||
| continue; | ||
| } | ||
|
|
||
| const isImage = !!imageBang; | ||
| const type = isImage ? "image" : "link"; | ||
|
|
@@ -135,37 +90,8 @@ export default { | |
| normalizeIdentifier(destination).toLowerCase(); | ||
|
|
||
| if (definitionIdentifiers.has(url)) { | ||
| const { | ||
| lineOffset: startLineOffset, | ||
| columnOffset: startColumnOffset, | ||
| } = findOffsets(text, matchIndex); | ||
| const { | ||
| lineOffset: endLineOffset, | ||
| columnOffset: endColumnOffset, | ||
| } = findOffsets(text, matchIndex + matchLength); | ||
|
|
||
| const baseColumn = 1; | ||
| const nodeStartLine = node.position.start.line; | ||
| const nodeStartColumn = node.position.start.column; | ||
| const startLine = nodeStartLine + startLineOffset; | ||
| const endLine = nodeStartLine + endLineOffset; | ||
| const startColumn = | ||
| (startLine === nodeStartLine | ||
| ? nodeStartColumn | ||
| : baseColumn) + startColumnOffset; | ||
| const endColumn = | ||
| (endLine === nodeStartLine | ||
| ? nodeStartColumn | ||
| : baseColumn) + endColumnOffset; | ||
|
|
||
| context.report({ | ||
| loc: { | ||
| start: { | ||
| line: startLine, | ||
| column: startColumn, | ||
| }, | ||
| end: { line: endLine, column: endColumn }, | ||
| }, | ||
| loc: node.position, | ||
| messageId: "referenceLikeUrl", | ||
| data: { | ||
| type, | ||
|
|
@@ -177,12 +103,8 @@ export default { | |
| return null; | ||
| } | ||
|
|
||
| const startOffset = | ||
| node.position.start.offset + matchIndex; | ||
| const endOffset = startOffset + matchLength; | ||
|
|
||
| return fixer.replaceTextRange( | ||
| [startOffset, endOffset], | ||
| return fixer.replaceText( | ||
| node, | ||
| `${prefix}[${label}][${destination}]`, | ||
| ); | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -267,8 +267,8 @@ ruleTester.run("no-reference-like-urls", rule, { | |
| data: { type: "link", prefix: "" }, | ||
| line: 1, | ||
| column: 1, | ||
| endLine: 1, | ||
| endColumn: 20, | ||
| endLine: 2, | ||
| endColumn: 9, | ||
|
Comment on lines
-270
to
+271
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like there's a bug in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| }, | ||
| ], | ||
| }, | ||
|
|
@@ -333,8 +333,8 @@ ruleTester.run("no-reference-like-urls", rule, { | |
| data: { type: "image", prefix: "!" }, | ||
| line: 1, | ||
| column: 1, | ||
| endLine: 1, | ||
| endColumn: 21, | ||
| endLine: 2, | ||
| endColumn: 9, | ||
|
Comment on lines
-336
to
+337
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as #525 (comment) |
||
| }, | ||
| ], | ||
| }, | ||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what cases were being checked by
(?!\()at the end, but no tests are failing now that I've removed it since the regex gets only the image/link node text so it can't check for a parenthesis after the image/link text.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think it's safe to remove it, since I tried to find edge cases but couldn't detect any now that we're inspecting the
linkandimagenodes directly.