@@ -207,11 +207,31 @@ fn disable_for_this_line<'a>(
207207 rope : & Rope ,
208208 source_text : & str ,
209209) -> FixWithPosition < ' a > {
210- let mut start_position = offset_to_position ( rope, error_offset, source_text) ;
211- start_position. character = 0 ; // TODO: character should be set to match the first non-whitespace character in the source text to match the existing indentation.
210+ // 1. search for the first line break character before the error_offset in source_text (if any)
211+ // 2. count all whitespace characters after the line break character
212+ // 3. set start_position.character to the line break character
213+ // 4. add the same whitespace characters to the content of the fix
214+ let bytes = source_text. as_bytes ( ) [ ..error_offset as usize ] . iter ( ) . rev ( ) ;
215+ let mut line_break_offset = error_offset;
216+ for byte in bytes {
217+ if * byte == b'\n' || * byte == b'\r' {
218+ break ;
219+ }
220+ line_break_offset -= 1 ;
221+ }
222+ let whitespace_string = source_text
223+ . as_bytes ( )
224+ . get ( line_break_offset as usize ..error_offset as usize )
225+ . and_then ( |slice| std:: str:: from_utf8 ( slice) . ok ( ) )
226+ . map ( |s| s. chars ( ) . filter ( |c| c. is_whitespace ( ) ) . collect :: < String > ( ) )
227+ . unwrap_or_default ( ) ;
228+
229+ let start_position = offset_to_position ( rope, line_break_offset, source_text) ;
212230 let end_position = start_position. clone ( ) ;
213231 FixWithPosition {
214- content : Cow :: Owned ( format ! ( "// oxlint-disable-next-line {rule_name}\n " ) ) ,
232+ content : Cow :: Owned ( format ! (
233+ "{whitespace_string}// oxlint-disable-next-line {rule_name}\n "
234+ ) ) ,
215235 span : SpanPositionMessage :: new ( start_position, end_position)
216236 . with_message ( Some ( Cow :: Owned ( format ! ( "Disable {rule_name} for this line" ) ) ) ) ,
217237 }
@@ -344,6 +364,127 @@ mod test {
344364 assert_eq ! ( fix. span. start. character, 6 ) ;
345365 }
346366
367+ #[ test]
368+ fn disable_for_this_line_single_line ( ) {
369+ let source = "console.log('hello');" ;
370+ let rope = Rope :: from_str ( source) ;
371+ let fix = super :: disable_for_this_line ( "no-console" , 8 , & rope, source) ;
372+
373+ assert_eq ! ( fix. content, "// oxlint-disable-next-line no-console\n " ) ;
374+ assert_eq ! ( fix. span. start. line, 0 ) ;
375+ assert_eq ! ( fix. span. start. character, 0 ) ;
376+ }
377+
378+ #[ test]
379+ fn disable_for_this_line_with_spaces ( ) {
380+ let source = " console.log('hello');" ;
381+ let rope = Rope :: from_str ( source) ;
382+ let fix = super :: disable_for_this_line ( "no-console" , 10 , & rope, source) ;
383+
384+ assert_eq ! ( fix. content, " // oxlint-disable-next-line no-console\n " ) ;
385+ assert_eq ! ( fix. span. start. line, 0 ) ;
386+ assert_eq ! ( fix. span. start. character, 0 ) ;
387+ }
388+
389+ #[ test]
390+ fn disable_for_this_line_with_tabs ( ) {
391+ let source = "\t \t console.log('hello');" ;
392+ let rope = Rope :: from_str ( source) ;
393+ let fix = super :: disable_for_this_line ( "no-console" , 10 , & rope, source) ;
394+
395+ assert_eq ! ( fix. content, "\t \t // oxlint-disable-next-line no-console\n " ) ;
396+ assert_eq ! ( fix. span. start. line, 0 ) ;
397+ assert_eq ! ( fix. span. start. character, 0 ) ;
398+ }
399+
400+ #[ test]
401+ fn disable_for_this_line_mixed_tabs_spaces ( ) {
402+ let source = "\t \t console.log('hello');" ;
403+ let rope = Rope :: from_str ( source) ;
404+ let fix = super :: disable_for_this_line ( "no-console" , 12 , & rope, source) ;
405+
406+ assert_eq ! ( fix. content, "\t \t // oxlint-disable-next-line no-console\n " ) ;
407+ assert_eq ! ( fix. span. start. line, 0 ) ;
408+ assert_eq ! ( fix. span. start. character, 0 ) ;
409+ }
410+
411+ #[ test]
412+ fn disable_for_this_line_multiline_with_tabs ( ) {
413+ let source = "function test() {\n \t console.log('hello');\n }" ;
414+ let rope = Rope :: from_str ( source) ;
415+ let fix = super :: disable_for_this_line ( "no-console" , 27 , & rope, source) ;
416+
417+ assert_eq ! ( fix. content, "\t // oxlint-disable-next-line no-console\n " ) ;
418+ assert_eq ! ( fix. span. start. line, 1 ) ;
419+ assert_eq ! ( fix. span. start. character, 0 ) ;
420+ }
421+
422+ #[ test]
423+ fn disable_for_this_line_multiline_with_spaces ( ) {
424+ let source = "function test() {\n console.log('hello');\n }" ;
425+ let rope = Rope :: from_str ( source) ;
426+ let fix = super :: disable_for_this_line ( "no-console" , 30 , & rope, source) ;
427+
428+ assert_eq ! ( fix. content, " // oxlint-disable-next-line no-console\n " ) ;
429+ assert_eq ! ( fix. span. start. line, 1 ) ;
430+ assert_eq ! ( fix. span. start. character, 0 ) ;
431+ }
432+
433+ #[ test]
434+ fn disable_for_this_line_complex_indentation ( ) {
435+ let source = "function test() {\n \t \t console.log('hello');\n }" ;
436+ let rope = Rope :: from_str ( source) ;
437+ let fix = super :: disable_for_this_line ( "no-console" , 33 , & rope, source) ;
438+
439+ assert_eq ! ( fix. content, "\t \t // oxlint-disable-next-line no-console\n " ) ;
440+ assert_eq ! ( fix. span. start. line, 1 ) ;
441+ assert_eq ! ( fix. span. start. character, 0 ) ;
442+ }
443+
444+ #[ test]
445+ fn disable_for_this_line_no_indentation ( ) {
446+ let source = "function test() {\n console.log('hello');\n }" ;
447+ let rope = Rope :: from_str ( source) ;
448+ let fix = super :: disable_for_this_line ( "no-console" , 26 , & rope, source) ;
449+
450+ assert_eq ! ( fix. content, "// oxlint-disable-next-line no-console\n " ) ;
451+ assert_eq ! ( fix. span. start. line, 1 ) ;
452+ assert_eq ! ( fix. span. start. character, 0 ) ;
453+ }
454+
455+ #[ test]
456+ fn disable_for_this_line_crlf_with_tabs ( ) {
457+ let source = "function test() {\r \n \t console.log('hello');\r \n }" ;
458+ let rope = Rope :: from_str ( source) ;
459+ let fix = super :: disable_for_this_line ( "no-console" , 28 , & rope, source) ;
460+
461+ assert_eq ! ( fix. content, "\t // oxlint-disable-next-line no-console\n " ) ;
462+ assert_eq ! ( fix. span. start. line, 1 ) ;
463+ assert_eq ! ( fix. span. start. character, 0 ) ;
464+ }
465+
466+ #[ test]
467+ fn disable_for_this_line_deeply_nested ( ) {
468+ let source = "if (true) {\n \t \t if (nested) {\n \t \t \t console.log('deep');\n \t \t }\n }" ;
469+ let rope = Rope :: from_str ( source) ;
470+ let fix = super :: disable_for_this_line ( "no-console" , 40 , & rope, source) ;
471+
472+ assert_eq ! ( fix. content, "\t \t \t // oxlint-disable-next-line no-console\n " ) ;
473+ assert_eq ! ( fix. span. start. line, 2 ) ;
474+ assert_eq ! ( fix. span. start. character, 0 ) ;
475+ }
476+
477+ #[ test]
478+ fn disable_for_this_line_at_start_of_file ( ) {
479+ let source = "console.log('hello');" ;
480+ let rope = Rope :: from_str ( source) ;
481+ let fix = super :: disable_for_this_line ( "no-console" , 0 , & rope, source) ;
482+
483+ assert_eq ! ( fix. content, "// oxlint-disable-next-line no-console\n " ) ;
484+ assert_eq ! ( fix. span. start. line, 0 ) ;
485+ assert_eq ! ( fix. span. start. character, 0 ) ;
486+ }
487+
347488 fn assert_position ( source : & str , offset : u32 , expected : ( u32 , u32 ) ) {
348489 let position = offset_to_position ( & Rope :: from_str ( source) , offset, source) ;
349490 assert_eq ! ( position. line, expected. 0 ) ;
0 commit comments