Skip to content

Commit 28723c2

Browse files
fix: report locations with <CR> linebreaks in no-reference-like-urls (#525)
* fix: report locations with <CR> linebreaks in `no-reference-like-urls` * update regex end * add type for node Co-authored-by: 루밀LuMir <[email protected]> * update type names --------- Co-authored-by: 루밀LuMir <[email protected]>
1 parent 3ab4442 commit 28723c2

File tree

2 files changed

+17
-95
lines changed

2 files changed

+17
-95
lines changed

src/rules/no-reference-like-urls.js

Lines changed: 13 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,17 @@
88
//-----------------------------------------------------------------------------
99

1010
import { normalizeIdentifier } from "micromark-util-normalize-identifier";
11-
import { findOffsets } from "../util.js";
1211

1312
//-----------------------------------------------------------------------------
1413
// Type Definitions
1514
//-----------------------------------------------------------------------------
1615

1716
/**
18-
* @import { SourceRange } from "@eslint/core"
19-
* @import { Heading, Paragraph, TableCell } from "mdast";
17+
* @import { Image, Link } from "mdast";
2018
* @import { MarkdownRuleDefinition } from "../types.js";
21-
* @typedef {"referenceLikeUrl"} NoReferenceLikeUrlMessageIds
22-
* @typedef {[]} NoReferenceLikeUrlOptions
23-
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReferenceLikeUrlOptions, MessageIds: NoReferenceLikeUrlMessageIds }>} NoReferenceLikeUrlRuleDefinition
19+
* @typedef {"referenceLikeUrl"} NoReferenceLikeUrlsMessageIds
20+
* @typedef {[]} NoReferenceLikeUrlsOptions
21+
* @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReferenceLikeUrlsOptions, MessageIds: NoReferenceLikeUrlsMessageIds }>} NoReferenceLikeUrlsRuleDefinition
2422
*/
2523

2624
//-----------------------------------------------------------------------------
@@ -29,23 +27,13 @@ import { findOffsets } from "../util.js";
2927

3028
/** Pattern to match both inline links: `[text](url)` and images: `![alt](url)`, with optional title */
3129
const linkOrImagePattern =
32-
/(?<=(?<!\\)(?:\\{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;
33-
34-
/**
35-
* Checks if a given index is within any skip range.
36-
* @param {number} index The index to check
37-
* @param {Array<SourceRange>} skipRanges The skip ranges
38-
* @returns {boolean} True if index is in a skip range
39-
*/
40-
function isInSkipRange(index, skipRanges) {
41-
return skipRanges.some(range => range[0] <= index && index < range[1]);
42-
}
30+
/(?<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;
4331

4432
//-----------------------------------------------------------------------------
4533
// Rule Definition
4634
//-----------------------------------------------------------------------------
4735

48-
/** @type {NoReferenceLikeUrlRuleDefinition} */
36+
/** @type {NoReferenceLikeUrlsRuleDefinition} */
4937
export default {
5038
meta: {
5139
type: "problem",
@@ -67,66 +55,33 @@ export default {
6755

6856
create(context) {
6957
const { sourceCode } = context;
70-
/** @type {Array<SourceRange>} */
71-
const skipRanges = [];
7258
/** @type {Set<string>} */
7359
const definitionIdentifiers = new Set();
74-
/** @type {Array<Heading | Paragraph | TableCell>} */
60+
/** @type {Array<Image | Link>} */
7561
const relevantNodes = [];
7662

7763
return {
7864
definition(node) {
7965
definitionIdentifiers.add(node.identifier);
8066
},
8167

82-
heading(node) {
83-
relevantNodes.push(node);
84-
},
85-
86-
"heading :matches(html, inlineCode)"(node) {
87-
skipRanges.push(sourceCode.getRange(node));
88-
},
89-
90-
paragraph(node) {
68+
"image, link"(/** @type {Image | Link} */ node) {
9169
relevantNodes.push(node);
9270
},
9371

94-
"paragraph :matches(html, inlineCode)"(node) {
95-
skipRanges.push(sourceCode.getRange(node));
96-
},
97-
98-
tableCell(node) {
99-
relevantNodes.push(node);
100-
},
101-
102-
"tableCell :matches(html, inlineCode)"(node) {
103-
skipRanges.push(sourceCode.getRange(node));
104-
},
105-
10672
"root:exit"() {
10773
for (const node of relevantNodes) {
10874
const text = sourceCode.getText(node);
10975

110-
let match;
111-
while ((match = linkOrImagePattern.exec(text)) !== null) {
76+
const match = linkOrImagePattern.exec(text);
77+
if (match !== null) {
11278
const {
11379
imageBang,
11480
label,
11581
destination,
11682
title: titleRaw,
11783
} = match.groups;
11884
const title = titleRaw?.slice(1, -1);
119-
const matchIndex = match.index;
120-
const matchLength = match[0].length;
121-
122-
if (
123-
isInSkipRange(
124-
matchIndex + node.position.start.offset,
125-
skipRanges,
126-
)
127-
) {
128-
continue;
129-
}
13085

13186
const isImage = !!imageBang;
13287
const type = isImage ? "image" : "link";
@@ -135,37 +90,8 @@ export default {
13590
normalizeIdentifier(destination).toLowerCase();
13691

13792
if (definitionIdentifiers.has(url)) {
138-
const {
139-
lineOffset: startLineOffset,
140-
columnOffset: startColumnOffset,
141-
} = findOffsets(text, matchIndex);
142-
const {
143-
lineOffset: endLineOffset,
144-
columnOffset: endColumnOffset,
145-
} = findOffsets(text, matchIndex + matchLength);
146-
147-
const baseColumn = 1;
148-
const nodeStartLine = node.position.start.line;
149-
const nodeStartColumn = node.position.start.column;
150-
const startLine = nodeStartLine + startLineOffset;
151-
const endLine = nodeStartLine + endLineOffset;
152-
const startColumn =
153-
(startLine === nodeStartLine
154-
? nodeStartColumn
155-
: baseColumn) + startColumnOffset;
156-
const endColumn =
157-
(endLine === nodeStartLine
158-
? nodeStartColumn
159-
: baseColumn) + endColumnOffset;
160-
16193
context.report({
162-
loc: {
163-
start: {
164-
line: startLine,
165-
column: startColumn,
166-
},
167-
end: { line: endLine, column: endColumn },
168-
},
94+
loc: node.position,
16995
messageId: "referenceLikeUrl",
17096
data: {
17197
type,
@@ -177,12 +103,8 @@ export default {
177103
return null;
178104
}
179105

180-
const startOffset =
181-
node.position.start.offset + matchIndex;
182-
const endOffset = startOffset + matchLength;
183-
184-
return fixer.replaceTextRange(
185-
[startOffset, endOffset],
106+
return fixer.replaceText(
107+
node,
186108
`${prefix}[${label}][${destination}]`,
187109
);
188110
},

tests/rules/no-reference-like-urls.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,8 @@ ruleTester.run("no-reference-like-urls", rule, {
267267
data: { type: "link", prefix: "" },
268268
line: 1,
269269
column: 1,
270-
endLine: 1,
271-
endColumn: 20,
270+
endLine: 2,
271+
endColumn: 9,
272272
},
273273
],
274274
},
@@ -333,8 +333,8 @@ ruleTester.run("no-reference-like-urls", rule, {
333333
data: { type: "image", prefix: "!" },
334334
line: 1,
335335
column: 1,
336-
endLine: 1,
337-
endColumn: 21,
336+
endLine: 2,
337+
endColumn: 9,
338338
},
339339
],
340340
},

0 commit comments

Comments
 (0)