@@ -267,121 +267,93 @@ type File struct {
267267 pkg * Package
268268}
269269
270- // BlockSegment represents a segment of a basic block that can be either
271- // executable code or commented-out code.
272- type BlockSegment struct {
273- start token.Pos
274- end token.Pos
275- hasCode bool // true if this segment contains executable code
270+ // Range represents a contiguous range of executable code within a basic block.
271+ type Range struct {
272+ pos token.Pos
273+ end token.Pos
276274}
277275
278- // splitBlockByComments analyzes a block range and splits it into segments,
279- // separating executable code from any commented lines.
276+ // codeRanges analyzes a block range and returns the ranges that contain
277+ // executable code (excluding comment-only and blank lines) .
280278// It uses go/scanner to tokenize the source and identify which lines
281- // contain executable code vs. only comments.
282- func (f * File ) splitBlockByComments (start , end token.Pos ) []BlockSegment {
279+ // contain executable code.
280+ //
281+ // Precondition: start < end (both are valid positions within f.content).
282+ // start and end are positions in origFile (f.fset).
283+ func (f * File ) codeRanges (start , end token.Pos ) []Range {
283284 startOffset := f .offset (start )
284285 endOffset := f .offset (end )
285-
286- if startOffset >= endOffset || endOffset > len (f .content ) {
287- return []BlockSegment {{start : start , end : end , hasCode : true }}
288- }
289-
290286 src := f .content [startOffset :endOffset ]
291287 origFile := f .fset .File (start )
292288
293- // Create a new file set for scanning this block
294- fset := token .NewFileSet ()
295- file := fset .AddFile ("" , - 1 , len (src ))
289+ // Create a temporary file for scanning this block.
290+ // We use a separate FileSet because we're scanning a slice of the original source,
291+ // so positions in scanFile are relative to the block start, not the original file.
292+ scanFile := token .NewFileSet ().AddFile ("" , - 1 , len (src ))
296293
297294 var s scanner.Scanner
298- s .Init (file , src , nil , scanner .ScanComments )
295+ s .Init (scanFile , src , nil , scanner .ScanComments )
299296
300- // Track which lines have code vs only comments
297+ // Track which lines have executable code
301298 lineHasCode := make (map [int ]bool )
302- lineHasComment := make (map [int ]bool )
303299
304300 for {
305301 pos , tok , lit := s .Scan ()
306302 if tok == token .EOF {
307303 break
308304 }
305+ if tok == token .COMMENT {
306+ continue // Skip comments - we only care about code
307+ }
309308
310- // Calculate start and end lines using the standard pattern from go/ast
311- // (e.g., ast.Comment.End() returns pos + len(text))
312- startLine := file .Line (pos )
313- endLine := file .Line (pos + token .Pos (len (lit )) - 1 )
314- if endLine < startLine {
315- endLine = startLine
309+ // Use PositionFor with adjusted=false to ignore //line directives.
310+ startLine := scanFile .PositionFor (pos , false ).Line
311+ endLine := startLine
312+ if tok == token .STRING {
313+ // Only string literals can span multiple lines.
314+ // TODO(adonovan): simplify when https://go.dev/issue/74958 is resolved.
315+ endLine = scanFile .PositionFor (pos + token .Pos (len (lit )), false ).Line
316316 }
317317
318- if tok == token .COMMENT {
319- // Mark all lines spanned by this comment
320- for line := startLine ; line <= endLine ; line ++ {
321- lineHasComment [line ] = true
322- }
323- } else {
324- // Mark all lines spanned by this token as having code
325- for line := startLine ; line <= endLine ; line ++ {
326- lineHasCode [line ] = true
327- }
318+ for line := startLine ; line <= endLine ; line ++ {
319+ lineHasCode [line ] = true
328320 }
329321 }
330322
331- // Build segments based on line transitions
332- // The scanner has already built the line table in file, so we can use
333- // file.LineStart() to get positions directly instead of manual calculation.
334- var segments []BlockSegment
335- var currentSegmentStart token.Pos = start
336- inCommentedSection := false
323+ // Build ranges for contiguous sections of code lines.
324+ var ranges []Range
325+ var codeStart token.Pos // start of current code range (in origFile)
326+ inCode := false
337327
338- totalLines := file .LineCount ()
328+ totalLines := scanFile .LineCount ()
339329 for line := 1 ; line <= totalLines ; line ++ {
340330 hasCode := lineHasCode [line ]
341- hasComment := lineHasComment [line ]
342-
343- if inCommentedSection && hasCode {
344- // End of commented section, start of code section
345- lineOffset := file .Offset (file .LineStart (line ))
346- segmentEnd := origFile .Pos (startOffset + lineOffset )
347- segments = append (segments , BlockSegment {
348- start : currentSegmentStart ,
349- end : segmentEnd ,
350- hasCode : false ,
351- })
352- currentSegmentStart = segmentEnd
353- inCommentedSection = false
354- } else if ! inCommentedSection && ! hasCode && hasComment {
355- // End of code section, start of commented section
356- lineOffset := file .Offset (file .LineStart (line ))
357- segmentEnd := origFile .Pos (startOffset + lineOffset )
358- if currentSegmentStart < segmentEnd {
359- segments = append (segments , BlockSegment {
360- start : currentSegmentStart ,
361- end : segmentEnd ,
362- hasCode : true ,
363- })
364- }
365- currentSegmentStart = segmentEnd
366- inCommentedSection = true
331+
332+ if hasCode && ! inCode {
333+ // Start of a new code range
334+ lineOffset := scanFile .Offset (scanFile .LineStart (line ))
335+ codeStart = origFile .Pos (startOffset + lineOffset )
336+ inCode = true
337+ } else if ! hasCode && inCode {
338+ // End of code range
339+ lineOffset := scanFile .Offset (scanFile .LineStart (line ))
340+ codeEnd := origFile .Pos (startOffset + lineOffset )
341+ ranges = append (ranges , Range {pos : codeStart , end : codeEnd })
342+ inCode = false
367343 }
368344 }
369345
370- // Add the final segment
371- if currentSegmentStart < end {
372- segments = append (segments , BlockSegment {
373- start : currentSegmentStart ,
374- end : end ,
375- hasCode : ! inCommentedSection ,
376- })
346+ // Close any open code range at the end
347+ if inCode {
348+ ranges = append (ranges , Range {pos : codeStart , end : end })
377349 }
378350
379- // If no segments were created, return the original block as a code segment
380- if len (segments ) == 0 {
381- return []BlockSegment {{ start : start , end : end , hasCode : true }}
351+ // If no ranges were created, return the original block
352+ if len (ranges ) == 0 {
353+ return []Range {{ pos : start , end : end }}
382354 }
383355
384- return segments
356+ return ranges
385357}
386358
387359// findText finds text in the original source, starting at pos.
@@ -838,8 +810,9 @@ func (f *File) newCounter(start, end token.Pos, numStmt int) string {
838810 panic ("internal error: counter var unset" )
839811 }
840812 stmt = counterStmt (f , fmt .Sprintf ("%s[%d]" , f .fn .counterVar , slot ))
841- stpos := f .fset .Position (start )
842- enpos := f .fset .Position (end )
813+ // Use PositionFor with adjusted=false to ignore //line directives.
814+ stpos := f .fset .PositionFor (start , false )
815+ enpos := f .fset .PositionFor (end , false )
843816 stpos , enpos = dedup (stpos , enpos )
844817 unit := coverage.CoverableUnit {
845818 StLine : uint32 (stpos .Line ),
@@ -873,13 +846,11 @@ func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt,
873846 // Special case: make sure we add a counter to an empty block. Can't do this below
874847 // or we will add a counter to an empty statement list after, say, a return statement.
875848 if len (list ) == 0 {
876- // Split the empty block by comments and only add counter if there's executable content
877- segments := f .splitBlockByComments (insertPos , blockEnd )
878- for _ , segment := range segments {
879- if segment .hasCode {
880- f .edit .Insert (f .offset (segment .start ), f .newCounter (segment .start , segment .end , 0 )+ ";" )
881- break // Only insert one counter per basic block
882- }
849+ // Only add counter if there's executable content (not just comments)
850+ ranges := f .codeRanges (insertPos , blockEnd )
851+ if len (ranges ) > 0 {
852+ r := ranges [0 ]
853+ f .edit .Insert (f .offset (r .pos ), f .newCounter (r .pos , r .end , 0 )+ ";" )
883854 }
884855 return
885856 }
@@ -930,19 +901,16 @@ func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt,
930901 end = blockEnd
931902 }
932903 if pos != end { // Can have no source to cover if e.g. blocks abut.
933- // Split the block by comments and create counters only for executable segments
934- segments := f .splitBlockByComments (pos , end )
935- for i , segment := range segments {
936- if segment .hasCode {
937- // Only create counter for executable code segments
938- // Insert counter at the beginning of the executable segment
939- insertOffset := f .offset (segment .start )
940- // For the first segment, use the original insertPos if it's before the segment
941- if i == 0 {
942- insertOffset = f .offset (insertPos )
943- }
944- f .edit .Insert (insertOffset , f .newCounter (segment .start , segment .end , last )+ ";" )
904+ // Create counters only for executable code ranges
905+ ranges := f .codeRanges (pos , end )
906+ for i , r := range ranges {
907+ // Insert counter at the beginning of the code range
908+ insertOffset := f .offset (r .pos )
909+ // For the first range, use the original insertPos if it's before the range
910+ if i == 0 {
911+ insertOffset = f .offset (insertPos )
945912 }
913+ f .edit .Insert (insertOffset , f .newCounter (r .pos , r .end , last )+ ";" )
946914 }
947915 }
948916 list = list [last :]
@@ -1116,7 +1084,7 @@ type block1 struct {
11161084
11171085// offset translates a token position into a 0-indexed byte offset.
11181086func (f * File ) offset (pos token.Pos ) int {
1119- return f .fset .Position (pos ).Offset
1087+ return f .fset .PositionFor (pos , false ).Offset
11201088}
11211089
11221090// addVariables adds to the end of the file the declarations to set up the counter and position variables.
@@ -1158,8 +1126,9 @@ func (f *File) addVariables(w io.Writer) {
11581126 // - 32-bit ending line number
11591127 // - (16 bit ending column number << 16) | (16-bit starting column number).
11601128 for i , block := range f .blocks {
1161- start := f .fset .Position (block .startByte )
1162- end := f .fset .Position (block .endByte )
1129+ // Use PositionFor with adjusted=false to ignore //line directives.
1130+ start := f .fset .PositionFor (block .startByte , false )
1131+ end := f .fset .PositionFor (block .endByte , false )
11631132
11641133 start , end = dedup (start , end )
11651134
0 commit comments