1+ import type { TSESLint } from '@typescript-eslint/utils' ;
12import { createRule } from '../util' ;
23import { oneLine } from '../util/messages' ;
34
@@ -134,13 +135,20 @@ export default createRule<Options, MessageIds>({
134135 const comments = sourceCode . getAllComments ( ) ;
135136
136137 for ( const comment of comments ) {
138+ // start of the comment value, right after the `//` or `/*`
139+ const valueStart = comment . range [ 0 ] + 2 ;
140+
137141 if ( comment . type === 'Line' ) {
138142 const firstChar = comment . value ?. charCodeAt ( 0 ) ;
139143 const secondChar = comment . value [ 1 ] ;
140144 const lastChar = comment . value [ comment . value . length - 1 ] ;
141145
142146 if ( firstChar !== SPACE_CHARCODE && firstChar !== SLASH_CHARCODE && ! isRegion ( comment . value ) ) {
143- context . report ( { node : comment , messageId : 'shouldStartWithSpace' } ) ;
147+ context . report ( {
148+ node : comment ,
149+ messageId : 'shouldStartWithSpace' ,
150+ fix : ( fixer ) => fixer . insertTextBeforeRange ( [ valueStart , valueStart ] , ' ' ) ,
151+ } ) ;
144152
145153 // if this one fails, the others are interpreted incorrectly
146154 continue ;
@@ -151,11 +159,19 @@ export default createRule<Options, MessageIds>({
151159 isCapital ( secondChar ) &&
152160 ! isCapitalizedOrAllowed ( comment . value . slice ( 1 ) , ignoredWords )
153161 ) {
154- context . report ( { node : comment , messageId : 'lineCommentCapital' } ) ;
162+ context . report ( {
163+ node : comment ,
164+ messageId : 'lineCommentCapital' ,
165+ fix : ( fixer ) => fixer . replaceTextRange ( [ valueStart + 1 , valueStart + 2 ] , secondChar . toLowerCase ( ) ) ,
166+ } ) ;
155167 }
156168
157169 if ( lastChar === '.' && ! comment . value . endsWith ( 'etc.' ) && ! comment . value . endsWith ( '...' ) ) {
158- context . report ( { node : comment , messageId : 'lineCommentEnding' } ) ;
170+ context . report ( {
171+ node : comment ,
172+ messageId : 'lineCommentEnding' ,
173+ fix : ( fixer ) => fixer . removeRange ( [ comment . range [ 1 ] - 1 , comment . range [ 1 ] ] ) ,
174+ } ) ;
159175 }
160176
161177 continue ;
@@ -169,9 +185,37 @@ export default createRule<Options, MessageIds>({
169185 continue ;
170186 }
171187
188+ const valueLines = lines ;
189+ const indent = ' ' . repeat ( comment . loc . start . column + 1 ) ;
190+
191+ const lineStartOffsets : number [ ] = [ ] ;
192+
193+ let currentOffset = 0 ;
194+
195+ for ( const valueLine of valueLines ) {
196+ lineStartOffsets . push ( currentOffset ) ;
197+ currentOffset += valueLine . length + 1 ;
198+ }
199+
200+ // absolute source range of the line at `index` in the sliced `lines` array
201+ const lineRange = ( index : number ) : [ number , number ] => {
202+ const start = valueStart + lineStartOffsets [ index + 1 ] ;
203+
204+ return [ start , start + valueLines [ index + 1 ] . length ] ;
205+ } ;
206+
172207 // verify the first char is a '*'
173208 if ( lines [ 0 ] !== STAR ) {
174- context . report ( { node : comment , messageId : 'shouldStartWithBlock' } ) ;
209+ context . report ( {
210+ node : comment ,
211+ messageId : 'shouldStartWithBlock' ,
212+ fix : ( fixer ) => {
213+ const content = valueLines [ 0 ] . replace ( / ^ \* + \s * / , '' ) . trim ( ) ;
214+ const range : [ number , number ] = [ valueStart , valueStart + valueLines [ 0 ] . length ] ;
215+
216+ return fixer . replaceTextRange ( range , content ? `*\n${ indent } * ${ content } ` : '*' ) ;
217+ } ,
218+ } ) ;
175219 continue ;
176220 }
177221
@@ -199,12 +243,29 @@ export default createRule<Options, MessageIds>({
199243
200244 if ( isLastLine ) {
201245 if ( ! BLOCK_COMMENT_END . test ( line ) ) {
202- context . report ( { node : comment , messageId : 'shouldEndWithBlock' } ) ;
246+ context . report ( {
247+ node : comment ,
248+ messageId : 'shouldEndWithBlock' ,
249+ fix : ( fixer ) => {
250+ const [ start ] = lineRange ( i ) ;
251+ const contentEnd = start + line . replace ( / \s + $ / , '' ) . length ;
252+
253+ return fixer . replaceTextRange ( [ contentEnd , comment . range [ 1 ] - 2 ] , `\n${ indent } ` ) ;
254+ } ,
255+ } ) ;
203256 break ;
204257 }
205258
206259 if ( EMPTY_BLOCK_COMMENT_LINE . test ( prevLine ) ) {
207- context . report ( { node : comment , messageId : 'noSpaceBeforeEnd' } ) ;
260+ context . report ( {
261+ node : comment ,
262+ messageId : 'noSpaceBeforeEnd' ,
263+ fix : ( fixer ) => {
264+ const [ prevStart , prevEnd ] = lineRange ( i - 1 ) ;
265+
266+ return fixer . removeRange ( [ prevStart - 1 , prevEnd ] ) ;
267+ } ,
268+ } ) ;
208269 break ;
209270 }
210271
@@ -226,7 +287,16 @@ export default createRule<Options, MessageIds>({
226287
227288 if ( isCurrentLineJSDoc && ! insideCodeBlock ) {
228289 if ( prevLine && ! EMPTY_BLOCK_COMMENT_LINE . test ( prevLine ) && ! JS_DOC_REGEX . test ( prevLine ) ) {
229- context . report ( { node : comment , messageId : 'spaceBeforeJSDoc' , data : { ...lineData } } ) ;
290+ context . report ( {
291+ node : comment ,
292+ messageId : 'spaceBeforeJSDoc' ,
293+ data : { ...lineData } ,
294+ fix : ( fixer ) => {
295+ const [ start ] = lineRange ( i ) ;
296+
297+ return fixer . insertTextBeforeRange ( [ start , start ] , `${ indent } *\n` ) ;
298+ } ,
299+ } ) ;
230300 break ;
231301 }
232302
@@ -238,7 +308,23 @@ export default createRule<Options, MessageIds>({
238308 }
239309
240310 if ( ! isCurrentLineEmpty && ! BLOCK_COMMENT_LINE_START . test ( line ) ) {
241- context . report ( { node : comment , messageId : 'invalidBlockCommentLine' , data : { ...lineData } } ) ;
311+ context . report ( {
312+ node : comment ,
313+ messageId : 'invalidBlockCommentLine' ,
314+ data : { ...lineData } ,
315+ fix : ( fixer ) => {
316+ const [ start ] = lineRange ( i ) ;
317+ const starMatch = line . match ( / ^ \s * \* / ) ;
318+
319+ if ( starMatch ) {
320+ return fixer . insertTextAfterRange ( [ start , start + starMatch [ 0 ] . length ] , ' ' ) ;
321+ }
322+
323+ const leadingWhitespace = line . match ( / ^ \s * / ) ?. [ 0 ] ?? '' ;
324+
325+ return fixer . insertTextAfterRange ( [ start , start + leadingWhitespace . length ] , '* ' ) ;
326+ } ,
327+ } ) ;
242328 break ;
243329 }
244330
@@ -249,7 +335,14 @@ export default createRule<Options, MessageIds>({
249335 prevListItemIndentation = line . match ( LIST_ITEM_INDENTATION ) ?. [ 1 ] . length ;
250336
251337 if ( spaces !== 1 ) {
252- context . report ( { node : comment , messageId : 'invalidListItem' , data : { ...lineData } } ) ;
338+ context . report ( {
339+ node : comment ,
340+ messageId : 'invalidListItem' ,
341+ data : { ...lineData } ,
342+ fix : ( fixer ) => {
343+ return fixer . replaceTextRange ( lineRange ( i ) , line . replace ( / ^ ( \s * \* \s * (?: - | \d \. ) ) \s * / , '$1 ' ) ) ;
344+ } ,
345+ } ) ;
253346 break ;
254347 }
255348
@@ -276,7 +369,20 @@ export default createRule<Options, MessageIds>({
276369 const firstChar = text [ 0 ] ;
277370
278371 if ( isLetter ( firstChar ) && ! ( isCapital ( firstChar ) || isCapitalizedOrAllowed ( text , ignoredWords ) ) ) {
279- context . report ( { node : comment , messageId : 'paragraphCapitalized' , data : { ...lineData } } ) ;
372+ context . report ( {
373+ node : comment ,
374+ messageId : 'paragraphCapitalized' ,
375+ data : { ...lineData } ,
376+ fix : ( fixer ) => {
377+ const [ start ] = lineRange ( i ) ;
378+ const prefixLength = line . length - text . length ;
379+
380+ return fixer . replaceTextRange (
381+ [ start + prefixLength , start + prefixLength + 1 ] ,
382+ firstChar . toUpperCase ( ) ,
383+ ) ;
384+ } ,
385+ } ) ;
280386 break ;
281387 }
282388 }
@@ -287,8 +393,19 @@ export default createRule<Options, MessageIds>({
287393 if ( isNextLineEmpty || isLastContentLine || isNextLineCodeBlock ) {
288394 const lastChar = line [ line . length - 1 ] ;
289395
396+ const appendDot = ( fixer : TSESLint . RuleFixer ) => {
397+ const [ start ] = lineRange ( i ) ;
398+
399+ return fixer . insertTextAfterRange ( [ start , start + line . replace ( / \s + $ / , '' ) . length ] , '.' ) ;
400+ } ;
401+
290402 if ( isLastContentLine && ! allowedParagraphEndings . some ( ( ending ) => ending === lastChar ) ) {
291- context . report ( { node : comment , messageId : 'shouldEndWithDot' , data : { ...lineData } } ) ;
403+ context . report ( {
404+ node : comment ,
405+ messageId : 'shouldEndWithDot' ,
406+ data : { ...lineData } ,
407+ fix : appendDot ,
408+ } ) ;
292409 break ;
293410 }
294411
@@ -297,6 +414,7 @@ export default createRule<Options, MessageIds>({
297414 node : comment ,
298415 messageId : 'invalidParagraphEnding' ,
299416 data : { ...lineData , allowedParagraphEndings : `[${ allowedParagraphEndings . join ( ' ' ) } ]` } ,
417+ fix : appendDot ,
300418 } ) ;
301419
302420 break ;
0 commit comments