Skip to content

Commit 4b91e8d

Browse files
addressing review comments / change approach to identify and split by code block
1 parent f740d24 commit 4b91e8d

File tree

2 files changed

+217
-144
lines changed

2 files changed

+217
-144
lines changed

src/cmd/cover/cover.go

Lines changed: 74 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
11181086
func (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

Comments
 (0)