processor/workers_test.go GO 2,220 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,220.
1// SPDX-License-Identifier: MIT23package processor45import (6	"bytes"7	"reflect"8	"strings"9	"testing"10)1112func (job *FileJob) SetContent(content string) {13	job.Content = []byte(content)14	job.Bytes = int64(len(job.Content))15}1617func TestIsWhitespace(t *testing.T) {18	if !isWhitespace(' ') {19		t.Errorf("Expected to be true")20	}21}2223func TestIsBinaryTrue(t *testing.T) {24	DisableCheckBinary = false2526	if !isBinary(0, 0) {27		t.Errorf("Expected to be true")28	}29}3031func TestIsBinaryDisableCheck(t *testing.T) {32	DisableCheckBinary = true3334	if isBinary(0, 0) {35		t.Errorf("Expected to be false")36	}37}3839func TestCountStatsLines(t *testing.T) {40	Trace = false41	Debug = false42	Verbose = false4344	fileJob := FileJob{45		Content: []byte(""),46		Lines:   0,47	}4849	// Both tokei and sloccount count this as 0 so lets follow suit50	// cloc ignores the file itself because it is empty51	CountStats(&fileJob)52	if fileJob.Lines != 0 {53		t.Errorf("Zero lines expected got %d", fileJob.Lines)54	}5556	// Interestingly this file would be 0 lines in "wc -l" because it only counts newlines57	// all others count this as 158	fileJob.Lines = 059	fileJob.SetContent("a")60	CountStats(&fileJob)61	if fileJob.Lines != 1 {62		t.Errorf("One line expected got %d", fileJob.Lines)63	}6465	fileJob.Lines = 066	fileJob.SetContent("a\n")67	CountStats(&fileJob)68	if fileJob.Lines != 1 {69		t.Errorf("One line expected got %d", fileJob.Lines)70	}7172	// tokei counts this as 1 because it's still on a single line unless something follows73	// the newline it's still 1 line74	fileJob.Lines = 075	fileJob.SetContent("1\n")76	CountStats(&fileJob)77	if fileJob.Lines != 1 {78		t.Errorf("One line expected got %d", fileJob.Lines)79	}8081	fileJob.Lines = 082	fileJob.SetContent("1\n2\n")83	CountStats(&fileJob)84	if fileJob.Lines != 2 {85		t.Errorf("Two lines expected got %d", fileJob.Lines)86	}8788	fileJob.Lines = 089	fileJob.SetContent("1\n2\n3")90	CountStats(&fileJob)91	if fileJob.Lines != 3 {92		t.Errorf("Three lines expected got %d", fileJob.Lines)93	}9495	content := ""96	for i := range 5000 {97		content += "a\n"98		fileJob.Lines = 099		fileJob.SetContent(content)100		CountStats(&fileJob)101		if fileJob.Lines != int64(i+1) {102			t.Errorf("Expected %d got %d", i+1, fileJob.Lines)103		}104	}105}106107func TestCountStatsCode(t *testing.T) {108	fileJob := FileJob{109		Content: []byte(""),110		Code:    0,111	}112113	// Both tokei and sloccount count this as 0 so lets follow suit114	// cloc ignores the file itself because it is empty115	CountStats(&fileJob)116	if fileJob.Code != 0 {117		t.Errorf("Zero lines expected got %d", fileJob.Code)118	}119120	// Interestingly this file would be 0 lines in "wc -l" because it only counts newlines121	// all others count this as 1122	fileJob.Code = 0123	fileJob.SetContent("a")124	CountStats(&fileJob)125	if fileJob.Code != 1 {126		t.Errorf("One line expected got %d", fileJob.Code)127	}128129	fileJob.Code = 0130	fileJob.SetContent("i++ # comment")131	CountStats(&fileJob)132	if fileJob.Code != 1 {133		t.Errorf("One line expected got %d", fileJob.Code)134	}135136	fileJob.Code = 0137	fileJob.SetContent("i++ // comment")138	CountStats(&fileJob)139	if fileJob.Code != 1 {140		t.Errorf("One line expected got %d", fileJob.Code)141	}142143	fileJob.Code = 0144	fileJob.SetContent("a\n")145	CountStats(&fileJob)146	if fileJob.Code != 1 {147		t.Errorf("One line expected got %d", fileJob.Code)148	}149150	// tokei counts this as 1 because it's still on a single line unless something follows151	// the newline it's still 1 line152	fileJob.Code = 0153	fileJob.SetContent("1\n")154	CountStats(&fileJob)155	if fileJob.Code != 1 {156		t.Errorf("One line expected got %d", fileJob.Code)157	}158159	fileJob.Code = 0160	fileJob.SetContent("1\n2\n")161	CountStats(&fileJob)162	if fileJob.Code != 2 {163		t.Errorf("Two lines expected got %d", fileJob.Code)164	}165166	fileJob.Code = 0167	fileJob.SetContent("1\n2\n3")168	CountStats(&fileJob)169	if fileJob.Code != 3 {170		t.Errorf("Three lines expected got %d", fileJob.Code)171	}172173	content := ""174	for i := range 100 {175		content += "a\n"176		fileJob.Code = 0177		fileJob.SetContent(content)178		CountStats(&fileJob)179		if fileJob.Code != int64(i+1) {180			t.Errorf("Expected %d got %d", i+1, fileJob.Code)181		}182	}183}184185func TestCountStatsWithQuotes(t *testing.T) {186	fileJob := FileJob{}187188	fileJob.Code = 0189	fileJob.Comment = 0190	fileJob.Complexity = 0191	fileJob.SetContent(`var test = "/*";`)192	CountStats(&fileJob)193	if fileJob.Code != 1 {194		t.Errorf("One line expected got %d", fileJob.Code)195	}196	if fileJob.Comment != 0 {197		t.Errorf("No line expected got %d", fileJob.Comment)198	}199200	fileJob.Code = 0201	fileJob.Comment = 0202	fileJob.Complexity = 0203	fileJob.SetContent(`t = " if ";`)204	CountStats(&fileJob)205	if fileJob.Code != 1 {206		t.Errorf("One line expected got %d", fileJob.Code)207	}208	if fileJob.Comment != 0 {209		t.Errorf("No line expected got %d", fileJob.Comment)210	}211	if fileJob.Complexity != 0 {212		t.Errorf("No line expected got %d", fileJob.Complexity)213	}214215	fileJob.Code = 0216	fileJob.Comment = 0217	fileJob.Complexity = 0218	fileJob.SetContent(`t = " if switch for while do loop != == && || ";`)219	CountStats(&fileJob)220	if fileJob.Code != 1 {221		t.Errorf("One line expected got %d", fileJob.Code)222	}223	if fileJob.Comment != 0 {224		t.Errorf("No line expected got %d", fileJob.Comment)225	}226	if fileJob.Complexity != 0 {227		t.Errorf("No line expected got %d", fileJob.Complexity)228	}229}230231func TestCountStatsBlankLines(t *testing.T) {232	fileJob := FileJob{233		Content: []byte(""),234		Blank:   0,235	}236237	CountStats(&fileJob)238	if fileJob.Blank != 0 {239		t.Errorf("Zero lines expected got %d", fileJob.Blank)240	}241242	fileJob.Blank = 0243	fileJob.SetContent(" ")244	CountStats(&fileJob)245	if fileJob.Blank != 1 {246		t.Errorf("One line expected got %d", fileJob.Blank)247	}248249	fileJob.Blank = 0250	fileJob.SetContent("\n")251	CountStats(&fileJob)252	if fileJob.Blank != 1 {253		t.Errorf("One line expected got %d", fileJob.Blank)254	}255256	fileJob.Blank = 0257	fileJob.SetContent("\n ")258	CountStats(&fileJob)259	if fileJob.Blank != 2 {260		t.Errorf("Two line expected got %d", fileJob.Blank)261	}262263	fileJob.Blank = 0264	fileJob.SetContent("            ")265	CountStats(&fileJob)266	if fileJob.Blank != 1 {267		t.Errorf("One line expected got %d", fileJob.Blank)268	}269270	fileJob.Blank = 0271	fileJob.SetContent("            \n             ")272	CountStats(&fileJob)273	if fileJob.Blank != 2 {274		t.Errorf("Two lines expected got %d", fileJob.Blank)275	}276277	fileJob.Blank = 0278	fileJob.SetContent("\r\n\r\n")279	CountStats(&fileJob)280	if fileJob.Blank != 2 {281		t.Errorf("Two lines expected got %d", fileJob.Blank)282	}283284	fileJob.Blank = 0285	fileJob.SetContent("\r\n")286	CountStats(&fileJob)287	if fileJob.Blank != 1 {288		t.Errorf("One line expected got %d", fileJob.Blank)289	}290}291292func TestCountStatsComplexityCount(t *testing.T) {293	ProcessConstants()294	fileJob := FileJob{}295296	checks := []string{297		"if ",298		"	if ",299		"if a.equals(b) {",300		"if(",301		" if(i.equals(0))",302		"    if(",303		"    if( ",304	}305306	for _, check := range checks {307		fileJob.Complexity = 0308		fileJob.SetContent(check)309		fileJob.Language = "Java"310		CountStats(&fileJob)311		if fileJob.Complexity != 1 {312			t.Errorf("Expected complexity of 1 got %d for %s", fileJob.Complexity, check)313		}314	}315}316317func TestCountStatsComplexityCountFalse(t *testing.T) {318	ProcessConstants()319	fileJob := FileJob{}320321	checks := []string{322		"if",323		"aif ",324		"aif(",325	}326327	for _, check := range checks {328		fileJob.Complexity = 0329		fileJob.SetContent(check)330		fileJob.Language = "Java"331		CountStats(&fileJob)332		if fileJob.Complexity != 0 {333			t.Errorf("Expected complexity of 0 got %d for %s", fileJob.Complexity, check)334		}335	}336337}338339func TestCountStatsComplexityRustQuestionOperator(t *testing.T) {340	ProcessConstants()341342	checks := []struct {343		content string344		want    int64345	}{346		{"foo()?;", 1},347		{"foo() ?;", 1},348		{"foo()?.bar();", 1},349		{"foo()? as u16;", 1},350		{"foo()??;", 2},351		{"let y = x?;", 1},352	}353354	for _, c := range checks {355		fileJob := FileJob{Language: "Rust"}356		fileJob.SetContent(c.content)357		CountStats(&fileJob)358		if fileJob.Complexity != c.want {359			t.Errorf("Expected complexity of %d got %d for %q", c.want, fileJob.Complexity, c.content)360		}361	}362}363364// ?Sized is a trait-bound prefix, not the try operator. The postfix matcher365// must skip it whether or not whitespace separates it from the preceding366// token (`T: ?Sized`, `T:?Sized`, `Debug+?Sized` are all valid Rust).367func TestCountStatsComplexityRustQuestionSizedNotCounted(t *testing.T) {368	ProcessConstants()369370	checks := []string{371		"fn foo<T: ?Sized>() {}",372		"fn foo<T:?Sized>() {}",373		"fn foo<T:? Sized>() {}",374		"struct Bar<T: ?Sized>(T);",375		"where T: Debug + ?Sized,",376		"where T: Debug+?Sized,",377	}378379	for _, content := range checks {380		fileJob := FileJob{Language: "Rust"}381		fileJob.SetContent(content)382		CountStats(&fileJob)383		if fileJob.Complexity != 0 {384			t.Errorf("Expected complexity of 0 got %d for %q (?Sized must not count)", fileJob.Complexity, content)385		}386	}387}388389func TestCountStatsComplexityTypeScriptPostfixOperators(t *testing.T) {390	ProcessConstants()391392	checks := []struct {393		content string394		want    int64395	}{396		{"obj.attr?.method();", 1},397		{"path ?? url;", 1},398		{"path??url;", 1},399		{"value ??= fallback;", 1},400		{"value??=fallback;", 1},401		{"function get(path?: string) { return path ?? url; }", 1},402		{"interface User { age?: number; method?(): void; }", 0},403	}404405	for _, c := range checks {406		fileJob := FileJob{Language: "TypeScript"}407		fileJob.SetContent(c.content)408		CountStats(&fileJob)409		if fileJob.Complexity != c.want {410			t.Errorf("Expected complexity of %d got %d for %q", c.want, fileJob.Complexity, c.content)411		}412	}413}414415type linecounter struct {416	blanks   int417	comments int418	code     int419	loc      int420	stop     bool421}422423func (l *linecounter) ProcessLine(job *FileJob, currentLine int64, lineType LineType) bool {424	l.loc++425	switch lineType {426	case LINE_BLANK:427		l.blanks++428	case LINE_COMMENT:429		l.comments++430	case LINE_CODE:431		l.code++432	}433	return !l.stop434}435436func TestCountStatsCallback(t *testing.T) {437	ProcessConstants()438	fileJob := FileJob{}439440	fileJob.SetContent(`package foo441442import com.foo.bar;443444// this is a comment445class A {446}`)447	var lc linecounter448	fileJob.Language = "Java"449	fileJob.Callback = &lc450	CountStats(&fileJob)451	if lc.loc != 7 {452		t.Errorf("Expected loc of 7 got %d", lc.loc)453	}454	if lc.blanks != 2 {455		t.Errorf("Expected loc of 2 got %d", lc.blanks)456	}457	if lc.comments != 1 {458		t.Errorf("Expected loc of 1 got %d", lc.comments)459	}460	if lc.code != 4 {461		t.Errorf("Expected loc of 4 got %d", lc.code)462	}463}464465func TestCountStatsCallbackInterrupt(t *testing.T) {466	ProcessConstants()467	fileJob := FileJob{}468469	fileJob.SetContent(`package foo470471import com.foo.bar;472473// this is a comment474class A {475}`)476	var lc linecounter477	lc.stop = true478	fileJob.Language = "Java"479	fileJob.Callback = &lc480	CountStats(&fileJob)481	if lc.loc != 1 {482		t.Errorf("Expected loc of 1 got %d", lc.loc)483	}484}485486// Edge case condition where if ending with comment it would be counted487// as code due to how internal state work.488func TestCountStatsEdgeCase1(t *testing.T) {489	ProcessConstants()490	fileJob := FileJob{491		Language: "Java",492	}493494	fileJob.SetContent(`/**/495`)496497	CountStats(&fileJob)498499	if fileJob.Lines != 1 {500		t.Errorf("Expected 1 lines got %d", fileJob.Lines)501	}502503	if fileJob.Comment != 1 {504		t.Errorf("Expected 1 lines got %d", fileJob.Comment)505	}506}507508// Turns out that some languages such as Rust support509// nested comments. Check that it works here510func TestCountStatsNestedComments(t *testing.T) {511	ProcessConstants()512	fileJob := FileJob{513		Language: "Rust",514	}515516	fileJob.SetContent(`/*/**/*/`)517518	CountStats(&fileJob)519520	if fileJob.Lines != 1 {521		t.Errorf("Expected 1 lines got %d", fileJob.Lines)522	}523524	if fileJob.Code != 0 {525		t.Errorf("Expected 0 lines got %d", fileJob.Code)526	}527528	if fileJob.Comment != 1 {529		t.Errorf("Expected 1 lines got %d", fileJob.Comment)530	}531532	if fileJob.Blank != 0 {533		t.Errorf("Expected 0 lines got %d", fileJob.Blank)534	}535}536537// Java does not support nested multiline comments538func TestCountStatsNestedCommentsJava(t *testing.T) {539	ProcessConstants()540	fileJob := FileJob{541		Language: "Java",542	}543544	fileJob.SetContent(`/*/**/*/`)545546	CountStats(&fileJob)547548	if fileJob.Lines != 1 {549		t.Errorf("Expected 1 lines got %d", fileJob.Lines)550	}551552	if fileJob.Code != 1 {553		t.Errorf("Expected 1 lines got %d", fileJob.Code)554	}555556	if fileJob.Comment != 0 {557		t.Errorf("Expected 0 lines got %d", fileJob.Comment)558	}559560	if fileJob.Blank != 0 {561		t.Errorf("Expected 0 lines got %d", fileJob.Blank)562	}563}564565func TestCountStatsNestedCommentsRegression(t *testing.T) {566	ProcessConstants()567	fileJob := FileJob{568		Language: "Rust",569	}570571	fileJob.SetContent(`t/*/**/*/`)572573	CountStats(&fileJob)574575	if fileJob.Lines != 1 {576		t.Errorf("Expected 1 lines got %d", fileJob.Lines)577	}578579	if fileJob.Code != 1 {580		t.Errorf("Expected 1 lines got %d", fileJob.Code)581	}582583	if fileJob.Comment != 0 {584		t.Errorf("Expected 0 lines got %d", fileJob.Comment)585	}586587	if fileJob.Blank != 0 {588		t.Errorf("Expected 0 lines got %d", fileJob.Blank)589	}590}591592func TestCountStatsSingleCommentRegression(t *testing.T) {593	ProcessConstants()594	fileJob := FileJob{595		Language: "Rust",596	}597598	fileJob.SetContent(`t = "599/*600";`)601602	CountStats(&fileJob)603604	if fileJob.Lines != 3 {605		t.Errorf("Expected 3 lines got %d", fileJob.Lines)606	}607608	if fileJob.Code != 3 {609		t.Errorf("Expected 3 lines got %d", fileJob.Code)610	}611612	if fileJob.Comment != 0 {613		t.Errorf("Expected 0 lines got %d", fileJob.Comment)614	}615616	if fileJob.Blank != 0 {617		t.Errorf("Expected 0 lines got %d", fileJob.Blank)618	}619}620621func TestCountStatsStringCheck(t *testing.T) {622	ProcessConstants()623	fileJob := FileJob{624		Language: "Rust",625	}626627	fileJob.SetContent(`let does_not_start = // "628"until here,629test/*630test"; // a quote: "`)631632	CountStats(&fileJob)633634	if fileJob.Lines != 4 {635		t.Errorf("Expected 4 lines got %d", fileJob.Lines)636	}637638	if fileJob.Code != 4 {639		t.Errorf("Expected 4 code lines got %d", fileJob.Code)640	}641642	if fileJob.Comment != 0 {643		t.Errorf("Expected 0 comment lines got %d", fileJob.Comment)644	}645646	if fileJob.Blank != 0 {647		t.Errorf("Expected 0 blank lines got %d", fileJob.Blank)648	}649}650651func TestCountStatsBosque(t *testing.T) {652	ProcessConstants()653	fileJob := FileJob{654		Language: "Bosque",655	}656657	fileJob.SetContent(`//This is a bosque test658method offsetMomentum(px: Float, py: Float, pz: Float): Body {659      return this<~(vx=Float::div(px->negate(), Body::solar_mass), vy=Float::div(py->negate(), Body::solar_mass), vz=Float::div(pz->negate(), Body::solar_mass));660}`)661662	CountStats(&fileJob)663664	if fileJob.Lines != 4 {665		t.Errorf("Expected 4 lines got %d", fileJob.Lines)666	}667668	if fileJob.Code != 3 {669		t.Errorf("Expected 4 code lines got %d", fileJob.Code)670	}671672	if fileJob.Comment != 1 {673		t.Errorf("Expected 0 comment lines got %d", fileJob.Comment)674	}675676	if fileJob.Blank != 0 {677		t.Errorf("Expected 0 blank lines got %d", fileJob.Blank)678	}679}680681func TestCheckForMatchNoMatch(t *testing.T) {682	ProcessConstants()683684	fileJob := FileJob{685		Language: "Rust",686		Content:  []byte("one does not simply walk into mordor"),687	}688689	matches := &Trie{}690	matches.Insert(TSlcomment, []byte("//"))691	matches.Insert(TSlcomment, []byte("--"))692693	match, _, _ := matches.Match(fileJob.Content)694695	if match != 0 {696		t.Errorf("Expected no match")697	}698}699700func TestCheckForMatchHasMatch(t *testing.T) {701	ProcessConstants()702703	fileJob := FileJob{704		Language: "Rust",705		Content:  []byte("// one does not simply walk into mordor"),706	}707708	matches := &Trie{}709	matches.Insert(TSlcomment, []byte("//"))710	matches.Insert(TSlcomment, []byte("--"))711712	match, _, _ := matches.Match(fileJob.Content)713714	if match != TSlcomment {715		t.Errorf("Expected match")716	}717}718719func TestCheckForMatchSingleNoMatch(t *testing.T) {720	ProcessConstants()721722	fileJob := FileJob{723		Language: "Rust",724		Content:  []byte("// one does not simply walk into mordor"),725	}726727	matches := []byte("*/")728729	match := checkForMatchSingle('/', 0, 100, matches, &fileJob)730731	if match != false {732		t.Errorf("Expected no match")733	}734}735736func TestCheckForMatchSingleMatch(t *testing.T) {737	ProcessConstants()738739	fileJob := FileJob{740		Language: "Rust",741		Content:  []byte("*/ one does not simply walk into mordor"),742	}743744	matches := []byte("*/")745746	match := checkForMatchSingle('*', 0, 100, matches, &fileJob)747748	if match != true {749		t.Errorf("Expected match")750	}751}752753func TestCheckComplexityMatch(t *testing.T) {754	ProcessConstants()755756	fileJob := FileJob{757		Language: "Java",758		Content:  []byte("for (int i=0; i<100; i++) {"),759	}760761	matches := &Trie{}762	matches.Insert(TComplexity, []byte("for "))763	matches.Insert(TComplexity, []byte("for("))764765	match, n, _ := matches.Match(fileJob.Content)766767	if match != TComplexity || n != 4 {768		t.Errorf("Expected match")769	}770}771772func TestCheckComplexityNoMatch(t *testing.T) {773	ProcessConstants()774775	fileJob := FileJob{776		Language: "Java",777		Content:  []byte("far (int i=0; i<100; i++) {"),778	}779780	matches := &Trie{}781	matches.Insert(TComplexity, []byte("for "))782	matches.Insert(TComplexity, []byte("for("))783784	match, _, _ := matches.Match(fileJob.Content)785786	if match != 0 {787		t.Errorf("Expected no match")788	}789}790791func TestCountStatsRubyRegression(t *testing.T) {792	ProcessConstants()793	fileJob := FileJob{794		Language: "Ruby",795	}796797	fileJob.SetContent(`=begin798=end799t`)800801	CountStats(&fileJob)802803	if fileJob.Lines != 3 {804		t.Errorf("Expected 3 lines got %d", fileJob.Lines)805	}806807	if fileJob.Code != 1 {808		t.Errorf("Expected 1 code lines got %d", fileJob.Code)809	}810811	if fileJob.Comment != 2 {812		t.Errorf("Expected 2 comment lines got %d", fileJob.Comment)813	}814815	if fileJob.Blank != 0 {816		t.Errorf("Expected 0 blank lines got %d", fileJob.Blank)817	}818}819820func TestFileProcessorWorker(t *testing.T) {821	inputChan := make(chan *FileJob, 10000)822823	inputChan <- &FileJob{824		Filename:  "testing.go",825		Location:  "./",826		Extension: "go",827		Content:   []byte("this is some content"),828	}829830	close(inputChan)831	outputChan := make(chan *FileJob, 10000)832833	Duplicates = true834835	ctx := processorContext{remap: newRemapConfig("", "")}836	ctx.fileProcessorWorker(inputChan, outputChan)837838	for res := range outputChan {839		if res.Bytes == 0 {840			t.Error("Expect bytes to have something")841		}842	}843}844845func TestParseRemapRulesIgnoresInvalidEntries(t *testing.T) {846	rules := parseRemapRules("match:Go,invalid,too:many:parts,:Rust")847848	if len(rules) != 2 {849		t.Fatalf("expected 2 rules, got %d", len(rules))850	}851852	if string(rules[0].pattern) != "match" || rules[0].language != "Go" {853		t.Fatalf("unexpected first rule: %#v", rules[0])854	}855856	if string(rules[1].pattern) != "" || rules[1].language != "Rust" {857		t.Fatalf("unexpected second rule: %#v", rules[1])858	}859}860861func TestHardRemapLanguageUsesParsedRules(t *testing.T) {862	job := &FileJob{863		Language: "Plain Text",864		Location: "./test.txt",865		Content:  []byte("prefix -*- C++ -*- suffix"),866	}867868	ctx := processorContext{remap: newRemapConfig("-*- C++ -*-:C Header", "")}869	remapped := ctx.hardRemapLanguage(job)870871	if !remapped {872		t.Fatal("expected file to be remapped")873	}874875	if job.Language != "C Header" {876		t.Fatalf("expected remapped language to be C Header, got %s", job.Language)877	}878}879880func TestEdgeCase(t *testing.T) {881	ProcessConstants()882	fileJob := FileJob{883		Language: "C#",884	}885886	// For C# we can enter a string using @" or " but if we do the former,887	// and we don't skip over the full length we exit the string in this case888	// which means we pick up the /* and the count is incorrect889	fileJob.SetContent(`@"\ /*"890a`)891892	CountStats(&fileJob)893894	if fileJob.Lines != 2 {895		t.Errorf("Expected 2 lines")896	}897898	if fileJob.Code != 2 {899		t.Errorf("Expected 2 lines got %d", fileJob.Code)900	}901902	if fileJob.Comment != 0 {903		t.Errorf("Expected 0 lines got %d", fileJob.Comment)904	}905}906907func TestEdgeCaseOther(t *testing.T) {908	ProcessConstants()909	fileJob := FileJob{910		Language: "C#",911	}912913	// For C# we can enter a string using @" or " but if we do the former,914	// and we don't skip over the full length we exit the string in this case915	// which means we pick up the /* and the count is incorrect916	fileJob.SetContent(`@"C:\" /*917a */`)918919	CountStats(&fileJob)920921	if fileJob.Lines != 2 {922		t.Errorf("Expected 2 lines")923	}924925	if fileJob.Code != 1 {926		t.Errorf("Expected 1 lines got %d", fileJob.Code)927	}928929	if fileJob.Comment != 1 {930		t.Errorf("Expected 1 lines got %d", fileJob.Comment)931	}932}933934func TestCountStatsCSharpIgnoreEscape(t *testing.T) {935	ProcessConstants()936	fileJob := FileJob{937		Language: "C#",938	}939940	fileJob.SetContent(`namespace Ns941{942   public class Cls943   {944       private const string BasePath = @"a:\";945946       [Fact]947       public void MyTest()948       {949           // Arrange.950           Foo();951952           // Act.953           Bar();954955           // Assert.956           Baz();957       }958   }959}`)960961	CountStats(&fileJob)962963	if fileJob.Lines != 20 {964		t.Errorf("Expected 20 lines")965	}966967	if fileJob.Code != 14 {968		t.Errorf("Expected 14 lines got %d", fileJob.Code)969	}970971	if fileJob.Comment != 3 {972		t.Errorf("Expected 3 lines got %d", fileJob.Comment)973	}974975	if fileJob.Blank != 3 {976		t.Errorf("Expected 3 lines got %d", fileJob.Blank)977	}978}979980func TestCheckBomSkipUTF8(t *testing.T) {981	fileJob := &FileJob{982		Content: []byte{239, 187, 191}, // UTF-8 BOM983	}984985	skip := checkBomSkip(fileJob)986	if skip != 3 {987		t.Errorf("Expected skip length to match 3 got %d", skip)988	}989}990991func TestCheckBomSkip(t *testing.T) {992	Verbose = true993	for _, v := range ByteOrderMarks {994		fileJob := &FileJob{995			Content: v,996		}997998		skip := checkBomSkip(fileJob)999		if skip != 0 {1000			t.Errorf("Expected skip length to match %d got %d", len(v), skip)1001		}1002	}1003}10041005// Captures checking if a quote is prefixed by \ such as in1006// a char which should otherwise trigger the string state which is incorrect1007func TestCountStatsIssue73(t *testing.T) {1008	ProcessConstants()1009	fileJob := FileJob{1010		Language: "Java",1011	}10121013	fileJob.SetContent(`'\"'{1014code10151016`)1017	fileJob.Bytes = int64(len(fileJob.Content))10181019	CountStats(&fileJob)10201021	if fileJob.Lines != 3 {1022		t.Errorf("Expected 3 lines")1023	}10241025	if fileJob.Code != 2 {1026		t.Errorf("Expected 2 lines got %d", fileJob.Code)1027	}10281029	if fileJob.Comment != 0 {1030		t.Errorf("Expected 0 lines got %d", fileJob.Comment)1031	}10321033	if fileJob.Blank != 1 {1034		t.Errorf("Expected 1 lines got %d", fileJob.Blank)1035	}1036}10371038func TestCountStatsIssue106(t *testing.T) {1039	ProcessConstants()1040	fileJob := FileJob{1041		Language: "Go",1042	}10431044	fileJob.SetContent("foo = `\nabc\"\ndef\n`")10451046	CountStats(&fileJob)1047}10481049func TestMinifiedGeneratedCheck(t *testing.T) {1050	fileJob := FileJob{1051		Language: "Go",1052	}10531054	fileJob.SetContent("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890ABCDEF")1055	Minified = true1056	CountStats(&fileJob)1057	Minified = false10581059	if fileJob.Minified != true {1060		t.Error("Expected minified to come back true")1061	}1062}10631064func TestMinifiedGeneratedCheckTwoLines(t *testing.T) {1065	fileJob := FileJob{1066		Language: "Go",1067	}10681069	fileJob.SetContent("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890ABCDEF\n1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890ABCDEF")1070	Minified = true1071	CountStats(&fileJob)1072	Minified = false10731074	if fileJob.Minified != true {1075		t.Error("Expected minified to come back true")1076	}1077}10781079func TestMinifiedGeneratedCheckEdge(t *testing.T) {1080	fileJob := FileJob{1081		Language: "Go",1082	}10831084	fileJob.SetContent("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890ABCD")1085	Minified = true1086	CountStats(&fileJob)1087	Minified = false10881089	if fileJob.Minified != false {1090		t.Error("Expected minified to come back false")1091	}1092}10931094func TestGenerated(t *testing.T) {1095	fileJob := FileJob{1096		Language: "Go",1097	}10981099	fileJob.SetContent(`1100// Code generated by some tool, DO NOT EDIT.11011102// Package some contains something.1103package some1104`)1105	Generated = true1106	GeneratedMarkers = []string{"do not edit", "generated"}1107	CountStats(&fileJob)1108	Generated = false11091110	if fileJob.Generated != true {1111		t.Error("Expected generated to come back true")1112	}11131114	if fileJob.Language != "Go (gen)" {1115		t.Errorf("Expected Language \"Go (gen)\", received %q", fileJob.Language)1116	}1117}11181119func TestCountStatsIssue182(t *testing.T) {1120	ProcessConstants()1121	fileJob := FileJob{1122		Language: "Pascal",1123	}11241125	fileJob.SetContent(`uses1126    someunit;11271128{This is a comment}1129procedure Something1130var1131    avar: String;1132begin1133    Println('Oho');1134end;1135{This is a comment}1136procedure Nothing1137begin1138end.1139`)11401141	CountStats(&fileJob)11421143	if fileJob.Code != 11 {1144		t.Errorf("Expected 11 lines got %d", fileJob.Code)1145	}11461147	if fileJob.Comment != 2 {1148		t.Errorf("Expected 2 lines got %d", fileJob.Comment)1149	}11501151	if fileJob.Blank != 1 {1152		t.Errorf("Expected 1 lines got %d", fileJob.Blank)1153	}1154}11551156func TestCountStatsIssue182Delphi(t *testing.T) {1157	ProcessConstants()1158	fileJob := FileJob{1159		Language: "Pascal",1160	}11611162	fileJob.SetContent(`// this isnt a comment in pascal but is in delphi1163`)11641165	CountStats(&fileJob)11661167	if fileJob.Code != 0 {1168		t.Errorf("Expected 0 lines got %d", fileJob.Code)1169	}11701171	if fileJob.Comment != 1 {1172		t.Errorf("Expected 1 lines got %d", fileJob.Comment)1173	}11741175	if fileJob.Blank != 0 {1176		t.Errorf("Expected 0 lines got %d", fileJob.Blank)1177	}1178}11791180//////////////////////////////////////////////////1181// Content Classification Tests1182//////////////////////////////////////////////////11831184// Classification should not change any line counts1185func TestClassifyContentCountInvariance(t *testing.T) {1186	ProcessConstants()11871188	content := `package main11891190import "fmt"11911192// main function1193func main() {1194	fmt.Println("hello") // inline comment1195	/* block1196	   comment */1197}1198`1199	// Run without classification1200	fj1 := FileJob{Language: "Go"}1201	fj1.SetContent(content)1202	CountStats(&fj1)12031204	// Run with classification1205	fj2 := FileJob{Language: "Go", ClassifyContent: true}1206	fj2.SetContent(content)1207	CountStats(&fj2)12081209	if fj1.Lines != fj2.Lines {1210		t.Errorf("Lines mismatch: %d vs %d", fj1.Lines, fj2.Lines)1211	}1212	if fj1.Code != fj2.Code {1213		t.Errorf("Code mismatch: %d vs %d", fj1.Code, fj2.Code)1214	}1215	if fj1.Comment != fj2.Comment {1216		t.Errorf("Comment mismatch: %d vs %d", fj1.Comment, fj2.Comment)1217	}1218	if fj1.Blank != fj2.Blank {1219		t.Errorf("Blank mismatch: %d vs %d", fj1.Blank, fj2.Blank)1220	}1221	if fj1.Complexity != fj2.Complexity {1222		t.Errorf("Complexity mismatch: %d vs %d", fj1.Complexity, fj2.Complexity)1223	}1224}12251226// ContentByteType should stay nil when ClassifyContent is false (default)1227func TestClassifyContentNilGuard(t *testing.T) {1228	ProcessConstants()1229	fileJob := FileJob{Language: "Go"}1230	fileJob.SetContent("x := 1\n")1231	CountStats(&fileJob)12321233	if fileJob.ContentByteType != nil {1234		t.Error("Expected ContentByteType to be nil when ClassifyContent is false")1235	}1236}12371238// Code-only file: all non-whitespace bytes should be ByteTypeCode1239func TestClassifyContentCodeOnly(t *testing.T) {1240	ProcessConstants()1241	fileJob := FileJob{Language: "Go", ClassifyContent: true}1242	fileJob.SetContent("x := 1")1243	CountStats(&fileJob)12441245	if fileJob.ContentByteType == nil {1246		t.Fatal("Expected ContentByteType to be non-nil")1247	}12481249	for i, b := range fileJob.Content {1250		bt := fileJob.ContentByteType[i]1251		if b == ' ' || b == '\t' || b == '\n' || b == '\r' {1252			continue // whitespace can be blank or code depending on state1253		}1254		if bt != ByteTypeCode {1255			t.Errorf("byte %d (%q): expected ByteTypeCode(%d), got %d", i, string(b), ByteTypeCode, bt)1256		}1257	}1258}12591260// Comment-only file: "// comment" bytes after // should be ByteTypeComment1261func TestClassifyContentCommentOnly(t *testing.T) {1262	ProcessConstants()1263	fileJob := FileJob{Language: "Go", ClassifyContent: true}1264	fileJob.SetContent("// this is a comment")1265	CountStats(&fileJob)12661267	if fileJob.ContentByteType == nil {1268		t.Fatal("Expected ContentByteType to be non-nil")1269	}12701271	// After the first byte that triggers comment state, subsequent bytes should be comment1272	hasComment := false1273	for i := range fileJob.Content {1274		if fileJob.ContentByteType[i] == ByteTypeComment {1275			hasComment = true1276		}1277	}1278	if !hasComment {1279		t.Error("Expected at least some ByteTypeComment bytes for a comment-only line")1280	}12811282	if fileJob.Comment != 1 {1283		t.Errorf("Expected 1 comment line, got %d", fileJob.Comment)1284	}1285}12861287// Multi-line comment: inner bytes should be ByteTypeComment1288func TestClassifyContentMultiLineComment(t *testing.T) {1289	ProcessConstants()1290	fileJob := FileJob{Language: "Go", ClassifyContent: true}1291	fileJob.SetContent("/* hello\nworld */")1292	CountStats(&fileJob)12931294	if fileJob.ContentByteType == nil {1295		t.Fatal("Expected ContentByteType to be non-nil")1296	}12971298	// Check that "hello" inside the comment is classified as comment1299	helloStart := bytes.Index(fileJob.Content, []byte("hello"))1300	for i := helloStart; i < helloStart+5; i++ {1301		if fileJob.ContentByteType[i] != ByteTypeComment {1302			t.Errorf("byte %d (%q): expected ByteTypeComment(%d), got %d", i, string(fileJob.Content[i]), ByteTypeComment, fileJob.ContentByteType[i])1303		}1304	}1305}13061307// String literal: bytes inside "..." should be ByteTypeString1308func TestClassifyContentString(t *testing.T) {1309	ProcessConstants()1310	fileJob := FileJob{Language: "Go", ClassifyContent: true}1311	fileJob.SetContent(`x := "hello"`)1312	CountStats(&fileJob)13131314	if fileJob.ContentByteType == nil {1315		t.Fatal("Expected ContentByteType to be non-nil")1316	}13171318	// Find "hello" inside the string1319	helloStart := bytes.Index(fileJob.Content, []byte("hello"))1320	for i := helloStart; i < helloStart+5; i++ {1321		if fileJob.ContentByteType[i] != ByteTypeString {1322			t.Errorf("byte %d (%q): expected ByteTypeString(%d), got %d", i, string(fileJob.Content[i]), ByteTypeString, fileJob.ContentByteType[i])1323		}1324	}13251326	// "x" at the start should be code1327	if fileJob.ContentByteType[0] != ByteTypeCode {1328		t.Errorf("byte 0 (%q): expected ByteTypeCode(%d), got %d", string(fileJob.Content[0]), ByteTypeCode, fileJob.ContentByteType[0])1329	}1330}13311332func TestMojoRawTStringsDoNotCountComplexity(t *testing.T) {1333	ProcessConstants()13341335	for _, prefix := range []string{"rt\"", "tr\"", "rT\"", "tR\""} {1336		content := "value = " + prefix + "for item in items\""1337		fileJob := FileJob{Language: "Mojo"}1338		fileJob.SetContent(content)1339		CountStats(&fileJob)13401341		if fileJob.Complexity != 0 {1342			t.Errorf("prefix %q counted string contents as complexity: %d", prefix, fileJob.Complexity)1343		}1344	}1345}13461347// Mixed line: "x := 1 // comment" has code then comment1348func TestClassifyContentMixedLine(t *testing.T) {1349	ProcessConstants()1350	fileJob := FileJob{Language: "Go", ClassifyContent: true}1351	fileJob.SetContent("x := 1 // comment")1352	CountStats(&fileJob)13531354	if fileJob.ContentByteType == nil {1355		t.Fatal("Expected ContentByteType to be non-nil")1356	}13571358	// 'x' should be code1359	if fileJob.ContentByteType[0] != ByteTypeCode {1360		t.Errorf("byte 0 (%q): expected ByteTypeCode, got %d", string(fileJob.Content[0]), fileJob.ContentByteType[0])1361	}13621363	// "comment" text after // should be ByteTypeComment1364	commentStart := bytes.Index(fileJob.Content, []byte("comment"))1365	for i := commentStart; i < commentStart+7; i++ {1366		if fileJob.ContentByteType[i] != ByteTypeComment {1367			t.Errorf("byte %d (%q): expected ByteTypeComment, got %d", i, string(fileJob.Content[i]), fileJob.ContentByteType[i])1368		}1369	}13701371	// Line should be counted as code (code + comment = code line)1372	if fileJob.Code != 1 {1373		t.Errorf("Expected 1 code line, got %d", fileJob.Code)1374	}1375}13761377// Python docstring: content classified as ByteTypeComment1378func TestClassifyContentPythonDocstring(t *testing.T) {1379	ProcessConstants()1380	fileJob := FileJob{Language: "Python", ClassifyContent: true}1381	fileJob.SetContent(`"""1382docstring content1383"""`)1384	CountStats(&fileJob)13851386	if fileJob.ContentByteType == nil {1387		t.Fatal("Expected ContentByteType to be non-nil")1388	}13891390	// "docstring content" should be classified as comment1391	docStart := bytes.Index(fileJob.Content, []byte("docstring"))1392	if docStart >= 0 {1393		for i := docStart; i < docStart+9; i++ {1394			if fileJob.ContentByteType[i] != ByteTypeComment {1395				t.Errorf("byte %d (%q): expected ByteTypeComment(%d), got %d",1396					i, string(fileJob.Content[i]), ByteTypeComment, fileJob.ContentByteType[i])1397			}1398		}1399	}1400}14011402// Python raw-string docstrings (r"""/r”') should be counted as comments just1403// like plain """/”' docstrings. https://github.com/boyter/scc raw docstrings1404func TestCountStatsPythonRawDocstring(t *testing.T) {1405	ProcessConstants()14061407	content := `r'''This is a module docstring'''14081409class C:1410  r'''1411  This is a class docstring1412  '''14131414  def f():1415    r"""This is a function docstring.1416    simple quotes and double quotes are equivalent1417    """1418    pass1419`14201421	fileJob := FileJob{Language: "Python"}1422	fileJob.SetContent(content)1423	CountStats(&fileJob)14241425	if fileJob.Comment != 7 {1426		t.Errorf("Expected 7 comment lines (docstrings) got %d", fileJob.Comment)1427	}1428	if fileJob.Code != 3 {1429		t.Errorf("Expected 3 code lines got %d", fileJob.Code)1430	}1431	if fileJob.Blank != 2 {1432		t.Errorf("Expected 2 blank lines got %d", fileJob.Blank)1433	}14341435	plain := strings.ReplaceAll(content, "r'''", "'''")1436	plain = strings.ReplaceAll(plain, "r\"\"\"", "\"\"\"")14371438	plainJob := FileJob{Language: "Python"}1439	plainJob.SetContent(plain)1440	CountStats(&plainJob)14411442	if plainJob.Comment != fileJob.Comment || plainJob.Code != fileJob.Code || plainJob.Blank != fileJob.Blank {1443		t.Errorf("Raw docstring counts (c=%d code=%d b=%d) differ from plain (c=%d code=%d b=%d)",1444			fileJob.Comment, fileJob.Code, fileJob.Blank,1445			plainJob.Comment, plainJob.Code, plainJob.Blank)1446	}1447}14481449// FilterContentByType: returns filtered content correctly1450func TestFilterContentByType(t *testing.T) {1451	ProcessConstants()1452	fileJob := FileJob{Language: "Go", ClassifyContent: true}1453	fileJob.SetContent("x := 1 // comment\n")1454	CountStats(&fileJob)14551456	// Filter to only code1457	codeOnly := fileJob.FilterContentByType(ByteTypeCode)1458	if codeOnly == nil {1459		t.Fatal("Expected non-nil result from FilterContentByType")1460	}14611462	// Should preserve newlines1463	if codeOnly[len(codeOnly)-1] != '\n' {1464		t.Error("Expected newline to be preserved")1465	}14661467	// 'x' should be preserved1468	if codeOnly[0] != 'x' {1469		t.Errorf("Expected 'x' to be preserved, got %q", string(codeOnly[0]))1470	}14711472	// "comment" should be replaced with spaces1473	commentStart := bytes.Index(fileJob.Content, []byte("comment"))1474	for i := commentStart; i < commentStart+7; i++ {1475		if codeOnly[i] != ' ' {1476			t.Errorf("byte %d: expected space, got %q", i, string(codeOnly[i]))1477		}1478	}1479}14801481// FilterContentByType returns nil when ContentByteType is nil1482func TestFilterContentByTypeNil(t *testing.T) {1483	fileJob := FileJob{}1484	fileJob.SetContent("hello")14851486	result := fileJob.FilterContentByType(ByteTypeCode)1487	if result != nil {1488		t.Error("Expected nil result when ContentByteType is nil")1489	}1490}14911492// FilterContentByType with multiple keep types1493func TestFilterContentByTypeMultiple(t *testing.T) {1494	ProcessConstants()1495	fileJob := FileJob{Language: "Go", ClassifyContent: true}1496	fileJob.SetContent(`x := "hello" // comment`)1497	CountStats(&fileJob)14981499	// Keep both code and string, filter out comments1500	result := fileJob.FilterContentByType(ByteTypeCode, ByteTypeString)1501	if result == nil {1502		t.Fatal("Expected non-nil result")1503	}15041505	// 'x' (code) should be preserved1506	if result[0] != 'x' {1507		t.Errorf("Expected 'x' preserved, got %q", string(result[0]))1508	}15091510	// "hello" (string) content should be preserved1511	helloStart := bytes.Index(fileJob.Content, []byte("hello"))1512	for i := helloStart; i < helloStart+5; i++ {1513		if result[i] != fileJob.Content[i] {1514			t.Errorf("byte %d: expected %q preserved, got %q", i, string(fileJob.Content[i]), string(result[i]))1515		}1516	}1517}15181519//////////////////////////////////////////////////1520// Benchmarks Below1521//////////////////////////////////////////////////15221523func BenchmarkCountStatsLinesEmpty(b *testing.B) {1524	fileJob := FileJob{1525		Content: []byte(""),1526	}15271528	for i := 0; i < b.N; i++ {1529		CountStats(&fileJob)1530	}1531}15321533func BenchmarkCountStatsLinesSingleChar(b *testing.B) {1534	fileJob := FileJob{1535		Content: []byte("a"),1536	}15371538	for i := 0; i < b.N; i++ {1539		CountStats(&fileJob)1540	}1541}15421543func BenchmarkCountStatsLinesTwoLines(b *testing.B) {1544	fileJob := FileJob{1545		Content: []byte("a\na"),1546	}15471548	for i := 0; i < b.N; i++ {1549		CountStats(&fileJob)1550	}1551}15521553func BenchmarkCountStatsLinesThreeLines(b *testing.B) {1554	fileJob := FileJob{1555		Content: []byte("a\na\na"),1556	}15571558	for i := 0; i < b.N; i++ {1559		CountStats(&fileJob)1560	}1561}15621563func BenchmarkCountStatsLinesShortLine(b *testing.B) {1564	fileJob := FileJob{1565		Content: []byte("1234567890"),1566	}15671568	for i := 0; i < b.N; i++ {1569		CountStats(&fileJob)1570	}1571}15721573func BenchmarkCountStatsLinesShortEmptyLine(b *testing.B) {1574	fileJob := FileJob{1575		Content: []byte("          "),1576	}15771578	for i := 0; i < b.N; i++ {1579		CountStats(&fileJob)1580	}1581}15821583func BenchmarkCountStatsLinesThreeShortLines(b *testing.B) {1584	fileJob := FileJob{1585		Content: []byte("1234567890\n1234567890\n1234567890"),1586	}15871588	for i := 0; i < b.N; i++ {1589		CountStats(&fileJob)1590	}1591}15921593func BenchmarkCountStatsLinesThreeShortEmptyLines(b *testing.B) {1594	fileJob := FileJob{1595		Content: []byte("          \n          \n          "),1596	}15971598	for i := 0; i < b.N; i++ {1599		CountStats(&fileJob)1600	}1601}16021603func BenchmarkCountStatsLinesLongLine(b *testing.B) {1604	fileJob := FileJob{1605		Content: []byte("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"),1606	}16071608	for i := 0; i < b.N; i++ {1609		CountStats(&fileJob)1610	}1611}16121613func BenchmarkCountStatsLinesLongMixedLine(b *testing.B) {1614	fileJob := FileJob{1615		Content: []byte("1234567890          1234567890          1234567890          1234567890          1234567890          "),1616	}16171618	for i := 0; i < b.N; i++ {1619		CountStats(&fileJob)1620	}1621}16221623func BenchmarkCountStatsLinesLongAlternateLine(b *testing.B) {1624	fileJob := FileJob{1625		Content: []byte("a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a "),1626	}16271628	for i := 0; i < b.N; i++ {1629		CountStats(&fileJob)1630	}1631}16321633func BenchmarkCountStatsLinesFiveHundredLongLines(b *testing.B) {1634	b.StopTimer()1635	content := ""1636	for range 500 {1637		content += "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n"1638	}16391640	fileJob := FileJob{1641		Content: []byte(content),1642	}1643	b.StartTimer()1644	for i := 0; i < b.N; i++ {1645		CountStats(&fileJob)1646	}1647}16481649func BenchmarkCountStatsLinesFiveHundredLongLinesTriggerComplexityIf(b *testing.B) {1650	b.StopTimer()1651	content := ""1652	for range 500 {1653		content += "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\n"1654	}16551656	fileJob := FileJob{1657		Content: []byte(content),1658	}1659	b.StartTimer()1660	for i := 0; i < b.N; i++ {1661		CountStats(&fileJob)1662	}1663}16641665func BenchmarkCountStatsLinesFiveHundredLongLinesTriggerComplexityFor(b *testing.B) {1666	b.StopTimer()1667	content := ""1668	for range 500 {1669		content += "fofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofo\n"1670	}16711672	fileJob := FileJob{1673		Content: []byte(content),1674	}1675	b.StartTimer()1676	for i := 0; i < b.N; i++ {1677		CountStats(&fileJob)1678	}1679}16801681func BenchmarkCountStatsLinesFourHundredLongLinesMixed(b *testing.B) {1682	b.StopTimer()1683	content := ""1684	for range 100 {1685		content += "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n"1686		content += "1234567890          1234567890          1234567890          1234567890          1234567890          \n"1687		content += "                                                                                                    \n"1688		content += "a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a \n"1689	}16901691	fileJob := FileJob{1692		Content: []byte(content),1693	}1694	b.StartTimer()1695	for i := 0; i < b.N; i++ {1696		CountStats(&fileJob)1697	}1698}16991700func BenchmarkCheckByteEqualityReflect(b *testing.B) {1701	b.StopTimer()1702	one := []byte("for")1703	two := []byte("for")17041705	count := 017061707	b.StartTimer()1708	for i := 0; i < b.N; i++ {1709		equal := reflect.DeepEqual(one[1:], two[1:])17101711		if equal {1712			count++1713		}1714	}17151716	b.Log(count)1717}17181719func BenchmarkCheckByteEqualityBytes(b *testing.B) {1720	b.StopTimer()1721	one := []byte("for")1722	two := []byte("for")17231724	count := 017251726	b.StartTimer()1727	for i := 0; i < b.N; i++ {1728		equal := bytes.Equal(one[1:], two[1:])17291730		if equal {1731			count++1732		}1733	}17341735	b.Log(count)1736}17371738// This appears to be faster than bytes.Equal because it does not need1739// to do length comparison checks at the start1740func BenchmarkCheckByteEqualityLoop(b *testing.B) {1741	b.StopTimer()1742	one := []byte("for")1743	two := []byte("for")17441745	count := 017461747	b.StartTimer()1748	for i := 0; i < b.N; i++ {1749		equal := true17501751		for j := 1; j < len(one); j++ {1752			if one[j] != two[j] {1753				equal = false1754				break1755			}1756		}17571758		if equal {1759			count++1760		}1761	}17621763	b.Log(count)1764}17651766// Check if the 1 offset makes a difference, which it does by ~1 ns1767func BenchmarkCheckByteEqualityLoopWithAdditional(b *testing.B) {1768	b.StopTimer()1769	one := []byte("for")1770	two := []byte("for")17711772	count := 017731774	b.StartTimer()1775	for i := 0; i < b.N; i++ {1776		equal := true17771778		// Don't start at 1 like the above but 0 to do a full scan1779		for j := range one {1780			if one[j] != two[j] {1781				equal = false1782				break1783			}1784		}17851786		if equal {1787			count++1788		}1789	}17901791	b.Log(count)1792}17931794func BenchmarkCheckArrayCheck(b *testing.B) {1795	array := []byte{1796		'a',1797		'b',1798		'c',1799		'd',1800		'e',1801		'f',1802		'g',1803		'h',1804		'i',1805		'j',1806	}18071808	var searchFor byte = 'j'1809	found := 018101811	for i := 0; i < b.N; i++ {1812		for index := 0; index < len(array); index++ {1813			if array[index] == searchFor {1814				found++1815				break1816			}1817		}1818	}18191820	b.Log(found)1821}18221823func BenchmarkCheckMapCheck(b *testing.B) {1824	array := map[byte]bool{1825		'a': true,1826		'b': true,1827		'c': true,1828		'd': true,1829		'e': true,1830		'f': true,1831		'g': true,1832		'h': true,1833		'i': true,1834		'j': true,1835	}18361837	var searchFor byte = 'j'1838	found := 018391840	for i := 0; i < b.N; i++ {18411842		_, ok := array[searchFor]18431844		if ok {1845			found++1846		}1847	}18481849	b.Log(found)1850}18511852func BenchmarkStringLoop(b *testing.B) {1853	b.StopTimer()18541855	var str strings.Builder1856	for range 10000 {1857		str.WriteString("1")1858	}1859	search := str.String()1860	count := 01861	b.StartTimer()18621863	for i := 0; i < b.N; i++ {1864		for j := 0; j < len(search); j++ {1865			if search[j] != '\n' {1866				count++1867			}18681869		}1870	}1871	b.Log(count)1872}18731874func BenchmarkByteLoop(b *testing.B) {1875	b.StopTimer()18761877	var str strings.Builder1878	for range 10000 {1879		str.WriteString("1")1880	}1881	search := []byte(str.String())1882	count := 01883	b.StartTimer()18841885	for i := 0; i < b.N; i++ {1886		for j := range search {1887			if search[j] != '\n' {1888				count++1889			}18901891		}1892	}1893	b.Log(count)1894}18951896func BenchmarkLoopInLoop(b *testing.B) {1897	search := []byte("this is a long from for string which we will search")1898	matches := [][]byte{1899		[]byte("if"),1900		[]byte("if("),1901		[]byte("else"),1902		[]byte("while"),1903		[]byte("while("),1904		[]byte("for"),1905		[]byte("foreach"),1906	}1907	endPoint := len(search)1908	b.ResetTimer()19091910	potentialMatch := true1911	for i := 0; i < b.N; i++ {19121913		potentialMatch = true1914		for index := range search {19151916			for k := range matches {19171918				for j := 0; j < len(matches[k]); j++ {1919					if index+j >= endPoint || matches[k][j] != search[index+j] {1920						potentialMatch = false1921					}1922				}1923			}19241925		}19261927	}1928	b.Log(potentialMatch)1929}19301931func BenchmarkFlattenedLoop(b *testing.B) {1932	index := 01933	search := []byte("this is a long from for string which we will search")1934	matches := []byte("if if( else while while( for foreach")19351936	b.ResetTimer()19371938	potentialMatch := true1939	count := 01940	for i := 0; i < b.N; i++ {19411942		potentialMatch = true1943		for j := range matches {1944			if matches[j] == ' ' {1945				count = 01946			} else {1947				if matches[j] != search[index+count] {1948					potentialMatch = false1949				}19501951			}1952		}19531954	}19551956	b.Log(potentialMatch)1957}19581959func BenchmarkCheckComplexity(b *testing.B) {1960	ProcessConstants()19611962	fileJob := FileJob{1963		Language: "Java",1964		Content:  []byte("A little while ago, I passed my first year mark of working for Google. This also marked the "),1965	}19661967	matches := &Trie{}1968	matches.Insert(TComplexity, []byte("for "))1969	matches.Insert(TComplexity, []byte("for("))1970	matches.Insert(TComplexity, []byte("if "))1971	matches.Insert(TComplexity, []byte("if("))1972	matches.Insert(TComplexity, []byte("switch "))1973	matches.Insert(TComplexity, []byte("while "))1974	matches.Insert(TComplexity, []byte("else "))1975	matches.Insert(TComplexity, []byte("|| "))1976	matches.Insert(TComplexity, []byte("&& "))1977	matches.Insert(TComplexity, []byte("!= "))1978	matches.Insert(TComplexity, []byte("== "))19791980	b.ResetTimer()1981	for i := 0; i < b.N; i++ {1982		for j := 0; j < len(fileJob.Content); j++ {1983			matches.Match(fileJob.Content)1984		}1985	}1986}19871988func BenchmarkCheckLen(b *testing.B) {1989	matches := [][]byte{1990		[]byte("for "),1991		[]byte("for("),1992		[]byte("if "),1993		[]byte("if("),1994		[]byte("switch "),1995		[]byte("while "),1996		[]byte("else "),1997		[]byte("|| "),1998		[]byte("&& "),1999		[]byte("!= "),2000		[]byte("== "),

Code quality findings 77

Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = ctx.hardRemapLanguage(job)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = ctx.hardRemapLanguage(job)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = ctx.hardRemapLanguage(job)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = ctx.unknownRemapLanguage(job)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = ctx.unknownRemapLanguage(job)
Unstructured output; use a structured logging library (e.g., slog, zap, zerolog, logrus)
info correctness fmt-println
fmt.Println("hello") // inline comment
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 i, b := range fileJob.Content {
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
helloStart := bytes.Index(fileJob.Content, []byte("hello"))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
helloStart := bytes.Index(fileJob.Content, []byte("hello"))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
commentStart := bytes.Index(fileJob.Content, []byte("comment"))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
docStart := bytes.Index(fileJob.Content, []byte("docstring"))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
commentStart := bytes.Index(fileJob.Content, []byte("comment"))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
helloStart := bytes.Index(fileJob.Content, []byte("hello"))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(""),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("a"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("a\na"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("a\na\na"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("1234567890"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(" "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("1234567890\n1234567890\n1234567890"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(" \n \n "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("1234567890 1234567890 1234567890 1234567890 1234567890 "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(content),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(content),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(content),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(content),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
one := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
two := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
one := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
two := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
one := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
two := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
one := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
two := []byte("for")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
search := []byte(str.String())
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("else"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("while"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("while("),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("for"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("foreach"),
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for j := 0; j < len(matches[k]); j++ {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if index+j >= endPoint || matches[k][j] != search[index+j] {
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
search := []byte("this is a long from for string which we will search")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches := []byte("if if( else while while( for foreach")
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for i := 0; i < b.N; i++ {
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("if("))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("switch "))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("while "))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("else "))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("|| "))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("&& "))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("!= "))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
matches.Insert(TComplexity, []byte("== "))
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("for "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("for("),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("if "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("switch "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("while "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("else "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("|| "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("&& "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("!= "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("== "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("for "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("switch "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("while "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("else "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("|| "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("&& "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("!= "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
[]byte("== "),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(content),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte(content),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("prefix -*- C++ -*- suffix"),
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
Content: []byte("#!/bin/sh\nprefix -*- C++ -*- suffix"),

Get this view in your editor

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