processor/workers_cognitive_test.go GO 326 lines View on github.com → Search inside
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}

Code quality findings 15

Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer func() { Cognitive = false }()
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer func() { Cognitive = false }()
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer func() {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
// The per-line array must total to the whole-file Cognitive value, for flat and
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
" if a {\n }\n if b {\n }\n if c {\n }\n}\n",
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
" if a {\n if b {\n if c {\n }\n }\n }\n}\n",
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
"no trailing newline": "func main() {\n if a {\n if b {\n }\n }\n}",
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for name, content := range cases {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, content := range cases {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if got := sumInt64(job.CognitiveLine); got != job.Cognitive {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, content := range cases {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
" if c {\n" +
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
" if c {\n" +
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
"starts indented": " if a {\n }\n",
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for name, content := range cases {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.