Skip to content

Commit d6f2d0f

Browse files
authored
refactor: use getLocFromIndex in no-space-in-emphasis (#553)
* refactor: use `getLocFromIndex` in `no-space-in-emphasis` * wip: simplify * wip: simplify * wip: simplify * wip: simplify * wip: simplify * wip * wip: simplify * wip * wip
1 parent 7c2d562 commit d6f2d0f

File tree

1 file changed

+69
-183
lines changed

1 file changed

+69
-183
lines changed

src/rules/no-space-in-emphasis.js

Lines changed: 69 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,12 @@
33
* @author Pixel998
44
*/
55

6-
//-----------------------------------------------------------------------------
7-
// Imports
8-
//-----------------------------------------------------------------------------
9-
10-
import { findOffsets } from "../util.js";
11-
126
//-----------------------------------------------------------------------------
137
// Type Definitions
148
//-----------------------------------------------------------------------------
159

1610
/**
11+
* @import { SourceRange } from "@eslint/core";
1712
* @import { Heading, Paragraph, TableCell, Text } from "mdast";
1813
* @import { MarkdownRuleDefinition } from "../types.js";
1914
* @typedef {"spaceInEmphasis"} NoSpaceInEmphasisMessageIds
@@ -34,32 +29,8 @@ const whitespacePattern = /[ \t]/u;
3429
*/
3530
function createMarkerPattern(checkStrikethrough) {
3631
return checkStrikethrough
37-
? /(?<=(?<!\\)(?:\\{2})*)(?<marker>\*\*\*|\*\*|\*|___|__|_|~~|~)/gu
38-
: /(?<=(?<!\\)(?:\\{2})*)(?<marker>\*\*\*|\*\*|\*|___|__|_)/gu;
39-
}
40-
41-
/**
42-
* Finds all emphasis markers in the text.
43-
* @param {string} text The text to search.
44-
* @param {RegExp} pattern The marker pattern to use.
45-
* @typedef {{marker: string, startIndex: number, endIndex: number}} EmphasisMarker
46-
* @returns {Array<EmphasisMarker>} Array of emphasis markers.
47-
*/
48-
function findEmphasisMarkers(text, pattern) {
49-
/** @type {Array<EmphasisMarker>} */
50-
const markers = [];
51-
/** @type {RegExpExecArray | null} */
52-
let match;
53-
54-
while ((match = pattern.exec(text)) !== null) {
55-
markers.push({
56-
marker: match.groups.marker,
57-
startIndex: match.index,
58-
endIndex: match.index + match.groups.marker.length,
59-
});
60-
}
61-
62-
return markers;
32+
? /(?<=(?<!\\)(?:\\{2})*)(?:\*\*\*|\*\*|\*|___|__|_|~~|~)/gu
33+
: /(?<=(?<!\\)(?:\\{2})*)(?:\*\*\*|\*\*|\*|___|__|_)/gu;
6334
}
6435

6536
//-----------------------------------------------------------------------------
@@ -107,182 +78,97 @@ export default {
10778
const [{ checkStrikethrough }] = context.options;
10879
const markerPattern = createMarkerPattern(checkStrikethrough);
10980

81+
/** @type {string[]} */
82+
let buffer;
83+
11084
/**
11185
* Reports a surrounding-space violation if present.
112-
* @param {Object} params Options for the report arguments.
113-
* @param {string} params.originalText The original text of the node.
114-
* @param {number} params.checkIndex Character index to test for whitespace.
115-
* @param {number} params.highlightStartIndex Start index for highlighting.
116-
* @param {number} params.highlightEndIndex End index for highlighting.
117-
* @param {number} params.removeIndex Absolute index of the space to remove.
118-
* @param {number} params.nodeStartLine The starting line number for the node.
119-
* @param {number} params.nodeStartColumn The starting column number for the node.
86+
* @param {number} checkIndex Character index to test for whitespace.
87+
* @param {number} highlightStartIndex Start index for highlighting.
88+
* @param {number} highlightEndIndex End index for highlighting.
12089
* @returns {void}
12190
*/
122-
function reportWhitespace({
123-
originalText,
91+
function reportWhitespace(
12492
checkIndex,
12593
highlightStartIndex,
12694
highlightEndIndex,
127-
removeIndex,
128-
nodeStartLine,
129-
nodeStartColumn,
130-
}) {
131-
if (whitespacePattern.test(originalText[checkIndex])) {
132-
const {
133-
lineOffset: startLineOffset,
134-
columnOffset: startColumnOffset,
135-
} = findOffsets(originalText, highlightStartIndex);
136-
const {
137-
lineOffset: endLineOffset,
138-
columnOffset: endColumnOffset,
139-
} = findOffsets(originalText, highlightEndIndex);
140-
95+
) {
96+
if (whitespacePattern.test(sourceCode.text[checkIndex])) {
14197
context.report({
14298
loc: {
143-
start: {
144-
line: nodeStartLine + startLineOffset,
145-
column: nodeStartColumn + startColumnOffset,
146-
},
147-
end: {
148-
line: nodeStartLine + endLineOffset,
149-
column: nodeStartColumn + endColumnOffset,
150-
},
99+
start: sourceCode.getLocFromIndex(highlightStartIndex),
100+
end: sourceCode.getLocFromIndex(highlightEndIndex),
151101
},
152102
messageId: "spaceInEmphasis",
153103
fix(fixer) {
154-
return fixer.removeRange([
155-
removeIndex,
156-
removeIndex + 1,
157-
]);
104+
return fixer.removeRange([checkIndex, checkIndex + 1]);
158105
},
159106
});
160107
}
161108
}
162109

163-
/**
164-
* Checks a given node for emphasis markers with surrounding spaces.
165-
* @param {Heading|Paragraph|TableCell} node The node to check.
166-
* @param {string} maskedText The masked text preserving only direct text content.
167-
* @returns {void}
168-
*/
169-
function checkEmphasis(node, maskedText) {
170-
const originalText = sourceCode.getText(node);
171-
const markers = findEmphasisMarkers(maskedText, markerPattern);
172-
const nodeStartLine = node.position.start.line;
173-
const nodeStartColumn = node.position.start.column;
174-
const nodeStartOffset = node.position.start.offset;
175-
176-
const markerGroups = new Map();
177-
for (const marker of markers) {
178-
if (!markerGroups.has(marker.marker)) {
179-
markerGroups.set(marker.marker, []);
180-
}
181-
markerGroups.get(marker.marker).push(marker);
182-
}
183-
184-
for (const group of markerGroups.values()) {
185-
for (let i = 0; i < group.length - 1; i += 2) {
186-
const startMarker = group[i];
187-
reportWhitespace({
188-
originalText,
189-
checkIndex: startMarker.endIndex,
190-
highlightStartIndex: startMarker.startIndex,
191-
highlightEndIndex: startMarker.endIndex + 2,
192-
removeIndex: nodeStartOffset + startMarker.endIndex,
193-
nodeStartLine,
194-
nodeStartColumn,
195-
});
196-
197-
const endMarker = group[i + 1];
198-
reportWhitespace({
199-
originalText,
200-
checkIndex: endMarker.startIndex - 1,
201-
highlightStartIndex: endMarker.startIndex - 2,
202-
highlightEndIndex: endMarker.endIndex,
203-
removeIndex: nodeStartOffset + endMarker.startIndex - 1,
204-
nodeStartLine,
205-
nodeStartColumn,
206-
});
207-
}
208-
}
209-
}
210-
211-
/**
212-
* Manager for building a masked character array for a node's direct text content.
213-
* @typedef {{ buffer: string[], startOffset: number }} BufferState
214-
*/
215-
const bufferManager = {
216-
/** @type {BufferState | null} */
217-
state: null,
218-
219-
/**
220-
* Initialize state with a whitespace-masked character buffer for the node.
221-
* @param {Heading|Paragraph|TableCell} node Heading, Paragraph, or TableCell node to enter.
222-
* @returns {void}
223-
*/
224-
enter(node) {
110+
return {
111+
"heading, paragraph, tableCell"(
112+
/** @type {Heading | Paragraph | TableCell} */ node,
113+
) {
225114
const [startOffset, endOffset] = sourceCode.getRange(node);
226-
this.state = {
227-
buffer: new Array(endOffset - startOffset).fill(" "),
228-
startOffset,
229-
};
230-
},
231115

232-
/**
233-
* Add the content of a Text node into the current buffer at the correct offsets.
234-
* @param {Text} node Text node whose characters will be copied into the buffer.
235-
* @returns {void}
236-
*/
237-
addText(node) {
238-
const start =
239-
node.position.start.offset - this.state.startOffset;
240-
const text = sourceCode.getText(node);
241-
for (let i = 0; i < text.length; i++) {
242-
this.state.buffer[start + i] = text[i];
243-
}
116+
// Initialize `buffer` with a whitespace-masked character array.
117+
buffer = new Array(endOffset - startOffset).fill(" ");
244118
},
245119

246-
/**
247-
* Join the character buffer into a masked string, run checks, then clear state.
248-
* @param {Heading|Paragraph|TableCell} node Heading, Paragraph, or TableCell node to exit.
249-
* @returns {void}
250-
*/
251-
exit(node) {
252-
checkEmphasis(node, this.state.buffer.join(""));
253-
this.state = null;
254-
},
255-
};
120+
":matches(heading, paragraph, tableCell) > text"(
121+
/** @type {Text} */ node,
122+
) {
123+
const [startOffset, endOffset] = sourceCode.getRange(node);
124+
const parentNodeStartOffset = // Parent node can be `Heading`, `Paragraph`, or `TableCell`.
125+
sourceCode.getParent(node).position.start.offset;
256126

257-
return {
258-
heading(node) {
259-
bufferManager.enter(node);
260-
},
261-
"heading > text"(node) {
262-
bufferManager.addText(node);
263-
},
264-
"heading:exit"(node) {
265-
bufferManager.exit(node);
127+
// Add the content of a `Text` node into the current buffer at the correct offsets.
128+
for (let i = startOffset; i < endOffset; i++) {
129+
buffer[i - parentNodeStartOffset] = sourceCode.text[i];
130+
}
266131
},
267132

268-
paragraph(node) {
269-
bufferManager.enter(node);
270-
},
271-
"paragraph > text"(node) {
272-
bufferManager.addText(node);
273-
},
274-
"paragraph:exit"(node) {
275-
bufferManager.exit(node);
276-
},
133+
":matches(heading, paragraph, tableCell):exit"(
134+
/** @type {Heading | Paragraph | TableCell} */ node,
135+
) {
136+
const maskedText = buffer.join("");
137+
/** @type {Map<string, SourceRange[]>} */
138+
const markerGroups = new Map();
139+
140+
/** @type {RegExpExecArray | null} */
141+
let match;
142+
143+
while ((match = markerPattern.exec(maskedText)) !== null) {
144+
const marker = match[0];
145+
const startOffset = // Adjust `markerPattern` match index to the full source code.
146+
match.index + node.position.start.offset;
147+
const endOffset = startOffset + marker.length;
148+
149+
if (!markerGroups.has(marker)) {
150+
markerGroups.set(marker, []);
151+
}
152+
markerGroups.get(marker).push([startOffset, endOffset]);
153+
}
277154

278-
tableCell(node) {
279-
bufferManager.enter(node);
280-
},
281-
"tableCell > text"(node) {
282-
bufferManager.addText(node);
283-
},
284-
"tableCell:exit"(node) {
285-
bufferManager.exit(node);
155+
for (const group of markerGroups.values()) {
156+
for (let i = 0; i < group.length - 1; i += 2) {
157+
const startMarker = group[i];
158+
reportWhitespace(
159+
startMarker[1],
160+
startMarker[0],
161+
startMarker[1] + 2,
162+
);
163+
164+
const endMarker = group[i + 1];
165+
reportWhitespace(
166+
endMarker[0] - 1,
167+
endMarker[0] - 2,
168+
endMarker[1],
169+
);
170+
}
171+
}
286172
},
287173
};
288174
},

0 commit comments

Comments
 (0)