Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
defer func() { Cognitive = false }()
1// SPDX-License-Identifier: MIT23package processor45import "testing"67// countCognitive runs CountStats over content for the given language with the8// Cognitive global enabled and returns the resulting FileJob so callers can9// assert on both Complexity and Cognitive.10func countCognitive(t *testing.T, language, content string) FileJob {11 t.Helper()12 ProcessConstants()1314 fileJob := FileJob{Language: language}15 fileJob.SetContent(content)16 CountStats(&fileJob)17 return fileJob18}1920// countCognitiveLines runs CountStats with both Cognitive and per-line tracking21// on, so callers can assert on the CognitiveLine array as well as the whole-file22// tally.23func countCognitiveLines(t *testing.T, language, content string) FileJob {24 t.Helper()25 ProcessConstants()2627 fileJob := FileJob{Language: language, TrackComplexityLines: true}28 fileJob.SetContent(content)29 CountStats(&fileJob)30 return fileJob31}3233func sumInt64(xs []int64) int64 {34 var total int6435 for _, x := range xs {36 total += x37 }38 return total39}4041// The per-line array must total to the whole-file Cognitive value, for flat and42// nested files alike.43func TestCognitiveLineSumEqualsCognitive(t *testing.T) {44 Cognitive = true45 defer func() { Cognitive = false }()4647 cases := map[string]string{48 "flat": "func main() {\n" +49 " if a {\n }\n if b {\n }\n if c {\n }\n}\n",50 "nested": "func main() {\n" +51 " if a {\n if b {\n if c {\n }\n }\n }\n}\n",52 "no trailing newline": "func main() {\n if a {\n if b {\n }\n }\n}",53 }5455 for name, content := range cases {56 job := countCognitiveLines(t, "Go", content)57 if got := sumInt64(job.CognitiveLine); got != job.Cognitive {58 t.Errorf("case %q: sum(CognitiveLine)=%d, want Cognitive=%d (line array %v)", name, got, job.Cognitive, job.CognitiveLine)59 }60 if job.Cognitive == 0 {61 t.Errorf("case %q: expected non-zero Cognitive", name)62 }63 }64}6566// CognitiveLine is trimmed to exactly Lines entries, the same invariant67// ComplexityLine holds, with and without a trailing newline.68func TestCognitiveLineLengthEqualsLines(t *testing.T) {69 Cognitive = true70 defer func() { Cognitive = false }()7172 cases := map[string]string{73 "trailing newline": "func main() {\n if a {\n }\n}\n",74 "no trailing newline": "func main() {\n if a {\n }\n}",75 }7677 for name, content := range cases {78 job := countCognitiveLines(t, "Go", content)79 if int64(len(job.CognitiveLine)) != job.Lines {80 t.Errorf("case %q: len(CognitiveLine)=%d, want Lines=%d", name, len(job.CognitiveLine), job.Lines)81 }82 // Must stay in lock-step with ComplexityLine's length.83 if len(job.CognitiveLine) != len(job.ComplexityLine) {84 t.Errorf("case %q: len(CognitiveLine)=%d != len(ComplexityLine)=%d", name, len(job.CognitiveLine), len(job.ComplexityLine))85 }86 }87}8889// When Cognitive is off, CognitiveLine stays nil even with per-line tracking on,90// exactly as ComplexityLine stays empty when its own tracking is off.91func TestCognitiveLineEmptyWhenDisabled(t *testing.T) {92 if Cognitive {93 t.Fatalf("Cognitive should default to false")94 }95 job := countCognitiveLines(t, "Go", "func main() {\n if a {\n }\n}\n")9697 if job.CognitiveLine != nil {98 t.Errorf("CognitiveLine should be nil when Cognitive disabled, got %v", job.CognitiveLine)99 }100 // ComplexityLine is still populated because TrackComplexityLines is on.101 if len(job.ComplexityLine) == 0 {102 t.Errorf("ComplexityLine should still be populated when only Cognitive is off")103 }104}105106func TestCognitiveFlatVsNested(t *testing.T) {107 Cognitive = true108 defer func() { Cognitive = false }()109110 flat := "func main() {\n" +111 " if a {\n" +112 " }\n" +113 " if b {\n" +114 " }\n" +115 " if c {\n" +116 " }\n" +117 "}\n"118119 nested := "func main() {\n" +120 " if a {\n" +121 " if b {\n" +122 " if c {\n" +123 " }\n" +124 " }\n" +125 " }\n" +126 "}\n"127128 flatJob := countCognitive(t, "Go", flat)129 nestedJob := countCognitive(t, "Go", nested)130131 if flatJob.Complexity != 3 {132 t.Errorf("flat Complexity: expected 3 got %d", flatJob.Complexity)133 }134 if nestedJob.Complexity != 3 {135 t.Errorf("nested Complexity: expected 3 got %d", nestedJob.Complexity)136 }137 if flatJob.Complexity != nestedJob.Complexity {138 t.Errorf("flat and nested should have equal Complexity, got %d vs %d", flatJob.Complexity, nestedJob.Complexity)139 }140141 if flatJob.Cognitive != 6 {142 t.Errorf("flat Cognitive: expected 6 got %d", flatJob.Cognitive)143 }144 if nestedJob.Cognitive != 9 {145 t.Errorf("nested Cognitive: expected 9 got %d", nestedJob.Cognitive)146 }147 if nestedJob.Cognitive <= flatJob.Cognitive {148 t.Errorf("nested Cognitive (%d) should be strictly greater than flat (%d)", nestedJob.Cognitive, flatJob.Cognitive)149 }150}151152func TestCognitiveDisabledByDefault(t *testing.T) {153 if Cognitive {154 t.Fatalf("Cognitive should default to false")155 }156 ProcessConstants()157158 nested := "func main() {\n" +159 " if a {\n" +160 " if b {\n" +161 " if c {\n" +162 " }\n" +163 " }\n" +164 " }\n" +165 "}\n"166167 fileJob := FileJob{Language: "Go"}168 fileJob.SetContent(nested)169 CountStats(&fileJob)170171 if fileJob.Cognitive != 0 {172 t.Errorf("Cognitive should be 0 when disabled, got %d", fileJob.Cognitive)173 }174 // Complexity must be unaffected by the cognitive machinery.175 if fileJob.Complexity != 3 {176 t.Errorf("Complexity should be 3 when Cognitive disabled, got %d", fileJob.Complexity)177 }178}179180func TestCognitiveTabsAndSpacesEquivalent(t *testing.T) {181 Cognitive = true182 defer func() { Cognitive = false }()183184 spaces := "func main() {\n" +185 " if a {\n" +186 " if b {\n" +187 " if c {\n" +188 " }\n" +189 " }\n" +190 " }\n" +191 "}\n"192193 tabs := "func main() {\n" +194 "\tif a {\n" +195 "\t\tif b {\n" +196 "\t\t\tif c {\n" +197 "\t\t\t}\n" +198 "\t\t}\n" +199 "\t}\n" +200 "}\n"201202 spacesJob := countCognitive(t, "Go", spaces)203 tabsJob := countCognitive(t, "Go", tabs)204205 if spacesJob.Cognitive != tabsJob.Cognitive {206 t.Errorf("tabs and spaces should yield equal Cognitive, got spaces=%d tabs=%d", spacesJob.Cognitive, tabsJob.Cognitive)207 }208 if tabsJob.Cognitive != 9 {209 t.Errorf("tabs Cognitive: expected 9 got %d", tabsJob.Cognitive)210 }211}212213func TestCognitiveCommentAndBlankDoNotAffectNesting(t *testing.T) {214 Cognitive = true215 defer func() { Cognitive = false }()216217 plain := "func main() {\n" +218 " if a {\n" +219 " }\n" +220 " if b {\n" +221 " }\n" +222 "}\n"223224 withNoise := "func main() {\n" +225 " if a {\n" +226 " }\n" +227 "\n" +228 " // deeply indented comment\n" +229 " if b {\n" +230 " }\n" +231 "}\n"232233 plainJob := countCognitive(t, "Go", plain)234 noiseJob := countCognitive(t, "Go", withNoise)235236 if plainJob.Cognitive != 4 {237 t.Errorf("plain Cognitive: expected 4 got %d", plainJob.Cognitive)238 }239 if noiseJob.Cognitive != plainJob.Cognitive {240 t.Errorf("comment/blank lines changed Cognitive: plain=%d noise=%d", plainJob.Cognitive, noiseJob.Cognitive)241 }242 if noiseJob.Complexity != plainJob.Complexity {243 t.Errorf("comment/blank lines changed Complexity: plain=%d noise=%d", plainJob.Complexity, noiseJob.Complexity)244 }245}246247func TestCognitiveStringsDoNotAffectNesting(t *testing.T) {248 Cognitive = true249 defer func() { Cognitive = false }()250251 // The `if fake` sits inside a multiline raw string: it must contribute no252 // complexity and its deep indent must not push the indent stack.253 content := "func main() {\n" +254 " s := `\n" +255 " if fake {\n" +256 " `\n" +257 " if a {\n" +258 " }\n" +259 "}\n"260261 job := countCognitive(t, "Go", content)262263 if job.Complexity != 1 {264 t.Errorf("Complexity: expected 1 (string contents ignored) got %d", job.Complexity)265 }266 if job.Cognitive != 2 {267 t.Errorf("Cognitive: expected 2 (real if at nesting 1) got %d", job.Cognitive)268 }269}270271func TestCognitivePostfixLanguage(t *testing.T) {272 Cognitive = true273 defer func() { Cognitive = false }()274275 // Rust uses `?` as a postfix complexity token, routed via276 // countComplexityPostfix. Each ? and the if must accrue weighted cognitive.277 content := "fn main() {\n" +278 " let x = foo()?;\n" +279 " if a {\n" +280 " let y = bar()?;\n" +281 " }\n" +282 "}\n"283284 job := countCognitive(t, "Rust", content)285286 // two `?` postfix tokens + one `if`287 if job.Complexity != 3 {288 t.Errorf("Complexity: expected 3 got %d", job.Complexity)289 }290 // foo()? at nesting 1 (+2), if at nesting 1 (+2), bar()? at nesting 2 (+3)291 if job.Cognitive != 7 {292 t.Errorf("Cognitive: expected 7 got %d", job.Cognitive)293 }294 if job.Cognitive == 0 {295 t.Errorf("postfix language should accrue Cognitive")296 }297}298299func TestCognitiveNoPanicPathological(t *testing.T) {300 Cognitive = true301 defer func() { Cognitive = false }()302303 cases := map[string]string{304 "empty": "",305 "single newline": "\n",306 "all whitespace": " \t \n \n\t\t",307 "no trailing nl": "func main() {\n if a {\n }\n}",308 "starts indented": " if a {\n }\n",309 "only indentation": "\t\t\t\t",310 }311312 for name, content := range cases {313 func() {314 defer func() {315 if r := recover(); r != nil {316 t.Errorf("case %q panicked: %v", name, r)317 }318 }()319 job := countCognitive(t, "Go", content)320 if job.Cognitive < 0 {321 t.Errorf("case %q produced negative Cognitive %d", name, job.Cognitive)322 }323 }()324 }325}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.