@@ -266,6 +266,197 @@ type File struct {
266266 pkg * Package
267267}
268268
269+ // BlockSegment represents a segment of a basic block that can be either
270+ // executable code or commented-out code.
271+ type BlockSegment struct {
272+ start token.Pos
273+ end token.Pos
274+ hasCode bool // true if this segment contains executable code
275+ }
276+
277+ // blockSplitter holds the state for splitting a block by comments.
278+ // It tracks cursor position, line state, and section boundaries
279+ // while scanning through source code character by character.
280+ type blockSplitter struct {
281+ // Source information
282+ file * token.File
283+ startOffset int
284+
285+ // Accumulated results
286+ segments []BlockSegment
287+
288+ // Cursor position
289+ i int
290+ indexStartCurrentLine int
291+ currentOffset int
292+ currentSegmentStart token.Pos
293+
294+ // Section state (persists across lines)
295+ inCommentedSection bool
296+
297+ // Line state (reset each line)
298+ lineHasCode bool
299+ lineHasComment bool
300+
301+ // Cursor state (tracks what we're inside)
302+ inSingleLineComment bool
303+ inMultiLineComment bool
304+ inString bool
305+ inRawString bool
306+ }
307+
308+ // processLine handles end-of-line processing: computes state transitions
309+ // and decides whether to create a new segment based on code/comment boundaries.
310+ func (bs * blockSplitter ) processLine () {
311+ lineStart := bs .currentOffset
312+ lineEnd := bs .currentOffset + (bs .i - bs .indexStartCurrentLine )
313+
314+ if bs .inCommentedSection && bs .lineHasCode {
315+ // End of commented section, start of code section
316+ segmentEnd := bs .file .Pos (lineStart )
317+ bs .segments = append (bs .segments , BlockSegment {
318+ start : bs .currentSegmentStart ,
319+ end : segmentEnd ,
320+ hasCode : false ,
321+ })
322+ bs .currentSegmentStart = bs .file .Pos (lineStart )
323+ bs .inCommentedSection = false
324+ } else if ! bs .inCommentedSection && ! bs .lineHasCode && bs .lineHasComment {
325+ // End of code section, start of commented section
326+ segmentEnd := bs .file .Pos (lineStart )
327+ if bs .currentSegmentStart < segmentEnd {
328+ bs .segments = append (bs .segments , BlockSegment {
329+ start : bs .currentSegmentStart ,
330+ end : segmentEnd ,
331+ hasCode : true ,
332+ })
333+ }
334+ bs .currentSegmentStart = bs .file .Pos (lineStart )
335+ bs .inCommentedSection = true
336+ }
337+
338+ bs .currentOffset = lineEnd
339+ }
340+
341+ // resetLineState resets line-specific state for a new line.
342+ func (bs * blockSplitter ) resetLineState () {
343+ bs .indexStartCurrentLine = bs .i
344+ bs .lineHasComment = bs .inMultiLineComment
345+ bs .lineHasCode = false
346+ bs .inSingleLineComment = false
347+ }
348+
349+ // inStringOrComment returns true if currently inside a string or comment.
350+ func (bs * blockSplitter ) inStringOrComment () bool {
351+ return bs .inString || bs .inRawString || bs .inSingleLineComment || bs .inMultiLineComment
352+ }
353+
354+ // splitBlockByComments analyzes a block range and splits it into segments,
355+ // separating executable code from any commented lines.
356+ // To do this, it reads character by character the original source code.
357+ func (f * File ) splitBlockByComments (start , end token.Pos ) []BlockSegment {
358+ startOffset := f .offset (start )
359+ endOffset := f .offset (end )
360+
361+ if startOffset >= endOffset || endOffset > len (f .content ) {
362+ return []BlockSegment {{start : start , end : end , hasCode : true }}
363+ }
364+
365+ originalSourceCode := f .content [startOffset :endOffset ]
366+
367+ bs := & blockSplitter {
368+ file : f .fset .File (start ),
369+ startOffset : startOffset ,
370+ currentOffset : startOffset ,
371+ currentSegmentStart : start ,
372+ }
373+
374+ for bs .i < len (originalSourceCode ) {
375+ char := originalSourceCode [bs .i ]
376+
377+ if char == '\\' && bs .inString && bs .i + 1 < len (originalSourceCode ) {
378+ bs .lineHasCode = true
379+ bs .i += 2 // Skip escaped character
380+ continue
381+ }
382+
383+ if char == '"' && ! bs .inRawString && ! bs .inSingleLineComment && ! bs .inMultiLineComment {
384+ bs .lineHasCode = true
385+ bs .inString = ! bs .inString
386+ bs .i ++
387+ continue
388+ }
389+
390+ if char == '`' && ! bs .inString && ! bs .inSingleLineComment && ! bs .inMultiLineComment {
391+ bs .lineHasCode = true
392+ bs .inRawString = ! bs .inRawString
393+ bs .i ++
394+ continue
395+ }
396+
397+ if char == '\n' {
398+ bs .i ++
399+ bs .processLine ()
400+ bs .resetLineState ()
401+ continue
402+ }
403+
404+ if bs .i + 1 < len (originalSourceCode ) {
405+ nextChar := originalSourceCode [bs .i + 1 ]
406+ if char == '/' && nextChar == '/' && ! bs .inString && ! bs .inRawString && ! bs .inMultiLineComment {
407+ bs .inSingleLineComment = true
408+ bs .lineHasComment = true
409+ bs .i += 2
410+ continue
411+ }
412+
413+ if char == '/' && nextChar == '*' && ! bs .inString && ! bs .inRawString && ! bs .inSingleLineComment {
414+ bs .inMultiLineComment = true
415+ bs .lineHasComment = true
416+ bs .i += 2
417+ continue
418+ }
419+
420+ if char == '*' && nextChar == '/' && bs .inMultiLineComment {
421+ bs .inMultiLineComment = false
422+ bs .lineHasComment = true
423+ bs .i += 2
424+ continue
425+ }
426+ }
427+
428+ // If we matched nothing else and the char is not a whitespace, we are in normal code.
429+ if ! bs .lineHasCode && ! isWhitespace (char ) && ! bs .inSingleLineComment && ! bs .inMultiLineComment {
430+ bs .lineHasCode = true
431+ }
432+
433+ bs .i ++
434+ }
435+
436+ // Process the last line if it doesn't end with a newline
437+ bs .processLine ()
438+
439+ // Add the final segment
440+ if bs .currentSegmentStart < end {
441+ bs .segments = append (bs .segments , BlockSegment {
442+ start : bs .currentSegmentStart ,
443+ end : end ,
444+ hasCode : ! bs .inCommentedSection ,
445+ })
446+ }
447+
448+ // If no segments were created, return the original block as a code segment
449+ if len (bs .segments ) == 0 {
450+ return []BlockSegment {{start : start , end : end , hasCode : true }}
451+ }
452+
453+ return bs .segments
454+ }
455+
456+ func isWhitespace (b byte ) bool {
457+ return b == ' ' || b == '\t' || b == '\n' || b == '\r'
458+ }
459+
269460// findText finds text in the original source, starting at pos.
270461// It correctly skips over comments and assumes it need not
271462// handle quoted strings.
@@ -755,7 +946,14 @@ func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt,
755946 // Special case: make sure we add a counter to an empty block. Can't do this below
756947 // or we will add a counter to an empty statement list after, say, a return statement.
757948 if len (list ) == 0 {
758- f .edit .Insert (f .offset (insertPos ), f .newCounter (insertPos , blockEnd , 0 )+ ";" )
949+ // Split the empty block by comments and only add counter if there's executable content
950+ segments := f .splitBlockByComments (insertPos , blockEnd )
951+ for _ , segment := range segments {
952+ if segment .hasCode {
953+ f .edit .Insert (f .offset (segment .start ), f .newCounter (segment .start , segment .end , 0 )+ ";" )
954+ break // Only insert one counter per basic block
955+ }
956+ }
759957 return
760958 }
761959 // Make a copy of the list, as we may mutate it and should leave the
@@ -805,7 +1003,20 @@ func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt,
8051003 end = blockEnd
8061004 }
8071005 if pos != end { // Can have no source to cover if e.g. blocks abut.
808- f .edit .Insert (f .offset (insertPos ), f .newCounter (pos , end , last )+ ";" )
1006+ // Split the block by comments and create counters only for executable segments
1007+ segments := f .splitBlockByComments (pos , end )
1008+ for i , segment := range segments {
1009+ if segment .hasCode {
1010+ // Only create counter for executable code segments
1011+ // Insert counter at the beginning of the executable segment
1012+ insertOffset := f .offset (segment .start )
1013+ // For the first segment, use the original insertPos if it's before the segment
1014+ if i == 0 {
1015+ insertOffset = f .offset (insertPos )
1016+ }
1017+ f .edit .Insert (insertOffset , f .newCounter (segment .start , segment .end , last )+ ";" )
1018+ }
1019+ }
8091020 }
8101021 list = list [last :]
8111022 if len (list ) == 0 {
0 commit comments