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 */
3530function 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