diff --git a/internal/engine/directives.go b/internal/engine/directives.go index adc5204..de8edd8 100644 --- a/internal/engine/directives.go +++ b/internal/engine/directives.go @@ -203,16 +203,14 @@ func configKeyForType(mt mutator.Type) string { // collectTokenLines returns the set of source lines that contain at least // one non-comment AST node, used to distinguish end-of-line directives from -// own-line ones. +// own-line ones. Returning false on *ast.CommentGroup stops the walk before +// it reaches the *ast.Comment children, so no Comment guard is needed. func collectTokenLines(set *token.FileSet, file *ast.File) map[int]bool { lines := map[int]bool{} ast.Inspect(file, func(n ast.Node) bool { if n == nil { return false } - if _, isComment := n.(*ast.Comment); isComment { - return false - } if _, isCG := n.(*ast.CommentGroup); isCG { return false } @@ -264,9 +262,6 @@ func largestNodeStartingAtLine(set *token.FileSet, file *ast.File, line int) (as if _, isCG := n.(*ast.CommentGroup); isCG { return false } - if _, isC := n.(*ast.Comment); isC { - return false - } if set.Position(n.Pos()).Line != line { return true } diff --git a/internal/engine/directives_internal_test.go b/internal/engine/directives_internal_test.go index 130b0d0..a19611b 100644 --- a/internal/engine/directives_internal_test.go +++ b/internal/engine/directives_internal_test.go @@ -245,6 +245,46 @@ func F() int { } } +func TestDirectiveIndex_NilReceiverIsSafe(t *testing.T) { + t.Parallel() + + // A nil index must answer "not suppressed" without panicking. The + // engine relies on this so it can call isSuppressed unconditionally + // even when no index has been built (currently never happens, but + // the guard is cheap and protects future call sites). + var idx *directiveIndex + if idx.isSuppressed(token.Position{Line: 1}, mutator.ArithmeticBase) { + t.Errorf("nil directiveIndex must return false from isSuppressed") + } +} + +func TestBuildDirectiveIndex_TypedFilterWithEmptyEntries(t *testing.T) { + t.Parallel() + + // Doubled commas / leading commas leave empty strings in the comma- + // split type list. Each empty entry should be skipped silently and + // the surrounding valid types still parsed. + src := `package p + +func F() int { + return 1 + 2 //nomutant:arithmetic-base,,invert-bitwise +} +` + set, file := parseSrc(t, src) + idx := buildDirectiveIndex(set, file) + + pos := positionOf(t, set, src, "+") + if !idx.isSuppressed(pos, mutator.ArithmeticBase) { + t.Errorf("expected ArithmeticBase to be suppressed (valid type before doubled comma)") + } + if !idx.isSuppressed(pos, mutator.InvertBitwise) { + t.Errorf("expected InvertBitwise to be suppressed (valid type after doubled comma)") + } + if idx.isSuppressed(pos, mutator.ConditionalsBoundary) { + t.Errorf("did NOT expect ConditionalsBoundary to be suppressed (not in filter)") + } +} + func TestBuildDirectiveIndex_NestedBlocks_Additive(t *testing.T) { t.Parallel()