processor/detector_test.go GO 628 lines View on github.com → Search inside
1// SPDX-License-Identifier: MIT23package processor45import (6	"slices"7	"strings"8	"testing"9)1011func TestDetectLanguage(t *testing.T) {12	ProcessConstants()13	AllowListExtensions = []string{"css"}14	_, ext := DetectLanguage("example.black.css")1516	if ext != "css" {17		t.Error("Expected css got", ext)18	}19	AllowListExtensions = []string{}20}2122func TestDetectLanguageMojoExtensions(t *testing.T) {23	ProcessConstants()2425	tests := map[string]string{26		"example.mojo": "mojo",27		"example.🔥":    "🔥",28	}29	for filename, wantExtension := range tests {30		possible, extension := DetectLanguage(filename)31		if extension != wantExtension {32			t.Errorf("DetectLanguage(%q) extension = %q, want %q", filename, extension, wantExtension)33		}34		if !slices.Contains(possible, "Mojo") {35			t.Errorf("DetectLanguage(%q) languages = %v, want Mojo", filename, possible)36		}37	}38}3940func TestDetectSheBangEmpty(t *testing.T) {41	ProcessConstants()4243	x, y := DetectSheBang([]byte{})4445	if x != "" || y == nil {46		t.Error("Expected no match got", x)47	}4849	x, y = DetectSheBang(nil)5051	if x != "" || y == nil {52		t.Error("Expected no match got", x)53	}54}5556func TestDetectSheBangPerl(t *testing.T) {57	ProcessConstants()5859	cases := []string{60		"#!/usr/bin/perl",61		"#!  /usr/bin/perl",62		"#!/usr/bin/perl -w",63		"#!/usr/bin/env perl",64		"#!  /usr/bin/env   perl",65		"#!/usr/bin/env perl -w",66		"#!  /usr/bin/env   perl   -w",67		"#!/opt/local/bin/perl",68		"#!/usr/bin/perl5",69	}7071	for _, c := range cases {72		x, y := DetectSheBang([]byte(c))7374		if x != "Perl" || y != nil {75			t.Error("Expected Perl match got", x, "for", c)76		}77	}78}7980func TestDetectSheBangPhp(t *testing.T) {81	ProcessConstants()8283	cases := []string{84		"#!/usr/bin/php5",85		"#!/usr/bin/php",86	}8788	for _, c := range cases {89		x, y := DetectSheBang([]byte(c))9091		if x != "PHP" || y != nil {92			t.Error("Expected PHP match got", x)93		}94	}95}9697func TestDetectSheBangPython(t *testing.T) {98	ProcessConstants()99100	cases := []string{101		"#!/usr/bin/python",102		"#!/usr/bin/python2",103		"#!/usr/bin/python3",104	}105106	for _, c := range cases {107		x, y := DetectSheBang([]byte(c))108109		if x != "Python" || y != nil {110			t.Error("Expected Python match got", x)111		}112	}113}114115func TestDetectSheBangAWK(t *testing.T) {116	ProcessConstants()117118	cases := []string{119		"#!/usr/bin/awk",120		"#!/usr/bin/gawk",121		"#!/usr/bin/mawk",122	}123124	for _, c := range cases {125		x, y := DetectSheBang([]byte(c))126127		if x != "AWK" || y != nil {128			t.Error("Expected AWK match got", x)129		}130	}131}132133func TestDetectSheBangCsh(t *testing.T) {134	ProcessConstants()135136	cases := []string{137		"#!/bin/csh",138		"#!/bin/tcsh",139	}140141	for _, c := range cases {142		x, y := DetectSheBang([]byte(c))143144		if x != "C Shell" || y != nil {145			t.Error("Expected C Shell match got", x)146		}147	}148}149150func TestDetectSheBangD(t *testing.T) {151	ProcessConstants()152153	cases := []string{154		"#!/usr/bin/env rdmd",155	}156157	for _, c := range cases {158		x, y := DetectSheBang([]byte(c))159160		if x != "D" || y != nil {161			t.Error("Expected D match got", x)162		}163	}164}165166func TestDetectSheBangNode(t *testing.T) {167	ProcessConstants()168169	cases := []string{170		"#!/usr/bin/env node",171		"#!/usr/bin/node",172	}173174	for _, c := range cases {175		x, y := DetectSheBang([]byte(c))176177		if x != "JavaScript" || y != nil {178			t.Error("Expected JavaScript match got", x)179		}180	}181}182183func TestDetectSheBangLisp(t *testing.T) {184	ProcessConstants()185186	cases := []string{187		"#!/usr/bin/env sbcl",188		"#!/usr/bin/sbcl",189	}190191	for _, c := range cases {192		x, y := DetectSheBang([]byte(c))193194		if x != "Lisp" || y != nil {195			t.Error("Expected Lisp match got", x)196		}197	}198}199200func TestDetectSheBangRacket(t *testing.T) {201	ProcessConstants()202203	cases := []string{204		"#!/usr/bin/env racket",205		"#!/usr/bin/racket",206	}207208	for _, c := range cases {209		x, y := DetectSheBang([]byte(c))210211		if x != "Racket" || y != nil {212			t.Error("Expected Racket match got", x)213		}214	}215}216217func TestDetectSheBangFish(t *testing.T) {218	ProcessConstants()219220	cases := []string{221		"#!/usr/bin/env fish",222		"#!/usr/bin/fish",223		"#!/bin/fish",224	}225226	for _, c := range cases {227		x, y := DetectSheBang([]byte(c))228229		if x != "Fish" || y != nil {230			t.Error("Expected Fish match got", x)231		}232	}233}234235func TestDetectSheBangShell(t *testing.T) {236	ProcessConstants()237238	cases := []string{239		"#!/usr/bin/env sh",240		"#!/bin/sh",241	}242243	for _, c := range cases {244		x, y := DetectSheBang([]byte(c))245246		if x != "Shell" || y != nil {247			t.Error("Expected Shell match got", x)248		}249	}250}251252func TestDetectSheBangRuby(t *testing.T) {253	ProcessConstants()254255	cases := []string{256		"#!/usr/bin/env ruby",257		"#!/usr/bin/ruby",258	}259260	for _, c := range cases {261		x, y := DetectSheBang([]byte(c))262263		if x != "Ruby" || y != nil {264			t.Error("Expected Ruby match got", x)265		}266	}267}268269func TestDetectSheBangLua(t *testing.T) {270	ProcessConstants()271272	cases := []string{273		"#!/usr/bin/env lua",274		"#!/usr/bin/lua",275	}276277	for _, c := range cases {278		x, y := DetectSheBang([]byte(c))279280		if x != "Lua" || y != nil {281			t.Error("Expected Lua match got", x)282		}283	}284}285286func TestDetectSheBangMultiple(t *testing.T) {287	ProcessConstants()288289	x, y := DetectSheBang([]byte(`#!/python/perl/ruby`))290291	if x != "Ruby" || y != nil {292		t.Error("Expected Ruby match got", x)293	}294}295296func TestDetectSheBangMultipleNewLine(t *testing.T) {297	ProcessConstants()298299	data := `#!/python/perl/ruby300python perl fish`301	x, y := DetectSheBang([]byte(data))302303	if x != "Ruby" || y != nil {304		t.Error("Expected Ruby match got", x)305	}306}307308func TestScanSheBang(t *testing.T) {309	cases := []string{310		"#!/usr/bin/perl",311		"#!  /usr/bin/perl",312		"#!/usr/bin/perl -w",313		"#!/usr/bin/env perl",314		"#!  /usr/bin/env   perl",315		"#!/usr/bin/env perl -w",316		"#!  /usr/bin/env   perl   -w",317		"#!/opt/local/bin/perl",318	}319320	for _, c := range cases {321		r, _ := scanForSheBang([]byte(c))322323		if r != "perl" {324			t.Errorf("Expected 'perl' got '%s' for %s", r, c)325		}326	}327}328329// Randomly try things to see what happens330func TestScanSheBangFuzz(t *testing.T) {331	for range 1000 {332		x, _ := scanForSheBang([]byte(randStringBytes(100)))333334		if x == "NEVERHAPPEN" {335			t.Errorf("Errr wot?")336		}337	}338}339340func TestCheckFullNameSheBang(t *testing.T) {341	ProcessConstants()342343	r, n := DetectLanguage("name")344345	if n != "name" {346		t.Error("Expected name to return")347	}348349	if r[0] != "#!" {350		t.Error("Expected #! return")351	}352}353354func TestCheckFullNameLicense(t *testing.T) {355	ProcessConstants()356357	r, n := DetectLanguage("license")358359	if n != "license" {360		t.Error("Expected name to return")361	}362363	if r[0] != "License" {364		t.Error("Expected License return")365	}366}367368func TestCheckFullNameXMake(t *testing.T) {369	ProcessConstants()370371	r, n := DetectLanguage("xmake.lua")372373	if n != "xmake.lua" {374		t.Error("Expected xmake.lua to return")375	}376377	if r[0] != "XMake" {378		t.Error("Expected XMake return")379	}380381	// count xmake.lua as a lua file if AllowListExtensions was set382	AllowListExtensions = []string{"lua"}383	r, n = DetectLanguage("xmake.lua")384385	if n != "lua" {386		t.Error("Expected lua to return")387	}388389	if r[0] != "Lua" {390		t.Error("Expected Lua return")391	}392	AllowListExtensions = []string{}393}394395func TestGuessLanguageCoq(t *testing.T) {396	ProcessConstants()397398	res := DetermineLanguage("", "", []string{"Coq", "SystemVerilog"}, []byte(`Require Hypothesis Inductive`))399400	if res != "Coq" {401		t.Error("Expected guessed language to have been Coq got", res)402	}403}404405func TestGuessLanguageQtTranslationSource(t *testing.T) {406	ProcessConstants()407408	content := []byte(`<?xml version="1.0" ?><!DOCTYPE TS><TS version="2.1" language="ca">`)409	res := DetermineLanguage("", "", []string{"Qt Translation Source", "TypeScript"}, content)410	if res != "Qt Translation Source" {411		t.Errorf("Expected guessed language to have been Qt Translation Source, got %s", res)412	}413}414415func TestGuessLanguageSystemVerilog(t *testing.T) {416	ProcessConstants()417418	res := DetermineLanguage("", "", []string{"Coq", "SystemVerilog"}, []byte(`endmodule posedge edge always wire`))419420	if res != "SystemVerilog" {421		t.Error("Expected guessed language to have been SystemVerilog got", res)422	}423}424425func TestDetectLanguageIEC61131S7DCL(t *testing.T) {426	ProcessConstants()427428	possible, ext := DetectLanguage("types.s7dcl")429	if ext != "s7dcl" {430		t.Error("Expected s7dcl got", ext)431	}432	found := slices.Contains(possible, "IEC61131-3")433	if !found {434		t.Error("Expected IEC61131-3 got", possible)435	}436}437438func TestGuessLanguageIEC61131SCL(t *testing.T) {439	ProcessConstants()440441	content := []byte(`FUNCTION_BLOCK "MotorControl"442VAR_INPUT443    Start : BOOL;444END_VAR445BEGIN446    IF Start THEN447        Speed := 100;448    END_IF;449END_FUNCTION_BLOCK`)450451	res := DetermineLanguage("motor.scl", "", []string{"IEC61131-3", "Scallop"}, content)452	if res != "IEC61131-3" {453		t.Error("Expected guessed language to have been IEC61131-3 got", res)454	}455}456457func TestGuessLanguageScallopSCL(t *testing.T) {458	ProcessConstants()459460	content := []byte(`rel classes = {0, 1, 2}461rel count_enroll_cs_in_class(c, n) :-462  n = count(s: student(c, s), enroll(s, "CS") where c: classes(c))463query count_enroll_cs_in_class`)464465	res := DetermineLanguage("scallop.scl", "", []string{"IEC61131-3", "Scallop"}, content)466	if res != "Scallop" {467		t.Error("Expected guessed language to have been Scallop got", res)468	}469}470471// .h is shared between C / C++ / Objective-C. These exercise the regex472// heuristic disambiguation added for https://github.com/boyter/scc/issues/574473474func TestDetectLanguageHeaderSharedExtension(t *testing.T) {475	ProcessConstants()476477	possible, ext := DetectLanguage("foo.h")478	if ext != "h" {479		t.Error("Expected h got", ext)480	}481	for _, want := range []string{"C Header", "C++ Header", "Objective C"} {482		if !slices.Contains(possible, want) {483			t.Errorf("Expected %s among candidates got %v", want, possible)484		}485	}486}487488func TestGuessLanguageHeaderCpp(t *testing.T) {489	ProcessConstants()490491	content := []byte(`#ifndef EXAMPLE_CLASS492#define EXAMPLE_CLASS493class ExampleClass {494    public:495        ExampleClass();496    private:497        int MethodB();498};499#endif`)500501	res := DetermineLanguage("example.h", "", []string{"C Header", "C++ Header", "Objective C"}, content)502	if res != "C++ Header" {503		t.Error("Expected guessed language to have been C++ Header got", res)504	}505}506507func TestGuessLanguageHeaderObjectiveC(t *testing.T) {508	ProcessConstants()509510	content := []byte(`#import <Foundation/Foundation.h>511512@interface MyClass : NSObject513@property (nonatomic, strong) NSArray *items;514@end`)515516	res := DetermineLanguage("myclass.h", "", []string{"C Header", "C++ Header", "Objective C"}, content)517	if res != "Objective C" {518		t.Error("Expected guessed language to have been Objective C got", res)519	}520}521522func TestGuessLanguageHeaderPlainCFallback(t *testing.T) {523	ProcessConstants()524525	content := []byte(`#ifndef FOO_H526#define FOO_H527int add(int a, int b);528void do_thing(void);529#endif`)530531	res := DetermineLanguage("foo.h", "", []string{"C Header", "C++ Header", "Objective C"}, content)532	if res != "C Header" {533		t.Error("Expected guessed language to have been C Header got", res)534	}535}536537// The result must not depend on the order candidates are supplied in, since538// ExtensionToLanguage is populated from a map with non-deterministic iteration.539func TestGuessLanguageHeaderDeterministic(t *testing.T) {540	ProcessConstants()541542	content := []byte(`template <typename T>543class Foo {544    std::vector<T> items;545};`)546547	orderings := [][]string{548		{"C Header", "C++ Header", "Objective C"},549		{"Objective C", "C++ Header", "C Header"},550		{"C++ Header", "Objective C", "C Header"},551	}552	for _, order := range orderings {553		res := DetermineLanguage("foo.h", "", order, content)554		if res != "C++ Header" {555			t.Errorf("Expected C++ Header for ordering %v got %s", order, res)556		}557	}558}559560func TestGuessLanguageLanguageSetNoPossible(t *testing.T) {561	res := DetermineLanguage("", "Java", []string{}, []byte(`endmodule posedge edge always wire`))562563	if res != "Java" {564		t.Error("Expected guessed language to have been Java got", res)565	}566}567568func TestGuessLanguageSingleLanguageSet(t *testing.T) {569	res := DetermineLanguage("", "Java", []string{"Rust"}, []byte(`endmodule posedge edge always wire`))570571	if res != "Rust" {572		t.Error("Expected guessed language to have been Rust got", res)573	}574}575576func TestGuessLanguageLanguageEmptyContent(t *testing.T) {577	res := DetermineLanguage("", "", []string{"Rust"}, []byte(``))578579	if res != "Rust" {580		t.Error("Expected guessed language to have been Rust got", res)581	}582}583584// Benchmarks below585586func BenchmarkScanSheBangFuzz(b *testing.B) {587	for i := 0; i < b.N; i++ {588		_, _ = scanForSheBang([]byte(randStringBytes(100)))589	}590}591592func BenchmarkScanSheBangReal(b *testing.B) {593	for i := 0; i < b.N; i++ {594		_, _ = scanForSheBang([]byte("#!  /usr/bin/env   perl   -w"))595	}596}597598func BenchmarkDetermineLanguage(b *testing.B) {599	ProcessConstants()600601	coqContent := []byte("Require Hypothesis Inductive\n")602	systemVerilogContent := []byte("endmodule posedge edge always wire\n")603	largeCoqContent := []byte("Require Hypothesis Inductive\n" + strings.Repeat("x", 25_000))604	largeSystemVerilogContent := []byte("endmodule posedge edge always wire\n" + strings.Repeat("y", 25_000))605	possibleLanguages := []string{"Coq", "SystemVerilog"}606607	benchmarks := []struct {608		name    string609		content []byte610	}{611		{name: "small_coq", content: coqContent},612		{name: "small_systemverilog", content: systemVerilogContent},613		{name: "large_coq_over_cutoff", content: largeCoqContent},614		{name: "large_systemverilog_over_cutoff", content: largeSystemVerilogContent},615	}616617	for _, benchmark := range benchmarks {618		b.Run(benchmark.name, func(b *testing.B) {619			b.ReportAllocs()620			b.SetBytes(int64(len(benchmark.content)))621622			for i := 0; i < b.N; i++ {623				_ = DetermineLanguage("", "", possibleLanguages, benchmark.content)624			}625		})626	}627}

Code quality findings 27

Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_, _ = scanForSheBang([]byte("#! /usr/bin/env perl -w"))
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = DetermineLanguage("", "", possibleLanguages, benchmark.content)
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 filename, wantExtension := range tests {
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
x, y := DetectSheBang([]byte(c))
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
r, _ := scanForSheBang([]byte(c))
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
x, _ := scanForSheBang([]byte(randStringBytes(100)))
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(`#ifndef EXAMPLE_CLASS
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(`template <typename T>
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
res := DetermineLanguage("", "Java", []string{}, []byte(`endmodule posedge edge always wire`))
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
res := DetermineLanguage("", "", []string{"Rust"}, []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
_, _ = scanForSheBang([]byte(randStringBytes(100)))
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
_, _ = scanForSheBang([]byte("#! /usr/bin/env perl -w"))
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
coqContent := []byte("Require Hypothesis Inductive\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
systemVerilogContent := []byte("endmodule posedge edge always wire\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
largeCoqContent := []byte("Require Hypothesis Inductive\n" + strings.Repeat("x", 25_000))

Get this view in your editor

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