processor/workers_test.go GO 2,110 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,110.
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 := 0; i < 5000; i++ {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 := 0; i < 100; i++ {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}13311332// Mixed line: "x := 1 // comment" has code then comment1333func TestClassifyContentMixedLine(t *testing.T) {1334	ProcessConstants()1335	fileJob := FileJob{Language: "Go", ClassifyContent: true}1336	fileJob.SetContent("x := 1 // comment")1337	CountStats(&fileJob)13381339	if fileJob.ContentByteType == nil {1340		t.Fatal("Expected ContentByteType to be non-nil")1341	}13421343	// 'x' should be code1344	if fileJob.ContentByteType[0] != ByteTypeCode {1345		t.Errorf("byte 0 (%q): expected ByteTypeCode, got %d", string(fileJob.Content[0]), fileJob.ContentByteType[0])1346	}13471348	// "comment" text after // should be ByteTypeComment1349	commentStart := bytes.Index(fileJob.Content, []byte("comment"))1350	for i := commentStart; i < commentStart+7; i++ {1351		if fileJob.ContentByteType[i] != ByteTypeComment {1352			t.Errorf("byte %d (%q): expected ByteTypeComment, got %d", i, string(fileJob.Content[i]), fileJob.ContentByteType[i])1353		}1354	}13551356	// Line should be counted as code (code + comment = code line)1357	if fileJob.Code != 1 {1358		t.Errorf("Expected 1 code line, got %d", fileJob.Code)1359	}1360}13611362// Python docstring: content classified as ByteTypeComment1363func TestClassifyContentPythonDocstring(t *testing.T) {1364	ProcessConstants()1365	fileJob := FileJob{Language: "Python", ClassifyContent: true}1366	fileJob.SetContent(`"""1367docstring content1368"""`)1369	CountStats(&fileJob)13701371	if fileJob.ContentByteType == nil {1372		t.Fatal("Expected ContentByteType to be non-nil")1373	}13741375	// "docstring content" should be classified as comment1376	docStart := bytes.Index(fileJob.Content, []byte("docstring"))1377	if docStart >= 0 {1378		for i := docStart; i < docStart+9; i++ {1379			if fileJob.ContentByteType[i] != ByteTypeComment {1380				t.Errorf("byte %d (%q): expected ByteTypeComment(%d), got %d",1381					i, string(fileJob.Content[i]), ByteTypeComment, fileJob.ContentByteType[i])1382			}1383		}1384	}1385}13861387// FilterContentByType: returns filtered content correctly1388func TestFilterContentByType(t *testing.T) {1389	ProcessConstants()1390	fileJob := FileJob{Language: "Go", ClassifyContent: true}1391	fileJob.SetContent("x := 1 // comment\n")1392	CountStats(&fileJob)13931394	// Filter to only code1395	codeOnly := fileJob.FilterContentByType(ByteTypeCode)1396	if codeOnly == nil {1397		t.Fatal("Expected non-nil result from FilterContentByType")1398	}13991400	// Should preserve newlines1401	if codeOnly[len(codeOnly)-1] != '\n' {1402		t.Error("Expected newline to be preserved")1403	}14041405	// 'x' should be preserved1406	if codeOnly[0] != 'x' {1407		t.Errorf("Expected 'x' to be preserved, got %q", string(codeOnly[0]))1408	}14091410	// "comment" should be replaced with spaces1411	commentStart := bytes.Index(fileJob.Content, []byte("comment"))1412	for i := commentStart; i < commentStart+7; i++ {1413		if codeOnly[i] != ' ' {1414			t.Errorf("byte %d: expected space, got %q", i, string(codeOnly[i]))1415		}1416	}1417}14181419// FilterContentByType returns nil when ContentByteType is nil1420func TestFilterContentByTypeNil(t *testing.T) {1421	fileJob := FileJob{}1422	fileJob.SetContent("hello")14231424	result := fileJob.FilterContentByType(ByteTypeCode)1425	if result != nil {1426		t.Error("Expected nil result when ContentByteType is nil")1427	}1428}14291430// FilterContentByType with multiple keep types1431func TestFilterContentByTypeMultiple(t *testing.T) {1432	ProcessConstants()1433	fileJob := FileJob{Language: "Go", ClassifyContent: true}1434	fileJob.SetContent(`x := "hello" // comment`)1435	CountStats(&fileJob)14361437	// Keep both code and string, filter out comments1438	result := fileJob.FilterContentByType(ByteTypeCode, ByteTypeString)1439	if result == nil {1440		t.Fatal("Expected non-nil result")1441	}14421443	// 'x' (code) should be preserved1444	if result[0] != 'x' {1445		t.Errorf("Expected 'x' preserved, got %q", string(result[0]))1446	}14471448	// "hello" (string) content should be preserved1449	helloStart := bytes.Index(fileJob.Content, []byte("hello"))1450	for i := helloStart; i < helloStart+5; i++ {1451		if result[i] != fileJob.Content[i] {1452			t.Errorf("byte %d: expected %q preserved, got %q", i, string(fileJob.Content[i]), string(result[i]))1453		}1454	}1455}14561457//////////////////////////////////////////////////1458// Benchmarks Below1459//////////////////////////////////////////////////14601461func BenchmarkCountStatsLinesEmpty(b *testing.B) {1462	fileJob := FileJob{1463		Content: []byte(""),1464	}14651466	for i := 0; i < b.N; i++ {1467		CountStats(&fileJob)1468	}1469}14701471func BenchmarkCountStatsLinesSingleChar(b *testing.B) {1472	fileJob := FileJob{1473		Content: []byte("a"),1474	}14751476	for i := 0; i < b.N; i++ {1477		CountStats(&fileJob)1478	}1479}14801481func BenchmarkCountStatsLinesTwoLines(b *testing.B) {1482	fileJob := FileJob{1483		Content: []byte("a\na"),1484	}14851486	for i := 0; i < b.N; i++ {1487		CountStats(&fileJob)1488	}1489}14901491func BenchmarkCountStatsLinesThreeLines(b *testing.B) {1492	fileJob := FileJob{1493		Content: []byte("a\na\na"),1494	}14951496	for i := 0; i < b.N; i++ {1497		CountStats(&fileJob)1498	}1499}15001501func BenchmarkCountStatsLinesShortLine(b *testing.B) {1502	fileJob := FileJob{1503		Content: []byte("1234567890"),1504	}15051506	for i := 0; i < b.N; i++ {1507		CountStats(&fileJob)1508	}1509}15101511func BenchmarkCountStatsLinesShortEmptyLine(b *testing.B) {1512	fileJob := FileJob{1513		Content: []byte("          "),1514	}15151516	for i := 0; i < b.N; i++ {1517		CountStats(&fileJob)1518	}1519}15201521func BenchmarkCountStatsLinesThreeShortLines(b *testing.B) {1522	fileJob := FileJob{1523		Content: []byte("1234567890\n1234567890\n1234567890"),1524	}15251526	for i := 0; i < b.N; i++ {1527		CountStats(&fileJob)1528	}1529}15301531func BenchmarkCountStatsLinesThreeShortEmptyLines(b *testing.B) {1532	fileJob := FileJob{1533		Content: []byte("          \n          \n          "),1534	}15351536	for i := 0; i < b.N; i++ {1537		CountStats(&fileJob)1538	}1539}15401541func BenchmarkCountStatsLinesLongLine(b *testing.B) {1542	fileJob := FileJob{1543		Content: []byte("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"),1544	}15451546	for i := 0; i < b.N; i++ {1547		CountStats(&fileJob)1548	}1549}15501551func BenchmarkCountStatsLinesLongMixedLine(b *testing.B) {1552	fileJob := FileJob{1553		Content: []byte("1234567890          1234567890          1234567890          1234567890          1234567890          "),1554	}15551556	for i := 0; i < b.N; i++ {1557		CountStats(&fileJob)1558	}1559}15601561func BenchmarkCountStatsLinesLongAlternateLine(b *testing.B) {1562	fileJob := FileJob{1563		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 "),1564	}15651566	for i := 0; i < b.N; i++ {1567		CountStats(&fileJob)1568	}1569}15701571func BenchmarkCountStatsLinesFiveHundredLongLines(b *testing.B) {1572	b.StopTimer()1573	content := ""1574	for i := 0; i < 500; i++ {1575		content += "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n"1576	}15771578	fileJob := FileJob{1579		Content: []byte(content),1580	}1581	b.StartTimer()1582	for i := 0; i < b.N; i++ {1583		CountStats(&fileJob)1584	}1585}15861587func BenchmarkCountStatsLinesFiveHundredLongLinesTriggerComplexityIf(b *testing.B) {1588	b.StopTimer()1589	content := ""1590	for i := 0; i < 500; i++ {1591		content += "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\n"1592	}15931594	fileJob := FileJob{1595		Content: []byte(content),1596	}1597	b.StartTimer()1598	for i := 0; i < b.N; i++ {1599		CountStats(&fileJob)1600	}1601}16021603func BenchmarkCountStatsLinesFiveHundredLongLinesTriggerComplexityFor(b *testing.B) {1604	b.StopTimer()1605	content := ""1606	for i := 0; i < 500; i++ {1607		content += "fofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofofo\n"1608	}16091610	fileJob := FileJob{1611		Content: []byte(content),1612	}1613	b.StartTimer()1614	for i := 0; i < b.N; i++ {1615		CountStats(&fileJob)1616	}1617}16181619func BenchmarkCountStatsLinesFourHundredLongLinesMixed(b *testing.B) {1620	b.StopTimer()1621	content := ""1622	for i := 0; i < 100; i++ {1623		content += "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n"1624		content += "1234567890          1234567890          1234567890          1234567890          1234567890          \n"1625		content += "                                                                                                    \n"1626		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"1627	}16281629	fileJob := FileJob{1630		Content: []byte(content),1631	}1632	b.StartTimer()1633	for i := 0; i < b.N; i++ {1634		CountStats(&fileJob)1635	}1636}16371638func BenchmarkCheckByteEqualityReflect(b *testing.B) {1639	b.StopTimer()1640	one := []byte("for")1641	two := []byte("for")16421643	count := 016441645	b.StartTimer()1646	for i := 0; i < b.N; i++ {1647		equal := reflect.DeepEqual(one[1:], two[1:])16481649		if equal {1650			count++1651		}1652	}16531654	b.Log(count)1655}16561657func BenchmarkCheckByteEqualityBytes(b *testing.B) {1658	b.StopTimer()1659	one := []byte("for")1660	two := []byte("for")16611662	count := 016631664	b.StartTimer()1665	for i := 0; i < b.N; i++ {1666		equal := bytes.Equal(one[1:], two[1:])16671668		if equal {1669			count++1670		}1671	}16721673	b.Log(count)1674}16751676// This appears to be faster than bytes.Equal because it does not need1677// to do length comparison checks at the start1678func BenchmarkCheckByteEqualityLoop(b *testing.B) {1679	b.StopTimer()1680	one := []byte("for")1681	two := []byte("for")16821683	count := 016841685	b.StartTimer()1686	for i := 0; i < b.N; i++ {1687		equal := true16881689		for j := 1; j < len(one); j++ {1690			if one[j] != two[j] {1691				equal = false1692				break1693			}1694		}16951696		if equal {1697			count++1698		}1699	}17001701	b.Log(count)1702}17031704// Check if the 1 offset makes a difference, which it does by ~1 ns1705func BenchmarkCheckByteEqualityLoopWithAdditional(b *testing.B) {1706	b.StopTimer()1707	one := []byte("for")1708	two := []byte("for")17091710	count := 017111712	b.StartTimer()1713	for i := 0; i < b.N; i++ {1714		equal := true17151716		// Don't start at 1 like the above but 0 to do a full scan1717		for j := 0; j < len(one); j++ {1718			if one[j] != two[j] {1719				equal = false1720				break1721			}1722		}17231724		if equal {1725			count++1726		}1727	}17281729	b.Log(count)1730}17311732func BenchmarkCheckArrayCheck(b *testing.B) {1733	array := []byte{1734		'a',1735		'b',1736		'c',1737		'd',1738		'e',1739		'f',1740		'g',1741		'h',1742		'i',1743		'j',1744	}17451746	var searchFor byte = 'j'1747	found := 017481749	for i := 0; i < b.N; i++ {1750		for index := 0; index < len(array); index++ {1751			if array[index] == searchFor {1752				found++1753				break1754			}1755		}1756	}17571758	b.Log(found)1759}17601761func BenchmarkCheckMapCheck(b *testing.B) {1762	array := map[byte]bool{1763		'a': true,1764		'b': true,1765		'c': true,1766		'd': true,1767		'e': true,1768		'f': true,1769		'g': true,1770		'h': true,1771		'i': true,1772		'j': true,1773	}17741775	var searchFor byte = 'j'1776	found := 017771778	for i := 0; i < b.N; i++ {17791780		_, ok := array[searchFor]17811782		if ok {1783			found++1784		}1785	}17861787	b.Log(found)1788}17891790func BenchmarkStringLoop(b *testing.B) {1791	b.StopTimer()17921793	var str strings.Builder1794	for i := 0; i < 10000; i++ {1795		str.WriteString("1")1796	}1797	search := str.String()1798	count := 01799	b.StartTimer()18001801	for i := 0; i < b.N; i++ {1802		for j := 0; j < len(search); j++ {1803			if search[j] != '\n' {1804				count++1805			}18061807		}1808	}1809	b.Log(count)1810}18111812func BenchmarkByteLoop(b *testing.B) {1813	b.StopTimer()18141815	var str strings.Builder1816	for i := 0; i < 10000; i++ {1817		str.WriteString("1")1818	}1819	search := []byte(str.String())1820	count := 01821	b.StartTimer()18221823	for i := 0; i < b.N; i++ {1824		for j := 0; j < len(search); j++ {1825			if search[j] != '\n' {1826				count++1827			}18281829		}1830	}1831	b.Log(count)1832}18331834func BenchmarkLoopInLoop(b *testing.B) {1835	search := []byte("this is a long from for string which we will search")1836	matches := [][]byte{1837		[]byte("if"),1838		[]byte("if("),1839		[]byte("else"),1840		[]byte("while"),1841		[]byte("while("),1842		[]byte("for"),1843		[]byte("foreach"),1844	}1845	endPoint := len(search)1846	b.ResetTimer()18471848	potentialMatch := true1849	for i := 0; i < b.N; i++ {18501851		potentialMatch = true1852		for index := 0; index < len(search); index++ {18531854			for k := 0; k < len(matches); k++ {18551856				for j := 0; j < len(matches[k]); j++ {1857					if index+j >= endPoint || matches[k][j] != search[index+j] {1858						potentialMatch = false1859					}1860				}1861			}18621863		}18641865	}1866	b.Log(potentialMatch)1867}18681869func BenchmarkFlattenedLoop(b *testing.B) {1870	index := 01871	search := []byte("this is a long from for string which we will search")1872	matches := []byte("if if( else while while( for foreach")18731874	b.ResetTimer()18751876	potentialMatch := true1877	count := 01878	for i := 0; i < b.N; i++ {18791880		potentialMatch = true1881		for j := 0; j < len(matches); j++ {1882			if matches[j] == ' ' {1883				count = 01884			} else {1885				if matches[j] != search[index+count] {1886					potentialMatch = false1887				}18881889			}1890		}18911892	}18931894	b.Log(potentialMatch)1895}18961897func BenchmarkCheckComplexity(b *testing.B) {1898	ProcessConstants()18991900	fileJob := FileJob{1901		Language: "Java",1902		Content:  []byte("A little while ago, I passed my first year mark of working for Google. This also marked the "),1903	}19041905	matches := &Trie{}1906	matches.Insert(TComplexity, []byte("for "))1907	matches.Insert(TComplexity, []byte("for("))1908	matches.Insert(TComplexity, []byte("if "))1909	matches.Insert(TComplexity, []byte("if("))1910	matches.Insert(TComplexity, []byte("switch "))1911	matches.Insert(TComplexity, []byte("while "))1912	matches.Insert(TComplexity, []byte("else "))1913	matches.Insert(TComplexity, []byte("|| "))1914	matches.Insert(TComplexity, []byte("&& "))1915	matches.Insert(TComplexity, []byte("!= "))1916	matches.Insert(TComplexity, []byte("== "))19171918	b.ResetTimer()1919	for i := 0; i < b.N; i++ {1920		for j := 0; j < len(fileJob.Content); j++ {1921			matches.Match(fileJob.Content)1922		}1923	}1924}19251926func BenchmarkCheckLen(b *testing.B) {1927	matches := [][]byte{1928		[]byte("for "),1929		[]byte("for("),1930		[]byte("if "),1931		[]byte("if("),1932		[]byte("switch "),1933		[]byte("while "),1934		[]byte("else "),1935		[]byte("|| "),1936		[]byte("&& "),1937		[]byte("!= "),1938		[]byte("== "),1939	}19401941	count := 01942	for i := 0; i < b.N; i++ {1943		for j := 0; j < len(matches); j++ {1944			count++1945		}1946	}19471948	b.Log(count)1949}19501951func BenchmarkCheckLenPrecalc(b *testing.B) {1952	matches := [][]byte{1953		[]byte("for "),1954		[]byte("for("),1955		[]byte("if "),1956		[]byte("if("),1957		[]byte("switch "),1958		[]byte("while "),1959		[]byte("else "),1960		[]byte("|| "),1961		[]byte("&& "),1962		[]byte("!= "),1963		[]byte("== "),1964	}19651966	count := 01967	for i := 0; i < b.N; i++ {1968		l := len(matches)1969		for j := 0; j < l; j++ {1970			count++1971		}1972	}19731974	b.Log(count)1975}19761977func BenchmarkCountStatsNoClassify(b *testing.B) {1978	ProcessConstants()1979	b.StopTimer()1980	content := ""1981	for i := 0; i < 500; i++ {1982		content += "x := 1 // comment\n"1983	}1984	fileJob := FileJob{1985		Language: "Go",1986		Content:  []byte(content),1987		Bytes:    int64(len(content)),1988	}1989	b.StartTimer()1990	for i := 0; i < b.N; i++ {1991		fileJob.Lines = 01992		fileJob.Code = 01993		fileJob.Comment = 01994		fileJob.Blank = 01995		fileJob.Complexity = 01996		fileJob.ComplexityLine = nil1997		fileJob.ContentByteType = nil1998		CountStats(&fileJob)1999	}2000}

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.