Skip to content

fix: stop table-driven tests from being reported as clones#32

Open
machado144 wants to merge 2 commits into
mainfrom
fix/table-literal-false-positive
Open

fix: stop table-driven tests from being reported as clones#32
machado144 wants to merge 2 commits into
mainfrom
fix/table-literal-false-positive

Conversation

@machado144

Copy link
Copy Markdown
Contributor

Closes #31

Table-driven tests matched their own rows. The reported "instances" were overlapping sliding windows over a single composite literal, not duplicated logic — which contradicted the README's promise that data literals are excluded.

Two defects

1. Overlapping instances. Seeds inside a file are spaced minTokens apart, but greedy extension grows every block to totalTokens, so neighbouring instances overlapped — one stretch of text counted several times.

#1  type-2  24 lines  3 instances
      table_test.go:15-38
      table_test.go:33-56   <- overlaps 15-38
      table_test.go:51-74   <- overlaps 33-56

2. Data tables. The rows of a collection literal are structurally identical by design. Literals are now recorded as token spans, and a clone whose every instance lies inside one span is dropped as data.

On the approach

Excluding literal tokens from detection (the issue's suggestion 1) was implemented first, then rejected on measurement. It punches holes: a short inline []TokenizedFile{a, b} in the middle of a duplicated block split that block in two and lost the finding.

Measured on this repo's own test suite:

approach clones removed genuine duplication lost
mask literal tokens 58 ~50
span rule (shipped) 5 0

A/B of the pre- and post-fix binaries over identical pristine source: 157 → 152 clones, 0 new. Four removals are overlapping-window artifacts; the fifth is a real []domain.Clone{…} fixture table. Self-scan under the repo's own config is unchanged (13 clones, 5.1%), so the CI gate is unaffected.

Language coverage

Go ([]T{…}, map[K]V{…}, []struct{…}{…}), Python, JavaScript, TypeScript, Ruby, Rust, PHP, Elixir, Swift, Dart, Lua.

Java, C, C++, C# and Kotlin are deliberately excluded — there [ is an index or an array type and { is also a block, so a literal cannot be told from code by tokens alone. Under-suppression is the intended failure mode: a missed false positive is noise, a swallowed real clone is a silent correctness loss.

Bugs found while building this

Fixed here rather than deferred:

  • markPythonFunctions skipped every function after the first. It ended a body at the next blank-line-separated def, then resumed scanning after that keyword. Those bodies were never in scope, so their duplication was invisible. In a file of N defs, only the first was ever scanned.
  • Dart bodies misread as literals. Bodies in languages without a func keyword come from a brace-depth heuristic that cannot tell a function body from a {…} map literal at the same depth. A real body follows ) or an arrow; a literal follows =, , or [.
  • TokenizeFile panicked on a nil language while markFunctionBodies documents nil as "no syntax knowledge". Unreachable today (collectFiles drops unknown-language files first), but the contract was incoherent.

Tests

469 pass with -race; lint clean. Every new test was run against main and fails there:

  • services/table_literal_test.go — the issue repro verbatim, plus gofmt-style rows
  • services/overlap_test.go — the no-overlap invariant, and that disjoint repeats still report
  • services/composite_literal_test.go — literal vs. logic, func type vs. func literal, barrier cases (empty, unterminated, start-of-stream, nil/opted-out language)
  • services/data_literal_langs_test.go — per language: tables recognised, indexing (m[k]) not mistaken for a literal, callbacks inside tables still checked
  • services/pyfuncboundary_test.go — 7 of 8 fail on main
  • integration/table_literal_test.go — the built binary at real defaults; all 4 fail on main

Negative coverage matters as much as positive here: duplication in test files, in table callback columns, and in blocks containing inline literals is each pinned by a test, so "no clones" can't quietly mean "detection broke".

A table-driven test matched its own rows: the "instances" were overlapping
sliding windows over one composite literal, not duplicated logic. Two
independent defects.

1. Overlapping instances. Seeds inside a file are spaced minTokens apart,
   but greedy extension grows each block to totalTokens, so neighbouring
   instances overlapped — the same text counted several times. Instances
   are now kept only when they stay clear of the previous one.

2. Data tables. The rows of a collection literal are structurally identical
   by design. Literals are recorded as token spans, and a clone whose every
   instance lies inside ONE span is dropped as data.

Masking literal tokens out of detection was tried first and rejected: a
short inline `[]T{a, b}` in the middle of a duplicated block split that
block and lost the finding. On this repo's own tests that approach dropped
~50 genuine clones; the span rule drops none.

Logic still counts — a callback column, two identical tables in different
places, and blocks containing inline literals are all still reported.

Covers Go, Python, JS/TS, Ruby, Rust, PHP, Elixir, Swift, Dart and Lua.
Java, C, C++, C# and Kotlin are excluded: there `[` is an index or array
type and `{` is also a block, so a literal can't be told from code.

Two bugs found while building this, fixed here:

- markPythonFunctions resumed scanning *after* the `def` that ended a body,
  stepping over it, so every function after the first was never in scope and
  its duplication was invisible.
- Bodies in languages without a `func` keyword come from a brace-depth
  heuristic that read a `{…}` map literal as a function body; a real body
  follows `)` or an arrow, a literal follows `=`, `,` or `[`.
- TokenizeFile dereferenced a nil language while markFunctionBodies
  documents nil as "no syntax knowledge".

Closes #31
README now states which literals are excluded, in which languages, and
what still counts as duplication. Adds the implementation plan for #31,
including the approach that was tried and rejected.
@github-actions

Copy link
Copy Markdown

✅ PR title follows the required format

Current title: fix: stop table-driven tests from being reported as clones

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False positive: table-driven test literals flagged as clones despite documented exclusion of data/config literals

1 participant